Fibonacci-Suche Grundidee wie bei der Binärsuche, aber andere Aufteilung Fibonacci-Zahlen: Informatik I • F0 = 0 Einführung • F1 = 1 • Fm = Fm−1 + Fm−2 für m ≥ 2 Rainer Schrader vereinfachende Annahme: Sei n = Fm − 1 für geeignetes m. Beispiel für (m = 8): Zentrum für Angewandte Informatik Köln 24. Mai 2005 1 8 = i = Fm−2 20 = n = Fm − 1 12 = Fm−1 − 1 Elemente 7 = Fm−2 − 1 Elemente 2 / 43 1 / 43 Fibonacci-Suche Fibonacci-Suche Beispiel für (m = 8): 1 1 8 = i = Fm−2 7 = Fm−2 − 1 Elemente 8 = i = Fm−2 20 = n = Fm − 1 20 = n = Fm − 1 12 = Fm−1 − 1 Elemente 7 = Fm−2 − 1 Elemente 12 = Fm−1 − 1 Elemente Suche im rechten Bereich: Fibonacci-Suche 1 5 = i = Fm−3 12 = Fm−1 − 1 Sei n = Fm − 1 für geeignetes m ≥ 2. • • setze i = Fm−2 7 = Fm−2 − 1 Elemente 4 = Fm−3 − 1 Elemente vergleiche den zu suchenden Schlüssel k mit A(i ): • ist A(i ) = k : Suche erfolgreich • ist A(i ) > k : durchsuche den linken Bereich mit Fm−2 − 1 Elementen Suche im linken Bereich: • ist A(i ) < k : durchsuche den rechten Bereich mit Fm−1 − 1 Elementen 1 3 = i = Fm−4 2 = Fm−4 − 1 Elemente 3 / 43 7 = Fm−2 − 1 4 = Fm−3 − 1 Elemente 4 / 43 Fibonacci-Suche Fibonacci-Suche fibonacci_search(A, m, k) // sucht nach Position mit Schlüssel k // im Bereich A[1..n] mit (n = F(m)-1), // liefert 0, falls nicht vorhanden programmtechnische Umsetzung Sei n = Fm − 1 für geeignetes m ≥ 2. • • wir speichern ein Paar (f1 , f2 ) = (Fm−3 , Fm−2 ) • Suche im rechten Intervall der Länge Fm−1 − 1: pos = -1; f1 = F(m-3); f2 = while (pos < 0) do if (k > A(i)) // if (f1 = 0) // then pos = 0 else i = i + f1 t = f1 f1 = f2 - f1 f2 = t end if // else if (k < A(i)) if (f2 = 1) // then pos = 0 else i = i - f1 f2 = f2 - f1 f1 = f1 -f2 end if else pos = i // end if return pos wir testen an der Stelle f2 • ist f1 = 0 : • ; Fm−3 = 0, Fm−2 = 1 • ; Fm−1 − 1 = Fm−3 + Fm−2 − 1 = 0, d.h. die Suche bricht ab. • andernfalls merken wir uns (f10 , f20 ) = (Fm−4 , Fm−3 ) = (f2 − f1 , f1 ) • Suche im linken Intervall der Länge Fm−2 − 1 : • ist f2 = 1, so ist Fm−2 − 1 = 0, d.h. die Suche bricht ab. • andernfalls merken wir uns (f10 , f20 ) = (Fm−5 , Fm−4 ) = (2f1 − f1 , f2 − f1 ) F(m-2); i = f2; Durchsuche den oberen Bereich nicht vorhanden Dursuche den unteren Bereich nicht vorhanden gefunden 7 / 43 5 / 43 Analyse der Fibonacci-Suche • • • • Analyse der Fibonacci-Suche wir starten mit einem Intervall der Länge Fm − 1 das nächste Intervall hat eine Länge von höchstens Fm−1 − 1 ... • Damit ist Fm ≈ c · 1.618m mit einer Konstanten c. • Für n + 1 = Fm ≈ c · 1.618m benötigen wir maximal m Vergleiche • damit folgt: Cmax (n) = Θ(log1.618 (n + 1)) = Θ(log2 n). damit benötigen wir höchstens m Schlüsselvergleiche • Lemma √ √ √ 1 h“ 1 + 5 ”m “ 1 − 5 ”m i D 1 1 + 5 m E Fm = √ − = √ ( ) . 2 2 2 5 5 Es gilt auch (ohne Beweis): Cavg (n) = Θ(log2 n). Beweis per Induktion. • Das ist die gleiche Größenordnung wie bei der Binärsuche, aber hier haben wir keine Divisionen, nur Additionen und Subtraktionen. • genauer: keine Shiftoperationen. Hierbei steht hi für kaufmännisches Runden. 8 / 43 9 / 43 Exponentielle Suche Exponentielle Suche Wenn n sehr groß ist, kann es sinnvoll sein, zunächst einen Bereich zu bestimmen, in dem ein gegebener Schlüssel liegen muß, falls er vorhanden ist. Idee der exponentiellen Suche exponential_search(A, n, k) // suche nach Position mit Schlüssel k im Bereich A[1..n] • wir verdoppeln in jedem Schritt den Suchbereich • bis wir ein Intervall gefunden haben mit A( 2i ) < k ≤ A(i ) • in diesem Intervall suchen wir binär weiter. 1 2 4 8 if ((k<A(1) or k>A(n))) return 0 else i = 1 while ((k>A(i)) und (i<n)) do i = i + i if (i>n) i = n return binary_search(A,i/2,i,k) 16 12 / 43 10 / 43 Analyse der exponentiellen Suche Interpolationssuche • Annahme: alle Schlüssel sind verschieden, positiv und ganzzahlig. • • • ; Schlüssel wachsen mindestens so schnell wie die Indizes der Elemente • ; wird in der while-Schleife d -mal verdoppelt, so gilt Sucht man im Telefonbuch den Namen „Ackermann“, so wird man vorne aufschlagen, bei „Knuth“ eher in der Mitte. j m Binärsuche: m = l + 21 (r − l ) Unter der Annahme der gleichmäßigen Verteilung der Schlüssel ersetzen wir durch die erwartete Position des Suchschlüssels 2d −1 ≤ A(2d −1 ) < k < A(2d ) • • • • • — m= l+ ; d < 1 + log k = Θ(log k ) Vergleiche. 1 2 k − A(l ) (r − l ) . A(r ) − A(l ) der Suchbereich enthält dann höchstens 2d −1 < k Schlüssel ; Θ(log k ) Vergleiche in binary_search im worst case. Es lässt sich zeigen: die Gesamtlaufzeit beträgt somit Θ(log k ). Satz sinnvoll, wenn k n gilt. Sind die n Schlüssel unabhängig und gleichverteilt aus einem Intervall I, so beträgt die mittlere Suchzeit O(log log n). Aber: worst-case Θ(n), schlechter als binäre Suche. 13 / 43 14 / 43 quadratische Binärsuche quadratische Binärsuche Versuch, unter Beibehaltung der guten mittleren Laufzeit die worst-case-Laufzeit zu verbessern. setze l := 0, r := n + 1 und rufe quadratische Binärsuche(A,k,l,r) auf. quadratische Binärsuche quadratische Binärsuche(A,k,l,r) • • • • • seien wie vorher A(1) < . . . < A(n) führe lineare Suche auf diesen Subintervallen duch und bestimme das Subintervall, in dem x liegen müßte (3) ist k > A(aktuell ), setze l := aktuell + 1 • wende das Verfahren rekursiv auf dieses Subintervall an. √ √ (5) bestimme durch lineare Suche ein i mit A(l + d(i − 1) n e) ≤ k ≤ A(l + di n e) zusätzlich A(0) < A(1), A(n + 1) > A(n) k −A(l ) (1) setze aktuell := l + d A(r )−A(l ) · (r − l )e führe einen Schritt der Interpolationssuche aus teile das verbleibende Suchintervall in Subintervalle der Größe √ (2) ist k = A(aktuell ), stop. n auf (4) ist k < A(aktuell ), setze r := aktuell − 1 (6) wende Verfahren rekursiv an auf das Intervall √ [l + d(i − 1) n e, √ n √ n √ n √ n l + di √ n e]. √ n 15 / 43 16 / 43 quadratische Binärsuche quadratische Binärsuche Satz Unter der obigen Voraussetzung betragen die mittleren Kosten Tavg (n) der quadratischen Binärsuche O(log log n). Es läßt sich zeigen: Lemma Beweis: √ Es gilt: Tavg (1) ≤ 1, Tavg (2) ≤ 2 und Tavg (n) ≤ C + Tavg ( n) für n ≥ 3. Seine die Schlüssel unabhängig und gleichverteilt über (A(0), A(n + 1)). Dann ist die mittlere Anzahl C der Vergleiche pro Programmaufruf der quadratischen Binärsuche höchstens 3. Wir zeigen per Induktion Tavg (n) ≤ 2 + C log log n: Tavg (n + 1) ≤ C + Tavg ( 1 • das Verfahren findet im Mittel nach drei Schritten das Intervall der √ Größe p n + 1) = C + 2 + C log log((n + 1) 2 ) n, in dem k liegen müßte. 1 log(n + 1) 2 = 2 + C log log(n + 1) = C + 2 + C log • Daraus läßt sich die mittlere Laufzeit abschätzen: √ 1 1 Im schlimmsten Fall beträgt die Suchzeit n 2 + n 4 + . . . = O( n) Einheiten. 17 / 43 18 / 43 quadratische Binärsuche exponentielle und binäre Suche setze l := 0, r := n + 1 und rufe quadratische Binärsuche(A,k,l,r) auf. 2. Versuch, unter Beibehaltung der guten mittleren Laufzeit die worst-case-Laufzeit zu verbessern. quadratische Binärsuche(A,k,l,r) exponentielle und binäre Suche • • • • • • • k −A(l ) (1) setze aktuell := l + d A(r )−A(l ) · (r − l )e seien wie vorher A(1) < . . . < A(n) (2) ist k = A(aktuell ), stop. zusätzlich A(0) < A(1), A(n + 1) > A(n) (3) ist k > A(aktuell ), setze l := aktuell + 1 führe einen Schritt der Interpolationssuche aus (4) ist k < A(aktuell ), setze r := aktuell − 1 zerlege √ √ Suchintervall in Subintervalle der Größe √ das√verbleibende n, 2 n, 4 n, . . . 2i n auf √ teile das verbleibende Suchintervall in Subintervalle der Größe n auf (5) bestimme: √ √ S(l + 2i −1 n) < k ≤ S(l + 2i n). √ √ (6) auf dem Intervall [l + 2i −1 n, l + 2i n] bestimme j durch binäre Suche mit führe binäre Suche auf diesen Subintervallen duch und bestimme das Subintervall, in dem x liegen müßte √ √ S(l + (j − 1) n) < k ≤ S(l + j n). wende das Verfahren rekursiv auf dieses Subintervall an. √ √ (7) Wende Verfahren rekursiv auf das Intervall [l + (j − 1) n, l + j n] an 19 / 43 20 / 43 exponentielle und binäre Suche selbstorganisierende Listen Szenario: • Datenstruktur: verkettete, unsortierte Listen • wiederholte Suchanfragen • unterschiedliche Häufigkeiten für die Schlüssel Satz Unter der obigen Voraussetzung gilt für die exponentielle und binäre Suche: • • die Anzahl der Vergleiche im schlechtesten Fall ist O(log n) die Anzahl der Vergleiche im Mittel ist O(log log n) ( da log i ≤ i ) • organisiere Liste so um, dass • häufige Anfragen am Anfang der Liste 21 / 43 22 / 43 selbstorganisierende Listen Suchverfahren Stragien: • zur Suche haben wir bisher lediglich Vergleichsoperationen auf den Schlüsseln zugelassen • • wir wollen jetzt arithmetische Operationen auf den Schlüsseln erlauben, Bei jeder Anfrage an einen Schlüssel: • vertausche Listenelement mit Element davor, oder • erhöhe einen Anfragenzähler und sortiere nach fallendem Zähler, oder • setze Element an die Spitze der Liste. um daraus die mögliche Position des Datums zu berechnen. 24 / 43 23 / 43 Hashverfahren Anwendungen: Symboltabellen für Compiler (Schlüssel: Bezeichner (Identifiers)) • Bezeichner dürfen 80 Zeichen lang sein • das erste Zeichen muss ein Buchstabe sein • die folgenden Zeichen können auch Sonderzeichen sein Hashverfahren • zulässige Bezeichner: 26 · 3779 • davon kommen in einem Programm nur k ≪ 26 · 3779 vor gesucht: Datenstruktur, • die Suchen, Einfügen, Entfernen effizient unterstützt • deren Größe nur von k abhängt 25 / 43 26 / 43 Hashverfahren Hashverfahren Idee: „verallgemeinerte Felder“ gegeben: Datensätze • • • • • Datensätze werden über Schlüssel identifiziert • zu unterstützende Operationen: Suchen, Einfügen, Entfernen • bisher: lineare geordnete Listen • Suchen mittels Schlüsselvergleichen • jetzt: wir verwenden ein Feld T (m) (Hashtabelle) m ist die Größe der Hashtabelle dem Schlüssel k wird eine Adresse h(k ) in T zugeordnet aber 2 Schlüssel können auf dieselbe Adresse abgebildet werden (Kollisionen) Kollisionen: • Adressen werden durch eine arithmetische Berechnung ermittelt • jeder Schlüssel k wird über eine Adresse h(k ) angesprochen • müssen behandelt werden, • sollen möglichst selten auftreten, • sollen möglichst effizient aufgelöst werden. 28 / 43 27 / 43 Hashverfahren Hashverfahren Szenario • die Schlüssel entstammen einer (im allgemeinen großen) Menge U Inhaltsangabe: (Universum) • • • • • Die Wahl der Hashfunktion • Kollisionsbehandlungen 29 / 43 die zu speichernden Schlüssel K bilden eine Teilmenge von U üblicherweise ist |K | |U | die Größe m der Hashtabelle wird vorab festgelegt, oft ist m ≤ |K | ebenso die Hashfunktion h : U → {0, . . . m − 1} 30 / 43 Direkte Adressierung Direkte Adressierung Illustration: (m = 10) der einfachste Fall: (alle möglichen Schlüssel) 6 1 • • • • • • 5 8 T (k ) zeigt auf einen Datensatz mit Schlüssel k suche(T,k): gib T (k ) zurück füge_ein(T,x): lösche(T,x): 2 10 2 verwende Feld T [0, . . . , m − 1] (Adresstabelle) 10 3 K (aktulle Schlüssel) Implementierung der Operationen: 0 10 U • Alle Schlüssel sind verschieden. • U = {0, 1, . . . , m − 1}. 5 8 3 7 alle diese Operationen benötigen im worst-case O(1) Zeit. 0 9 2 3 4 5 7 8 9 10 T (x .key ) = nil 1 6 10 10 T (x .key ) zeigt auf x 4 32 / 43 31 / 43 Hashtabellen Hashtabellen Illustration: • im Allgemeinen gilt jedoch: |K | |U | 0 U 1 • Ziele: h(k1 ) • Θ(|K |) Speicherplatz • Suche im Durchschnitt in Θ(1) Zeit h(k4 ) K k3 k1 • Methode: Benutzung einer Hashfunktion h(k2 ) = h(k5 ) k4 h(k3 ) k2 h : U → {0, 1, 2, . . . , m − 1} k5 33 / 43 m−1 34 / 43 Hashtabellen Wahl der Hashfunktion • • • • • Probleme: Was zeichnet eine gute Hashfunktion aus? Ziel: Die Hashadressen sollen gleichverteilt in {0, 1, . . . , m − 1} sein. Die Verteilung der Adressen hängt ab von der Verteilung der Schlüssel. Sei p(k ) die Wahrscheinlichkeit, daß Schlüssel k ∈ U vorkommt. Gleichverteilungsziel: (1) Wie soll man die Hashfunktion wählen? X (2) Wie geht man mit Kollisionen um? p(k ) = k : h(k )=j 1 m für j = 0, 1, . . . , m − 1. • Beispiel: Schlüssel sind reelle Zahlen, unabhängig gleichverteilt im Intervall [0, 1). Dann erfüllt h(k ) = bkmc das Gleichverteilungsziel. • Problem: Im Allgemeinen kennen wir p(k ) nicht. 35 / 43 36 / 43 Wahl der Hashfunktion Wahl der Hashfunktion Divisionsmethode h(k ) = k mod m • Generalannahme: Die Schlüssel sind nichtnegative ganze Zahlen. Eigenschaften • Es ist immer möglich, Schlüssel so zu interpretieren, z.B. (1) h(k ) kann schnell berechnet werden. • Charakterstrings: Jedem Zeichen entspricht im ASCII-Code eine Zahl im Bereich [0, . . . , 127], z.B. • p= ˆ 112, t= ˆ 116 ⇒ (2) Die richtige Wahl von m (Tabellengröße) ist sehr wichtig. pt = ˆ 112 × 128 + 116 = 14452 Beispiele • Aufteilung in männlich/weiblich • Aufteilung nach Wochentagen der Geburt • Aufteilung nach Geburtstagen 37 / 43 38 / 43 Divisionsmethode Divisionsmethode zu (2): Man sollte vermeiden: • m = 2i : alle bis auf die letzten Binärziffern werden ignoriert. • m = 10i : analog bei Dezimalzahlen. • m = r i : analog bei r -adischen Zahlen. allgemein gilt: • sei A ein Alphabet mit den Buchstaben 0, . . . , 2p − 1 • sei k eine Zeichenkette über A, k 0 eine Permutation von k • dann ist: k ≡ k 0 mod 2p . • m = r i ± j für kleines j : • z.B. m = 27 − 1 = 127: • pt = (112 · 128 + 116) mod 127 = 14452 mod 127 = 101 Übungsaufgabe: warum? • tp = (116 · 128 + 112) mod 127 = 14960 mod 127 = 101 • dasselbe passiert, wenn in einer längeren Zeichenkette zwei Buchstaben vertauscht werden 39 / 43 40 / 43 Divisionsmethode Wahl der Hashfunktion Multiplikationsmethode i • Gute Wahl: Primzahl m, die kein r ± j , j klein, teilt. Sei 0 < A < 1. Setze: h(k ) = bm(k · A mod 1)c = bm (k · A − bk · Ac)c | {z } • Praktisch bewährt ∈[0,1) • Beispiel: Die Hashtabelle soll ca. 1000 Einträge aufnehmen, die Schlüssel sind Zeichenketten, interpretiert als 2-adische Zahlen. Eigenschaften Gute Wahl: m = 701, da 29 = 512 und 210 = 1024. (1) Die Wahl von m ist unkritisch. (2) Wir erhalten eine gleichmäßige Verteilung für U = {1, 2, . . . , n} bei einer guten Wahl von A. 41 / 43 42 / 43 Multiplikationsmethode zu (2): Irrationale Zahlen sind eine gute Wahl, denn: Satz Sei ξ eine irrationale Zahl. Plaziert man die Punkte ξ − bξc, 2ξ − b2ξc, . . . , nξ − bnξc in das Intervall [0, 1], dann haben die n + 1 Intervallteile höchstens drei verschiedene Längen. Außerdem fällt der nächste Punkt (n + 1)ξ − b(n + 1)ξc in einen der größten Intervallteile. 43 / 43