Babeș-Bolyai Universität Cluj Napoca Fakultät für Mathematik und Informatik Grundlagen der Programmierung MLG5005 Rekursion Rekursion ● ● ● Neue Denkweise Wikipedia: “Als Rekursion bezeichnet man den Aufruf oder die Definition einer Funktion durch sich selbst.” Rekursion ist eine Form der Wiederholung ● Rekursion ermöglicht ● elegante Algorithmen ● Komplexitätsanalyse Rekursion ● Beginn der Fibonacci-Folge: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, . . . ● Rekursive Definition der Fibonacci-Folge: a. fib(n) = 0, wenn n = 0 b. fib(n) = 1, wenn n = 1 c. fib(n) = fib(n − 1)+fib(n − 2), wenn n ≥ 2 ● ● Die Rekursion in (2) stoppt, wenn n = 0 oder n = 1. Abbruchbedingung? Rekursion ● Fibonacci-Definition in Python Fibonacci-Zahlen Zu Beginn eines Jahres gibt es genau ein Paar neugeborener Kaninchen. Dieses Paar wirft nach 2 Monaten ein neues Kaninchenpaar und dann monatlich jeweils ein weiteres Paar. Jedes neugeborene Paar vermehrt sich auf die gleiche Weise. Rekursion ● der Fakultätsfunktion: 4! = 4 * 3 * 2 * 1 ● Rekursive Definition der Fakultätsfunktion: a. faku(n) = 1, wenn n = 0 b. faku(n) = n * faku(n-1), wenn n > 0 ● Python: Rekursion ● Euklidischer Algorithmus des GGT: a. ggT(x, y) = x, wenn y = 0 b. ggT(x, y) = ggT((x − y), y), wenn x > y c. ggT(x, y) =ggT(x,(y − x)) wenn x ≤ y ● GGT = größter gemeinsamer Teiler. ● Sie stoppt, wenn y = 0 Rekursion ● Euklidischer Algorithmus in Python Rekursion ● ● Abbruchbedingung? Analog zu unendlichen Schleifen mit for- and while- Schleifen Rekursion vs. Iteration ● ● ● Rekursive Algorithmen sind oft natürlicher und einfacher zu finden als iterative Die Korrektheit rekursiver Algorithmen ist oft einfacher zu prüfen Rekursive Lösungen sind i.A. statisch kürzer und – auch weil verständlicher – änderungsfreundlicher Rekursion vs. Iteration ● Jeder rekursive Algorithmus kann in einen iterativen transformiert werden, indem man den rekursiven Aufruf mit Laufzeitkeller simuliert Rekursion vs. Iteration ● Von Iteration zu Rekursion Rekursion in Mathematik die rekursiv konstruierten Dreiecke finden sich in der Gesamtstruktur wieder Rekursion in Mathematik Pythagoras-Baum Auf einem Quadrat wird auf der Oberseite ein Thaleskreis gezeichnet und dieser beliebig geteilt. Der entstehende Punkt wird mit dem Grundelement verbunden, so dass ein rechtwinkliges Dreieck entsteht. Aus den beiden entstandenen Schenkeln des Dreiecks wird wieder jeweils ein Quadrat konstruiert. An den beiden entstandenen Quadraten wird der Vorgang wiederholt. Rekursion in Mathematik Baum(Seite) { falls Seite zu kurz -> Ende; errichte über der Seite ein Quadrat; zeichne über der gegenüberliegenden Seite ein Dreieck mit vorgegebenen Winkeln; Baum(neue Dreieckseite1); Baum(neue Dreieckseite2); } Rekursion in Natur ● Romanescogemüse Rekursion in Kunst ● Droste Effect Vorteile rekursiver Algorithmen ● kürzere Formulierung ● leichter verständliche Lösung ● Einsparung von Variablen ● ● teilweise sehr effiziente Problemlösungen (z.B. Quicksort) Bei rekursiven Datenstrukturen (zum Beispiel Bäume, Graphen) besonders empfehlenswert Nachteile rekursiver Algorithmen ● ● ● weniger effizientes Laufzeitverhalten (Overhead bei Funktionsaufruf) Verständnisprobleme bei Programmieranfängern Konstruktion rekursiver Algorithmen "gewöhnungsbedürftig" Komplexität Beurteilung von Algorithmen ● ● ● viele Algorithmen, um dieselbe Funktion zu realisieren Welche Algorithmen sind die besseren? nicht-funktionaler Eigenschaften: ● ● Zeiteffizienz: Wie lange dauert die Ausführung? Speichereffizienz: Wie viel Speicher wird zur Ausführung benötigt? ● Benötigte Netzwerkbandbreite ● Einfachheit des Algorithmus ● Aufwand für die Programmierung Ressourcenbedarf ● ● Prozesse verbrauchen: ● Rechenzeit ● Speicherplatz Die Ausführungszeit hängt ab von: ● der konkreten Programmierung ● Prozessorgeschwindigkeit ● Programmiersprache ● Qualität des Compilers Beispiel Leistungsverhalten von Algorithmen ● ● ● ● Speicherplatzkomplexität: Wird primärer & sekundärer Speicherplatz effizient genutzt? Laufzeitkomplexität: Steht die Laufzeit im akzeptablen / vernünftigen / optimalen Verhältnis zur Aufgabe? Theorie: liefert untere Schranke, die für jeden Algorithmus gilt, der das Problem löst Spezieller Algorithmus liefert obere Schranke für die Lösung des Problems Laufzeit Die Laufzeit T(x) eines Algorithmus A bei Eingabe x ist definiert als die Anzahl von Basisoperationen, die Algorithmus A zur Berechnung der Lösung bei Eingabe x benötigt ● Ziel: Laufzeit = Funktion der Größe der Eingabe http://xkcd.com/399/ Laufzeit ● ● ● Sei P ein gegebenes Programm und x Eingabe für P, |x| Länge von x, und T(x) die Laufzeit von P auf x Ziel: beschreibe den Aufwand eines Algorithmus als Funktion der Größe des Inputs Der beste Fall: T(n) = inf {T(x) | |x| = n, x Eingabe für P} ● Der schlechteste Fall: T(n) = sup {T(x) | |x| = n, x Eingabe für P} Minimum-Suche ● Eingabe: Array von n Zahlen ● Ausgabe: index i, so dass a[i]<a[j], für alle j Minimum-Suche Kosten: c1 c2 c3 c4 Max Anzahl: 1 n-1 n-1 n-1 Zeit: T(n) = c1 + (n-1) (c2+c3+c4)< c5n + c1 n= Größe des Arrays Weiteres Beispiel ● Wir betrachten folgenden Algorithmus, der die Funktion f(n) = 1! * 2! * ... * (n-2)! * (n-1)! Weiteres Beispiel ● Anzahl M(n): M(n) = (n-1) + M(n-1)= (n-1) + (n-2) + M(n-2) = (n-1) (n-2)/2 ● Anzahl der Inkrementierungen: I(n) = n + M(n+1)=n(n+1)/2 ● Anzahl der Vergleiche V(n) = I(n+1) = (n+1)(n+2)/2 ● Die Anzahl benötigter Zuweisungen Z(n): Z(n) = 1 + n + I(n) = 1 + n(n+3)/2 Algorithmisches Modell ● Für eine präzise mathematische Laufzeitanalyse benötigen wir ein Rechenmodell, das definiert ● Welche Operationen zulässig sind. ● Welche Datentypen es gibt. ● Wie Daten gespeichert werden. ● ● Wie viel Zeit Operationen auf bestimmten Daten benötigen. Formal ist ein solches Rechenmodell gegeben durch die Random Accsess Maschine (RAM) Basisoperationen und deren Kosten ● ● Als Basisoperationen bezeichnen wir: ● Arithmetische Operationen ● Datenverwaltung ● Kontrolloperationen Kosten: Zur Vereinfachung nehmen wir an, daß jede dieser Operationen bei allen Operanden gleich viel Zeit benötigt Asymptotische Komplexität ● ● ● Laufzeit und Speicherverbrauch wird in einer asymptotischen Notation beschrieben, die weitgehend von unwesentlichen Details abstrahiert. Sie macht nur Aussagen über das Verhalten für „sehr große“ Eingabegrößen. Vergleich von zwei Komplexitätsfunktionen über alle natürlichen Zahlen ist nicht ganz einfach Asymptotische Komplexität ● Vergleich von zwei Komplexitätsfunktionen über alle natürlichen Zahlen ist nicht ganz einfach Wir übernehmen eine mathematische Notation, die zum Vergleichen von Funktionen a) bis auf einen Faktor und b) bis auf eine endliche Anzahl von Ausnahmen verwendet wird Asymptotische Komplexität ● ● Groß-Oh-Notation: bringt zum Ausdruck, dass eine Funktion f(n) höchstens so schnell wächst wie eine andere Funktion g(n). g(n) Ist also die obere Schranke für f(n). Funktion f(n) ist in der Menge O(g(n)), wenn es ein c > 0 und ein n0 ∈ N gibt, so dass für alle n ≥ n0 gilt: f(n) ≤ c . g(n) Asymptotische Komplexität ● O-Notation: Abstraktion durch ● ● ● Ignorieren endlich vieler Anfangswerte (Spezialfälle) durch n ≥ n0 Einführung des konstanten Faktors c in der Definition, der von nur durch Konstanten hervorgerufenen Unterschieden abstrahiert Beispiel: ● ● T1(n) = 100 * n ∈ O(n): T1(n) wächst höchstens so schnell wie n T2(n) = n*n ∈ O(n*n): T2(n) wächst höchstens so schnell wie n*n Asymptotische Komplexität Häufige Größenordnung der Komplexität: ● ● ● ● ● ● O(1): In konstanter (von n unabhängiger) Zeit ausführbar O(log n): Bei Verdoppelung von n läuft das Programm um eine konstante Zeit länger O(n) Linear: Laufzeit proportional zu n O(n*n): Quadratische Laufzeit; z. B. wenn je zwei Datenelemente zu kombinieren sind O(n^3): Kubische Laufzeit; nur für kleinere n geeignet O(2^n): Exponentielles Wachstum; solche Programme sind in der Praxis fast immer wertlos Asymptotische Komplexität ● Groß-Omega-Notation: bringt zum Ausdruck, dass eine Funktion f(n) mindestens so schnell wächst wie eine andere Funktion g(n). g(n) Ist also die untere Schranke für f(n). ● Definition: Funktion f(n) ist in der Menge Ω(g(n)), wenn es ein c > 0 und ein n0 ∈ N gibt, so dass für alle n ≥ n0 gilt: f(n) ≥ c . g(n) Asymptotische Komplexität ● ● Gross-Theta-Notation: Wenn eine Funktion f(n) sowohl von oben als auch von unten durch g(n) beschränkt ist, so schreibt man f(n) = Ө (g(n)). Formal ist die Menge Ө(g(n)) definiert durch Ө(g(n)) = O(g(n)) ∩ Ω(g(n)) g(n) Ist also die exakte Schranke für f(n). Beispiele