Visão geral da tabela de hash simples
Como atualização, uma tabela de hash é uma maneira de armazenar um valor em uma chave específica em uma estrutura de dados. Por exemplo, eu poderia armazenar valor "a"
sob a chave 1
e depois recuperá-lo procurando a chave 1
na tabela de hash.
O exemplo mais simples de uma tabela de hash que eu consigo pensar em cima da minha cabeça é uma tabela de hash que pode armazenar apenas números inteiros, onde a chave para a entrada da tabela de hash também é o valor que está sendo armazenado. Digamos que sua tabela seja do tamanho 8 e seja basicamente uma matriz na memória:
---------------------------------
| | | | | | | | |
---------------------------------
0 1 2 3 4 5 6 7
Função Hash
As funções de hash fornecem um índice de onde armazenar seu valor. Uma função hash bastante simples para esta tabela seria adicionar 1 ao valor que você deseja armazenar e modificá- lo por 8 (o tamanho da tabela). Em outras palavras, sua função hash é (n+1)%8
onde n
é o número inteiro que você deseja armazenar.
Inserções
Se você deseja inserir um valor nessa tabela de hash, chame sua função de hash (neste caso (n+1)%8
) no valor que deseja inserir para fornecer um índice. Por exemplo, se quisermos inserir 14, chamaríamos (14 + 1) % 8
e obteríamos índice 7
, portanto, inseriríamos o valor no índice 7
.
---------------------------------
| | | | | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
Da mesma forma, podemos inserir 33, 82 e 191 da seguinte maneira:
---------------------------------
|191| |33 |82 | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
Colisões
Mas o que acontece se tentarmos inserir algo que colidiria com uma entrada? 2 deve ir no índice 3
, mas é adotado por 82. Existem várias maneiras de resolver esse problema, a mais simples é chamar nossa função hash repetidas vezes até encontrarmos um espaço vazio.
Portanto, a lógica é a seguinte:
- (2 + 1)% 8 = 3
- O índice 3 está cheio
- Conecte 3 novamente à nossa função hash. ( 3 + 1)% 8 = 4 , que está vazio.
- Coloque nosso valor no índice 4 .
Agora a tabela de hash se parece com isso, com o valor 2 armazenado no índice 4
.
---------------------------------
|191| |33 |82 |2 | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
A desvantagem desta solução é que, em breve, nossa mesa ficará cheia! Se você souber que o tamanho dos dados é limitado, isso não deve ser um problema, desde que sua tabela seja grande o suficiente para armazenar todos os valores possíveis. Se você quiser aguentar mais, poderá lidar com colisões de maneira diferente. Vamos voltar para onde estávamos antes de inserir 2.
---------------------------------
|191| |33 |82 | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
Se você se lembra, (2+1)%8
nos dá o índice 3
, que é usado. Se você não deseja que sua tabela de hash seja preenchida, você pode usar cada índice de tabela como uma lista vinculada e anexá-la à lista nesse índice. Então, em vez de chamar a função hash novamente, simplesmente anexamos à lista no índice 3
:
-----
| 2 |
---------------------------------
|191| |33 |82 | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
Essa lista pode crescer tanto quanto a memória permitir. Eu posso inserir 18 e ele será anexado a 2:
-----
|18 |
-----
| 2 |
---------------------------------
|191| |33 |82 | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
Pesquisas
A pesquisa de valores em sua tabela de hash é rápida, pois sua tabela de hash é de um tamanho bastante grande. Você simplesmente chama sua função hash e obtém o índice. Digamos que você queira ver se o 82 está na sua mesa. A função de pesquisa chamaria (82+1)%8
= 3
e examinaria o item no índice 3
e o retornaria para você. Se você pesquisou 16, a função de pesquisa procuraria no índice 1
e verificaria que ela não existe.
As pesquisas também precisam lidar com colisões!
Se você tentar procurar o valor 2, sua tabela de hash teria que usar a mesma lógica de colisão usada para armazenar os dados e para recuperá-los. Dependendo da maneira como sua tabela de hash funciona, você deve fazer o hash da chave repetidamente até encontrar a entrada que procura (ou encontrar um espaço em branco) ou percorrer a lista vinculada até encontrar o item (ou chegou ao fim da lista)
Sumário
Portanto, as tabelas de hash são uma boa maneira de armazenar e acessar pares de valores-chave rapidamente. Neste exemplo, usamos a mesma chave que o valor, mas nas tabelas de hash do mundo real, as chaves não são tão limitadas. As funções de hash funcionarão nas teclas para gerar um índice e, em seguida, a chave / valor poderá ser armazenada nesse índice. As tabelas de hash não devem ser iteradas, embora seja possível. Como você pode ver, as tabelas de hash podem ter muitos espaços em branco, e a iteração através deles seria uma perda de tempo. Mesmo que a tabela de hash tenha lógica para ignorar pesquisas de espaço em branco em seu iterador, você seria mais adequado usando uma estrutura de dados projetada para iteradores, como listas vinculadas.