edubun
Nederlands

Hashtabellen

Als 47 in een tabel met 7 buckets staat, gaat de computer direct naar bucket 47 mod 7 = 5 in plaats van de hele tabel te doorzoeken.

Een sleutel omzetten in een positie

Tot nu toe betekende een waarde vinden zoeken: maximaal n stappen in een ongesorteerde array of gelinkte lijst. Arrays kunnen elke positie echter in één stap bereiken. Wat als de sleutel zelf de positie aangaf? Bij kleine gehele sleutels, bijvoorbeeld studentnummers van 0 tot 99, kun je een array van 100 cellen bewaren en student k in cel k opslaan. De MIT-notities noemen dit een array met directe toegang. Dit faalt als de mogelijke sleutels enorm talrijk zijn, zoals telefoonnummers of woorden, omdat je niet voor elke mogelijke sleutel een cel kunt betalen.

Een hashfunctie lost dit op door elke sleutel in een klein bereik posities te persen, 0 tot m − 1. Een eenvoudige voor gehele getallen is h(k) = k mod m, de rest na delen door m. De array met m cellen is de hashtabel en de cellen heten vaak buckets. Om 47 met m = 7 op te slaan, bereken je 47 mod 7 = 5 en zet je het in bucket 5. Om 47 later te vinden bereken je hetzelfde en kijk je alleen in bucket 5.

Botsingen en ketens

Er is een probleem. Met 7 buckets en meer dan 7 mogelijke sleutels moeten sommige sleutels een bucket delen: 12, 33 en 47 geven allemaal rest 5. Dit heet een botsing en de MIT-notities wijzen erop dat dit door het duivenhokprincipe onvermijdelijk is. De gebruikelijke oplossing is ketenvorming: elke bucket bevat een lijstje, een keten, van elke sleutel die daarheen hasht. 47 vinden kost dan 1 stap voor de hash plus één vergelijking per gecontroleerde sleutel in keten 5. Als 12 en 33 ervoor staan, is dat 1 + 3 = 4 stappen.

Hoe lang zijn de ketens? Bij een gelijkmatige verdeling bevat elke keten ongeveer n/m sleutels, de beladingsfactor. Houd m ongeveer zo groot als n en de ketens blijven kort, zodat zoeken, invoegen en verwijderen gemiddeld een constant aantal stappen kosten. Het gevaar is een slechte verdeling. Als elke sleutel naar dezelfde bucket hasht, bevat één keten alle n sleutels en is de tabel niet beter dan een gelinkte lijst. Stel in het lab 7 buckets in en voeg 14, 21 en 28 in: ze komen allemaal in bucket 0.

Laden

Sleep om te draaien. Scroll of knijp om te zoomen.

Een hashtabel met 7 buckets met 12, 25, 33, 47 en 70, en h(k) = k mod 7. Kies een sleutel en voeg in, zoek of verwijder. De teller is 1 voor de hash plus 1 voor elke sleutelvergelijking in de keten.
Echte hashfuncties mengen alle bits van een sleutel en goed ontworpen bibliotheken kiezen ze zodat gewone gegevens gelijkmatig spreiden. MIT 6.006 gaat verder en kiest willekeurig een hashfunctie uit een familie, zodat geen vaste sleutelverzameling elke keer slecht is. De eenvoudige k mod m in het lab is gekozen omdat je die met de hand kunt controleren.

Hashtabellen in JavaScript

Je schrijft zelden zelf een hashtabel, omdat JavaScript Map en Set heeft. De ECMAScript-specificatie zegt dat ze ‘geïmplementeerd moeten worden met hashtabellen of andere mechanismen die gemiddeld toegangstijden bieden die sublineair zijn in het aantal elementen’. Met andere woorden, map.get(key) en set.has(x) moeten sneller zijn dan de hele verzameling doorzoeken. MDN voegt toe dat een Map sleutels in invoegvolgorde bewaart en, anders dan een gewoon object, geen standaardsleutels bevat die met de jouwe kunnen botsen.

const price = new Map();
price.set("apple", 1.2);
price.set("bread", 2.5);
console.log(price.get("bread")); // 2.5, no search through the list
console.log(price.has("milk"));  // false
const seen = new Set([4, 8, 15]);
console.log(seen.has(8));        // true
AchtergrondHet idee van hashing wordt toegeschreven aan Hans Peter Luhn van IBM. Donald Knuths geschiedenis van zoeken beschrijft dat Luhn in januari 1953 een intern IBM-memorandum schreef met het voorstel records in genummerde buckets te zetten, met ketens voor botsende records. Zijn hashfuncties werkten op cijfers: tel naburige cijferparen op en bewaar het laatste cijfer van elke som, zodat 31415926 verandert in 4548. Andere IBM-ingenieurs aan de IBM 701 hadden rond dezelfde tijd hetzelfde idee. De eerste gepubliceerde beschrijving kwam van Arnold Dumey in 1956.
HashfunctieZet een sleutel om in een bucketnummer van 0 tot m − 1, bijvoorbeeld k mod m.
BotsingTwee verschillende sleutels krijgen dezelfde bucket. Onvermijdelijk bij meer sleutels dan buckets.
KetenvormingElke bucket bewaart een lijst van elke sleutel die daarheen hashte.
Beladingsfactorn/m, de gemiddelde ketenlengte.
Map en SetIngebouwde sleutelverzamelingen van JavaScript, die gemiddeld sneller dan lineair moeten zijn.
  1. Naar welke bucket gaat 47 bij h(k) = k mod 7?
  2. De tabel bevat 12, 25, 33, 47, 70 (in die volgorde ingevoegd) met m = 7. Hoeveel stappen kost 47 vinden (1 voor de hash plus vergeleken sleutels)?
  3. Dezelfde tabel: hoeveel stappen om 12 te vinden?
  4. Dezelfde tabel: 19 is niet opgeslagen. Hoeveel stappen kost zoeken naar 19?
  5. Dezelfde tabel: wat is de beladingsfactor n/m? (twee decimalen)
  6. Wat is een botsing?
    • Een te grote sleutel
    • Twee verschillende sleutels hashen naar dezelfde bucket
    • Een sleutel tweemaal verwijderen
    • Een tabel zonder sleutels
  7. Waarom kan geen hashfunctie botsingen vermijden als er meer mogelijke sleutels dan buckets zijn?
    • Computers ronden getallen af
    • Het duivenhokprincipe
    • Sleutels zijn altijd even
    • Ketens zijn te kort
  8. Hoe verloopt zoeken als elke sleutel in dezelfde bucket komt?
    • Nog steeds 1 stap
    • Zoals zoeken in een gelinkte lijst van n sleutels
    • Het wordt onmogelijk
    • Zoals binair zoeken
  9. Wat vereist de ECMAScript-specificatie van de toegangstijden van Map?
    • Precies 1 stap
    • Gemiddeld sublineair in het aantal elementen
    • Θ(n log n)
    • Niets
  10. De hash van Luhn uit 1953 telde naburige cijferparen op en bewaarde het laatste cijfer van elke som. Wat wordt 31415926?
Belangrijkste punten
  • Een hashfunctie zet een sleutel om in een bucketnummer; k mod m is een eenvoudige.
  • Botsingen zijn onvermijdelijk; ketenvorming bewaart een lijst in elke bucket.
  • Met een kleine beladingsfactor n/m kosten zoeken, invoegen en verwijderen gemiddeld een constant aantal stappen.
  • JavaScript Map en Set moeten gemiddeld sneller dan lineair zijn.
Bronnen van deze cursus (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).

Geraadpleegd: 2026-10-18