You are on page 1of 4

HASHING (Transformação de Chaves)

1. Introdução

Os algoritmos que realizam a pesquisa linear (O(N)) e a pesquisa binária (O(log2N))


comparam a chave procurada com todas ou com parte das chaves armazenadas na estrutura de
dados que contém os registros, logo o tempo médio de resposta dos mesmos aumenta com o
aumento do número N de registros.
Existe uma técnica surpreendentemente simples e eficiente chamada hashing, que permite
armazenar e localizar um registro através de um cálculo aritmético baseado no valor de sua chave.
Deste modo, o tempo de resposta médio para executar estas operações independe do tamanho da
tabela, portanto são feitas a um custo O(1). É aplicada principalmente em compiladores, verificadores
de ortografia e em gerenciadores de bancos de dados, mas existem numerosas outras aplicações.
O princípio básico da técnica hashing é simples: em vez de o índice de cada registro ser
definido segundo a posição relativa de sua chave em relação às demais, o índice é calculado de
forma absoluta através de uma função conveniente, chamada função de hashing, ou função de
espalhamento. Em outras palavras, a função de hashing transforma ou converte o valor da chave de
cada registro num índice (endereço ou posição) da tabela hash (normalmente um vetor), permitindo
armazenar e acessar diretamente cada registro. Estes índices calculados (endereços da tabela onde
os registros serão armazenados e pesquisados) parecem ser aleatórios, não havendo uma conexão
óbvia entre eles e as chaves que lhe deram origem.
Infelizmente existe um inconveniente: duas ou mais chaves distintas podem ser indexadas
com o mesmo índice de entrada na tabela e quando isso ocorre, diz-se que houve uma colisão. Como
dois registros não podem ocupar a mesma posição na tabela, é necessária a utilização de algum
método para contornar o problema das colisões. A inserção, pesquisa e exclusão de registros
através de hashing consiste basicamente:
1) Na transformação da chave K de cada registro num índice ou endereço, através de uma
função h(K), chamada função de hashing, ou de espalhamento.
2) No tratamento das colisões, que ocorrem quando dois ou mais registros com chaves
distintas são indexados com um mesmo índice ou endereço.

2. Funções de Hashing

Uma função de hashing ideal deve ser facilmente computada e deve transformar os valores
das chaves em índices uniformemente distribuídos, aparentemente aletórios, para diminuir o número
de colisões. Para uma tabela de tamanho M, a função de hashing h(K) deve gerar índices dentro do
intervalo [0, M-1]. Qualquer função que contemple estes requisitos pode ser utilizada como função
de hashing, todavia as funções mais utilizadas são as baseadas no método da divisão e da
multiplicação.

UFGD / FCET — Prof. Wellington Lima dos Santos — Algoritmos e Estruturas de Dados II.
2.1 Método da Divisão
É simples e eficiente tanto no que diz respeito à rapidez (aritmética de inteiros) como no
espalhamento dos registros, conforme a escolha do tamanho tabela. Consiste em dividir o valor da
chave K pelo tamanho M da tabela, tomando-se como índice do registro o resto desta divisão:

h(K) = K mod M

Chaves de outros tipos de dados como strings, por exemplo, podem ser facilmente
transformadas em números inteiros, uma vez que toda informação é representada no computador
como uma seqüência de bits, que pode ser interpretada como a representação binária de um inteiro.
Para que haja uma distribuição mais uniforme dos registros e menor incidência de colisões, M
não deve ser uma potência de 2, tampouco um número par. Mais que isso, é recomendável que M
seja um número primo, não próximo de uma potência de 2.

Exemplo 1: Determinar os índices dos registros com chaves iguais a [33 44 63 66 84 93] para uma
tabela hash de tamanho 7.

K h(K) = K mod 7
33 5
44 2*
63 0**
66 3
84 0**
93 2*

Pode-se observar que os registros de chaves 44 e 93 foram indexados com índice 2 e os de


chaves 63 e 84 foram indexados com o índice 0, ilustrando a ocorrência de duas colisões.

2.2 Método da Multiplicação


Também é um bom método, porém ligeiramente mais lento uma vez que requer operações de
ponto flutuante. O índice do registro é obtido multiplicando-se a parte fracionária do produto (K A) pelo
tamanho M da tabela:

h(K) = (K A mod 1) M

5 −1
em que 0 < A < 1. Usualmente considera-se A= = 0.618033988749894848...
2
Exemplo 2: Determinar os índices dos registros com chaves iguais a [33 44 63 66 84 93] para uma
tabela hash de tamanho 7.
K h(K) = (K 0,618 mod 1) 7
33 2
44 1
63 6*
66 5
84 6*
93 3

UFGD / FCET — Prof. Wellington Lima dos Santos — Algoritmos e Estruturas de Dados II.
Agora os registros de chaves 63 e 84 colidiram no índice 6 da tabela. A principal vantagem
deste método é que não há restrição quanto à escolha de M.

