http://www.mpi-sb.mpg.de/~sschmitt/info5-ss01 IS UN R S SS 2001 E R SIT S Schmitt, Schömer SA IV A Grundlagen zu Datenstrukturen und Algorithmen A VIE N Lösungsvorschläge für das 10. Übungsblatt Letzte Änderung am 3. Juli 2001 Aufgabe 1 a) b) c) d) Die mittlere Laufzeit des Algorithmus ist O(n): Im schlechtesten Fall benötigt der Algorithmus Laufzeit O(n): Die Erwartete Laufzeit des Algorithmus ist O(n): Die Laufzeit des Algorithmus ist amortisiert O(n): a,c) average-case analysis: Aussage a und d unterscheiden sich nicht von einander. Es ist der im Durchschnitt zu erwartende Fall gemeint, der average-case. In beiden Aussagen ist also der Erwartungswert der Laufzeit gemeint. Dies bezieht sich sowohl auf Algorithmen deren Laufzeit nur durch die Problemstellung bestimmt wird (deterministische Algorithmen), als auch auf solche, die auch auf dem selben Problem unterschiedliche Laufzeiten haben können ( probabilistische Algorithmen wie z.B. Quicksort ). Es wird dabei jedoch, im Gegensatz zu den anderen Aussagen, keine Garantie gegeben, da die Laufzeit im Einzelfall erheblich ,sowohl nach oben als auch nach unten, abweichen kann. b) worst-case analysis: Aussage b bezieht sich auf den schlechtesten Fall, so zu sagen den Super-GAU für diesen Algorithmus, den worst-case. Diese Aussage ist somit eine Garantie, daß der Algorithmus niemals langsamer ist als O(n). d) amortized analysis: Diese Aussage ist ebenfalls eine Garantie, die wie folgt lautet: Wird dieser Algorithmus k mal (k ∈ N bel.) hintereinander auf die selbe Datenstruktur angewendet, so ist die Gesamtlaufzeit in O(k*n). Dies bedeutet insbesondere, daß der Erwartungswert der Laufzeit (s. a,c) in O(n) liegt. Es ist jedoch weiterhin möglich, daß eine einzelne Ausführung des Algorithmus langsamer bzw schneller ist als O(n). Es wird aber eben garantiert, daß sich das so zu sagen weghebt. Diese Amortisierte Analyse wird jedoch meistens auf eine ganze Gruppe von Algorithmen, man spricht dann auch von Operationen, angewendet, die dann miteinander verrechnet werden. (s.Fibonacci Heaps) Aufgabe 2 3 6 5 9 7 11 2 13 12 8 heapify: 10 15 2 3 5 6 7 9 11 13 12 8 15 15 erase_min 10 3 5 5 6 13 10 9 7 11 13 12 15 10 8 5 erase_min 8 6 8 6 9 7 13 7 11 10 12 15 12 decrease_key(13,4) 4 6 5 9 11 7 12 5 6 6 9 11 10 12 15 erase_min 8 7 8 9 15 7 11 12 15 decrease_key(8,3) 3 6 5 9 11 7 15 12 12 12 10 10 Aufgabe 3 1. Zunächst wird durch insert() eine einfache doppeltverkettete Liste erzeugt, und ein Zeiger auf das Minimum gesetzt: 6 5 9 7 13 10 11 2 12 8 15 min 3 2. Bei erase min() findet nun eine Umformung des Heaps statt: Zunächst wird das Minimum gelöscht, anschließend finden Verschmelzoperationen dergestalt statt, daß nach und nach Knoten in der Wurzelliste mit gleicher Kinderzahl zusammengefasst werden, bis sich nur noch Knoten mt unterschiedlicher Kinderzahl in der Wurzelliste einfinden. Zusätzlich wird der min-Zeiger umgebogen: min 3 5 7 6 9 13 10 8 12 7 8 min 11 15 3 6 5 13 9 10 11 min 8 3 6 5 7 9 13 12 10 11 15 12 15 3. Beim 2. erase min() wird die 3 gelöscht, und alle ihre Kinder in die Wurzelliste eingetragen. Anschließend wird wieder verschmolzen. Um die Heap-Eigenschaft (also Vater kleiner als Kind) zu erhalten, schmelzen wir stets die größere Wurzel unter die kleinere: min 6 5 7 9 13 8 10 15 12 11 min 6 5 15 9 7 8 13 10 12 11 min 6 5 15 9 8 7 12 13 10 11 4. Bei decrease key( 13, 4) wird zunächst die 13 mit 4 überschrieben. In unserem Fall ist nun das Kind kleiner als der Elter, eine Heap-Verletzung. Somit schneiden wir die 4 (mit all ihren Kindern, sind aber keine da!) heraus, und setzten sie in die Wurzelliste. Die 7 hat nun ein Kind verloren, muß somit markiert werden. Dann noch Umbiegen des min-Zeigers. min 6 5 15 9 7 8 12 4 10 11 min 6 5 15 9 4 8 7 12 10 11 5. erase min() schneidet nun lediglich die 4 heraus, und setzt den min-Zeiger um: min 6 5 15 9 8 12 7 10 11 6. decrease key( 8, 3) überschreibt erst die 8 mit der 3, und setzt den Teilbaum mit Wurzel 3 in die Wurzelliste. min 6 5 15 9 3 7 12 10 11 5 15 9 3 7 min 6 12 10 11 5 hat ein Kind verloren, warum muß es nicht markiert werden? Ganz einfach: Wir markieren einen Knoten genau dann, wenn er 1. irgendwann in der Wurzelliste stand 2. später als Kind eines anderen Knotens verschmolzen wurde 3. dann ein Kind verloren hat 5 erfüllt die 2. Bedingung nicht. An und für sich wären wir hier nun fertig. Es stellt sich jedoch noch die Frage, wofür dieses Markieren überhaupt gut ist: Es soll verhindern, daß ein Knoten mehr als ein Kind verliert, da wir dann die Laufzeitschranken nicht mehr halten können. Wir sagen nun: Ein Knoten wird genau dann abgeschnitten und in die Wurzelliste gesetzt, wenn er Punkte 1. bis 3. erfüllt hat und 4. zuletzt ein zweites Kind verloren hat oder mit anderen Worten: Verliert ein bereits markierter Knoten ein Kind, wird er von seinem Elter abgeschnitten und in die Wurzelliste gesetzt. Wir veranschaulichen dies mit einem weiteren decrease key( 10, 2). 7. Wie immer wird erst der Schlüssel überschrieben, anschließend der Baum mit Wurzel 2 in die Wurzelliste eingetragen. Jedoch war 7 markiert, und muß nun ebenfalls in der Wurzelliste landen. Außerdem wird die Markierung aufgehoben. 5 15 min 6 3 7 9 12 2 11 min 2 6 5 11 15 9 7 3 12 min 7 2 6 5 3 11 15 9 12 Dieser Heap wird bei der nächsten erase min()-Operation wieder verschmolzen. Das wär’s. Aufgabe 4 Dieser Lösungsvorschlag soll nicht in erster Linie zeigen, wie man eine solche Aufgabe löst. Es geht mir hier darum eine Gefahr bei der vollständigen Induktion aufzuzeigen, die einem schnell zum Verhängnis wird, wenn man nicht sorgfältig arbeitet. Behauptung Es sei fn die n-te Fibonacci-Zahl und φ = √ 1+ 5 . 2 ∀n ∈ N, n ≥ 2 : fn ≥ φn−2 Beweis durch vollständige Induktion über n. Sei n = 2 fn = f0 + f1 = 1 = φ0 = φn−2 Sei n > 2 und die Aussage für n − 1 schon bewiesen. fn = fn−1 + fn−2 ≥ φn−3 + φn−4 Induktionsannahme √ 3+ 5 = φ (φ + 1) = φ ·φ · √ 2 4 3+ 5 √ = φn−2 · · 2 1+2 5+5 n−2 = φ n−4 n−2 −2 Da drängt sich einem die Frage auf, warum wir nicht gleich die stärkere Aussage fn = φn−2 zeigen. Der Beweis wäre doch der gleiche bis auf die Induktionsannahme? Aber wenn wir mal √ n = 3 einsetzen bekommen wir heraus f3 = 2 aber φ1 = 3+2 5 . Wo liegt also der Fehler? Ganz einfach. Wir haben den Induktionsanfang nur für n = 2 gemacht, aber im Induktionsschritt verwendet, dass die Induktionsannahme auch für n − 2 gilt. Deshalb ist unser Beweis im Grunde nichts wert, da wir zwei Induktionsanfänge benötigen. Für n = 3 gilt nur fn ≥ φn−2 . Also ist der Induktionsschritt für n > 3 durchzuführen. Aufgabe 5 Wir zeigen zunächst, daß die angegebene Eigenschaft invariant unter den Ablöseund Verschmelz-Operationen sind. • Ablöse-Operation: Die Ablöse-Operation wird durchgeführt, wenn ein markierter Knoten ein weiteres Kind verliert. Der markierte Knoten wird dann (falls er nicht ein Wurzelknoten ist) mitsamt seinem Unterbaum aus dem entsprechenden Baum gelöscht und in die Wurzelliste eingefügt. Wir werden nachher (bei der Betrachtung der Operation decrease_key) sehen, daß einem markierter Knoten, der ein zweites Kind verloren hat, 4REs zur Verfügung stehen. Eine davon wird für das Löschen verbraucht. Eine andere behält der Knoten, da er als Wurzelknoten in der Wurzelliste 1RE benötigt. Die restlichen 2REs werden dem Vaterknoten übergeben. Die Invariante ist damit wieder erfüllt: War der Vaterknoten noch nicht markiert, dann wird er jetzt markiert und besitzt 2RE’s. War er bereits markiert und verliert jetzt ein zweites Kind, so hat er jetzt 4RE’s zur Verfügung, so daß wieder eine Ablöse-Operation durchgeführt werden kann. • Verschmelze-Operation: Die Verschmelze-Operation setzt einen Knoten aus der Wurzelliste mitsamt seinem Unterbaum in die Kinderliste eines anderen Knotens aus der Wurzelliste. Diese Operation kostet 1RE, die von dem Knoten, der aus der Wurzelliste entfernt wird, bezahlt werden kann. Die Invariante bleibt erhalten. Jetzt betrachten wir die einzelnen Operationen minimum, insert, decrease_key und erase_min. • minimum: Hier wird an der Struktur nichts geändert, es wird einfach der Zeiger auf das Minimum (in der Wurzelliste) zurückgegeben. Eine RE wird der Operation minimum zur Ausführung mitgegeben, weitere RE’s werden nicht gebraucht. • insert: Hier wird ein neuer Knoten (als Wurzel eines Fibonacci Baums) in die Wurzelliste eingetragen. Die anderen Knoten werden nicht betrachtet. Der neue Knoten soll 1RE erhalten. Für die Operation insert muß man also 2RE bezahlen: Eine für die Ausführung und eine für den neuen Knoten. • decrease_key: Für diese Operation bezahlen wir 3REs. Dabei wird eine für die eigentliche Operation benötigt. Bei dieser Operation wird der Schlüssel erniedrigt und der Knoten dann als neue Wurzel in die Wurzelliste gesetzt. Die anderen beiden REs werden dem Vaterknoten übergeben. Falls dieser noch kein Kind verloren hat, wird er jetzt markiert und besitzt die geforderten 2REs. War er bereits markiert, so besitzt er jetzt 4REs, womit man die Ablöseoperationen durchführen kann. Man sieht also, daß die drei Operationen minimum, insert und decrease_key nur konstant viele REs benötigen, um die Invariante aufrecht zu erhalten. Die Laufzeit für diese Operationen ist also amortisiert konstant. • erase_min: Bei dieser Operation wird das Minimum gelöscht und die Kinder des Minimums als neue Wurzeln in die Wurzelliste eingetragen. Dann werden Wurzeln mit gleicher Kinderzahl verschmolzen. Wir haben gesehen, daß die Verschmelze-Operationen aufgrund der Invariante ohne zusätzliche REs bezahlt werden können. erase_min muß also ’nur’ für das Löschen des Minimums, das Eintragen der Kinder in die Wurzelliste und das Aufrechterhalten der Invariante bezahlen. Das Löschen des Minimums kostet 1RE (die auch von dem Minimum selbst bezahlt werden kann). Das Eintragen der Kinder in die Wurzelliste mit Aufrechterhaltung der Invariante kostet 2RE pro Kind: 1RE für das Eintragen und 1RE für die neue Wurzel in der Wurzelliste. In der Vorlesung wurde gezeigt, daß die maximale Kinderzahl einer Wurzel in einem Fibonacci Heap O(log n) ist. Damit muß man für erase_min 2O(log n) = O(log n)RE bezahlen.