Tabelas hash
Com 47 guardado numa tabela de 7 baldes, o computador vai direto ao balde 47 mod 7 = 5 em vez de procurar na tabela inteira.
Transformando uma chave numa posição
Até agora, achar um valor queria dizer buscar: até n passos num array desordenado ou numa lista encadeada. Os arrays, porém, chegam a qualquer posição em um passo. E se a própria chave dissesse a posição? Se as suas chaves fossem números inteiros pequenos, digamos números de matrícula de 0 a 99, você poderia ter um array de 100 células e guardar o aluno k na célula k. As notas do MIT chamam isso de array de acesso direto. Isso falha quando as chaves possíveis são enormes, como números de telefone ou palavras, porque não dá para ter uma célula para cada chave possível.
Uma função hash resolve isso espremendo qualquer chave numa faixa pequena de posições, de 0 a m − 1. Uma simples para números inteiros é h(k) = k mod m, o resto da divisão por m. O array de m células é a tabela hash, e as células dela costumam ser chamadas de baldes (buckets). Para guardar 47 com m = 7, calcule 47 mod 7 = 5 e ponha-o no balde 5. Para achar 47 depois, calcule a mesma coisa e olhe só no balde 5.
Colisões e encadeamento
Há um problema. Com 7 baldes e mais de 7 chaves possíveis, algumas chaves precisam dividir um balde: 12, 33 e 47 dão todos resto 5. Isso se chama colisão, e as notas do MIT observam que ela não pode ser evitada, pelo princípio da casa dos pombos. O remédio comum é o encadeamento: cada balde guarda uma pequena lista, uma cadeia, com todas as chaves que caíram nele. Achar 47 custa então 1 passo para o hash mais uma comparação para cada chave conferida na cadeia 5. Se 12 e 33 estiverem na frente, são 1 + 3 = 4 passos.
Qual é o comprimento das cadeias? Se as chaves se espalham por igual, cada cadeia tem cerca de n/m chaves, um número chamado fator de carga. Mantenha m mais ou menos tão grande quanto n e as cadeias ficam curtas, então achar, inserir e apagar levam um número constante de passos em média. O perigo é um espalhamento ruim. Se toda chave cair no mesmo balde, uma cadeia guarda todas as n chaves e a tabela não é melhor que uma lista encadeada. No laboratório, escolha 7 baldes e insira 14, 21 e 28: todos caem no balde 0.
Arraste para girar. Role ou faça pinça para dar zoom.
Tabelas hash em JavaScript
Você quase nunca escreve uma tabela hash sozinho, porque o JavaScript tem Map e Set. A especificação ECMAScript diz que eles "precisam ser implementados usando tabelas hash ou outros mecanismos que, em média, ofereçam tempos de acesso sublineares em relação ao número de elementos". Em outras palavras, map.get(key) e set.has(x) precisam ser mais rápidos que buscar na coleção inteira. O MDN acrescenta que um Map mantém as chaves na ordem de inserção e, ao contrário de um objeto comum, não tem chaves padrão que possam colidir com as suas.
const price = new Map();
price.set("apple", 1.2);
price.set("bread", 2.5);
console.log(price.get("bread")); // 2.5, sem percorrer a lista
console.log(price.has("milk")); // false
const seen = new Set([4, 8, 15]);
console.log(seen.has(8)); // true| Função hash | Transforma uma chave num número de balde de 0 a m − 1, por exemplo k mod m. |
| Colisão | Duas chaves diferentes caem no mesmo balde. Inevitável quando há mais chaves que baldes. |
| Encadeamento | Cada balde guarda uma lista de todas as chaves que caíram nele. |
| Fator de carga | n/m, o comprimento médio das cadeias. |
| Map e Set | As coleções com chave embutidas no JavaScript, que precisam ser mais rápidas que o linear em média. |
- Com h(k) = k mod 7, em qual balde o 47 entra?
- A tabela guarda 12, 25, 33, 47, 70 (inseridos nessa ordem) com m = 7. Quantos passos leva achar o 47 (1 para o hash mais as chaves comparadas)?
- Mesma tabela: quantos passos para achar o 12?
- Mesma tabela: 19 não está guardado. Quantos passos leva uma busca por 19?
- Mesma tabela: qual é o fator de carga n/m? (duas casas decimais)
- O que é uma colisão?
- Uma chave grande demais
- Duas chaves diferentes caindo no mesmo balde
- Apagar uma chave duas vezes
- Uma tabela sem chaves
- Por que nenhuma função hash consegue evitar colisões quando há mais chaves possíveis que baldes?
- Os computadores arredondam números
- O princípio da casa dos pombos
- As chaves são sempre pares
- As cadeias são curtas demais
- Se toda chave cair no mesmo balde, como fica a busca por uma chave?
- Continua 1 passo
- Como buscar numa lista encadeada de n chaves
- Fica impossível
- Como a busca binária
- O que a especificação ECMAScript exige dos tempos de acesso de um Map?
- Exatamente 1 passo
- Sublineares em relação ao número de elementos, em média
- Θ(n log n)
- Nada
- O hash de Luhn de 1953 somava pares vizinhos de algarismos e guardava o último algarismo de cada soma. Em que se transforma 31415926?
- Uma função hash transforma uma chave num número de balde; k mod m é uma simples.
- Colisões são inevitáveis; o encadeamento guarda uma lista em cada balde.
- Com o fator de carga n/m mantido pequeno, achar, inserir e apagar levam passos constantes em média.
- Map e Set do JavaScript precisam ser mais rápidos que o linear em média.
Fontes deste curso (11)
- MIT OpenCourseWare 6.006 Introduction to Algorithms, Spring 2020 (Demaine, Ku, Solomon)
- lecture notes 2 Data Structures (interface vs data structure, Sequence and Set interfaces, stack = insert_last/delete_last, queue = insert_last/delete_first, array, linked list, dynamic array, amortized Θ(1) append)
- 4 Hashing (chaining, pigeonhole collisions, chain length n/m)
- 6 Binary Trees Part 1 (height, traversal order, O(h) operations) and 7 Binary Trees Part 2: AVL (Adelson-Velsky and Landis, 1962) at ocw.mit.edu/courses/6-006-introduction-to-algorithms-spring-2020/pages/lecture-notes
- MDN Web Docs, Array.prototype.shift (shifts all values left by 1)
- Array.prototype.unshift, Map (insertion order, Map vs Object, spec requires sublinear average access) and Set (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects)
- ECMAScript 2025 Language Specification (tc39.es/ecma262)
- 24.1 Map Objects and 24.2 Set Objects ("must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements")
- Array.prototype.shift steps (Repeat, while k < len: move element k to k - 1)
- D. E. Knuth, The Art of Computer Programming vol. 3 (2nd ed. 1998) section 6.4 History (H. P. Luhn, IBM internal memorandum January 1953, hashing with chaining; digit pairs added mod 10, 31415926 -> 4548) via gwern.net copy, and IEEE Spectrum, "Hans Peter Luhn and the Birth of the Hashing Algorithm" (2018)
- "A brief history of the stack", SIGCIS (sigcis.org/files/A brief history.pdf: Bauer and Samelson applied for the patent in 1957, US 3,047,228 granted July 31, 1962; IPL list processing by Newell, Simon and Shaw at RAND 1956) and Wikipedia, Information Processing Language (Newell, Shaw, Simon, RAND and Carnegie Institute of Technology, about 1956).
Data de verificação: 2026-10-18