Armazena um item de chave $k$ no índice $k$ do array.
Se o tamanho da maior chave, $u$, satifizer a restricão $u \le 2^{w}$, onde $w$ é o tamanho em bits da palavra da máquina, a complexidade de tempo de acesso, inserção, e exclusão $\Theta(1)$.
Complexidade de expaço $\Omega(k)$, para $k \gg n$
Hashtables
Objetivo é armazenar $m$ chaves, onde $m = \Theta(n)$, de um universo de chaves $\mathcal{U}$ tal que $|\mathcal{U}| \gg m$.
Utilizamos uma relação $h :\mathcal{U} \rightarrow \{0, 1, \dots, m-1\}$
Exemplos de aplicação:
Dicionários em Python
Compiladores e Interpretadores (tabela de símbolos)
Rede (mapeamento IP$\rightarrow$host)
Procura de substring
Sincronização de arquivos
Problemas:
Chaves não são inteiros não-negativos
Birthday Problem
Em um grupo de 366 pessoas, a probabilidade de duas pessoas nascerem no mesmo dia é 1.0.
Dado que $u \gg m$, haverá uma ocorrência onde $h(a) = h(b)$, para $a \neq b$, causando uma colisão de chaves.
Prehash
Uma função $h(k): \mathbb{K} \rightarrow \mathbb{N}$
Closed Addressing (Chaining)
Quando $h(a) = h(b)$, ou seja, ocorre uma colisão de chaves.
A entrada $m[h(k)]$ da tabela armazena uma lista encadeada de valores, ao invés de um único valor.
Assumindo que $h(k)$ resulta em uma distribuição uniforme, independente de $h(k_{i})$.
O tamanho esperados das listas é $\alpha = \frac{n}{m}$ (fator de carga)
Logo, o tamanho das lista é $\Theta(1)$ se $m = \Theta(n)$.
A complexidade de tempo é $O(1 + | \alpha |)$
Funções Hash
Método da Divisão: $h(k) = k\mod{m}$
Só é boa se $m$ é primo e não está próximo a $2^x$ ou $10^x$.
Método da Multiplicação: $h(k) = [(ak)\mod{2^{w}}] \gg (w-r)$
funciona bem quando $a$ é ímpar e fica próximo do meio entre $2^{r-1}$ e $2^{r}$
$w$ é o tamanho da palavra da máquina (ex.: 64-bits)
Double hashing: $h(k, p) = (h_1(k) + ph_2(k)) mod m
$h_2(k)$ e $m$ devem ser primos relativos
$m = 2^r$ e $h_2(k)$ é impar para todo $k$.
Cukoo Hashing
Utiliza $i$ funções hash, e cada uma se refere a uma tabela de tamanho $\frac{m}{i}$.
Sempre que há uma colisão na tabela $j$, o elemento é inserido nessa tabela $j$ e o elemento existente na tabela $j$ inserido na tabel $(j + 1) \mod i$.
Caso haja um ciclo na inserção a tabela precisa ser aumentada (rehash)
Como um ciclo pode se revelar em $O(n)$, é comum as implementações utilizarem um número máximo de tentativas de inserção de $O(\log{n})$ e se não for possível a inserção assumir a existência de um ciclo.
Quando maior o tamanho da tabela maior o número de evento chache miss.
Tem excelente resultado para tabelas de tamanho médio/grande com 3 chaves e $\alpha \approx 0.9$.
Complexidade de Tempo:
Busca: $O(1)$
Inserção: $O(1)$ amortizada
Robin Hood Hashing
Move os elementos de forma semelhante ao linear probing, porém só move o elemento atual se a distância dele para a posição original (PSL - probe sequence length) for menor do que a do elemento na tabela.
Trabalha de forma muito melhor com cache do que Cukoo Hashing.
Tem bom resultado com tabelas onde $\alpha \approx 0.9$.
Complexidade de Tempo para operações em tabelas hash
Expectativa de O(1) para inserção e busca.
Expectativa de O(1) para exclusão, porém pode ser necessário utilizar exclusão lógica.
Operações como min, max, previous e next tem $O(n)$