Datenstrukturen und Algorithmen VO INF.02031UF 5. Gestreute Speicherung (Hashing) 5. Hashing [email protected] 1 Gestreute Speicherung (Hashing) • Wir suchen eine Datenstruktur, die das Wörterbuchproblem effizient löst • Wörterbuchoperationen: – Einfügen – Suchen – Entfernen • Anwendungen: Telefonbuch, Wörterbuch, Symboltabelle beim Kompilieren, … 5. Hashing [email protected] 2 Gestreute Speicherung (Hashing) Wörterbuchoperationen: – Einfügen – Suchen – Entfernen Behandelte Datenstrukturen: • Lineares Feld (Liste): Einfügen O(1); Suchen, Entfernen O(n). • Sortiertes lineares Feld: – Suchen: O(log n) – Einfügen, Entfernen: Problematisch (Sortierung muss aufrecht erhalten werden) O(n) • Halde: Suchen problematisch O(n) 5. Hashing [email protected] 3 Gestreute Speicherung (Hashing) • Idee: Anstatt zu suchen, berechne die Adresse eines Datums aus seinem Wert (Schlüssel) in O(1) Zeit. • Hashtabelle: lineares Feld T[0..m-1]; Datum mit Wert w wird in T[h(w)] gespeichert. • Hashfunktion: h: U → {0, 1, …, m-1} U = Universum aller möglichen Schlüssel Aktuelle Schlüssel w j=0 h(w) = j Kollision Hashtabelle T j=1 h (w) = h (w´) j = m-2 j = m-1 5. Hashing [email protected] 4 Gestreute Speicherung (Hashing) • Die Hashfunktion sollte möglichst wenig Kollisionen liefern • Ideale Hashfunktion (ein theoretisches Konstrukt!): 1 Prh(w) j m w U , j {0,..., m 1} • Behandlung von Kollisionen: – Überläuferlisten (Chaining) – Offene Adressierung • Lineare und quadratische Sondierung • Doppeltes Hashing 5. Hashing [email protected] 5 Hash-Funktionen • Was ist eine gute Hashfunktion? – Jeder Index j=0,…,m-1 sollte gleichwahrscheinlich sein, um möglichst wenig Kollisionen zu liefern – h(w) soll möglichst effizient berechnet werden – Ähnliche Werte sollten möglichst gut getrennt werden – h(w) soll unabhängig von Mustern in den Daten sein • Wir kennen selten die genaue Verteilung der Werte • ⇒ Heuristische Wahl der Hashfunktion • Wir betrachten ℎ ∶ ℕ → {0, 1, … , 𝑚 − 1} 5. Hashing [email protected] 6 Hash-Funktionen • Divisionsmethode: – Dividiere den Wert durch m und nimm den Rest: h(w) w mod m – z.B.: w=100, m=12, h(100) = 100 mod 12 = 4 – Vorteil: schnell berechenbar – Nachteil: nicht für alle m gut • m=2k, m=10k: hängt nur von den letzten k Bits/Ziffern ab • Gut für m Primzahl und nicht zu nahe an 2k, 10k 5. Hashing [email protected] 7 Hash-Funktionen • Multiplikationsmethode: – Multipliziere den Wert mit einer fixen Konstante A, 0<A<1, und multipliziere den gebrochenen Teil des Resultates mit m: h( w) m frac( w A) – Vorteil: m ist unkritisch (m=2k: durch Shift-Operationen effizient berechenbar) Guter Wert für A: 5. Hashing A 5 1 0,6180 ... 2 [email protected] 8 Behandlung von Kollisionen – Überläuferlisten (Chaining) – Offene Adressierung • Lineare und quadratische Sondierung • Doppeltes Hashing U = Universum aller möglichen Schlüssel Aktuelle Schlüssel w j=0 h(w) = j Kollision Hashtabelle T j=1 h (w) = h (w´) j = m-2 j = m-1 5. Hashing [email protected] 9 Überläuferlisten (Chaining) • Bei einer Kollision werden die Daten in einer verketteten Liste angelegt: Einfügen: Am Beginn der Liste T[h(w)] Suchen: Durchsuchen der Liste T[h(w)] Löschen: Suchen von w, Ausklinken aus Liste T[h(w)] 5. Hashing [email protected] 10 Überläuferlisten (Chaining) • Bei einer Kollision werden die Daten in einer verketteten Liste angelegt: Erwartete Laufzeit Einfügen: O(1) Suchen: O(1+α) Löschen: O(1+α) n m Belegungsfaktor der Hashtabelle O(1+α) = O(1), wenn n=O(m) Worst-case: Θ(1+ n) für Suchen, Löschen wenn zufällig alle Werte in dieselbe Liste gestreut 5. Hashing [email protected] 1 prob m n 1 11 Offene Adressierung • Alternative Methode zur Behandlung von Kollisionen • Alle Werte werden in T[0..m-1] selbst gespeichert ⇒ α=n/m≤1 • Bei einer Kollision wird solange eine neue Adresse berechnet, bis ein freier Platz gefunden wird h(w,i) = j h : U 0,1,, m 1 0,1,, m 1 i=0,1,2,…m-1 Versuchzahl (Probing) i=1 noch frei i=2 5. Hashing besetzt [email protected] 12 Offene Adressierung • Ideale Hashfunktion: Für jeden Wert w ist h(w,0), h(w,1), …, h(w,m1) mit Wahrscheinlichkeit 1/m! eine der m! Permutationen von 0, 1, …, m-1. • In der Praxis verwendete Näherungen: h( w, i) h( w) i mod m – Linear Probing: Problem: benachbarte Felder wahrscheinlicher belegt (primary clustering) – Quadratic Probing: h( w, i ) h( w) f (i ) mod m f(i)…quadratische Funktion; bei einer Kollision immer noch dieselbe Indexfolge (secondary clustering) – Double Hashing: 5. Hashing h( w, i) h1 ( w) ih2 ( w)mod m [email protected] 13 Offene Adressierung • Suchen von w • Einfügen von w h(w,i=0) h(w,i=0) h(w,i=1) w h(w,i=2) h(w,i=1) w=? w noch frei besetzt 5. Hashing h(w,i=2) w • erhöhe i bis w gefunden wurde oder i=m ist [email protected] 14 Offene Adressierung • Löschen von w' • Suchen von w h(w,i=0) h(w,i=0) h(w,i=1) w=? h(w,i=1) w' h(w,i=2) w=? w w wird nicht mehr ausgeführt noch frei besetzt 5. Hashing [email protected] 15 Offene Adressierung • Eine Lösung für das Löschen von w' noch frei h(w,i=0) h(w,i=1) besetzt gelöscht w=? h(w,i=2) w • Anmerkung: Beim Einfügen werden gelöschte Felder gleich wie freie Felder behandelt 5. Hashing [email protected] 16 Offene Adressierung • Einfügen und Suchen: EINFÜGE(T, w) 1: i ← 0 2: REPEAT 3: ind ← h(w,i) 4: IF T[ind] frei THEN 5: T[ind] ← w 6: return 7: i ← i+1 8: UNTIL i=m 9: return „overflow“ SUCHE(T, w) 1: i ← 0 2: REPEAT 3: ind ← h(w,i) 4: IF T[ind] = w THEN 5: return ind 6: i ← i+1 7: UNTIL (T[ind] frei) or (i=m) 8: return „nicht gefunden“ Erwartete Laufzeit O 1 1 5. Hashing [email protected] 17 Gestreute Speicherung (Hashing) • Das Vergrößern von Hashtabellen wurde nicht behandelt – Neue größere Tabelle anlegen und Elemente übertragen -> langsam – Neue zweite Tabelle anlegen und nur dort Einfügen -> geht online Lineares Feld Lineare Liste Gestreute Speicherung Überläuferlisten offene Adressierung α=n/m (z.B. 10) α=n/m (z.B. 0.5) Suchen O(n) O(n) O(1+α) ≈ O(1/(1-α)) Einfügen O(1) O(1) O(1) wie oben Suchen und Entfernen O(n) O(n) O(1+α) wie oben 5. Hashing [email protected] 18