Algorithms & Data Structures 2 Fast Searching WS2017 B. Anzengruber-Tanase (Institute for Pervasive Computing, JKU Linz) (Institute for Pervasive Computing, JKU Linz)
SEQUENTIELLE SUCHE Suche nach Element mit Schlüsselwert k Falls nicht bekannt, ob die Elemente der Liste nach ihren Schlüsselwerten sortiert sind, besteht nur die Möglichkeit, die Liste sequentiell zu durchlaufen und elementweise zu überprüfen (sequentielle bzw. lineare Suche) Erfolglose Suche erfordert N Schleifendurchläufe Erfolgreiche Suche verlangt im ungünstigsten Fall N - 1 Schleifendurchläufe (und N Schlüsselvergleiche) mittlere Anzahl von Schleifendurchläufen bei erfolgreicher Suche: Algorithms & Datastructures 2 // 2017W // 2
BINÄRE SUCHE Auf sortierten Listen können Suchvorgänge effizienter durchgeführt werden Suche nach Schlüssel k in Liste mit aufsteigend sortierten Schlüsseln: 1. Falls Liste leer ist, endet die Suche erfolglos. Sonst: Betrachte Element Inhalt[m] an mittlerer Position m. 2. Falls k = Inhalt[m].key dann ist das gesuchte Element gefunden. 3. Falls k < Inhalt[m].key, dann durchsuche die linke Teilliste von Position 1 bis m-1 nach demselben Verfahren. 4. Sonst (k > Inhalt[m].key) durchsuche die rechte Teilliste von Position m + 1 bis Listenende nach demselben Verfahren. Algorithms & Datastructures 2 // 2017W // 3
BINÄRE SUCHE 2 4 5 7 8 9 12 14 17 19 22 25 27 28 33 37 low mid high 2 4 5 7 8 9 12 14 17 19 22 25 27 28 33 37 Schränke den zu durchsuchenden Bereich Schritt für Schritt ein Divide-and-Conquer Beispiel: findelement(22) low mid high 2 4 5 7 8 9 12 14 17 19 22 25 27 28 33 37 low midhigh 2 4 5 7 8 9 12 14 17 19 22 25 27 28 33 37 low = mid = high Algorithms & Datastructures 2 // 2017W // 4
BINÄRE SUCHE Pseudocode Algorithm BinarySearch(S, k, low, high) if low > high then return NO_SUCH_KEY else mid (low+high)/2 if k = key(mid) then return key(mid) else if k < key(mid)then return BinarySearch(S, k, low, mid-1) else return BinarySearch(S, k, mid+1,high) Algorithms & Datastructures 2 // 2017W // 5
BINÄRE SUCHE :: LAUFZEIT Nach jedem Vergleich halbiert sich die Größe des zu durchsuchenden Bereiches: In einer Array-basierten Implementierung erfordert der Zugriff auf ein bestimmtes Element nach seiner Position O(1) daher Laufzeit der Binären Suche: C min ( N ) = 1 C max ( N ) = [ log 2 (N+1)] C avg ( N ) log 2 (N+1) - 1, für große N Vergleich Suchbereich 0 N 1 N/2 2 N/4...... 2 i N/2 i log 2 N 1 Algorithms & Datastructures 2 // 2017W // 6
FIBONACCI SUCHE Ähnlich der Binärsuche, jedoch wird Suchbereich entsprechend der Folge der Fibonacci-Zahlen geteilt. Definition der Fibonacci-Zahlen F 0 = 0 F 1 = 1 F m = F m-1 + F m-2 für m >= 2. Teilung einer Liste mit N = F m -1 sortierten Elementen: 1 i N F m-2-1 F m-1-1 F m - 1 Algorithms & Datastructures 2 // 2017W // 7
FIBONACCI SUCHE Element an der Position i = F m-2 wird mit dem Schlüssel k verglichen Wird Gleichheit festgestellt, endet die Suche erfolgreich. Ist k größer, wird der rechte Bereich mit F m-1-1 Elementen, ansonsten der linke Bereich mit F m-2-1 Elementen, auf dieselbe Weise durchsucht. Kosten für N = F m -1 sind im schlechtesten Fall m-2 Suchschritte notwendig, d. h. O (m) Schlüsselvergleiche Da gilt F m c + 1.618 m, folgt C max ( N ) = O ( log 1.618 ( N+1 ) ) = O (log 2 N) Algorithms & Datastructures 2 // 2017W // 8
SPRUNGSUCHE Prinzip Zunächst wird der sortierte Datenbestand in Sprüngen überquert, um den Abschnitt zu lokalisieren, der ggf. den gesuchten Schlüssel enthält, danach wird der Schlüssel im gefundenen Abschnitt nach irgendeinem Verfahren gesucht. L............... 1 m 2m 3m N Algorithms & Datastructures 2 // 2017W // 9
EINFACHE SPRUNGSUCHE konstante Sprünge zu Positionen m, 2*m, 3*m,... Sobald k <= L[i].key mit i = j*m für (j = 1, 2,...), wird im Abschnitt L[m(j-1) +1] bis L[j*m] sequentiell nach dem Suchschlüssel k gesucht. Mittlere Suchkosten ein Sprung koste a; ein sequentieller Vergleich b Einheiten Optimale Sprungweite bzw. falls a = b Komplexität Algorithms & Datastructures 2 // 2017W // 10
EINFACHE SPRUNGSUCHE konstante Sprünge zu Positionen m, 2*m, 3*m,... Sobald k <= L[i].key mit i = j*m für (j = 1, 2,...), wird im Abschnitt L[m(j-1) +1] bis L[j*m] sequentiell nach dem Suchschlüssel k gesucht. Mittlere Suchkosten ein Sprung koste a; ein sequentieller Vergleich b Einheiten Optimale Sprungweite bzw. falls a = b Komplexität Algorithms & Datastructures 2 // 2017W // 11
ZWEI-EBENEN-SPRUNGSUCHE statt sequentieller Suche im lokalisierten Abschnitt wird wiederum eine Quadratwurzel-Sprungsuche angewendet, bevor dann sequentiell gesucht wird Mittlere Kosten: a Kosten eines Sprungs auf der ersten Ebene; b Kosten eines Sprungs auf der zweiten Ebene; c Kosten für einen sequentiellen Vergleich Für a = b = c ergibt sich: Algorithms & Datastructures 2 // 2017W // 12
ZWEI-EBENEN-SPRUNGSUCHE Verbesserung durch optimale Abstimmung der Sprungweiten m 1 und m 2 der beiden Ebenen Mit a = b = c ergeben sich als optimale Sprungweiten m 1 = N 2/3 und m 2 = N 1/3 mittlere Suchkosten: Verallgemeinerung zu n-ebenen-verfahren ergibt ähnlich günstige Kosten wie Binärsuche (Übereinstimmung bei log 2 n Ebenen) Sprungsuche vorteilhaft, wenn Binärsuche nicht anwendbar ist (z. B. bei blockweisem Einlesen der sortierten Sätze vom Externspeicher) Algorithms & Datastructures 2 // 2017W // 13
EXPONENTIELLE SUCHE Anwendung wenn Länge des sortierten Suchbereichs zunächst unbekannt bzw. sehr groß ist. Vorgehensweise für Suchschlüssel k wird zunächst obere Grenze für den zu durchsuchenden Abschnitt bestimmt i := 1; while k > L[i].key do i = 2i; Für i > 1 gilt für den auf diese Weise bestimmten Suchabschnitt L[i DIV 2].key < K <= L[i].key Suche innerhalb des Abschnitts mit irgendeinem Verfahren Algorithms & Datastructures 2 // 2017W // 14
EXPONENTIELLE SUCHE Sind in der sortierten Liste nur positive, ganzzahlige Schlüssel ohne Duplikate gespeichert, wachsen Schlüsselwerte mindestens so stark wie die Indizes der Elemente. i wird höchstens log 2 k mal verdoppelt Bestimmung des gesuchten Intervalls erfordert maximal log 2 k Schlüsselvergleiche Suche innerhalb des Abschnitts (z. B. mit Binärsuche) erfordert auch höchstens log 2 k Schlüsselvergleiche Gesamtaufwand O (log 2 k) Algorithms & Datastructures 2 // 2017W // 15
INTERPOLATIONSSUCHE Schnellere Lokalisierung des Suchbereichs indem Schlüsselwerte selbst betrachtet werden, um Abstand zum Schlüssel k abzuschätzen nächste Suchposition pos wird aus den Werten ug und og = der Unter- und Obergrenze des aktuellen Suchbereichs berechnet: Sinnvoll, wenn der Schlüsselwert im betreffenden Bereich einigermaßen gleichverteilt ist erfordert dann im Mittel lediglich log 2 (log 2 (N+1)) Schlüsselvergleiche Im schlechtesten Fall (stark ungleichmäßige Werteverteilung) entsteht jedoch linearer Suchaufwand => O (N) Algorithms & Datastructures 2 // 2017W // 16
DICTIONARY ADT Dictionary ist abstraktes Modell einer Datenbank Dictionary speichert Schlüssel-Element-Paare Wichtigste Operation: Suchen nach Schlüssel /** A container storing (key, value) pairs. Keys and values must not be * null, but it is allowed to store multiple pairs with equal keys. */ public interface Dictionary<K, V> { int size(); boolean isempty(); /** Find item with specified key. * @return value of item, null if not found */ V get(k key); /** Find all items with specififed key. * @return values of items, null if not found */ V[] getall(k key); /** Insert specified (key, value) pair. Both key and value must not * be null or an IllegalArgumentException will be thrown. */ void insert(k key, V value); /** Remove item with specified key and return its value. */ V remove(k key); /** Remove all items with specified key and return their values. */ V[] removeall(k key); } Algorithms & Datastructures 2 // 2017W // 17
DICTIONARY IMPLEMENTIERUNG ALS SEQUENZ ungeordnete Sequenz 34 14 12 22 18 Suchen, Löschen O(N) Einfügen O(1) array-basierte geordnete Sequenz 12 14 18 22 34 Suchen O(log N) mit Binärer Suche Einfügen, Löschen O(N) Anwendung: Look-Up-Tables (häufiges Suchen, Einfügen und Löschen dagegen selten) => Implementierung als (binäre) Suchbäume: siehe Kapitel Trees Algorithms & Datastructures 2 // 2017W // 18
SKIP-LISTEN Grundprinzip: Elemente sind nach einer Zufallsauswahl angeordnet, für die gilt, dass Suchen und Einfügen eine mittlere Laufzeit von O(log N) hat Skipliste ist eine Folge von Sequenzen {S 0, S 1, S 2,..., S h } jede Sequenz Si speichert eine Teilmenge der Einträge eines Dictionaries D in nicht-absteigender Reihenfolge zusätzliche Pseudoeinträge mit Schlüsseln - (kleiner als jeder andere Schlüssel in D) und + (größer als jeder andere Schlüssel in D) S 0 enthält alle Einträge aus D und die 2 Pseudoeinträge S h enthält nur die 2 Pseudoeinträge für i=1..h-1: Si enthält zufällig generierte Teilmenge der Einträge von S i-1 und die 2 Pseudoeinträge Auswahlprinzip: jedes Element aus S i-1 kommt mit Wahrscheinlichkeit 1/2 in S i, S i enthält daher im Mittel N/2 i Einträge h wird oft als Höhe der Skipliste bezeichnet, im Mittel h = log N Algorithms & Datastructures 2 // 2017W // 19
BEISPIEL :: SKIP-LISTEN Höhe h = 5 S 5 - + S 4-17 + S 3-17 25 55 + S 2-17 25 31 55 + S 1-12 17 25 31 38 44 55 + S 0-12 17 20 25 31 38 39 44 50 55 + Algorithms & Datastructures 2 // 2017W // 20
POSITIONEN IM ADT SKIP-LISTE Einträge sind in Levels (horizontal) und Säulen (vertikal) angeordnet Jeder Level entspricht einer Sequenz In einer Säule befindet sich jeweils der entsprechende identische Eintrag jeder Sequenz (Säulen haben unterschiedliche Höhe!) Positionen: after(p): gibt Eintrag rechts von p auf selbem Level zurück before(p): gibt Eintrag links von p auf selbem Level zurück below(p): gibt Eintrag unterhalb von p in selber Säule zurück above(p): gibt Eintrag oberhalb von p in selber Säule zurück Wenn angesprochene Position nicht mit einem Eintrag besetzt ist, wird Position null zurückgegeben Implementierung: doppelt verkettete Listen werden entlang der Säulen nochmals doppelt verkettet Zugriff auf Nachbarpositionen für gegeben Position p erfolgt dann mit O(1) Algorithms & Datastructures 2 // 2017W // 21
SUCHEN IN SKIP-LISTEN Grundprinzip: Starte Suche auf oberster Ebene der Skipliste Finde Eintrag mit größtem Schlüssel kleiner oder gleich dem gesuchten Schlüssel key Pseudocode: Algorithm SkipSearch(key) Input: zu suchender Schlüssel key Output: Position p in S für die gilt, dass Schlüssel an Position p größter Schlüssel kleiner oder gleich key ist Sei p die topmost-left Position in S while below(p) null do p below(p) {gehe nach unten} while key(after(p)) key do p after(p) {scan forward} return p Algorithms & Datastructures 2 // 2017W // 22
BEISPIEL :: SKIP-LISTEN S 5 S 4 S 3 S 2 S 1 S 0 - find(50) below(p) null 17 50 + > 50-17 below(p) null 25 50 55 > 50-17 25 55 + below(p) null 31 50 55 > 50-17 25 31 55 + below(p) null 38 50 44 50 55 > 50-12 17 25 31 38 44 55 + below(p) null 50 50 55 > 50-12 17 20 25 31 38 39 44 50 55 + Algorithms & Datastructures 2 // 2017W // 23 below(p) = null gefunden! + +
EINFÜGEN IN SKIP-LISTEN Grundprinzip: Eintrag wird auf unterster Ebene eingefügt Nach Zufallsprinzip wird entschieden, wie viele der oberen Ebenen Referenz auf Eintrag enthalten Pseudocode: Algorithm SkipInsert(key,val) Input: Eintrag (key,val) Output: keiner nach p auf selber Ebene p SkipSearch(key) q insertafterabove(p,null,(key,val)) while random() < 1/2 do while above(p) == null do p before(p) p above(p) q insertafterabove(p,q,(key,val)) {auf unterster Ebene} {scan backward} {eine Ebene nach oben} {Eintrag einfügen} verbinde nach unten Algorithms & Datastructures 2 // 2017W // 24
BEISPIEL :: EINFÜGEN IN SKIP-LISTEN SkipSearch(31) Beispiel: Füge 31 ein S 5 - insertafterabove(p,null,(31,val)) + S 4-17 + S 3-17 25 55 + S 2-17 25 55 + S 1-12 17 25 38 44 55 + S 0 Algorithms & Datastructures 2 // 2017W // 25-12 17 20 25 38 39 44 50 55 + p
BEISPIEL :: EINFÜGEN IN SKIP-LISTEN SkipSearch(31) S 5 S 4 S 3 insertafterabove(p,null,(31,val)) - + sei random() < 1/2 above(p) <> null, daher p above(p) - 17 + insertafterabove(p,q,(31,val)) sei random() >= 1/2 fertig! - 17 25 55 + S 2-17 25 55 + S 1-12 17 25 31 38 44 55 + S 0 Algorithms & Datastructures 2 // 2017W // 26-12 17 20 25 31 38 39 44 50 55 + p q
LÖSCHEN IN SKIP-LISTEN Grundprinzip Suche zu löschendes Element mit Schlüssel key falls key nicht existiert, gib NO_SUCH_KEY zurück falls key gefunden wurde, lösche Eintrag an dieser Position und alle darüber liegenden Einträge Algorithm SkipRemove(key) Input: Eintrag (key) Output: keiner p SkipSearch(key) if(p.key!= key) return(null); while p!= null do p.left.right = p.right p.right.left = p.left p above(p) {auf unterster Ebene} {not found} {eine Ebene nach oben} Algorithms & Datastructures 2 // 2017W // 27
BEISPIEL :: LÖSCHEN IN SKIP-LISTEN Beispiel: Lösche 25 S 5 - SkipSearch(25) removeallabove(p) + S 4-17 + S 3-17 25 55 + S 2-17 25 55 + S 1-12 17 25 38 44 55 + S 0 Algorithms & Datastructures 2 // 2017W // 28-12 17 20 25 38 39 44 50 55 + p
BEISPIEL :: LÖSCHEN IN SKIP-LISTEN Beispiel: Lösche 25 S 5 - + S 4-17 + S 3-17 55 + S 2-17 55 + S 1-12 17 38 44 55 + S 0-12 17 20 38 39 44 50 55 + Algorithms & Datastructures 2 // 2017W // 29
Algorithms & Data Structures 2 Fast Searching WS2017 B. Anzengruber-Tanase (Institute for Pervasive Computing, JKU Linz) (Institute for Pervasive Computing, JKU Linz)