Algorithmen und Datenstrukturen Fachhochschule Regensburg Komplexitätsanalyse, Laufzeitberechnung, Big-O-Notation 4. Übungsblatt Algorithmen und Datenstrukturen Name: ________________________ Lehrbeauftragter: Prof. Sauer Vorname: _____________________ Laufzeitberechnung Die Laufzeit ist bestimmt durch die Anzahl der durchgeführten elementaren Operationen (Grundrechenarten, Vergleiche, Feldzugriffe, Zugriffe auf die Komponenten einer Struktur, etc.) Die Angabe der Laufzeit in Abhängigkeit von konkreten Eingabewerten ist im Allg. nicht möglich oder sehr aufwendig. Daher betrachtet man die Laufzeit häufig in Abhängigkeit von der Größe (dem Umfang) der Eingabe. Definition: T(n) = Anzahl der elementaren Operationen, die zur Bearbeitung einer Eingabe der Größe n bearbeitet werden. Eine Analyse der Laufzeit bezieht sich auf den besten, den schlechtesten und den mittleren Fall: -Tmin(n) = minimale Anzahl der Operationen, die durchgeführt werden, um eine Eingabe der Größe n zu bearbeiten. - Tmax(n) = maximale Anzahl der elementaren Operationen, die durchgeführt werden, um eine Eingabe der Größe n zu bearbeiten. Ist eine Wahrscheinlichkeitsverteilung der Eingabedaten gegeben, kann auch eine mittlere Laufzeit Tmit(n) ermittelt werden. Bsp.: Sequentielle Suche in Folgen Gegeben ist eine Zahl n ≥ 0 , n Zahlen a1, a2, …, an (alle verschieden), eine Zahl b. Gesucht ist der Index i = 1,2,…,n, so dass b == ai, falls ein Index existiert. Andernfalls ist i = n+1. Lösung: i = 1; while (i <= n && b != ai) i = i + 1; Aufwand der Suche: Ergebnis hängt von der Eingabe ab, d.h. von n, a1, …, an und b 1. erfolgreiche Suche (wenn b == ai): S = i Schritte 2. erfolglose Suche S = n+1 Schritte Ziel: globalere Aussagen, die nur von einer einfachen Größe abhängen, z.B. von der Länge n der Folge. Fragen: 1) Wie groß ist S für gegebenes n im schlechtesten Fall? 2) Wie groß ist S für gegebenes n im Mittel Analyse für erfolgreiche Suche: - im schlechtesten Fall: b wird erst im letzten Schritt gefunden: b = an , S = n im schlechtesten Fall - im Mittel - Wiederholte Anwendung mit verschiedenen Eingaben - Annahme über Häufigkeit: Wie oft wird b an erster, zweiter, … letzter Stelle gefunden? - Insgesamt für N-Suchvorgänge N N N N N n(n + 1) n +1 ⋅ 1 + ⋅ 2 + ... + ⋅ n = (1 + 2 + ... + n ) = ⋅ =N n n n n n 2 2 n +1 M Schritte, also S = im Mittel bei Gleichverteilung - für eine Suche S = N 2 M = 1 Algorithmen und Datenstrukturen Asymptotische Analyse (O-Notation) 1 (Analyse der Komplexität durch Angabe einer Funktion f : N → N als Maß für den Aufwand) Definition: f (n) ist in der Größenordnung von g (n ) „ f ( n) = O ( g ( n)) “, falls Konstante c und n0 existieren 2 , so dass f ( n) ≤ c ⋅ g ( n) für n ≥ n0 . f ( n) ist für genügend große n durch eine Konstante c beschränkt, d.h. f wächst nicht schneller als g. g ( n) Ziel der Charakterisierung T ( n) = O ( g ( n)) ist es, eine möglichst einfache Funktion g (n ) zu finden. Bspw. ist T ( n ) = O ( n) besser als T ( n) = O (5n + 10) . Wünschenswert ist auch die Charakterisierung der Laufzeit mit einer möglichst kleinen Größenordnung. Die O-Natation besteht in der Angabe einer asymptotischen oberen Aufwandsfunktion (Wachstumsgeschwindigkeit bzw. Größenordnung) Schranke für die Vorgehensweise bei der Analyse Die Algorithmen werden gemäß ihrer Kontrollstruktur von innen nach außen analysiert. In der Laufzeit, die sich dann ergibt, werden anschließend die Konstanten durch den Übergang zur O-Notation beseitigt. Vorgehensweise für Kontrollstrukturen Anweisungen: Anweisungen, die aus einer konstanten Anzahl von elementaren Operationen bestehen, erhalten eine konstante Laufzeit. Sequenz A1,A2,…,An. Werden für die einzelnen Anweisungen, die Laufzeiten T1, T2,…,Tn ermittelt, dann ergibt sich für die Sequenz die Laufzeit T=T1+T2+…+Tn Schleife, die genau n-mal durchlaufen wird, z.B. for-Schleife ohne break: for (i=1;i<=n;i++) A; Wird für A die Laufzeit Ti ermittelt, dann ergibt sich als Laufzeit für die for-Schleife T = n ∑T i =1 i . Eigentlich müsste zu Ti noch eine Konstante C1 für i <=n und i++ und C2 für i = 1 hinzugezählt werden. Beim späteren Übergang zur O-Notation würde die Konstanten jedoch wegfallen 3 . Fallunterscheidung (mit else-Teil): if (B) A1, else A2; Hier muß zwischen der Laufzeit im besten und schlechtesten Fall unterschieden werden: Tmin=min(T1,T2), Tmax=max(T1,T2), wobei T1 die Laufzeit für A1 und T2 die Laufzeit für A2 ist. Man geht davon aus, dass die Bedingung B konstante Zeit benötigt und wegen des späteren Übergangs zur O-Notation einfachheitshalber nicht mitgezählt werden muß. Schleife mit k-maligen Durchläufen, wobei n1<=k<=n2. Diese tritt typischerweise bei while-Schleifen auf. Es muß dann eine Analyse für den besten Fall (k=n1) und den schlechtesten Fall (k=n2) durchgeführt werden. Rekursion mit n → n − 1 : Es ergeben sich rekursive Gleichungen für die Laufzeiten Bsp.: rekursive Fakultätsberechnung int fak(int n) { if (n == 0) return 1; else return n*fak(n-1); } Man erhält folgende Laufzeit: Tn = C 0 für n = 0 Tn = C1 + T (n − 1) ) für n > 0 Durch wiederholtes Einsetzen: T ( n) = C1 + C1 + ... + C1 + C 0 = O ( n) 1442443 n − mal Rekursion mit Teile und Herrsche. 1 2 Skriptum, 1.2.6.2 geeignetes n0 und c müssen angegeben werden, um zu zeigen, dass f ( n) = O( g ( n)) gilt 3 vgl. Skriptum, 1.2.7 2 Algorithmen und Datenstrukturen f ( x, n ) { if (n == 1) (1) { // Basisfall /* löse Pr oblem direkt , Ergebnis sei loes * / return loes; } else { // Teileschritt /* teile x in 2Teilprobleme x1und x 2 jeweils derGröße n / 2 * / loe1 = f ( x1, n / 2); loe2 = f ( x 2, n / 2); // Herrscheschritt /* Setze Loesung loes für x aus loe1und loe2 zusammen * / return loes; ( 2) (3) } } Für den Basisfall (1) wird eine konstante Anzahl C0 Operationen angesetzt. (2) und (3) benötigen linearen Aufwand und damit C1 ⋅ n Operationen. T ( n) = C 0 falls n = 1 T (n) = C1 ⋅ n + 2 ⋅ T (n / 2) 4 5 Durch Einsetzen ergibt sich: T (n) = C1 n + 2(C1 ⋅ n / 2 + 2T (n / 4) ) = 2 ⋅ C1 ⋅ n + 4 ⋅ T (n / 4) ) Durch nochmaliges Einsetzen ergibt sich: T (n) = 3 ⋅ C1 ⋅ n + 8 ⋅ T (n / 8) ) T (n) = log 2 (n) ⋅ C1 ⋅ n + 2 log n T (1) log n Mit 2 = n und T (1) = C 0 erhält man: T (n) = C1 ⋅ n ⋅ log 2 (n) + C 0 ⋅ n Zur Lösung von Rekurrenzgleichungen haben sind 2 Verfahrenstechniken bekannt: Substitutionsmethode bzw. Mastertheorem. Lösung mit der Substitutionsmethode: „Rate eine Lösung“ (z.B. über den Rekursionsbaum) Beweise die Korrektheit der Lösung per Induktion Lösung mit dem Mastertheorem: Mit dem Mastertheorem kann man sehr einfach Rekurrenzen der Form ⎛N⎞ T ( n) = 2 ⋅ T ⎜ ⎟ + Θ(n) berechnen ⎝2⎠ 4 n lässt sich ⎣log 2 n ⎦ -mal halbieren. Falls n eine Zweierpotenz ist (d.h. n = 2k), lässt sich n sogar exakt log 2 n = k oft halbieren. n soll der Einfachheit halber hier eine Zweierpotenz sein. 5 Rekurrenzgleichung: Die Analyse rekursiver Algorithmen führt meistens auf eine sog. Rekurrenzgleichung 3 Algorithmen und Datenstrukturen Vollständige Induktion Das Beweisverfahren der vollständigen Induktion ist ein Verfahren, mit dem Aussagen über natürliche Zahlen bewiesen werden können. Neben Aussagen über natürliche Zahlen können auch damit gut Aussagen bewiesen werden die - rekursiv definierte Strukturen und - abzählbare Strukturen betreffen. Grundidee: Eine Aussage ist gültig für alle natürlichen Zahlen n ∈ N , wenn man nachweisen kann: Die Aussage gilt für die erste natürliche Zahl n = 1 (Induktionsanfang) Wenn die Aussage für eine natürliche Zahl n gilt, dann gilt sie auch für ihren Nachfolger n+1 (Induktionsschritt) Einf. Bsp.: S ( n) = n 1 ∑ i = 1 + 2 + 3 + ... + n = 2 ⋅ n ⋅ (n + 1) i =1 Beweis: Induktionsanfang: 1 ⋅ 1 ⋅ (1 + 1) = 1 2 Induktionsschritt: Induktionsvoraussetzung: 1 ⋅ k ⋅ (k + 1) 2 1 ⋅ (k + 1) ⋅ (k + ) 2 2 k +1 k 1 1 1 i = i + (k + 1) ) = ⋅ k ⋅ (k + 1) + k + 1 = ⋅ (k 2 + k ) + (2k + 2) ∑ ∑ 2 2 2 i =1 i =1 1 1 = k 2 + k + 2k + 2 = (k + 2) ⋅ (k + 1) 2 2 Zu zeigen, dass gilt: ( ) Rekurrenzrelationen 6 Eine Rekurrenzrelation (kurz Rekurrenz) ist eine Methode, eine Funktion durch einen Ausdruck zu definieren, der die zu definierende Funktion selbst enthält, z.B. Fibonacci-Zahlen 7 . Wie löst man Rekurrenzgleichungen? Es gibt 2 Verfahren: Substitutionsmethode bzw. Mastertheorem. Abschätzen von Rekurrenzrelationen Asymptotische Abschätzung mit dem Master-Theorem Das Mastertheorem hilft bei der Abschätzung der Rekurrenzen der Form T ( n) = a ⋅ T ( n / b) + f ( n) 8 Master-Theorem - a ≥ 1 und b > 1 sind Konstanten. f (n) ist eine Finktion und T (n ) ist über den nicht-negativen ganzen Zahlen durch folgende Rekurrenzgleichung definiert: T (n) = a ⋅ T (n / b ) + f (n) . Interpretiere n / b so, dass entweder ⎣n / b ⎦ oder ⎡n / b ⎤ - Dann kann T (n ) folgendermaßen asymptotisch abgeschätzt werden: ⎧ Θ(n logb a ) falls gilt : ∃ε > 0 mit f (n) = O(n logb a −ε ) ⎪ T (n) = ⎨ Θ n logb a ⋅ log n falls gilt : f (n) = Θ(n logb a ) ⎪Θ( f (n)) falls : ∃ε > 0 mit f (n) = Ω(n logb a +ε ) ∧ ∃c < 1 : ∀n > n0 : a ⋅ f (n / b) ≤ c ⋅ f (n) ⎩ ( ) 6 Rekurrenzrelationen sind ein wichtiges Hilfsmittel bei der Analyse rekursiv formulierter Algorithmen vgl. Skriptum 1.2.7.1, 3.2.3 8 Solche Rekurrenzen treten oft bei der Analyse sogenannter Divide-and-Conquer-Algorithmen auf. 7 4 Algorithmen und Datenstrukturen Anwendung des Theorems an einigen Beispielen 1. T ( n) = 9 ⋅ T ( n / 3) + n a = 9, b = 3, f (n) = n ) mit ε = 1 gilt, kann Fall 1 des Master-Theorems angewendet werden. log 9 2 Somit gilt: T ( n) = Θ n 3 = Θ(n ) 2. T ( n) = T ( 2n / 3) + 1 a = 1, b = 3 / 2 log a log a log 1 0 Da n b = n 3 / 2 = n = 1 ist, gilt f ( n) = Θ( n b ) = Θ(1) , und es kommt Fall 2 des Masterlog 1 Theorems zur Anwendung. Somit gilt: T ( n) = Θ( n 3 / 2 log n) = Θ(log n) 3. T ( n) = 3 ⋅ T ( n / 4) + n ⋅ log n a = 3, b = 4, f ( n) = n log n log a log 3 0.379 Es ist n b = n 4 = O ( n ). Somit ist f (n) = Ω(n log 4 3+ε ) mit ε ≈ 0.2 . Weiterhin gilt für hinreichend große n: a ⋅ f (n) = 3 ⋅ (n / 4) ⋅ log(n / 4) ≤ (3 / 4 )n log n . Fall 3 des Master-Theorems kann damit angewandt werden: T ( n) = O ( f ( n)) = Θ( n ⋅ log n) Da f ( n) = O ( n log 3 9 −ε ( ) Achtung! Es gibt Fälle, in denen die Struktur der Gleichung zu passen „scheint“, aber kein Fall des Master-Theorems existiert, für den alle Bedingungen erfüllt sind. Analyse von Algorithmen über O-Notation Bestimmen der Laufzeitkomplexität von Algorithmen in O-Notation durch Abschätzung der Größenordnung mit einfachen Regeln 9 . for-Schleifen Aufwand: Laufzeit = max. Laufzeit der inneren Anweisungen * Anzahl der Iterationen Bsp.: for (i=1; i < n;i++) a[i]=0; Wenn innere Operation O(1), dann n ⋅ O (1) = O ( n) geschachtelte for-Schleifen Bsp.: for (i=1;i<n;i++) for (j=1;j<n;j++) k=k+1; n ⋅ n ⋅ O (1) = O (n 2 ) Sequenz Bsp.: for (i=1; i < n;i++) a[i]=0; for (i=1;i<n;i++) for (j=1;j<n;j++) a[i]=a[i]+a[j]+i+j; O (n) + O (n 2 ) = O (n 2 ) if-else-Bedingungen Laufzeit: Aufwand für Test + max(Aufwand für A1, Aufwand für A2) Bsp.: if (x > 100) y=x; else for (i=1;i<n;i++) if (a[i]>y) y=a[i]; O (n) P- vs. NP-Probleme 10 - Grenze zwischen effizient lösbaren (polynomialer Aufwand) und nicht effizient lösbaren (exponentieller Aufwand) Problemen 9 vgl. 1.2.7 vgl. Skriptum, 1.2.7.3 10 5 Algorithmen und Datenstrukturen - Problemkasse P: Menge aller Probleme, die mit Hilfe deterministischer Algorithmen in polynomialer Zeit gelöst werden können. - Problemklasse NP: Menge aller Probleme, die nur mit Hilfe nichtdeterministischer Algorithmen in polynomialer Zeit gelöst werden können -- Nichtdeterminismus: „Raten“ der richtigen Variante bei mehreren Lösungen -- Umsetzung mit deterministischen Algorithmen: exponentieller Aufwand NP-Vollständigkeit - Allgemein P ⊆ NP - Jedoch offenes Problem, ob sich NP-Probleme nicht doch mit polynomialen Aufwand lösen lassen (also P=NP) - Beweisbar: Existenz einer Klasse von verwandten Problemen aus NP mit folgender Eigenschaft: Falls eines dieser Probleme in polynomialer Zeit mit einem deterministischen Algorithmus gelöst werden könnte, so ist dies für alle Probleme aus NP möglich. - Man spricht dann von NP-vollständigen Problemen. Bsp. 1. TSP-Problem - Problem der Graphentheorie - Gegeben: Graph mit n Knoten (Städten) und m Kanten (Straßen, mit Entfernungsangaben versehen B 5 3 6 A C 4 E 7 3 4 4 7 D 1 F 4 5 G 5 H Gesucht: kürzeste Route, die alle Städte besucht und am Startpunkt zurückkehrt. 2. Rucksack-Problem Wanderer will seinen Rucksack mit verschiedenen Gegenständen packen (auch Knapsack problem) Gegeben: Rucksack mit Kapazität C, n Gegenstände jeweils Gewicht gi und Wert wi Gesucht: Auswahl der Gegenstände ((Indexierung I ⊆ 1,..., n ), deren Gesamtgewicht die Kapazität nicht überschreitet ( ∑g i∈I { i } ≤ C ) und die Summe der Werte ∑w i∈I 6 i maximal ist