ETH Zürich Institut für Theoretische Informatik Prof. Dr. Angelika Steger Florian Meier, Ralph Keusch HS 2016 Algorithmen und Komplexität Lösungsvorschlag zu Übungsblatt 1 Lösungsvorschlag zu Aufgabe 1 (a) Ja, da limn→∞ ln n log2 n = ln 2. (b) Nein, da lim supn→∞ (c) Ja, da lim supn→∞ lim n→∞ n! nn n log2 n = ∞. = limn→∞ n·(n−1)····1 n·n····n ≤ limn→∞ 1 n = 0. Via dn/2e bn/2c n! 1 dn/2e n 1 dn/2e ≤ lim ≤ lim + n→∞ 2 n→∞ nn n n n (oder mit der Stirling-Formel) sieht man sogar dass der Limes exponentiell gegen 0 geht. (d) Nein, siehe (c). (e) Nein, da lim supn→∞ (f) Ja, da limn→∞ 1/n 1 22n 2n = lim supn→∞ 2n = ∞. = 0. Lösungsvorschlag zu Aufgabe 2 Das folgende Programm verdoppelt die Zahlen in den Speicherzellen M1 , . . . , Mn : Eingabe: n natürliche Zahlen in M1 , . . . , Mn sowie M0 = n. Ausgabe: Die Zahlen in M1 , . . . , Mn verdoppelt. 1: M−2 ← 1 2: M−1 ← M M0 3: M−1 ← M−1 + M−1 4: M M0 ← M−1 5: M0 ← M0 − M−2 6: GOTO 2 IF M0 > 0. Die Zahl in M0 sagt uns, wieviele Zahl wir verdoppeln müssen. Wir starten bei Mn und kopieren in Befehlszeile 2 die Zahl von Mn nach M−1 . Dort wird sie verdoppelt, anschliessend überschreiben wir Mn mit dem neuen Wert. Wir haben Mn somit verdoppelt, machen unseren Zähler in M0 um eins kleiner und fahren danach mit Mn−1 fort. Dieses Prozedere wiederholen wir solange bis M0 = 0. Dann sind alle Zahlen verdoppelt und das Programm ist fertig. Lösungsvorschlag zu Aufgabe 3 Wir betrachten die beiden Algorithmen zuerst im Einheitskostenmodell. Es bezeichne L(n) die Laufzeit von Algorithmus F IB A für die Eingabe n. Aufgrund der rekursiven Definition gilt dann L ( n ) = L ( n − 1) + L ( n − 2) + 1 (die 1 entspricht der Addition der beiden vorhergehenden Fibonacci-Zahlen), wobei L(0) = L(1) = O(1) . 1 (1) Man sieht, dass L(n) selbst einer Fibonacci-ähnlichen Rekursionsgleichung folgt und mindestens so schnell wächst wie Fn . Für n ≥ 1 gilt die untere Schranke n −2 3 Fn ≥ , 2 wie man leicht mittels vollständiger Induktion zeigt: Als Induktionsschritt haben wir Fn+1 = Fn + Fn−1 ≥ n −1 n −2 n −3 n −3 3 3 3 3 3 + = +1 ≥ ; 2 2 2 2 2 als Induktionsanfang F (1) = 1 ≥ 3 −1 2 = 2 3 und F (2) = 1 ≥ 3 0 = 1. 2 3 n 2 , d.h. exponentiell. Ganz genau Die Laufzeit von F IB A wächst also mindestens so schnell wie könnte man durch Lösen √ der inhomogenen Differenzengleichung (1) zeigen, dass die Laufzeit Θ(Φn ) ist, wobei Φ = 1+2 5 ≈ 1.61 („Goldener Schnitt“). Dies hängt natürlich zusammen mit der expliziten Darstellung der Fibonacci-Folge Fn = √1 (Φn − (1 − Φ)n ), welche man als Lösung 5 der zu (1) zugehörigen homogenen Differenzengleichung erhält. Die Laufzeit von F IB A wächst also gleich schnell wie die Fibonacci-Zahlen selbst. Nun zu F IB B. Hier wird die FOR-Schleife (n − 1)-mal, d.h. O(n)-mal durchlaufen, wo bei in jedem Durchlauf konstant viele, d.h. O(1) Operationen ausgeführt werden. Wir erhalten also eine Laufzeit von O(n) · O(1) = O(n) für F IB B. Man entscheidet sich deshalb für F IB B. Die Laufzeit lässt sich weiter verbessern, indem man die Identität n 1 1 Fn+1 Fn = 1 0 Fn Fn−1 ausnutzt: Wenn n eine Zweierpotenz ist, reicht es, die Matrix log n-mal zu quadrieren. Da jede dieser log n Matrixmultiplikationen eine konstante Anzahl an Additionen und Multiplikationen benötigt, ergibt sich im Einheitskostenmodell eine totale Laufzeit von O(log n). (Die Aussage gilt auch, wenn n keine Zweierpotenz ist - warum?) Soweit die Rechnung im Einheitskostenmodell. Leider haben wir bei der ganzen Sache gemogelt: die Fibonaccizahl Fn ist exponentiell viel grösser als die Eingabe n, d.h., wir haben die “ungeschriebenen Regeln” verletzt. Diese besagen, dass wir in einer Speicherzelle nur polynomial beschränkte Zahlen abspeichern dürfen, d.h. Zahlen, die kleiner sind als nk für ein beliebiges aber festes k. Schauen wir uns also noch die Laufzeitanalyse an, wenn die Anzahl Bitoperationen gezählt wird: Die Laufzeit von F IB A folgt nun der Rekursion L(n) = L(n − 1) + L(n − 2) + O(log Fn ) = L(n − 1) + L(n − 2) + O(n) , da der Zeitbedarf für die Addition der zwei vorhergehenden Fibonaccizahlen nun proportional zur Anzahl involvierter Bits ist und die Fibonacci-Zahlen wie erwähnt exponentiell schnell wachsen. Er ergibt sich immer noch eine Laufzeit von O(Φn ) (dies gilt selbst dann, wenn statt O(n) ein beliebiges Polynom steht). Für F IB B überlegen wir uns, dass wir O(n) Additionen ausführen, von denen die i-te O(log Fi ) = O(i ) Bitoperationen benötigt. Als Gesamtlaufzeit ergibt sich n ∑ O(i) = O i =1 2 n2 . Der Trick mit dem sukzessiven Quadrieren einer Matrix führt im Bitkostenmodell zu keiner direkten Verbesserung der Laufzeit: Die Länge der involvierten Zahlen verdoppelt sich in jedem Schritt, d.h. beim i-ten Mal Quadrieren haben die Matrixeinträge O 2i Bits, und das Quadrie ren mittels normaler schriftlicher Multiplikation benötigt O 2i O 2i = O 4i Bitoperationen. Durch Summation einer geometrischen Reihe ergibt sich eine Laufzeit von log n ∑ O 4i = O 4log n = O n2 . i =1 Der Schönhage-Strassen-Algorithmus (1971) kann zwei Zahlen der Länge n in O(n log n log log n) Bitoperationen multiplizieren. Wenn dieser Algorithmus für das iterative Quadrieren der Matrix verwendet wird, ergibt sich analog eine Laufzeit von log n ∑ i =1 log n O 2i log 2i log log 2i = O(log n log log n) ∑ O 2i = O(n log n log log n) i =1 für die Berechnung der n-ten Fibonacci-Zahl, d.h. quasilineare Laufzeit in Bitoperationen(!). Allerdings ist die Konstante, die sich hinter dem O(. . . ) verbirgt, sehr gross, so dass dieser Algorithmus nicht unbedingt der für praktische Zwecke bestgeeignetste ist. 3