Datenstrukturen Mariano Zelke Sommersemester 2012 Organisatorisches I Heute wird das 6. Übungsblatt ausgeteilt, das bis Donnerstag, den 5. Juli, 12:00 Uhr abzugeben ist. I Die Besprechung von Blatt 5 findet nur in der Woche 2.-6. Juli (vorletzte Vorlesungswoche) statt. I In der Woche vom 9.-13. Juli (letzte Vorlesungswoche) findet die Besprechung von Blatt 6 statt. Mariano Zelke Datenstrukturen 2/22 Organisatorisches I Am 27. Juli, 9:00 Uhr findet die Klausur in Hörsaal HV & HVI statt. I Denken Sie an eine rechtzeitige Anmeldung! I Sie dürfen ein handschriftlich beschriebenes DIN A4-Blatt als Hilfsmittel mitbringen. I Im Zeitraum vor der Klausur (17.-24. Juli) finden Helpdesk-Termine der Tutoren statt. Die genauen Termine erscheinen in Kürze auf der VL-Website. I Dort finden Sie im gleichen Zeitraum auch die Liste der Bonuspunkte für die Klausur. Prüfen Sie dann Ihren Eintrag! Mariano Zelke Datenstrukturen 3/22 Der abstrakte Datentyp Wörterbuch“ ” Ein Wörterbuch für eine gegebene Menge S besteht aus den folgenden Operationen: I insert(x): Füge x zu S hinzu, d.h. setze S = S ∪ {x}. I remove(x): Entferne x aus S, d.h. setze S = S − {x}. I lookup(x): Finde heraus, ob x in liegt, und wenn ja, greife gegebenenfalls auf den Datensatz von x zu. I In einer Firmendatenbank werden Kundendaten in der Form (Kundenummer, Info) abgespeichert. Die Kundennummer stellt den Schlüssel x dar. I I I I Mariano Zelke insert(x): Füge den Datensatz eines neuen Kunden mit Kundenummer x ein. remove(x): Entferne den Datensatz des entsprechenden Kunden. lookup(x): Greife auf den Datensatz des Kunden mit Kundennummer x zu. Datenstrukturen 4/22 Datenstrukturen für Wörterbücher I Wie sollten statische Wörterbücher, also Wörterbücher die nur lookup benutzen, implementiert werden? I I Wir könnten die Menge der n gespeicherten Schlüssel zuerst sortieren. Eine lookup-Operation gelingt dann in logarithmischer Zeit durch binäre Suche. Oder aber wir haben noch mehr Glück und haben eine schnell berechenbare Namensfunktion, die für jeden Schlüssel die Position des Schlüssels bestimmt. Leider sind die interessanten Wörterbücher dynamisch. I Könnten wir Heaps benutzen? Das Einfügen gelingt mühelos, aber schon das Suchen ist extrem mühselig. Wir benötigen eine Datenstruktur, die schnell durchsuchbar und an beliebigen Stellen modifizierbar ist. Mariano Zelke Datenstrukturen 5/22 Binäre Suchbäume T sei ein geordneter binärer Baum. Jeder Knoten v von T speichert ein Paar daten(v ) = (Schlüssel(v ), Info(v )). T heißt binärer Suchbaum, wenn T die folgenden Eigenschaften hat: (a) Für jeden Schlüsselwert x gibt es höchstens einen Knoten v mit Schlüssel (v ) = x. (b) Für jeden Knoten v , jeden Knoten vlinks im linken Teilbaum von v und jeden Knoten vrechts im rechten Teilbaum von v gilt Schlüssel(vlinks ) < Schlüssel(v ) < Schlüssel(vrechts ). Binäre Suchbäume unterstützen die binäre Suche! Mariano Zelke Datenstrukturen 6/22 Operation lookup(x) lookup(x) sucht im Binären Suchbaum folgendermaßen nach dem Knoten mit Schlüssel x: (1) Sei r die Wurzel des binären Suchbaums. Setze v = r . /* Wir beginnen die Suche an der Wurzel. */ (2) Wenn wir am Knoten v angelangt sind, vergleichen wir x mit Schlüssel(v ): I I I x = Schlüssel(v ): Wir haben den Schlüssel gefunden. x < Schlüssel(v ): Wir suchen im linken Teilbaum weiter. x > Schlüssel(v ): Diesmal muss im rechten Teilbaum weitergesucht werden. Lookup benötigt Zeit Θ(t), wobei t die Tiefe des Knoten ist, der den Schlüssel x speichert. Mariano Zelke Datenstrukturen 7/22 Binäre Suchbäume: Insert Suche zuerst nach x. Sollten wir x finden, überschreibe den alten Info-Teil; sonst füge den Schlüssel dort ein, wo die Suche scheitert. void bsbaum::insert (schluesseltyp x, infotyp info){ Knoten *Vater, *Zeiger; Vater = Kopf; Zeiger = Kopf->rechts; while ((Zeiger != 0) && (x != Zeiger->schluessel)){ Vater = Zeiger; if (x < Zeiger->schluessel) Zeiger = Zeiger->links; else Zeiger = Zeiger->rechts; } if (Zeiger == 0){ Zeiger = new Knoten (x, info, 0, 0); if (x < Vater->schluessel) Vater->links = Zeiger; else Vater->rechts = Zeiger; } else Zeiger->info = info; } Mariano Zelke Datenstrukturen 8/22 Binäre Suchbäume: Remove Zuerst suche den Schlüssel x. Wenn die Suche im Knoten v endet: I Wenn v ein Blatt ist: Entferne v . I Wenn v genau ein Kind w hat: Entferne v und mache den Vater von v zum Vater von w . Wenn v zwei Kinder hat: Ersetze v durch den kleinsten Schlüssel s im rechten Teilbaum von v . I I I I Mariano Zelke Der Knoten u speichere den Schlüssel s. u ist als linkester Knoten im rechten Teilbaum leicht zu finden. u hat kein linkes Kind und kann damit sofort entfernt werden. Datenstrukturen 9/22 Die Operationen eines binären Suchbaumes I I Die Operationen insert und remove beginnen mit einer Suche nach dem Schlüssel. remove setzt den Suchprozess mit einer Suche nach dem kleinsten Schlüssel im rechten Teilbaum fort. Wir können mit binären Suchbäumen auch sortieren: I I I (Link) Zuerst füge alle Schlüssel in einen leeren Suchbaum ein. Danach bestimme die sortierte Reihenfolge durch einen Inorder-Durchlauf. lookup, insert und remove benötigen Zeit höchstens Tiefe des Baums. Aber: Die Tiefe kann sehr groß werden: Die Folge insert(1,info), insert(2,info), insert(3,info), ..., insert(n, info) erzeugt einen Baum der (maximalen) Tiefe n − 1. Die minimale Tiefe ist blog2 nc, die maximale Tiefe n − 1. Wie groß ist die erwartete Tiefe? Mariano Zelke Datenstrukturen 10/22 Erwartete vs. maximale Tiefe I Die erwartete Zeit für eine erfolgreiche Suche ist beweisbar logarithmisch. Es kann sogar gezeigt werden, dass die erwartete Tiefe logarithmisch ist. Also ist die erwartete Zeit für lookup, insert und remove logarithmisch. I Trotzdem ist die worst-case Laufzeit intolerabel. I I Mariano Zelke Wir arbeiten weiter mit binären Suchbäumen, garantieren aber durch zusätzliche Operationen, dass der Baum tiefen-balanciert bleibt. Datenstrukturen 11/22 AVL-Bäume (Link) Entwickelt 1962 von G. M. Adelson-Velski and E. M. Landis. Ein binärer Suchbaum heißt AVL-Baum, wenn für jeden Knoten v mit linkem Teilbaum TL (v ) und rechtem Teilbaum TR (v ) gilt: | Tiefe(TL (v )) − Tiefe(TR (v )) | ≤ 1 b(v ) := Tiefe(TL (v )) − Tiefe(TR (v )) ist der Balance-Grad von v . Für AVL-Bäume ist stets b(v ) ∈ {−1, 0, 1}. Die zentralen Fragen: I Können wir stets Schlüssel so einfügen, dass der Absolutbetrag des Balance-Grads höchstens Eins ist? I Wie tief kann ein AVL-Baum mit n Knoten werden? Mariano Zelke Datenstrukturen 12/22 Beispiele und Gegenbeispiele AVL-Bäume und sind AVL-Bäume. und Auch das ist ein AVL-Baum: Balancegrad = −1 X Dies allerdings ist kein AVL-Baum: Mariano Zelke Balancegrad = −2 × Datenstrukturen 13/22 Die Tiefe von AVL-Bäumen min(t) sei die minimale Knotenzahl, die ein AVL-Baum der Tiefe t mindestens besitzen muss. I min(0) = 1 und min(1) = 2. I Und es gilt die Rekursion min(t) = min(t − 1) + min(t − 2) + 1. Wenn ein AVL-Baum die Tiefe t besitzt, dann muss ein Teilbaum die Tiefe t − 1 besitzen und hat mindestens min(t − 1) Knoten. Der andere Teilbaum hat mindestens Tiefe t − 2 und besitzt deshalb mindestens min(t − 2) Knoten. Es gilt min(t) ≥ 2t/2 . Die Tiefe eines AVL-Baums mit n Knoten ist deshalb höchstens 2 · log2 n. I Die Behauptung ist richtig für t = 0 und t = 1. I min(t + 1) = min(t) + min(t − 1) + 1 ≥ 2t/2 + 2(t−1)/2 + 1 ≥ 2 · 2(t−1)/2 = 2(t+1)/2 . Mariano Zelke Datenstrukturen 14/22 Lookup, Remove und Insert I I Da AVL-Bäume logarithmische Tiefe haben, ist die Laufzeit einer lookup-Operation höchstens logarithmisch. Wir drücken uns um die remove-Operation herum: I I Wir führen nur eine lazy remove Operation durch und markieren einen gelöschten Knoten als entfernt ohne ihn tatsächlich zu entfernen. Wenn allerdings mehr als 50 % aller Knoten markiert sind, dann beginnt ein Großreinemachen: Ein neuer AVL-Baum wird aus den nicht markierten Knoten des alten Baumes durch Insert-Operationen aufgebaut. Die Laufzeit für den Neuaufbaus ist groß, aber gegen die vielen blitzschnellen remove-Operationen amortisiert. Kritisch ist die Implementierung der insert-Operation. Mariano Zelke Datenstrukturen 15/22 Rotationen (Link) In einer Linksrotation ersetzt ein rechtes Kind den Vater. Der Vater wird zum linken Kind. v w w v T1 T3 T2 T3 T1 T2 Rechtsrotationen sind entsprechend definiert. v w w v T3 T1 T1 T2 T2 T3 Die Ordnungseigenschaft binärer Suchbäume bleibt erhalten. Mariano Zelke Datenstrukturen 16/22 Die Insert-Operation Um den Schlüssel x einzufügen, suche zuerst nach x und füge x am Ende einer erfolglosen Suche ein. I An welchen Knoten ist jetzt möglicherweise die AVL-Eigenschaft verletzt? Nur Knoten Suchpfads, also des Pfads von der Wurzel zum frisch eingefügten Blatt, können betroffen sein! I Wir laufen deshalb den Suchpfad möglicherweise ganz zurück, um die Balance-Eigenschaft zu reparieren. Die Situation: I Wir sind bis zum Knoten u zurückgelaufen. Die AVL-Eigenschaft gilt für u und alle Nachfahren von u. I Wenn die Reparatur fortzusetzen ist, müssen wir uns als Nächstes um den Vater v von u kümmern. I w bezeichne den Großvater von u. Mariano Zelke Datenstrukturen 17/22 Der Zick-Zick Fall w v u A Tiefe d B C Fallannahme: Ein neues Blatt wurde im Teilbaum mit Wurzel u eingefügt und Tiefe(C ) ≥ Tiefe(B). I Die Tiefe des Teilbaums von u muss um 1 angewachsen sein, denn ansonsten können wir die Reparatur in v beenden. I Sei d die neue, um 1 größere Tiefe des Teilbaums von u. Mariano Zelke Datenstrukturen 18/22 Der Zick-Zick Fall, Fortsetzung I Tiefe(A) ≥ d + 1 ist unmöglich, da sonst b(v ) ≥ 2 vor Einfügen des neuen Blattes gilt. I Wenn Tiefe(A) = d, brauchen wir nur den Balance-Grad b(v ) = 0 neu zu setzen. Die Reparatur kann abgebrochen werden, da der Teilbaum mit Wurzel v seine Tiefe nicht verändert hat. Wenn Tiefe(A) = d − 1, dann setze b(v ) = −1. I Diesmal müssen wir die Reparatur in w fortsetzen: Die Tiefe des Teilbaums mit Wurzel v ist um 1 angestiegen. I Der Fall Tiefe(A) ≤ d − 3 kann nicht auftreten, da sonst b(v ) ≤ −2 vor Einfügen des neuen Blatts gilt. Der Fall Tiefe(A) = d − 2 ist kritisch. Mariano Zelke Datenstrukturen 19/22 Tiefe(A) = d − 2 w w v u u A d −2 oder d −1 I v d −2 B d −1 C d −1 d −2 A B C d −2 oder d −1 Es ist Tiefe(B) ≤ Tiefe(C ) nach Fallannahme und deshalb ist Tiefe(C ) = d − 1. Da die AVL-Eigenschaft in u gilt, folgt d − 2 = Tiefe(C ) − 1 ≤ Tiefe(B) ≤ d − 1. Führe eine Linksrotation in v durch. I Die AVL-Eigenschaft gilt somit nach der Rotation für u und v . Setze b(u) und b(v ) entsprechend und fahre fort, wenn der neue Teilbaum von u tiefer ist als der alte Teilbaum von v . Mariano Zelke Datenstrukturen 20/22 Der Zack-Zack Fall w v u C A B Fallannahme: Ein neues Blatt wurde im Teilbaum mit Wurzel u eingefügt und Tiefe(A) ≥ Tiefe(B). Der Zack-Zack Fall wird analog zum Zick-Zick Fall behandelt. Mariano Zelke Datenstrukturen 21/22 Beispiel Nach dem Einfügen der Schlüssel 3, 9, 2, 8 2 entsteht dieser AVL-Baum: Nach dem Einfügen des weiteren Schlüssels 6 2 ist die Balance bei 9 verletzt. 3 9 8 3 b=2 9 8 6 Damit ist der Zack-Zack-Fall erreicht, der 2 eine Rechtsrotation bei 9 auslöst. 8 6 Der jetzt entstandene Zick-Zick-Fall löst eine Linksrotation bei 3 aus. Dabei wechselt die 6 vom 3 rechten Teilbaum des rotierten Knotens in den linken. 2 Mariano Zelke Wird nun noch Schlüssel 11 eingefügt, so ist die Balance bei 3 verletzt. 3 9 3 b = −2 2 8 6 9 11 8 9 6 Datenstrukturen 11 22/22