Datenstrukturen Mariano Zelke Sommersemester 2012 Laufzeitmessung Für welchen Rechner (welche Hardware) sollen wir die Laufzeit berechnen? I Analysiere die Laufzeit auf einem abstrakten Rechner: I I I I Der Speicher besteht aus Registern, die eine ganze Zahl speichern können. Eine CPU führt einfache boolesche und arithmetische Operationen aus. Daten werden zwischen CPU und Speicher durch (indirekte) Ladeund Speicherbefehle ausgetauscht. Damit ist die Laufzeitberechnung für unseren abstrakten Rechner gültig, aber wir erhalten für einen speziellen Rechner nur eine bis auf einen konstanten Faktor exakte Schätzung. Also: (Analysierte bzw. geschätzte Laufzeit) = ch ·(tatsächliche Laufzeit) für einen konstanten Wert ch Mariano Zelke Datenstrukturen 2/17 Laufzeitmessung Für welche Programmiersprache und welchen Compiler (welche Software) sollen wir die Laufzeit berechnen? I Wir sollten“ mit einer Assemblersprache arbeiten: Wir sind vom ” Compiler unabhängig und haben es mit einer geringen Anzahl verschiedener Anweisungen zu tun. I Aber die Programmierung ist viel zu umständlich und wir wählen deshalb C++ bzw. Pseudocode. I Wenn wir die Anzahl ausgeführter C++ Befehle zählen, erhalten wir für die tatsächliche Anzahl ausgeführter Assembler Anweisungen nur eine Schätzung, die bis auf einen konstanten Faktor exakt ist. Also: (Analysierte bzw. geschätzte Laufzeit) = cs ·(tatsächliche Laufzeit) für einen konstanten Wert cs Mariano Zelke Datenstrukturen 3/17 Laufzeitmessung Ziel für unsere Laufzeitanalyse kann nur sein, die Laufzeit so abzuschätzen, dass gilt (Analysierte bzw. geschätzte Laufzeit) = ch · cs · (tatsächliche Laufzeit), wobei ch und cs Konstanten sind, die von der tatsächlichen Hard- und Software abhängen. Mariano Zelke Datenstrukturen 4/17 Laufzeitmessung Typischerweise hängt die Laufzeit eines Algorithmus von der Eingabe ab. Für welche Eingabe sollen wir die Laufzeit berechnen? I I Eine Laufzeitbestimmung für alle möglichen Eingaben ist sinnlos. Betrachte stattdessen die worst-case Laufzeit eines Algorithmus oder einer Datenstruktur für jede Eingabelänge. I I Unsere Laufzeitbestimmung ist letztlich nur eine bis auf einen konstanten Faktor exakte Schätzung. Wir interessieren uns vor allen Dingen für die Laufzeit großer“ ” Eingaben und unterschlagen“ konstante Faktoren. ” Wie verhält sich die Datenstruktur oder der Algorithmus unter wachsender Eingabelänge? Also: Wir erhalten eine von der jeweiligen Rechnerumgebung unabhängige Laufzeitanalyse, die das Wachstumsverhalten der tatsächlichen Laufzeit verlässlich voraussagt. Mariano Zelke Datenstrukturen 5/17 Ein Beispiel: Das Teilfolgenproblem I Die Eingabe besteht aus n ganzen Zahlen a1 , . . . , an . I Definiere f (i, j) = ai + ai+1 + · · · + aj für 1 ≤ i ≤ j ≤ n. I Das Ziel ist die Berechnung von max{f (i, j)|1 ≤ i ≤ j ≤ n}, also die maximale Teilfolgensumme. 2, −3, 4, −1, −1, 4, 1, −2 Maximum der f (i, j): f (3, 7) = 7 Mariano Zelke Datenstrukturen 6/17 Ein Beispiel: Das Teilfolgenproblem I Die Eingabe besteht aus n ganzen Zahlen a1 , . . . , an . I Definiere f (i, j) = ai + ai+1 + · · · + aj für 1 ≤ i ≤ j ≤ n. I Das Ziel ist die Berechnung von max{f (i, j)|1 ≤ i ≤ j ≤ n}, also die maximale Teilfolgensumme. Wir betrachten nur Algorithmen A, die ausschließlich Additionen und Vergleichsoperationen auf den Daten ausführen. I AdditionenA (n) = maxa1 ,...an ,∈Z {Anzahl Additionen von A für Eingabe (a1 , . . . , an )} I VergleicheA (n) = maxa1 ,...,an ∈Z {Anzahl Vergleiche von A für Eingabe (a1 , . . . , an )}. I ZeitA (n) = AdditionenA (n) + VergleicheA (n) ist die worst-case Rechenzeit von Algorithmus A. Mariano Zelke Datenstrukturen 7/17 Das Teilfolgenproblem: Algorithmus A1 Max = −∞; for (i=1 ; i <= n; i++) for (j=i ; j <= n; j++){ Berechne f (i, j) mit j−i Additionen; Max = max{f (i, j), Max}; } Analysiere die geschachtelten For-Schleifen durch geschachtelte Summen: Wir benötigen j − i Additionen zur Berechnung von f (i, j). Also ist n X n X AdditionenA1 (n) = (j − i). i=1 j=i n n P P Eine obere Schranke: AdditionenA1 (n) ≤ n = n3 . i=1 j=1 n/4 n P P Eine untere Schranke: AdditionenA1 (n) ≥ i=1 j=3n/4+1 und damit folgt AdditionenA1 (n) ≥ Mariano Zelke n 4 · n 4 · n 2 Datenstrukturen = n3 32 n 2 . 8/17 Die worst-case Laufzeit von A1 I Wie viele Vergleiche werden für die n Zahlen a1 , . . . , an ausgeführt? VergleicheA1 (n) = n X n X 1= i=1 j=i n X (n − i + 1) i=1 = n + (n − 1) + · · · + 1 n X n · (n + 1) . = i= 2 i=1 I Und die worst-case Laufzeit? I I I ZeitA1 (n) = AdditionenA1 (n) + VergleicheA1 (n). n3 32 n3 32 ≤ AdditionenA1 (n) ≤ n3 . Also ist + n·(n+1) 2 ≤ ZeitA1 (n) ≤ n3 + n·(n+1) . 2 Wir möchten das wesentliche Ergebnis festhalten, nämlich, dass die Laufzeit kubisch ist. Mariano Zelke Datenstrukturen 9/17 Vergleich von Laufzeiten 4· n3 32 + n(n+1) 2 n3 32 + n(n+1) 2 ≤ ZeitA1 (n) 2n = ZeitB (n) n 0 Wir wollen ausdrücken, dass im Allgemeinen das Wachstum von ZeitB (n) höchstens so schnell ist wie das Wachstum von ZeitA1 (n). Aber es gilt nicht: ZeitB (n) ≤ ZeitA1 (n) für alle n ∈ N Allerdings gilt: ZeitB (n) ≤ 4 · ZeitA1 (n) für alle n ∈ N Mariano Zelke Datenstrukturen 10/17 Vergleich von Laufzeiten 4· n3 32 + n(n+1) 2 n3 32 + n(n+1) 2 ≤ ZeitA1 (n) 2n = ZeitB (n) n 0 Wir halten fest: Es gibt Konstante c > 0, so dass ZeitB (n) ≤ c· ZeitA1 (n) für alle n ∈ N Rechtfertigung für Skalierung mit c: Laufzeiten sind sowieso nur Schätzungen bis auf konstanten Faktor. Mariano Zelke Datenstrukturen 10/17 Vergleich von Laufzeiten n3 32 n2 3 + n(n+1) 2 ≤ ZeitA1 (n) + 3 = ZeitD (n) n 0 n0 Wir wollen ausdrücken, dass im Allgemeinen das Wachstum von ZeitD (n) höchstens so schnell ist wie das Wachstum von ZeitA1 (n). Aber es gilt nicht: ZeitD (n) ≤ ZeitA1 (n) für alle n ∈ N Allerdings gilt: ZeitD (n) ≤ ZeitA1 (n) für alle n ≥ n0 Mariano Zelke Datenstrukturen 10/17 Vergleich von Laufzeiten n3 32 n2 3 + n(n+1) 2 ≤ ZeitA1 (n) + 3 = ZeitD (n) n 0 n0 Wir halten fest: Es gibt n0 ∈ N, so dass ZeitD (n) ≤ ZeitA1 (n) für alle n ≥ n0 Rechtfertigung für Betrachtung erst ab n0 : Wir interessieren uns für Wachstumsverhalten und damit nur für große“ Eingaben ” Mariano Zelke Datenstrukturen 10/17 Die asymptotische Notation f , g : N → R≥0 seien Funktionen, die einer Eingabelänge n ∈ N eine nicht-negative Laufzeit f (n), bzw. g (n) zuweisen. I Die Groß-Oh Notation: f = O(g ) ⇔ Es gibt eine positive Konstante c > 0 und eine natürliche Zahl n0 ∈ N, so dass f (n) ≤ c · g (n) für alle n ≥ n0 gilt: f wächst höchstens so schnell wie g . I f = Ω(g ) ⇔ g = O(f ) : f wächst mindestens so schnell wie g . I f = Θ(g ) ⇔ f = O(g ) und g = O(f ) : f und g wachsen gleich schnell. I I = 0: f wächst Die Klein-Oh Notation: f = o(g ) ⇔ lim gf (n) n→∞ (n) langsamer als g . g (n) n→∞ f (n) f = ω(g ) ⇔ lim Mariano Zelke = 0: f wächst schneller als g . Datenstrukturen 11/17 Die asymptotische Notation, Beispiel 4· n3 32 + n(n+1) 2 n3 32 + n(n+1) 2 ≤ ZeitA1 (n) 2n = ZeitB (n) n 0 = n0 Es gilt: ZeitB (n) = O(ZeitA1 (n)) denn: Es gibt positive Konstante c = 4 und n0 = 0 so dass ZeitB (n) ≤ c · ZeitA1 (n) für alle n ≥ n0 Mariano Zelke Datenstrukturen 12/17 Die asymptotische Notation, Beispiel n3 32 n2 3 + n(n+1) 2 ≤ ZeitA1 (n) + 3 = ZeitD (n) n 0 n0 Es gilt: ZeitD (n) = O(ZeitA1 (n)) denn: Es gibt positive Konstante c = 1 und n0 = 2,62 so dass ZeitD (n) ≤ c · ZeitA1 (n) für alle n ≥ n0 Mariano Zelke Datenstrukturen 12/17 Die asymptotische Notation, Beispiel 3· n3 32 + n(n+1) 2 n3 32 n2 3 + n(n+1) 2 ≤ ZeitA1 (n) + 3 = ZeitD (n) n 0 n0 Es gilt: ZeitD (n) = O(ZeitA1 (n)) denn: Es gibt positive Konstante c = 3 und ein n0 = 1,5 so dass ZeitD (n) ≤ c · ZeitA1 (n) für alle n ≥ n0 Mariano Zelke Datenstrukturen 12/17 Die Laufzeit von A1 ist kubisch Wir müssen ZeitA1 (n) = O(n3 ) und n3 = O(ZeitA1 (n)) zeigen. I I Warum gilt ZeitA1 (n) = O(n3 )? n·(n+1) . 2 I Wir wissen ZeitA1 (n) ≤ n3 + I Also ist ZeitA1 (n) ≤ n3 + I ZeitA1 (n) = O(n3 ): Setze c = 2 und n0 = 1. n·2n 2 ≤ n3 + 2n3 2 = 2 · n3 . Warum gilt n3 = O(ZeitA1 (n))? I Wir wissen 3 n 32 n3 32 + n·(n+1) 2 ≤ ZeitA1 (n). ≤ ZeitA1 (n) und deshalb folgt n3 ≤ 32 · ZeitA1 (n). I Also ist I n3 = O(ZeitA1 (n)): Setze c = 32 und n0 = 0. ZeitA1 (n) = Θ(n3 ): Die Laufzeit wächst (ungefähr) um den Faktor 8, wenn wir die Eingabelänge verdoppeln. Mariano Zelke Datenstrukturen 13/17 Wie schnell dominiert die Asymptotik? Annahme: Ein einfacher Befehl benötigt 10−9 Sekunden. n2 n n3 n10 2n n! 1012 ≥ 1013 ≥ 1031 | {z } 16 32 256 1.024 4.096 32.768 ≥ ≥ 1015 65536 ≥ 4 · 109 64 4.096 262.144 ≥ 1018 | {z } ≥ 6 · 1019 | {z } 128 256 512 1024 106 16.384 65.536 262.144 1.048.576 ≥ 1012 | {z } mehr als 15 Minuten 2.097.152 16.777.216 134.217.728 ≥ 109 ≥ 1018 | {z } mehr als 10 Jahre mehr als 10 Jahre mehr als 600 Jahre Mariano Zelke Datenstrukturen mehr als 1014 Jahre 14/17 Grenzwerte Grenzwerte sollten das Wachstum voraussagen! Der Grenzwert der Folge f (n) g (n) f (n) n→∞ g (n) existiere und es sei lim = c. I Wenn c = 0, dann ist f = o(g ). (f wächst langsamer als g .) I Wenn 0 < c < ∞, dann ist f = Θ(g ). (f und g wachsen gleich schnell.) I Wenn c = ∞, dann ist f = ω(g ). (f wächst schneller als g .) I Wenn 0 ≤ c < ∞, dann ist f = O(g ). (f wächst höchstens so schnell wie g .) I Wenn 0 < c ≤ ∞, dann ist f = Ω(g ). (f wächst mindestens so schnell wie g .) Mariano Zelke Datenstrukturen 15/17 Betrachtung von Grenzwerten, Beispiel n3 32 + n(n+1) 2 ≤ ZeitA1 (n) 2n = ZeitB (n) n 0 ZeitA1 (n) lim ≥ lim n→∞ ZeitB (n) n→∞ n3 32 + n(n+1) 2 = lim n→∞ 2n n3 n(n + 1) + 64n 4n =∞ Damit gilt: ZeitA1 (n) = ω(ZeitB (n)) Das heißt, ZeitA1 (n) wächst schneller als ZeitB (n). Mariano Zelke Datenstrukturen 16/17 Betrachtung von Grenzwerten, Beispiel n3 32 n2 3 + n(n+1) 2 ≤ ZeitA1 (n) + 3 = ZeitD (n) n 0 ZeitD (n) lim ≤ lim n→∞ ZeitA1 (n) n→∞ n3 32 n2 3 +3 + n(n+1) 2 32n2 + 288 = 0 n→∞ 3n3 + 48n(n + 1) = lim Damit gilt: ZeitD (n) = o(ZeitA1 (n)) Das heißt, ZeitD (n) wächst langsamer als ZeitA1 (n). Mariano Zelke Datenstrukturen 16/17 Rechnen mit Grenzwerten f , g : N → R≥0 seien Funktionen. (a) Die Operation ◦ bezeichne eine der Operationen +, − oder ∗. Dann gilt lim (f (n) ◦ g (n)) = lim f (n) ◦ lim g (n), n→∞ n→∞ n→∞ falls die beiden Grenzwerte auf der rechten Seite existieren und endlich sind. f (n) n→∞ g (n) (b) lim lim f (n) = n→∞ lim g (n) , falls die beiden Grenzwerte auf der rechten n→∞ Seite existieren, endlich sind und lim g (n) 6= 0 gilt. n→∞ I I lim n→∞ n3 +n·(n+1)/2 n3 Also ist n3 + Mariano Zelke n3 3 n→∞ n = lim n·(n+1) 2 + lim n→∞ n·(n+1)/2 n3 = 1. = Θ(n3 ). Datenstrukturen 17/17