edubun
Italiano

Tabelle hash

Con 47 memorizzato in una tabella di 7 bucket, il computer va dritto al bucket 47 mod 7 = 5 invece di cercare in tutta la tabella.

Trasformare una chiave in una posizione

Finora trovare un valore voleva dire cercare: fino a n passi in un array non ordinato o in una lista concatenata. Gli array, però, raggiungono qualsiasi posizione in un passo. E se fosse la chiave stessa a indicare la posizione? Se le chiavi fossero piccoli numeri interi, per esempio numeri di matricola da 0 a 99, potresti tenere un array di 100 celle e mettere lo studente k nella cella k. Gli appunti del MIT lo chiamano direct access array (array ad accesso diretto). Non funziona quando le chiavi possibili sono enormi, come numeri di telefono o parole, perché non puoi permetterti una cella per ogni chiave possibile.

Una funzione hash risolve il problema comprimendo qualsiasi chiave in un piccolo intervallo di posizioni, da 0 a m − 1. Una semplice per i numeri interi è h(k) = k mod m, il resto della divisione per m. L'array di m celle è la tabella hash, e le sue celle sono spesso chiamate bucket. Per memorizzare 47 con m = 7, calcola 47 mod 7 = 5 e mettilo nel bucket 5. Per ritrovare 47 più tardi, fai lo stesso calcolo e guarda solo nel bucket 5.

Collisioni e concatenamento

C'è un problema. Con 7 bucket e più di 7 chiavi possibili, alcune chiavi devono condividere un bucket: 12, 33 e 47 danno tutte resto 5. Si chiama collisione, e gli appunti del MIT fanno notare che non si può evitare, per il principio dei cassetti. Il rimedio solito è il concatenamento (chaining): ogni bucket contiene una piccola lista, una catena, di tutte le chiavi finite lì. Trovare 47 costa allora 1 passo per l'hash più un confronto per ogni chiave controllata nella catena 5. Se 12 e 33 sono davanti, fa 1 + 3 = 4 passi.

Quanto sono lunghe le catene? Se le chiavi si distribuiscono in modo uniforme, ogni catena contiene circa n/m chiavi, un numero chiamato fattore di carico. Tieni m grande più o meno quanto n e le catene restano corte, così trovare, inserire ed eliminare richiedono in media un numero costante di passi. Il pericolo è una cattiva distribuzione. Se tutte le chiavi finiscono nello stesso bucket, una sola catena contiene tutte le n chiavi e la tabella non è migliore di una lista concatenata. Nel laboratorio imposta 7 bucket e inserisci 14, 21 e 28: finiscono tutte nel bucket 0.

Caricamento

Trascina per ruotare. Scorri o avvicina le dita per lo zoom.

Una tabella hash di 7 bucket che contiene 12, 25, 33, 47 e 70, con h(k) = k mod 7. Scegli una chiave e inserisci, cerca o elimina. Il contatore dei passi conta 1 per l'hash più 1 per ogni chiave confrontata nella catena.
Le vere funzioni hash mescolano tutti i bit di una chiave, e le librerie ben progettate le scelgono in modo che i dati comuni si distribuiscano in modo uniforme. Il corso 6.006 del MIT va oltre e sceglie a caso una funzione hash da una famiglia, così che nessun insieme fisso di chiavi sia sfavorevole ogni volta. Il semplice k mod m del laboratorio è stato scelto perché lo puoi controllare a mano.

Le tabelle hash in JavaScript

Raramente scrivi da solo una tabella hash, perché JavaScript ha Map e Set. La specifica ECMAScript dice che devono essere «implementati usando tabelle hash o altri meccanismi che, in media, forniscano tempi di accesso sublineari rispetto al numero di elementi». In altre parole, map.get(key) e set.has(x) devono essere più veloci che cercare in tutta la collezione. MDN aggiunge che una Map conserva le chiavi nell'ordine di inserimento e, a differenza di un oggetto semplice, non contiene chiavi predefinite che potrebbero scontrarsi con le tue.

const price = new Map();
price.set("apple", 1.2);
price.set("bread", 2.5);
console.log(price.get("bread")); // 2.5, nessuna ricerca nella lista
console.log(price.has("milk"));  // false
const seen = new Set([4, 8, 15]);
console.log(seen.has(8));        // true
ContestoL'idea dell'hashing è attribuita a Hans Peter Luhn dell'IBM. La storia della ricerca di Donald Knuth riporta che nel gennaio 1953 Luhn scrisse un memorandum interno dell'IBM in cui proponeva di mettere i record in bucket numerati, con il concatenamento per quelli in collisione. Le sue funzioni hash lavoravano sulle cifre: somma le coppie di cifre vicine e tieni l'ultima cifra di ogni somma, così 31415926 diventa 4548. Altri ingegneri dell'IBM che lavoravano all'IBM 701 ebbero la stessa idea più o meno nello stesso periodo, e la prima descrizione pubblicata venne da Arnold Dumey nel 1956.
Funzione hashTrasforma una chiave in un numero di bucket da 0 a m − 1, per esempio k mod m.
CollisioneDue chiavi diverse ottengono lo stesso bucket. Inevitabile quando le chiavi sono più dei bucket.
ConcatenamentoOgni bucket tiene una lista di tutte le chiavi finite lì.
Fattore di caricon/m, la lunghezza media delle catene.
Map e SetLe collezioni con chiave integrate in JavaScript, che in media devono essere più veloci di una ricerca lineare.
  1. Con h(k) = k mod 7, in quale bucket va 47?
  2. La tabella contiene 12, 25, 33, 47, 70 (inseriti in quest'ordine) con m = 7. Quanti passi richiede trovare 47 (1 per l'hash più le chiavi confrontate)?
  3. Stessa tabella: quanti passi per trovare 12?
  4. Stessa tabella: 19 non è memorizzato. Quanti passi richiede una ricerca di 19?
  5. Stessa tabella: qual è il fattore di carico n/m? (due cifre decimali)
  6. Che cos'è una collisione?
    • Una chiave troppo grande
    • Due chiavi diverse che finiscono nello stesso bucket
    • Eliminare due volte una chiave
    • Una tabella senza chiavi
  7. Perché nessuna funzione hash può evitare le collisioni quando le chiavi possibili sono più dei bucket?
    • I computer arrotondano i numeri
    • Il principio dei cassetti
    • Le chiavi sono sempre pari
    • Le catene sono troppo corte
  8. Se tutte le chiavi finiscono nello stesso bucket, come si comporta la ricerca di una chiave?
    • Sempre 1 passo
    • Come cercare in una lista concatenata di n chiavi
    • Diventa impossibile
    • Come la ricerca binaria
  9. Che cosa richiede la specifica ECMAScript per i tempi di accesso di Map?
    • Esattamente 1 passo
    • Sublineari rispetto al numero di elementi, in media
    • Θ(n log n)
    • Niente
  10. L'hash di Luhn del 1953 sommava le coppie di cifre vicine e teneva l'ultima cifra di ogni somma. Che cosa diventa 31415926?
Punti chiave
  • Una funzione hash trasforma una chiave in un numero di bucket; k mod m è una funzione semplice.
  • Le collisioni sono inevitabili; il concatenamento tiene una lista in ogni bucket.
  • Con un fattore di carico n/m piccolo, trovare, inserire ed eliminare richiedono in media un numero costante di passi.
  • Map e Set di JavaScript devono essere in media più veloci di una ricerca lineare.
Fonti di questo corso (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).

Consultato il: 2026-10-18