α)
3. Fator de Carga (α
É a razão entre o número de registros e o tamanho da tabela: α = N / M. Quanto menor o fator
de carga mais posições livres a tabela terá e menor será probabilidade de duas ou mais chaves
distintas serem indexadas como o mesmo índice ou posição da tabela.

4. Tratamento de Colisões
Por melhor que seja a função de hashing e por menor que seja o fator de carga sempre há a
probabilidade de ocorrência de colisões, que são inerentes à técnica e, portanto, devem ser tratadas
de algum modo. Basicamente existem dois métodos para o tratamento de colisões: encadeamento e
endereçamento aberto.

4.1 Encadeamento
Consiste em criar não uma tabela de registros, mas uma tabela contendo M listas
encadeadas (L1, L2, ..., LM) inicialmente vazias, ou apontando para nulo. Para armazenar um registro
de chave K, acessa-se a lista encadeada Lp, na posição p = h(K). Se Lp estiver vazia, aloca-se
memória para o primeiro nodo de Lp, dentro do qual o registro será armazenado. Se Lp não estiver
vazia e a chave K não for localizada em Lp, através de uma pesquisa seqüencial “barata”, significa
que houve uma colisão e um novo nodo será alocado e adicionado à Lp, para armazenar o registro.
Cada lista encadeada terá em média α = N / M registros, sendo possível α > 1.

Exemplo 3: Armazenar seqüencialmente os registros com chaves [33 44 63 66 84 93] numa tabela
hash de tamanho 7, com tratamento de colisões por encadeamento.

0 63 84
K h(K) = K mod 7 1
33 5 2 44 93
44 2
63 0 3 66
66 3
4
84 0
93 2 5 33
6

4.2 Endereçamento Aberto


Neste método é necessário que M > N e a estratégia consiste em procurar sucessivos
endereços alternativos para o novo registro até que um endereço livre seja encontrado. Assim, em
vez da utilização de uma única função hash h(K), utilizam-se h0(K), h1(K), h2(K)..., tal que:
hi(K) = (h(K) + f(i)) mod M i = 0, 1, 2, ..., M−1
h(K) = K mod M

UFGD / FCET — Prof. Wellington Lima dos Santos — Algoritmos e Estruturas de Dados II.
Conforme a escolha de f(i) tem-se:

4.2.1 Hashing com teste linear: f(i) = i

hi(K) = (h(K) + i) mod M

Por procurar endereços alternativos adjacentes, este método tende a produzir seqüências de
registros aglomerados (clustering) na tabela, tornando a pesquisa um pouco mais “onerosa”.

Exemplo 4: Armazenar seqüencialmente os registros com chaves [33 44 63 66 84 93] numa tabela
hash de tamanho 7, com tratamento de colisões endereçamento aberto com teste linear.

K I hi(K) = (h(K) + i) mod M 0 63


33 0 (33 mod 7 + 0) mod 7 = 5 (ok) 1 84
44 0 (44 mod 7 + 0) mod 7 = 2 (ok)
63 0 (63 mod 7 + 0) mod 7 = 0 (ok) 2 44
66 0 (66 mod 7 + 0) mod 7 = 3 (ok) 3 66
0 (84 mod 7 + 0) mod 7 = 0 (colisão)
84 4
1 (84 mod 7 + 1) mod 7 = 1 (ok) 93
0 (93 mod 7 + 0) mod 7 = 2 (colisão) 5 33
93 1 (93 mod 7 + 1) mod 7 = 3 (colisão)
2 (93 mod 7 + 2) mod 7 = 4 (ok) 6

4.2.2 Hashing com teste quadrático: f(i) = i2

hi(K) = (h(K) + i2) mod M

Como são procurados endereços alternativos não adjacentes, evita-se a formação de


agrupamentos, porém podem existir endereços livres na tabela que nunca são testados. Todavia isso
não chega a ser um problema, a menos que a tabela contenha pouquíssimos endereços livres, o que
pode e sempre deve ser evitado.

Exemplo 4: Armazenar seqüencialmente os registros com chaves [33 44 63 66 84 93] numa tabela
hash de tamanho 7, com tratamento de colisões endereçamento aberto com teste
quadrático.

K i hi(K) = (h(K) + i2) mod M 0 63


33 0 (33 mod 7 + 0) mod 7 = 5 (ok) 1 84
44 0 (44 mod 7 + 0) mod 7 = 2 (ok)
63 0 (63 mod 7 + 0) mod 7 = 0 (ok) 2 44
66 0 (66 mod 7 + 0) mod 7 = 3 (ok) 3 66
0 (84 mod 7 + 0) mod 7 = 0 (colisão)
84
1 (84 mod 7 + 1) mod 7 = 1 (ok) 4
0 (93 mod 7 + 0) mod 7 = 2 (colisão) 5
93 1 (93 mod 7 + 1) mod 7 = 3 (colisão) 33
2 (93 mod 7 + 4) mod 7 = 6 (ok) 6 93

UFGD / FCET — Prof. Wellington Lima dos Santos — Algoritmos e Estruturas de Dados II.