Laufzeit von Quicksort im Mittel. Wir wollen die erwartete Effizienz von Quicksort ermitteln. Wir nehmen an, die Wahrscheinlichkeit, dass das gewählte Pivot-Element aj das k-t kleinste Element der Folge ist, ist gleich für alle k = 1, . . . , n und damit ist gleich n1 . Die obige Beschreibung der Laufzeit für ein bestimmtes k können wir also erweitern und erhalten die folgende Gleichung für den mittleren Fall: n 1X (T (k − 1) + T (n − k)) T (n) = (n − 1) + n k=1 Bis auf die Reihenfolge, in der sie auftreten, sind die von den Ausdrücken T (k−1) und T (n − k) erzeugten Summanden gleich, deshalb lässt sich die Summe kürzer schreiben: = (n − 1) + n−1 2X T (k) n k=0 Wir multiplizieren auf beiden Seiten mit n und ersetzen n durch n − 1: nT (n) = n(n − 1) + 2 n−1 X T (k) k=0 (n − 1)T (n − 1) = (n − 1)(n − 2) + 2 n−2 X T (k) k=0 Um T (n) und T (n − 1) miteinander in Verbindung zu bringen, ziehen wir die oberen beiden Zeilen voneinander ab und erhalten: nT (n) − (n − 1)T (n − 1) = 2(n − 1) + 2T (n − 1) nT (n) − (n + 1)T (n − 1) = 2(n − 1) n+1 n−1 T (n) = T (n − 1) + 2( ) n n Da lim 2 n−1 n = 2 · lim n→∞ n→∞ n−1 n = 2, wird dieser Teil gestrichen und wir erhalten eine (hinreichend genaue) Ungleichung: T (n) ≤ n+1 T (n − 1) n Wir entwickeln die Formel ein weiteres mal, multiplizieren dann aus und erweitern beim Ergebnis die letzte 2 um den Faktor n+1 n+1 : n + 1 n T (n − 2) + 2 + 2 n n−1 n+1 n+1 n+1 = T (n − 2) + 2 +2 n−1 n n+1 T (n) ≤ 7 Wir setzen noch einmal ein: = n + 1n − 1 n+1 n+1 T (n − 3) + 2 + 2 +2 n−1 n−2 n n+1 n+1 n+1 n+1 n+1 = T (n − 3) + 2 +2 +2 n−2 n−1 n n+1 Wir können jetzt erahnen, wie die Folge aussieht. Um sie korrekt herzuleiten, müsste an dieser Stelle ein Induktionsbeweis geführt werden, aber uns genügt das intuitive“ Ergebnis: ” T (k) = n+1 n+1 n+1 n+1 n+1 T (n − k) + 2 +2 +2 + ... + 2 n−k+1 n−k+2 n−k+3 n−k+4 2n − k + 1 Setze k = n: T (n) = 1 1 n+1 1 T (0) + 2(n + 1) + + ... + 1 2 3 n+1 Zur Abschätzung dieser Formel benötigen wir die harmonischen Zahlen. Sie sind n P 1 wie folgt definiert: Hn = k. k=1 Wir betrachten die Graphen der Funktionen f (x) = 1 x und g(x) = 1 x+1 4 f(x) g(x) Hn 3.5 3 2.5 2 1.5 1 0.5 0 0 0.5 1 1.5 2 2.5 3 3.5 Die harmonischen Zahl Hn kann geometrisch interpretiert werden als die Größe der Fläche, die sich aus den Rechtecken mit den Flächen der Größe k1 · 1 für 8 4 alle k = {1, . . . , n} zusammensetzt. Der Graph von f begrenzt diese Fläche offenbar von oben, der Graph von g von unten. Also können wir Hn mit Hilfe von Integralen abschätzen. Da wir f nicht von 0 integrieren können, beginnen wir bei 1 und addieren noch 1 für das erste Rechteck hinzu. Z n Hn ≤ Z1 n Hn ≥ 0 1 dx + 1 = 1 + ln n − ln 1 = 1 + ln n x 1 dx = ln(n + 1) x+1 Die Abschätzung ist recht genau: Der Unterschied zwischen den Schranken ist höchstens 1 (genauer: die Euler-(Mascheroni-)Konstante, also ' 0, 5772156649). Es gilt somit: Hn = Θ(ln n) bzw. sogar Hn = ln n + Θ(1) Nun ist es uns möglich, die erwartete Laufzeit von Quicksort abzuschätzen: T (n) ≤ 2(n + 1)(Hn+1 − 1) ≤ 2(n + 1) ln n + 1 = 2n · ln n + Θ(ln n) Umgerechnet in den Zweierlogarithmus: T (n) ≤ 2n log n + Θ(log n) ' 1,38n · log n + Θ(log n) log e Zusammengefasst haben wir folgende Laufzeiten für die Algorithmen Mergesort (A4) und Quicksort (A4) ermittelt: Mergesort: n · log n+ lineare Terme Quicksort: 1,38n · log n+ logarithmische Terme Ein erster Vergleich zeigt, dass Quicksort etwa um den Faktor 1,38 langsamer ist. Wir werden gleich darauf zurück kommen, warum dies in der Praxis etwas anders aussieht. Vergleich der 4 Algorithmen in der Praxis. Einige Zahlen sollen zeigen, dass der Unterschied der Algorithmen bei realer Rechenzeit trotz immer schneller werdender Prozessoren noch erheblich ist und ein Nachdenken über effiziente Algorithmen sich durchaus lohnt. 9 Die Anzahl der durchgeführten Vergleiche bei einer Eingabe der Länge n: n 10 20 50 n · log n 33 86 282 1 2 2n − 45 190 1225 n 2 n! 3,6 · 106 2,4 · 1018 3,0 · 1064 Größe der Probleme, die eine Maschine mit 109 Vergleichen pro Sekunde mittels der Algorithmen lösen kann: 1 Sekunde 1 Stunde n · log n 4 · 107 1 · 1011 1 2 2n − n2 4,0 · 104 2,6 · 106 n! 13 16 Dasselbe für eine 10-mal schnellere Maschine: 1 Sekunde 1 Stunde n · log n 3,5 · 108 9,1 · 1011 − n2 1,0 · 105 8,4 · 106 1 2 2n n! 14 17 Man sieht: Bei schneller werdendem Rechner wächst die Größe der berechenbaren Probleme bei schlechten Algorithmen entsprechend langsam – bei dem Algorithmus, der alle Permutationen eines Arrays durchgeht, um den sortierten zu finden, kann hier mit einem 10-mal schnelleren Rechner gerade mal ein um ein Feld längeres Array berechnet werden. Vergleich von Quicksort und Mergesort. Wenn man, wie hier, nur die Anzahl der Vergleiche als Maß nimmt, ist Mergesort zwar schneller, in der Realität wird aber Quicksort effizienter sein, weil es weniger Daten im Speicher lesen und schreiben muss. Bei Mergesort müssen die Daten in zwei getrennten Arrays abgelegt werden, Quicksort kann immer im selben Array operieren. Bei einer großen Anzahl von Vergleichen (bei einem handelsüblichen PC bei ca. 4000000) sieht man deshalb auch einen deutlichen Anstieg des Aufwands bei Mergesort. Dies liegt daran, dass irgendwann der Hauptspeicher voll ist und der Hintergrundspeicher (Swap) benutzt werden muss. Es gibt einen eigenen Zweig der Forschung, der Algorithmen entwickelt, die den Zugriff auf den Hintergrundspeicher vermeiden oder minimieren, weil es durchaus Probleme gibt, die derart viel Platz brauchen, dass dies stark ins Gewicht fällt. Beispiele finden sich etwa in der Meteorologie, wo Programme mit Millionen von Einzeldaten operieren. 1.2 Berechnungsmodelle Berechnungsmodelle sind mathematische Modelle für Rechner, die verwendet werden, um präzise Aussagen über Berechnungen treffen zu können. Begriffe wie etwa Algorithmus“, Laufzeit“ oder Speicher“ werden mit ihnen formal ” ” ” definiert. Das bekannteste Beispiel ist wohl die 1936 von Alan Turing entwickelte 10 Turingmaschine, die in dieser Vorlesung aber nicht eigens wiederholt wird. Definition 1.2.1 (Registermaschine (Random Access Machine, RAM)). Eine Registermaschine besteht aus einem unendlichen, aus den Zellen R0 , R1 , ... bestehenden Speicher. Diese können jeweils eine beliebig große ganze Zahl enthalten. Die Unbegrenztheit von Speicher und Inhalt einer Zelle stellen somit jeweils eine Abstraktion von realen Computern dar. Ein Programm ist eine endliche Folge von Befehlen. Ein Beispiel-Befehlssatz könnte etwa wie folgt aussehen. Ein in klammern gesetztes Register (Ri ) steht hier für den Wert der Speicherzelle Rj , wobei j der Wert von Ri ist. A := B op C A := B goto L GGZ B, L GLZ B, L GZ B, L HALT wobei A : Ri oder (Ri ), i ∈ N0 B, C : Ri oder (Ri ), i ∈ N0 oder Konstante k ∈ N0 op ∈ {+, −, ∗, /} wobei L eine Zeile des Programms ist gehe nach L, wenn B > 0 gehe nach L, wenn B < 0 gehe nach L, wenn B = 0 Beende Abarbeitung des Programms Jedem Befehl kann man so eine Semantik zuordnen. Auch dies kann man formalisieren – den Zustand einer RAM kann man beschreiben, indem man mit einer Funktion c : N → Z angibt, welcher Wert in jeder Speicherzelle steht. Wenn man dazu eine eine Menge von Funktionen definiert, die für jeden Zustand und jeden Befehl den Folgezustand angeben, hätte man eine vollständige operationelle Semantik. Definition 1.2.2 (Berechnung einer Funktion auf einer RAM). Eine Registermaschine R berechnet eine Funktion f : Z∗ → Z∗ bedeutet: Falls in den ersten m Speicherzellen eine Folge a0 , ..., am steht, rechnet“ R (läuft, bis es zu einem ” HALT-Befehl kommt) und hat dann f (a0 , . . . , am ) = (b0 , . . . , bn ) in die ersten Speicherzellen geschrieben. 1.2.1 Laufzeit und Speicherbedarf einer RAM Wir wollen nun die Laufzeit und den Speicherbedarf eine RAM formal definieren. Dafür gibt es verschiedene Kriterien, von denen hier zwei vorgestellt werden. Das Einheitskostenmaß (EKM): Man nimmt an, dass erstens die Ausführung von jedem Befehl indifferent eine Zeiteinheit kostet, und dass zweitens ein Register unabhängig von seinem Inhalt eine Speichereinheit belegt. Dieses Maß ist Proportional zu den Kosten auf einem normalen Rechner, aber nur für beschränkt große Operanden: In der Realität wird eine Addition von zwei 1000-stelligen Zahlen mehr kosten als die von 2 oder 3-stelligen, weil eine 1000-stellige Zahl auf einem tatsächlichen Computer nicht von einem einzigen Register/ einer einzelnen Speicherstelle dargestellt wird. 11 Das logarithmische Kostenmaß: Beim logarithmischen Kostenmaß geht die Größe der Zahlen, mit denen man operiert, in die Berechnung der Kosten mit ein. Außerdem unterscheidet man, auf welches Register man zugreift. Wenn n eineP Zahl ist, sei L(n) die Länge der Binärdarstellung von n. Ein Befehl kostet dann k L(k), wobei k als Wert alle involvierten Adressen und die Werte aller Operanden annimmt. 12