300 likes | 379 Views
13. Kapitel: Hashfunktionen. 1) Divisionsrest-Verfahren ( kurz: Divisionsverfahren) h(K i ) = K i mod q, (q ~ m) Der entstehende Rest ergibt die relative Adresse in HT. Beispiel: Die Funktion nat wandle Namen in natürliche Zahlen um:
E N D
13. Kapitel: Hashfunktionen 1)Divisionsrest-Verfahren( kurz: Divisionsverfahren) h(Ki) = Ki mod q, (q ~ m) Der entstehende Rest ergibt die relative Adresse in HT. Beispiel: Die Funktion nat wandle Namen in natürliche Zahlen um: nat(Name) = ord (1. Buchstabe von Name ) - ord (“A”) h (Name) = nat (Name) mod m
Hash-Tabelle:m = 10 Schlüssel Daten 0 1 2 3 4 5 6 7 8 9 BOHR D1 CURIE D2 DIRAC D3 EINSTEIN D4 PLANCK D5 HEISENBERG D7 SCHRÖDINGER D8
1) m > n • Belegungsfaktor von HT: Verhältnis von aktuell belegten • Speicherplätzen (n) zur gesamten Anzahl der • Speicherplätze (m) = na / m • Für 0.85 erzeugen alle Hash-Funktionen viele • Kollisionen und damit einen hohen Zusatzaufwand. • 2) q gerade Zahl • sonst bleibt h (Ki) bei geradem Ki gerade und bei • ungeradem Ki ungerade. • 3) q bk • b sei die Basis der Schlüsseldarstellung. Wenn q = bk ist, • dann liefert h (Ki) die letzten k Stellen von Ki. • h(Ki) = Ki mod q Divisionsrest- Verfahren: Forderungen an Divisor q
a und c seien kleine ganze Zahlen. Der Divisor q soll nicht benachbart zu einer Potenz des Zahlensystems (in dem die Division durchgeführt wird) liegen, da sonst (x + a * bk c ) mod q ~ x mod q ist, d. h., bei gleichen Endziffern wiederholt sich fast die gleiche Menge von Adressen in verschiedenen Zahlenbereichen. 4) q a * bk c 5) q = Primzahl( größte Primzahl <= m) Die Hash-Funktion muss etwaige Regelmäßigkeiten in der Schlüsselverteilung eliminieren, damit nicht ständig die gleichen Plätze der HT getroffen werden. Bei äquidistantem Abstand der Schlüssel Ki + j * K, j = 0, 1, 2, ,... maximiert eine Primzahl die Distanz, nach der eine Kollision auftritt.
Ki mod q = (Ki + j * K) mod q oder • j * K = k * q, k = 1, 2, 3, ... • Eine Primzahl kann keine gemeinsamen Faktoren mit K besitzen, die den Kollisionsabstand verkürzen würden. • ==> wichtigste Forderung an q ! • 2) Faltung • Schlüssel wird in Teile zerlegt, die bis auf das letzte die • Länge einer Adresse für HT besitzen. • Schlüsselteile werden dann übereinander gefaltet und • addiert. Eine Kollision ergibt sich, wenn
Schlüssel Ki wird quadriert, t aufeinanderfolgende Stellen • werden aus der Mitte des Ergebnisses für die Adressierung • ausgewählt. • Es muss also bt = m gelten. • Mittlere Stellen lassen beste Gleichverteilung der Werte • erwarten. • Beispiel für b = 2, t = 4, m = 16 : Ki = 1100100 • Ki2 = 10011100010000 h (Ki) = 1000 • t 3) Mid-Square-Methode
Zufallsmethode: Ki dient als Saat für Zufallszahlengenerator • Ziffernanalyse: setzt Kenntnis der Schlüsselmenge K voraus. • Die t Stellen mit der besten Gleichverteilung der Ziffern • oder Zeichen in K werden von Ki zur Adressierung • ausgewählt. • Bewertung • Das Verhalten einer Hash-Funktion hängt von der gewählten Schlüsselmenge ab. • Deshalb lassen sie sich auch nur unzureichend theoretisch oder mit Hilfe von analytischen Modellen untersuchen. 4) Weitere Verfahren
Über die Güte der verschiedenen Hash-Funktionen liegen jedoch eine Reihe von empirischen Untersuchungen vor. • Das Divisionsrest-Verfahren ist im Mittel am • leistungsfähigsten; für bestimmte Schlüsselmengen können • jedoch andere Techniken besser abschneiden. • Keine Hash-Funktion ist immer besser als alle anderen. • Wenn die Schlüsselverteilung nicht bekannt ist, dann ist • das Divisionsrest-Verfahren die bevorzugte Hash-Technik. • ==> Wenn eine Hash-Funktion gegeben ist, lässt sich immer eine Schlüsselmenge finden, bei der sie besonders vieleKollisionen erzeugt.
Behandlung von Kollisionen • Zwei Ansätze, wenn h (Kq) = h (Kp): • Es wird für Kp eine freier Platz in HT gesucht ; alle • Überläufer werden im Primärbereich untergebracht • (open adressing). • Kp wird in einem separaten Überlaufbereich zusammen mit • allen anderen Überläufern gespeichert (separate overflow) • Die Methode der Kollisions-Auflösung entscheidet darüber, welche Folge und wie viele relative Adressen zur Ermittlung eines freien Platzes aufgesucht werden.
Adressfolge bei Speicherung und Suche für Schlüssel Kp sei • h0(Kp), h1(Kp), h2(Kp), ... • Bei einer Folge der Länge n treten also n-1 Kollisionen auf • Primärkollision: h (K p) = h (K q) • Sekundärkollision: h i (Kp) = h j(Kq) , i j • Offene Hash-Verfahren • Speicherung der Synonyme (Überläufer) im Primärbereich • Das eingesetzte Hash-Verfahren muss in der Lage sein, eine Sondierungsfolge, d. h. eine Permutation aller Hash-Adressen, zu berechnen.
1) Lineares Sondieren (linear probing) Von der Hausadresse aus wird sequentiell (modulo) gesucht. Diese Vorgehensweise kann mit jedem Hash-Verfahren kombiniert werden. Offensichtlich werden dabei alle Plätze in HT erreicht: h0 (K p) = h (K p) h i (K p) = ( h 0( K p ) - i ) mod m, i = 1, 2, ...
Beispiel: Einfüge-Reihenfolge: BECKETT, HESSE, BÖLL, HAUPTMANN, STEINBECK, SACHS, HAMSUN, SARTRE HT: m = 8 Schlüssel Schlüssel 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 Lösche
Häufung von Primär- oder Sekundärkollision auslösen. • Löschen: implizit oft Verschiebungen. Entstehende Lücken • in Suchsequenzen sind auszufüllen, da das Antreffen eines • freien Platzes die Suche beendet. Irgendeine Primär- oder Sekundärkollision kann eine
void Linsuche (Key X, Hashtab HT, Cardinal m, Cardinal j) { /* Suche in HT bei linearem Sondieren */ /*Bei erfolgreicher Suche zeigt j auf Position von X in HT */ Cardinal i; i = H [X]; /* H sei global definierte Hash-Funktion */ j = i ; /* unbelegter Eintrag in HT sei durch */ /* „ - “ - Zeichen charakterisiert */ while ( (HT [ j ] != X) && (HT[ j ] != „ - „ ) ) { j = (j -1 ) % m; if ( i == j) { printf ( „ X ist nicht in HT \n“); return ; } if ( HT [ j ] == „ - „ ) printf („ X ist nicht in HT \n“); } return; } Suche in einer Hash-Tabelle bei linearem Sondieren
(z.B. durch quadratisches Sondieren) h0 ( K p) = h ( K p) h i+1 ( K p ) = ( hi ( K p ) + f (i ) ) mod m oder h i+1 (K p) = (h i ( Kp ) + f ( i, h ( K p ) ) ) mod m , i= 1, 2, ... 2)Sondieren mit Zufallszahlen Mit Hilfe eines deterministischen Pseudo-Zufallszahlen-Generators wird die Folge der Adressen [1 ... m-1] mod m genau einmal erzeugt. Abhängig von k wird eine zufällige Hashadresse s(j, k) gewählt. h0 (K p) = h (K p) h i (K p) = ( h0 (K p) + z i ) mod m , i = 1, 2, ... Verbesserung: Modifikation der Überlauffolge
Einsatz einer zweiten Funktion für die Sondierungsfolge • h0 (Kp) = h (Kp) • hi (Kp) = ( h0 (Kp) + i * h‘ (Kp) ) mod m , i = 1, 2, ... • Dabei ist h‘ (K) so zu wählen, dass für alle Schlüssel K die • resultierende Sondierungsfolge eine Permutation aller • Hash-Adressen bildet. • 4) Kettung von Synonymen • Explizite Kettung aller Sätze einer Kollisionsklasse • verringert nicht die Anzahl der Kollisionsklassen; verkürzt • jedoch den Suchpfad beim Aufsuchen eines Synonyms. • Bestimmung eines freien Überlaufplatzes • (Kollisionsbehandlung) mit beliebiger Methode 3) Double-Hashing
Dynamische Speicherplatz-Belegung für Synonyme • Alle Sätze, die nicht auf ihrer Hausadresse unterkommen, • werden in einem separaten Bereich gespeichert. • Die Bestimmung der Überlaufadresse kann entweder • durch Double Hashing oder durch Kettung der Synonyme • erfolgen. • Die Synonym-Kettung erlaubt auch die Möglichkeit, den • Speicherplatz für Überläufer dynamisch zu belegen. • Suchen, Einfügen und Löschen sind auf Kollisionsklasse • beschränkt. • Unterscheidung nach Primär- und Sekundärbereich Hash-Verfahren mit separatem Überlaufbereich
HT: m = 7 Beispiel: Schlüssel 0 1 2 3 4 5 6 HAYDN * HÄNDEL * VIVALDI * BEETHOVEN * BACH * BRAHMS * CORELLI * * SCHUBERT * LISZT * MOZART * *
Kostenmaße • = n / m : Belegung von HT mit n Schlüsseln • Sn = # Suchschritte für das Auffinden eines Schlüssels - • entspricht den Kosten für erfolgreiche Suche und • Löschen (ohne Reorganisation) • Un = # der Suchschritte für die erfolglose Suche - • das Auffinden des ersten freien Platzes entspricht den • Einfügekosten • Grenzwerte:best case worst case • Sn = 1 Sn = n • Un = 1 Un = n + 1 Analyse des Hashing
Modell für das lineare Sondieren • sobald eine gewisse Größe überschreitet, verschlechtert • sich das Zugriffsverhalten sehr stark. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 • Je länger eine Liste ist, um so schneller wird sie noch • länger werden. • Zwei Listen können zusammen wachsen (Platz 3 und 14), • so dass durch neue Schlüssel eine Art Verdopplung der • Listenlänge eintreten kann.
Ergebnisse für das lineare Sondieren nach Knuth 1 1 - Sn 0.5 1 + ------- n m mit 0 = --- < 1 1 ( 1 - )2 Un 0.5 1 + --------- Abschätzung für offene Hash-Verfahren mit optimierter Kollisions-Behandlung (gleichmäßige HT-Verteilung von Kollisionen) 1 1 - 1 Sn ~ - --- * ln ( 1 - ) Un ~ --------
Anzahl der Suchschritte in HT Sn, Un Sn, Un 10 9 8 7 6 5 4 3 2 1 10 9 8 7 6 5 4 3 2 1 Un Sn Un Sn 0.1 0.3 0.5 0.7 0.9 0.1 0.3 0.5 0.7 0.9 bei „unabhängiger“ Kollisions-Auflösung bei linearem Sondieren
Modell für separate Überlaufbereiche • Annahme:n Schlüssel verteilen sich gleichförmig über die m möglichen Ketten. • Jede Synonym-Kette hat also im Mittel n/m = Schlüssel. • Wenn der i-te Schlüssel Ki in HT eingefügt wird, sind in jeder Kette ( i -1 ) / m Schlüssel. Die Suche nach Ki kostet also 1 + ( i -1 ) / m Schritte, da Ki an das jeweilige Ende einer Kette angehängt wird. • Erwartungswert für erfolgreiche Suche: Analyse des Hashing (2) n i - 1 m n - 1 2 * m 2 Sn = 1/n * 1 + ------ = 1 + ------- 1 + - i = 1
durchlaufen werden. Die Kostenformel hat somit folgende Struktur: Un = 1 + 1 * WS ( zu einer Hausadresse existiert ein Überläufer) + 2 * WS (zu einer Hausadresse existieren zwei Überläufer) + 3 * ..... Un - e - Bei der erfolglosen Suche muss immer die ganze Kette 0.5 0.75 1 1.5 2 3 4 5 Sn 1.25 1.37 1.5 1.75 2 2.5 3 3.5 Un 1.11 1.22 1.37 1.72 2.14 3.05 4.02 5.01
Separate Kettung ist auch der „unabhängigen“ Kollisions-Auflösung überlegen. Hashing ist i. a. ein sehr leistungsstarkes Verfahren. Selbst bei starker Überbelegung ( > 1 ) erhält man bei separater Kettung noch günstige Werte.
Wachstumsprobleme bei statischen Verfahren • Statische Allokation von Speicherbereichen: • Speicherausnutzung ? • Bei Erweiterung des Adressraumes: Rehashing • ==> Kosten, Verfügbarkeit, Adressierbarkeit Dynamische Hash-Verfahren S A‘ A h h‘ ==> alle Sätze erhalten eine neue Adresse
Eine im Vergleich zu statischen Hashing dynamische • Struktur erlaubt Wachstum und Schrumpfung des • Hash-Bereichs ( Datei ) • keine Überlauftechniken • Zugriffsfaktor 2 für direkte Suche • Viele konkurrierende Ansätze • Extendible Hashing ( Fagin et al., 1978 ) • Virtual Hashing und Linear Hashing ( Letwin, 1978, 1980 ) • Dynamic Hashing (Larson, 1978 ) Entwurfsziele
Hash-Funktion • berechnet Speicheradresse des Satzes • zielt auf bestmögliche Gleichverteilung der Sätze im • Hash-Bereich • Hashing bietet im Vergleich zu Bäumen eine eingeschränkte • Funktionalität • direkter Schlüsselzugriff • i. a. kein sortiert sequentieller Zugriff • ordnungserhaltendes Hashing nur in Sonderfällen • anwendbar • statisches Verfahren Zusammenfassung
Idealfall: Direkte Adressierung • nur in Ausnahmefällen möglich ( „dichte“ Schlüsselmenge) • jeder Satz kann mit einem Zugriff referenziert, eingefügt • oder gelöscht werden • Hash-Verfahren im Hauptspeicher • Standard: Divisions-Rest-Verfahren • bei offenen Hash-Verfahren ist der • Belegungsgrad 0.85 • dringend zu empfehlen • Kollisionsbehandlung mit separatem Überlaufbereich • i. a. effizienter und einfacher zu realisieren
Erweiterungen: dynamische Hashing-Verfahren • Reorganisationsfreiheit • Viele Vorschläge: Erweiterbares Hashing, • Lineares Hashing, ... • ( 2 Seitenzugriffe )