Robert Elsässer u.v.a. Paderborn, 29. Mai 2008 Beispiellösungen zu den Übungen Datenstrukturen und Algorithmen SS 2008 Blatt 6 Aufgabe 1 (6 Punkte): Zunächst sollte klar sein, daß ein vollständiger Binärer Suchbaum ein AVL-Baum ist. Um aus einem sortiertem Array einen vollständigen Binären Suchbaum zu erstellen, genügt es das mittlere Element des Array als Wurzel des Baums zu nehmen und das Verfahren rekursiv auf die zwei Hälften des Arrays anzuwenden. SortArrayToAVL(A, l, r, y) 1 if l ≥ r 2 then p[node] ← y 3 key[node] ← A[r] 4 lc[node] ← nil 5 rc[node] ← nil 6 return node 7 else 8 q ← l+r 2 9 p[node] ← y 10 key[node] ← A[q] 11 lc[node] ← SortArrayT oAV L(A,l,q-1,node) 12 rc[node] ← SortArrayT oAV L(A,q+1,r,node) 13 return node AVLLIN(A) 1 root[T ] ← SortArrayT oAV L(A,1,n,nil) Die Korrektheit wird durch Induktion über die Höhe des Baums bzw. über die Länge des Eingabearrays gezeigt. Zu zeigen ist, dass der Algorithmus bei Eingabe eines aufsteigend sortierten Arrays einen korrekten AVL-Baum konstruiert, der die Elemente des Arrays enthält. I.A: Bei einem Array der Länge 1 gilt l ≥ r, also befindet sich der Algorithmus im if“-Fall. ” Also wird das eine Element des Array als Wurzel des Baumes gesetzt. Der Parent und die Kinder werden auf nil gesetzt. Dieser Baum mit einem Knoten und der Höhe 0 entspricht einem korrektem AVL-Baum. I.V.: Der Algorithmus konstruiert aus einem Array der Länge m ≤ n einen AVL-Baum der Höhe blog mc. I.S Für ein Array der Länge n ermittelt der Algorithmus als Wurzel das mittlere Element des Arrays. Als linkes Kind erhält der Knoten den aus der vorderen Hälfte des Arrays konstruierten AVL-Baum, da alle Werte in dieser Hälfte kleiner sind als die Wurzel. Nach I.V. wird dieser korrekt konstruiert, da die vordere Hälfte des Array eine Länge m ≤ n besitzt. Gleiches gilt für das rechte Kind. Beide Teilbäume haben nach I.V. Höhe blog mc, da sich die Länge der beiden Hälften des Arrays um max. 1 unterscheiden, kann sich auch die Höhe der Teilbäume um max. 1 unterscheiden. Zudem sind beide Teilbäume AVL-Bäume, sodass der so konstruierte Baum ebenfalls einem AVL-Baum entspricht. Die Laufzeit ist bei einer Rekursiongleichung von T (n) ≤ 2T ( n2 ) + O(1) offensichtlich O(n). Aufgabe 2 (6 Punkte): Behauptung: Ein AVL-Baum ist nach dem Einfügen eines Knotens wieder ein korrekter AVL-Baum. Als erstes führt man folgendes Lemma ein: Lemma: Nach dem Aufruf der Operation AVLEinfügen(T,n) ohne Ausführung der Operation Balance wird die Höhe des AVL-Baumes nicht verändert oder um 1 erhöht. Beweis des Lemma: Sei die Höhe des Baumes h. Da es sich um einen AVL-Baum handelt, unterscheidet sich die Höhe des linken und des rechten Kindes jedes Knotens maximal um 1, insbesondere auch bei der Wurzel des Baumes. Also ist die Höhe des linken beziehungsweise die des rechten Kindes der Wurzel h − 1 oder h − 2. OBdA wird das einzufügende Element ohne Balance dabei auszuführen in den linken Teilbaum eingefügt. Fall 1: Die Höhe des linken Teilbaums der Wurzel ist h − 1. Fall 1.1: Das Element wird an einen Knoten angefügt. Dadurch ändert sich die Gesamthöhe des Teilbaumes nicht, weil die Höhe des Knotens kleiner der Gesamthöhe des Teilbaums sein muss, ansonten würde es sich um ein Blatt handeln. Also ändert sich auch nicht die Höhe des Baumes an sich. Fall 1.2: Das Element wird an einem Blatt angefügt. Fall 1.2.1: Das Blatt befindet sich auf einer Höhe kleiner h − 1. Dadurch ändert sich die Gesamthöhe des Teilbaumes nicht. Also ändert sich auch nicht die Höhe des Baumes an sich. Fall 1.2.2: Das Blatt befindet sich auf der Höhe h − 1. Dadurch steigt die Höhe es Teilbaums um eins auf h, da ganz unten am Teilbaum angefügt wurde. Demnach erhöht sich auch die Höhe des Baums an sich um eins auf h + 1. Fall 2: Die Höhe des linken Teilbaums der Wurzel ist h − 2. Fall 2.1: Das Element wird an einen Knoten angefügt. Dadurch ändert sich die Gesamthöhe des Teilbaumes nicht, weil die Höhe des Knotens kleiner der Gesamthöhe des Teilbaums sein muss, ansonten würde es sich um ein Blatt handeln. Also ändert sich auch nicht die Höhe des Baumes an sich. Fall 2.2: Das Element wird an einem Blatt angefügt. Fall 2.2.1: Das Blatt befindet sich auf einer Höhe kleiner h − 2. Dadurch ändert sich die Gesamthöhe des Teilbaumes nicht. Also ändert sich auch nicht die Höhe des Baumes an sich. Fall 2.2.2: Das Blatt befindet sich auf der Höhe h − 2. Dadurch steigt die Höhe es Teilbaums um eins auf h − 1, da ganz unten am Teilbaum angefügt wurde. Die Höhe des Baums ist demnach immer noch h. Das Einfügen eines Elements am rechten Teilbaum funktioniert analog. Daraus folgt die Korrektheit des Lemma, da sich die Höhe des Baums beim Einfügen eines Elements ohne Ausführen von Balance entweder nicht verändert oder maximal um 1 steigt. Mit Hilfe des Lemmas kann man die Behauptung durch vollständige Induktion über die Höhe h des Baumes beweisen. Induktionsanfang: h = 0 Nach dem Einfügen eines Elements befindet sich außer der Wurzel ein Element im Baum. Also ist die AVL-Eigenschaft erfüllt. Induktionsvorraussetzung: Ein AVL-Baum mit der Höhe < h ist nach dem Einfügen eines Elements wieder ein AVLBaum. Induktionsschritt: h − 1 → h Besitze der Baum T vor dem Einfügen des Elements u die Höhe h. Im folgenden sei y die Wurzel von T , x = lc[y], A der linke Teilbaum von x, B der rechte Teilbaum von x und C der rechte Teilbaum von y. Mit D bezeichnen wir den linken Teilbaum von y, also den Teilbaum mit Wurzel x. Der Knoten u wird zunächst an die korrekte Position in einen der beiden Unterbäume von y eingefügt. O.B.d.A. nehmen wir an, dass u im linken Teilbaum von y, also in D eingefügt wird. C und somit auch die Höhe von C bleiben daher unverändert (C bleibt ein AVL-Baum). Da die Höhe von D < h beträgt, ist D nach Induktionsvorraussetzung auch nach dem Einfügen von u weiterhin ein AVL-Baum. Nach obigem Lemma erhalten wir für die Höhe von D (unmittelbar vor Ausführung der Operation Balance) folgende Fälle: Fall 1: Die Höhe von D bleibt unverändert Da T vor dem Einfügen ein AVL-Baum war und C nicht verändert wird, bleibt der Wurzelknoten von T ausbalanciert und T besitzt insgesamt weiterhin die AVL-Eigenschaft und in Balance wird nichts verändert. Fall 2: Die Höhe von D erhöht sich um 1. Wir erhalten folgende Fälle: Fall 2.1: h [D] ∈ {h [C] , h [C] + 1} Die AVL-Eigenschaft ist auch in der Wurzel y von T erfüllt und T somit insgesamt ein AVL-Baum. In Balance wird wiederum nichts verändert. Fall 2.2: h [D] > h [C] + 1 Da T vor dem Einfügen ein AVL-Baum war gilt: h [D] > h [C] + 2 Das bedeutet, dass die AVL-Eigenschaft nun in jedem Knoten von T, außer der Wurzel erfüllt ist. T ist also ein beinahe-AVL-Baum. Wir erhalten wiederrum 2 Fälle: Fall 2.2.1: h [A] = H, h [B] ∈ {H, H − 1}, h [C] = H − 1 In diesem Fall gilt also: h [A] ≥ h [B]. Die Abfrage in Zeile 3 von Balance ergibt somit false und der Algorithmus führt eine Rechtsrotationum y aus (Zeile 5, Abbildung 3). Neuer Wurzelknoten dieses Teilbaums wird dadurch x. Das linke Kind von x bleibt unverändert. Somit bleibt die Höhe des linken Teilbaums von x h [A] = H. y wird rechtes Kind von x. Durch die Rechtsrotation wird B linker Teilbaum von y und C rechter Teilbaum. Somit gilt für die Höhe von y nun h [y] = = ∈ = max{h [lc [y]] + 1, h [rc [y]] + 1} max{h [B] + 1, h [C] + 1} {max{H − 1 + 1, H − 1 + 1}, max{H + 1, H − 1 + 1}} {H, H + 1} Die Höhe des linken und rechten Teilbaums von x unterscheiden sich nun um höchstens 1. Somit ist nun der Teilbaum mit Wurzel x ein AVL-Baum. Also ist auch der gesamte Baum wieder ein AVL-Baum. Fall 2.2.2: h [A] = H − 1, h [B] = H, h [C] = H − 1 In diesem Fall gilt: h [A] < h [B]. Die Abfrage in Zeile 3 von Balance ergibt somit true und der Algorithmus führt eine Linksrotation um x aus (Zeile 4). Durch die Rotation erhalten wir den in Abbildung 5 dargestellten Baum. Dabei bezeichne z die Wurzel des Teilbaums B und B’ und B” jeweils den linken und rechten Teilbaum von z. Dabei gilt für die Höhen von B’ und B”: h [B 0 ] = H − 1 und h [B 00 ] = H − 1 oder h [B 0 ] = H − 2 und h [B 00 ] = H − 1 oder h [B 0 ] = H − 1 und h [B 00 ] = H − 2 Im Anschluss daran, wird in Zeile 5 eine Rechtsrotation um x ausgeführt. Um herauszufinden, ob der entstandene Teilbaum die AVL-Eigenschaft besitzt, muss man die Höhe der beiden Teilbäume von z errechnen. Für die Höhe von x gilt: h [x] = ∈ = = max{h [A] + 1, h [B 0 ] + 1} {max{H − 1 + 1, H − 2 + 1}, max{H − 1 + 1, H − 1 + 1}} {max{H, H − 1}, H} {H} Für die Höhe von y gilt: h [y] = ∈ = = max{h [B 00 ] + 1, h [C] + 1} {max{H − 2 + 1, H − 1 + 1}, max{H − 1 + 1, H − 1 + 1}} {max{H − 1, H}, H} {H} Die Höhe des linken und rechten Teilbaums von z sind nun gleich. Somit ist der entstandene Teilbaum mit Wurzel z ein AVL-Baum. Demnach ist auch der gesamte Baum wieder ein AVL-Baum. Damit ist gezeigt, dass ein AVL-Baum nach dem Einfügen eines Elements wieder ein AVLBaum ist. Aufgabe 3 (6 Punkte): Der Suchbaum wird um das Feld leftsize[x] erweitert, leftsize[x] enthält die Baumgröße des linken Teilbaums mit der Wurzel x. Laufzeit und Korrektheit für Insert und Delete bleiben dabei erhalten. Bei der Operation INSERT muss bei jedem passierten Knoten, in dessen linken Teilbaum derKnoten eingefügt wird, leftsize um 1 erhöht werden. Die Anzahl der Element in dem Intervall ermittelt man indem die Anzahl der Elemente bestimmt die kleiner bzw. kleiner gleich der Grenzen des Intervalls sind und dann die Differenz bildet. LESSOREQUAL(x, k) 1 c←0 2 if x = nil 3 then 4 return 0 5 elsif k ≥ key[x] 6 then 7 c ← lef tsize[x] + LESSOREQU AL(rc[x], k) 8 else c ← LESSOREQU AL(lc[x], k) 9 return c INTERVAL(x, y) 1 lessx ← LESSOREQU AL(root[T],x-1) 2 leqy ← LESSOREQU AL(root[T],y) 3 return leqy − lessx Die Laufzeit von LESSOREQUAL ist O(log(n)), da jeder Knoten entlang eines Suchpfades von der Wurzel zu einem Blatt besucht wird. Die Korrektheit wird durch Induktion bewiesen. Der Algorithmus LESSOREQUAL zählt dabei alle Zahlen kleiner gleich k. I.A.: Bei einem Suchpfad der Länge 0 (Höhe 1), befindet man sich in einem Blatt, das heißt beim nächsten rekursiven Aufruf ist x = nil und der Algorithmus gibt 0 zurück. Die Anzahl der Zahlen kleiner gleich k ermittelt sich dann als lef tsize[x] + 0, wenn k ≥ key[x], wobei leftsize[x] die Anzahl der Knoten mit Wert ≤ key[x] (Knoten im linkem Teilbaum) enthält, oder 0, wenn k < key[x], was korrekt ist da dann der einzige Knoten im Baum größer als der gesuchte Wert ist. I.V.: Der Algorithmus ermittelt die korrekte Anzahl Knoten mit Wert kleiner gleich k für Suchpfade der Länge bzw. Bäume der Höhe m < n. I.S.: Bei einem Baum der Höhe n vergleicht der Algorithmus den aktuellen Knoten mit dem Wert k. Ist der Wert des Knoten größer als k befindet sich der Algorithmus im else“ Fall. ” Der Knotenn und alle Knoten im rechten Teilbaum sind also größer als k, also ermittelt sich die Anzahl der Knoten mit Wert kleiner gleich k, als Anzahl der Knoten mit Wert kleiner gleich k im linkem Teilbaum, für diesen rekursiven Aufruf reduziert sich die Höhe des Baumes und die Azahl wird nach I.V korrekt ermittelt. Ist der Wert des Knoten kleiner gleich k, sind dieser Knoten und alle Knoten im linkem Teilbaum kleiner gleich k. Die Anzahl dieser Knoten entspricht leftsize[x]. Zusätzlich muss noch die Anzahl im rechtem Teilbaum ermittelt werden. Hier gilt wieder die I.V. Da LESSOREQUAL korrekt arbeitet, ist auch die Korrektheit von INTERVAL(x,y) gezeigt. Aufgabe 4 (6 Punkte): Eine Idee für eine Datenstruktur, mit der man dieses Problem realisieren kann, ist eine AVL-Baum Struktur, bei der die einzelnen Knoten die Wagenmodelle repräsentieren. Um zu erreichen, auch auf alte Baupläne eines Typs zugreifen zu können, lässt man die Knoten des AVL-Baums auf Listen verlinken, wobei jeweils das erste Element der Liste den aktuellen Plan enthält. Durch die Listenstruktur kann man dann über diesen auf die restlichen Pläne zugreifen, indem man durch die Liste läuft. Einfügen eines Bauplans: Da ein Wagentyp durch einen Index repräsentiert wird, kann man zum Einfügen eines Bauplans neuen Wagentyps die Methode AVL-Einfügen leicht modifizieren, sodass sie als Element eine einelementige Liste mit dem Bauplan an der richtigen Stelle im Baum einfügt. Da das Erstellen dieser Liste in konstanter Zeit möglich ist, braucht auch der modifizierte Algorithmus nur Laufzeit O(log2 (n)) Falls der Bauplan eines Wagentyps aktualisiert werden soll, sucht man den richtigen Wagentyp mit der aus der Vorlesung bekannte Funktion AVL-Suchen() heraus. Jetzt erstellt man ein neues Listenelement, das den aktuellen Bauplan enthält und hängt anschließend die gefundene Liste des Wagenmodelles dahinter. Anschließend fügt man die neue Liste an der Stelle im Baum ein. Das aktualisieren der Liste geht in konstanter Zeit, also ist die Laufzeit der Suche ausschlaggebend. Die ist O(log2 (n)). Also geht das Einfügen bzw. aktualisieren eines Bauplans zu einem Wagenmodell in O(log2 (n)). Suchen eines Bauplans: Das Suchen eines Bauplans wird über die Funktion AVL-Suchen() realisiert, die laut Vorlesung O(log2 (n)) braucht. Man muss die Suche nicht modifizieren, da das erste Objekt der Liste zurückgegeben werden muss und dieses immer der aktuelle Bauplan ist.