Tabelas Hash Jeane Melo
Roteiro Introdução Motivação Definição Tabelas Hash Exemplos Funções hash Colisões
Introdução Estudamos a utilização de estruturas como listas e árvores para armazenar informações
Introdução Embora os resultados obtidos sejam bons, tais estruturas não permitem o acesso direto a uma informação a partir de uma chave Em algumas aplicações, é necessário obter o valor com poucas comparações (sem precisar percorrer todas as chaves).
Introdução Uma estrutura que possibilita este tipo de acesso são as Tabela Hash, também conhecidas como Tabelas de Indexação ou Tabelas de Espalhamento. 20 mod 8 = 4 45 mod 8 = 5 0 1 2 3 4 5 6 7 20? 11? 64 11 20 7 45? 11 mod 8 = 3
Introdução Tabelas Hash Construída através de um vetor que permite acesso direto aos elementos Ideal para aplicações tipo Dicionário onde desejamos fazer consultas aos elementos da tabela em tempo constante Compiladores Bancos de Dados Jogos Redes
Introdução Espaço de Chaves Tamanho do Vetor
Introdução Espaço Exemplo: Tabela de nomes (32 caracteres por nome) 26 32 > 16 32 = (2 4 ) 32 = 2 128
Introdução Objetivo: Mapear um espaço grande de chaves em um espaço de inteiros relativamente pequeno. O vetor é construído de modo que a localização de cada item (informação) é feita a partir do cálculo de um índice através de uma função denominada função de indexação ou hash function (função hash)
Tabelas Hash Estrutura de dados que permite o acesso as informações em tempo constante Consiste de um vetor no qual os elementos são distribuídos através de uma função denominada hash (hash function) A tabela armazena os registros com as chaves; os registros são endereçados através da hash function O inteiro gerado pela hash function é chamado hash code e é usado para localizar o item.
Hash function Formalmente: Seja M o tamanho da tabela A função de hashing h(k j ) [1,M] recebe uma chave k j {k 0,..,k m } e retorna um número i, que é o índice do subconjunto m i [1,M] onde o elemento que possui essa chave vai ser manipulado
Função Hash Transforma as chaves de pesquisa em endereços para a tabela Fornece o valor do endereço da chave na tabela hash Deve ser fácil de se computar e fazer uma distribuição equiprovável das chaves na tabela Exemplos de funções Hashing: Resto da Divisão Meio do Quadrado Método da Dobra Método da Multiplicação Hashing Universal
Função Hash Resto da divisão Forma mais simples e mais utilizada O endereço de um elemento na tabela é dado pelo resto da divisão da sua chave por M f h (x) = x mod M, onde M é o tamanho da tabela e x é um inteiro correspondendo à chave 0 <= f(x) <= M Depende extremamente do valor de M Preferência para M primo
Exemplo Espaço de chaves: Inteiros com 4 dígitos Mapear no conjunto: {0, 1,..., 7}. Uma possível função hash seria: f(x) = (5 x) mod 8
Exemplo Conjunto de entrada: 1055, 1492, 1776, 1918 e 1945 f(x) = (5 x) mod 8 Mapeamento gerado: Índice: 0 1 2 3 4 5 6 7 Chave: 1776 2002 1055 1492 1945 1918 Inserir 2002
Exemplo Índice: 0 1 2 3 4 5 6 7 Chave: 1776 2002 1055 1492 1945 1918 1812 Inserir 2002 Inserir 1812 Colisão
Colisão Duas chaves geraram o mesmo hash code, ou seja, foram mapeadas no mesmo índice Chaves distintas que geram um mesmo hash code (valor de hash) são denominadas sinônimos Paradoxo do aniversário Tabela de 365 posições e tomados aleatoriamente 50 pessoas teríamos pelo menos duas pessoas com mesma data de aniversário (uma colisão)
Tratamento de colisões Idealmente funções injetivas Difícil Classes de abordagens Closed Address Hashing (endereçamento fechado) Open Address Hashing (endereçamento aberto)
Endereçamento fechado Forma mais simples de tratamento de colisão Também chamado de hashing encadeado Idéia Usar uma lista encadeada para cada elemento da tabela Apenas sinônimos são acessados em uma busca
Endereçamento fechado Cada entrada H[i] da tabela hash é uma lista ligada, cujos elementos têm hash code i. Para inserir um elemento na tabela: 1. Compute o seu hash code i, e 2. Insira o elemento na lista ligada H[i].
Endereçamento fechado 20 mod 5 = 0 0 20 25 18 mod 5 = 3 25 mod 5 = 0 colisão com 20 1 2 3 4 18
Endereçamento fechado Problema Não se pode garantir que as listas tenham tamanhos próximos A busca pode se tornar seqüencial Árvores balanceadas (não se faz na prática) 0 1 2 3 20 4 0 88 32 15 11 60
Função hash Deve distribuir as chaves entre as posições de maneira uniforme 0 1 2 3 4 5 6 0 15 10 4 13 31 88 20
Endereçamento aberto Estratégia para guardar todas as chaves na tabela mesmo quando ocorre colisão H[i] contém uma chave, ao invés de um endereço Vantagem: Não utiliza espaço extra Quando há colisão um novo endereço é computado: rehashing
Endereçamento aberto Linear Probing Forma mais simples de rehashing Se ao obtermos hash code f (K) = i, a posição H[i] estiver ocupada, então a próxima posição disponível na tabela H será ocupada pela chave K : rehash (i) = (i+1) mod h.
Endereçamento aberto Entrada: 1055, 1492, 1776, 1812, 1918 e 1945 hash function: f (x) = (5 x) mod 8 Tratamento de colisão: linear probing Índice: 0 1 2 3 4 5 6 7 Chave: 1776 1055 1492 Para a chave 1812 rehash (4) = (4+1) mod 8 = 5 1812 Para a chave 1945 rehash (5) = (5+1) mod 8 = 6 rehash (6) = (6+1) mod 8 = 7 1812 1918 1945 1945 1945
Endereçamento aberto Linear probing recuperando uma chave K 1. Calcule o valor de f (K) = i. 2. Se H[i] está vazia, então K não está na tabela. 3. Se H[i] contém alguma chave diferente de K, então compute rehash (i) = i 1 = (i + 1) mod h. 4. Se H[i 1 ] está vazia, então K não está na tabela. Senão, se H[i 1 ] contém alguma chave diferente de K, então rehash (i 1 ), e assim sucessivamente.
Endereçamento aberto Efetuar o rehashing por linear probing pode trazer problemas de colisão Muitas colisões podem levar a um clustering (agrupamento) de chaves Muitos acessos para recuperar um certo registro Ocupação ideal da tabela em torno de 50%
Endereçamento aberto Double hash ou Hashing Duplo Método mais efetivo de efetuar rehashing Semelhante ao hash linear, porém, os incrementos são feitos por um valor d que depende da chave K d = HashIncr (K) rehash (j, d) = (j+d) mod h
Endereçamento aberto Entrada: 1055, 1492, 1776, 1812, 1918 e 1945 Função hash: f (x) = (5 x) mod 8 Colisões: Resolvidas por double hashing com HashIncr(K) = (K mod 7) + 1 Índice: 0 1 2 3 4 5 6 7 Chave: 1776 1812 1055 1492 1945 1918 rehash (4, 7) = (4 + 7) mod 8 = 3 1812 f (1812) = (5 1812) mod 8 = (9060 mod 8) = 4 HashIncr (1812) = (1812 mod 7) + 1 = 6 + 1 = 7 rehash (3, 7) = (3 + 7) mod 8 = 2
Referências Cormen Slides prof. Thales Castro Slides prof. Katia Guim (Cin UFPE)