Komplexität von Algorithmen Ziel Angabe der Effizienz eines Algorithmus unabhängig von Rechner, Programmiersprache, Compiler. Page 1
Eingabegröße n n Integer, charakterisiert die Größe einer Eingabe, die zu verarbeiten ist, z.b. Länge einer Liste, die zu durchsuchen ist, Anzahl der Knoten eines Baumes, die zu drucken sind, Anzahl von Elementen eines Arrays, die zu sortieren sind, Anzahl der Wörter in der Eingabe, die zu parsen sind. Komplexität eines Algorithmus a) Ausmaß an Speicherplatz b) Ausmaß an Rechenzeit O Obere Komplexitätsgrenze (höchstens) Untere Komplexitätsgrenze (mindestens) Genaue Komplexität (genau) Page 2
Bedeutung der O-Notation Aussage: Bedeutet: Das Programm erledigt seine Aufgabe in O(g(n)) Schritten. Tatsächliche Anzahl der ausgeführten Schritte ist nicht größer als eine Konstante K mal g(n). Definition der O-Notation f ( n) O ( g ( n)) f ist g.-ter Ordnung Genau dann, wenn es positive Konstanten K und n 0 gibt, so dass f(n) K g(n) für alle n n 0. Page 3
Beispiel f ( n) O ( g ( n)) f ist g.-ter Ordnung da 8n 3 + n 2 + 76 ist O(n 3 ) 8n 3 + n 2 + 76 85n 3 für alle n 1 Bedeutung der O-Notation 3000 2000 1000 K * g(n) f(n) 1 1 500 1000 1500 2000 n n 0 Page 4
Interpretationen der Grafik f(n) = O(g(n)) genau dann, wenn die Kurve zu K x g(n) für ein konstantes Vielfaches von g(n) oberhalb der Kurve zu f(n) liegt, sobald ein genügend großer Wert n 0 erreicht ist. Es gibt eine Proportionalitätskonstante K, so dass die Kurve zu f(n) oben durch die Kurve zu K x g(n) begrenzt ist, sobald ein genügend großes n 0 erreicht ist. Für alle Werte (außer endlichen vielen kleinen Werten) von n liegt die Kurve zu f(n) unterhalb der Kurve zu einem genügend großen konstanten Vielfachen von g(n). Eigenschaften der O-Notation Betont die dominante Größe (höchster Exponent) für Aufgaben mit großem Umfang. Ignoriert Größen kleinerer Ordnung und Aufgaben mit kleinem Umfang. Ignoriert Proportionalitätskonstante. Im Grunde eine sehr ungenaue Abschätzung! Es soll eben nur eine Abschätzung der oberen Grenze des Aufwands angegeben werden ("worst case"). Page 5
Definition der -Notation f ( n) ( g ( n)) f ist g.-ter Ordnung Genau dann, wenn es positive Konstanten K und n 0 gibt, so dass f(n) K g(n) für alle n n 0 Definition der -Notation f ( n) ( g ( n)) f ist g.-ter Ordnung Genau dann, wenn und f ( n) O ( g ( n)) f ( n) ( g ( n)) Page 6
Kosten Bedeutung von gross-o Beschreibt die allgemeine Form der Komplexitätskurve konstant O(1) logarithmisch O(log n) linear n log n O(n) O(nlogn) quadratisch O(n 2 ) kubisch O(n 3 ) exponentiell O(2 n ), Berechnung geschlossener Funktionen Suche unter gewissen Bedingungen Suche im ungünstigen Fall, Fakultät Sortieren Optimierungsprobleme O(2 N ) O(N 3 ) O(N 2 ) O(NlogN) O(N) O(logN) O(l) Problemgrösse N Page 7
zum Beispiel N log 2 N N*log 2 N N 2 2 N 1 0 0 2 1 2 4 2 8 8 3 24 16 4 64 32 5 160 64 6 384 128 7 896 1 4 16 64 256 1024 4096 16,384 2 4 16 256 65,536 4,294,967,296.. Wie kann man Gross-O bestimmen? 5 Fälle zur Bestimmung der Laufzeit-Komplexiät 1. Schleifen 2. geschachtelte Schleifen 3. Aneinanderreihung von Anweisungen 4. If-then-else Anweisung 5. Logarithmische Komplexität Page 8
Fall1: Schleifen Die Laufzeit in einer Schleife besteht hauptsächlich aus der Laufzeit innerhalb der Anweisungen in der Schleife multipliziert mit der Anzahl von Iterationen. for (i=1; i <= n; i++) { n Durchläufe m = m + 2; Konstante Zeit c Gesamtzeit = c * n = cn = O(N) Fall 2: geschachtelte Schleifen Analyse von Innen nach Aussen. Gesamte Laufzeit ist das Produkt aller Schleifen. äussere Schleife n- Durchläufe for (i=1; i<=n; i++) { for (j=1; j<=n; j++) { k = k+1; konstante Zeit innere Schleife n- Durchläufe Gesamtzeit = c * n * n * = cn 2 = O(N 2 ) Page 9
Fall 3: Aneinanderreihung von Anweisungen Addiere die Komplexität aller Anweisungen. konstante Zeit konstante Zeit äussere Schleife n- Durchläufe x = x +1; for (i=1; i<=n; i++) { m = m + 2; for (i=1; i<=n; i++) { for (j=1; j<=n; j++) { k = k+1; konstante Zeit n- Durchläufe innere Schleife n- Durchläufe Gesamtzeit = c 0 + c 1 n + c 2 n 2 = O(N 2 ) Fall 4: If-Anweisung Im schlechtesten Fall: der Test, plus entweder der if-true Teil oder der else Teil (der grössere von beiden!). Test: konstant nochmals if : konstant + konstant if (depth( )!= otherstack.depth( ) ) { return false; else { for (int i = 0; i < depth( ); i++) { if (!list[i].equals(otherstack.list[i])) return false; if-true Teil: konstant else Teil: ( konstant + konstant ) * n Laufzeit = c 0 + (c 1 + c 2 ) * n = O(N) Page 10
Fall 5: Logarithmische Komplexität Ein Algorithmus ist O(log N), wenn es konstante Zeit benötigt das Problem auf einen Bruchteil zu reduzieren (z.b. zu halbieren ) Beispiel binäres Suchen : Suche ein Wort in einem Wörterbuch mit n Seiten Suche in der Mitte des Buchs. Ist das Wort vor oder nach der Mitte? Wiederholen des Vorgangs im entsprechenden Teil des Wörterbuchs bis das Wort gefunden ist. Laufzeitanalyse des Summe-Algorithmus s ( n ) i n kann in linearer Zeit berechnet werden. i 1 Wie man aber schon seit Gauss weiß, ist das nicht optimal: 1 2 3... n -2 n-1 n n gerade: s(n) = ½ n (n+1) n ungerade: s(n) = ½ (n-1) n + n ----> = (n/2 ½) n + n ----> = (n/2 + ½) n ----> = ½ n (n+1)... erlaubt die Berechnung von s(n) in einem Schritt, also in konstanter Zeit. (Es wird nicht berücksichtigt, dass es eine Addition, eine Multiplikation und eine Division sind.) Page 11
Türme von Hanoi Gegeben: Drei Stäbe auf denen n unterschiedlich große Scheiben stecken. Stäbe und Scheiben eindeutig benannt, beispielsweise Stäbe A, B, C und für n = 3 Scheiben 1, 2, 3 Ziel: Kompletten Turm von einem Startstab (hier A) auf einen anderen Zielstab versetzen, z.b. B A B C Regeln 1. Es darf immer nur die oberste Scheibe von einem Stab auf einen anderen versetzt werden. 2. Eine größere Scheibe darf niemals auf einer kleineren liegen. Rekursion: Türme von Hanoi Lösen des Problems für die Spitze (n - 1) Scheiben. from to help Wenn man es für (n-1) Scheiben löst, dann geht es auch mit n. Rekursionsverankerung: 1 Scheibe transportieren. Page 12
Türme von Hanoi public class Hanoi { public static void main(string[] args){ hanoi( Integer.parseInt(args[0]), 'A', 'B', 'C' ); static void hanoi(int discs, char from, char to, char help) { if( discs!= 1) { hanoi(discs - 1, from, help, to); // now you can move the lowest disc! hanoi(discs - 1, help, to, from); System.out.println( discs + moved " + from + "-->" + to); Türme von Hanoi Page 13
Komplexität der Türme von Hanoi: Sie T n die Anzahl der Bewegungen um n Scheiben zu versetzen. T 0 = 0 T n = T n-1 + 1 + T n-1 => T n = 2 T n-1 +1 für n>1 Beweis durch umformen: wir untersuchen: T n = 2 T n-1 + 1 +1 T 0 = 0 T n +1= 2 T n-1 + 2 T 1 = 1 Sei U n = T n + 1 T 2 = 3 dann ist T 3 = 7 U o = 1 T n = 2 n -1????? und U n = 2 U n-1 für n > 0 => U n = 2 n => T n = 2 n -1 Die Türme von Hanoi sind O (2 n ) Fibonacci Folge Wie viele Kaninchen leben nach einem Jahr? (Leonardo da Pisa, Filius des Bonacci 1202) Angenommen, jedes Paar erzeugt jeden Monat ein weiteres Paar, wobei Kaninchen im Alter von zwei Monaten geschlechtsreif sind. Wie viele Paare Kaninchen leben nach einen Jahr ausgehend von einem Paar? Page 14
Fibonacci Folge 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 17711 28657 46368 75025 121393 196418 317811 514229 832040 Laufzeitanalyse Fibonacci-Algorithmus Fibonacci-Rekursionsgleichung: fib(0) = 0 fib(1) = fib(2) = 1 fib(i) = fib(i-1) + fib(i-2) für i > 2 Laufzeit: Tfib(1) = 1 und Tfib(2) = 1 Tfib (i) = Tfib(i-1) + Tfib(i-2) + 1 (+ 1 für die Addition) Die Fibonacci-Zahlen (und ihr rekursiver Berechnungsaufwand) wachsen schneller als jede polynomielle Funktion. Sie wachsen genauso wie Kaninchenpopulationen exponentiell. Page 15
Aufwand der rekursiven Fibonacci-Berechnung Bsp.: Berechnungsbaum für fib( 5 ) 5 3 2 Ergebnis 5 4 3 2 1 1 1 1 3 1 2 2 1 2 1 wie hoch ist der Aufwand? Berechnungsaufwand: Fibonacci rekursiv 7 6 5 5 4 2 rekursive Aufrufe mit einer Addition für jede Stufe: n > 2 Aufwand( n ) = Aufwand( n - 1 ) + Aufwand( n - 2 ). genau wie die Werte selbst: exponentiell (mit Basis 1,618034). Unnötig hoher Aufwand, da Fibonacci Zahlen mehrfach berechnet werden Was schlagen Sie zur Lösung vor? Page 16
Fibonacci iterativ Fibonacci Rekursionsformel: fib 1 = 1, fib 2 = 1, fib n = fib n - 1 + fib n - 2 (für n > 2) Jede Rekursion lässt sich als Schleife formulieren: int result = fib1 = fib2 = 1; i = 2; while ( i < n ) { i = i + 1; result = fib1 + fib2; // für nächsten Schleifendurchlauf: fib1 = fib2; fib2 = result; // while Idee: Vorgänger aufheben Berechnungsaufwand linear: O(n) n - 1 Schleifendurchläufe Gestiegener Speicherbedarf: 3 Variablen Lösen der Rekursionsgleichung! Beweis per Induktion Raten und Ausprobieren von Lösungen bzw -Abschätzungen (vgl. auch die Lösung zu s ( n ) i ) selten trivial!! Mathematik (Analysis) n i 1 Bekannte Algorithmen-Schemata nutzen LERNEN!! Page 17
Abschätzen der Fibonacci-Rekurrenz! Fibonacci-Rekursionsgleichung: fib(1) = fib(2) = 1 fib(i) = fib(i-1) + fib(i-2) für i > 2 Naheliegend ist, dass g(h) = 2 n eine obere Abschätzung ist fib(0) = 0 < 2 0 und fib (1) = 1 < 2 1 Induktionsschritt: Einsetzen in die Rekursionsgleichung ergibt fib(i) = fib(i-1) + fib(i-2) < 2 i-1 + 2 i-2 < 2*2 i-1 = 2 i die zweiten < Abschätzung ist jedoch ziemlich großzügig!! Abschätzen der Fibonacci-Rekurrenz! Untere Grenze: Wir zeigen, dass für gewisse Konstanten a,c gilt: f(n) >= ac n, n >= n 0 Induktives einsetzen in Rekursionsgleichung: fib(n) = fib(n-1) + fib(n-2) >= ac n-1 + ac n-2 = ac n (c/c 2 + 1/c 2 ) = ac n ((c+1)/c 2 ) Letzter Ausdruck sollte >= ac n sein d.h.: ((c+1)/c 2 ) >= 1 Dies ist äquivalent zu c 2 c 1 <= 0. Eine Lösung der quadratischen Gleichung lautet c = (1+ 5) / 2 (goldener Schnitt). Damit ist der Induktionsschritt gezeigt für c <= ( 5 + 1) / 2 = 1.618... Der Induktionsanfang gilt für geeignete Konstante a und Startwert n 0. Obere Grenze: Durch vertauschen von >= und <= zeigt man, dass f(n) <= bd n für d >= (1+ 5) / 2 und eine Konstante b gilt. Somit : 1 5 fib( n) 2 n Page 18
Weitere Komplexitätsklassen! Neben den bisher betrachteten deterministischen Algorithmen und Komplexitätsklassen gibt es noch nichtdeterministische Problemklassen: P bezeichnet die Klasse aller in polynomieller Zeit ablaufenden Algorithmen, bzw die Klasse aller Probleme, für die solche Algorithmen bekannt sind. NP bezeichnet die Klasse der Probleme, die nichtdeterministischpolynomiell lösbar sind: man rät eine (potentielle) Lösung und kann dann in polynomieller Zeit testen, ob es eine Lösung ist. Wenn es exponentiell viele Möglichkeiten gibt, die man jeweils in polynomieller Zeit generieren kann, ist das Problem deterministisch-exponentiell (EXP) Oft kann man das raten durch polynomielle Heuristiken unterstützen. Weitere Komplexitätsklassen! (2) Offensichtlich ist P NP. Ob NP P ist weiss man nicht, vermutlich nicht. Exponentiell und NP ist aber immer noch besser als unentscheidbar (Halteproblem). Auch da gibt es in der Komplexitätstheorie verschiedene Zwischenklassen... Der rekursive Fibonacci-Algorithmus ist exponentiell. Es gibt Besseres! - Beliebig gute Programmierkenntnisse nützen nichts, wenn man einen suboptimalen Algorithmus ausgewählt hat. Page 19
Literatur Helmut Balzert 1999 Lehrbuch Grundlagen der Informatik Spektrum Akademischer Verlag, Heidelberg Barton, Edward G. Jr.; Berwick Robert C. und Eric Sven Ristad (1987): Computational Complexity and Natural Language. Cambridge, Mass.: MIT Press. Standish, Thomas A. (1998): Data Structures in Java. Reading, Mass.: Addison Wesley Longman, Inc. Page 20