Hashing mit Verkettung (Algorithmus 3.5)
Die Hashtabelle mit separater Verkettung aus Sedgewick/Wayne, Algorithmus 3.5 (Listing 3.19) – hash() Schritt für Schritt, dann die Suche in einer einzigen der M Listen, mit dem Testclient aus Abbildung 3.56 und wahlweise den echten Hashwerten von Java.
So geht’s: Beim Öffnen ist der Testclient aus Abbildung 3.56 geladen: 5 Listen, SEARCHEXAMPLE, Wert = Position im Wort. Jede Operation beginnt mit hash(key) – die App rechnet den Index vor, die gewählte Liste wird blau umrandet. Danach läuft genau die sequenzielle Suche aus Algorithmus 3.1, aber nur in dieser einen Liste; ihr Code steht unten im Java-Kasten. Oben lässt sich umschalten: „Hashwerte wie Abbildung 3.56“ oder „Java hashCode()“, dazu die Zahl der Listen M (bei M = 1 ist alles wieder eine einzige Liste). Das Protokoll ist aufgebaut wie die Tabelle links in der Abbildung: Schlüssel, Hash, Ergebnis.
Erklärung
Hashing mit Verkettung (Algorithmus 3.5), Klasse SeparateChainingHashST. Ganz anderer Ansatz als die Bäume: Statt zu vergleichen, wird aus dem Schlüssel direkt berechnet, wo er liegt.
Zwei Bausteine:
- Hashfunktion: macht aus einem Schlüssel einen Arrayindex zwischen 0 und M−1.
- Kollisionsauflösung: Was tun, wenn zwei Schlüssel auf denselben Index fallen? Hier: Jeder Index bekommt seine eigene verkettete Liste, und alle kollidierenden Schlüssel landen darin.
Der Code (Listing 3.19):
st[]ist ein Array aus M Objekten vom TypSequentialSearchST, also genau den ungeordneten Listen aus Algorithmus 3.1 [22]. Wiederverwertung statt Neuschreiben.- Standard ist M = 997 (eine Primzahl, verteilt besser).
hash(key):(key.hashCode() & 0x7fffffff) % M. Das& 0x7ffffffflöscht das Vorzeichenbit, weilhashCode()negativ sein kann und ein negativer Index knallen würde. Dann modulo M.getundput: Index berechnen, dann einfachget/putder Liste an diesem Index aufrufen. Mehr nicht.- Der Cast
(SequentialSearchST<Key, Value>[])ist wieder nötig, weil Java keine generischen Arrays erzeugen kann, wie schon in [14] und [23].
Abbildung 3.56 (M = 5, SEARCHEXAMPLE, Wert = Position):
- Hashwerte: S→2, E→0, A→0, R→4, C→4, H→4, X→2, M→4, P→3, L→3.
- Liste 0: A 8, E 12 (E und A je mehrfach eingefügt, Werte überschrieben)
- Liste 1: leer
- Liste 2: X 7, S 0
- Liste 3: L 11, P 10
- Liste 4: M 9, H 5, C 4, R 3
- Neue Schlüssel stehen jeweils vorn in ihrer Liste, wie bei [22].
Kosten:
- N Schlüssel auf M Listen: Durchschnittslänge N/M, unabhängig davon, wie sie sich verteilen.
- Unter der Annahme gleichmäßiger Verteilung (Annahme J im Buch) sind fast alle Listen tatsächlich nahe N/M lang. Suche und Einfügen kosten dann rund N/M Vergleiche.
- Wählt man M proportional zu N, z. B. so, dass N/M zwischen 2 und 8 bleibt, ist das konstante Zeit im Schnitt. Dazu das Array mitwachsen lassen wie in [14] (Übung).
- Laut Buch mit 997 Listen etwa 1000-mal schneller als eine einzelne Liste.
- Schlimmster Fall: schlechte Hashfunktion, alle Schlüssel in einer Liste, wieder N.
Der Preis: Die Schlüssel liegen wild verstreut. Geordnete Operationen wie min, max, floor, rank, select oder Bereichssuche gehen nur durch Absuchen aller Listen. Die Bäume aus [24] und [25] können das, Hashing nicht.
Stand Kapitel 3:
| Suche | Einfügen | geordnete Ops | |
|---|---|---|---|
| 3.1 Sequenziell | N | N | nein |
| 3.2 Binäre Suche | lg N | N | ja |
| 3.3 BST | lg N Schnitt, N worst | lg N Schnitt, N worst | ja |
| 3.4 Rot-Schwarz | ≤ 2 lg N garantiert | ≤ 2 lg N garantiert | ja |
| 3.5 Hashing Verkettung | N/M, praktisch konstant | N/M, praktisch konstant | nein |
Faustregel: Ordnung gebraucht → Rot-Schwarz-Baum (TreeMap). Nur schnell suchen → Hashing (HashMap). Als Nächstes kommt Algorithmus 3.6, Hashing mit linearer Sondierung, also ohne Listen, alles in einem Array.