Programmieren 1 C Überblick 1. Einleitung 2. Graphische Darstellung von Algorithmen 3. Syntax und Semantik 4. Einstieg in C: Einfache Sprachkonstrukte und allgemeiner Programmaufbau 5. Skalare Standarddatentypen 6. Kontrollfluss 7. Operatoren und Ausdrücke 8. Felder und Zeiger 9. Speicherklassen 10. Strukturen und Unionen 11. Unterprogramm-Techniken (Funktionen) 13. Dateien und Dateiverarbeitung Prof. Dr. Björn Dreher Programmieren 1 C 131 Programmieren 1 C Überblick: 12.4 Nicht-lineare Rekursion Prof. Dr. Björn Dreher Programmieren 1 C 132
Typischerweise verwenden algorithmische Problemlösungen entweder Iterationen (implementiert in Form von Schleifen) oder Rekursionen Rekursive Lösungen sind nicht immer besser als iterative, manchmal sogar deutlich schlechter Aber oft führt ein rekursiver Algorithmus zu sehr eleganten Lösungen sonst hoch komplexer Aufgabenstellungen Prof. Dr. Björn Dreher Programmieren 1 C 133 Was genau ist Rekursion? Es ist eine Technik, bei der eine Aufgabe A dadurch gelöst wird, dass man eine andere Aufgabe A' löst Diese Aufgabe A' ist von genau derselben Art wie die Aufgabe A! Lösung von A: Löse Aufgabe A', was von derselben Art wie Aufgabe A ist. Obwohl A' von derselben Art wie A ist, ist die Aufgabe A' doch in einer gewissen Art kleiner! Prof. Dr. Björn Dreher Programmieren 1 C 134
Was genau ist Rekursion? Beispiel: Binäres Suchen in einem Wörterbuch: Suche(Wörterbuch, Wort) IF Wörterbuch besteht aus einer Seite THEN suche das Wort auf dieser Seite ELSE BEGIN Öffne das Wörterbuch in der Mitte Stelle fest, in welcher Hälfte das gesuchte Wort liegt IF Wort liegt in der ersten Hälfte THEN Suche(erste Wörterbuchhälfte, Wort) ELSE Suche(zweite Wörterbuchhälfte, Wort) END Strategie der Rekursion: Teile und erobere. Jeder Schritt wird etwas einfacher, bis man bei dem degenerierten Fall (Terminierungsbedingung) ankommt, der die sofortige Lösung bietet Prof. Dr. Björn Dreher Programmieren 1 C 135 Vier grundsätzliche Fragen, um eine rekursive Lösung zu konstruieren: 1. Wie kann man das Problem als ein kleineres Teilproblem derselben Art umdefinieren? 2. Wie verringert jeder rekursive Aufruf die Größe des Problems? 3. Welcher Fall des Problems kann als degenerierter Fall dienen? 4. Wird dieser degenerierte Fall auch erreicht? Prof. Dr. Björn Dreher Programmieren 1 C 136
Standard-Beispiel: Fakultät Berechnung der mathematischen Funktion Fakultät. Iterative Lösung wäre allerdings in diesem Fall effizienter! Die Funktion lässt sich folgendermaßen darstellen: n! = n * (n-1)! für n > 0 0! = 1 degenerierter Fall Danach wird zur Berechnung von n! die Berechnung von (n-1)! benötigt. Weiterhin gilt nach obiger Definition: (n-1)! = (n-1) * (n-2)! Also wird die Berechnung von (n-2)! benötigt, usw. Prof. Dr. Björn Dreher Programmieren 1 C 137 Standard-Beispiel: Fakultät (fortgesetzt) n! = n * (n-1)! für n > 0 0! = 1 degenerierter Fall Also ruft die Funktion Fakultät immer wieder sich selbst auf Der Parameter n wird in jedem Schritt um eins reduziert Schließlich landet man bei 0! Dann ist die Rekursion zu Ende. Prof. Dr. Björn Dreher Programmieren 1 C 138
Beispiel: Fakultät(4) 4! = 4 3! 3! = 3 2! 2! = 2 1! 1! = 1 0! 0! = 1 Damit haben wir den degenerierten Fall als Terminierungsbedingung der Rekursion erreicht. Die Rekursion ist zu Ende. Aber wo ist die Lösung? Aus unseren bisher erarbeiteten Erkenntnissen können wir nun - von hinten beginnend - die Lösungen jedes Schrittes in den vorhergehenden einsetzen, usw. Schließlich gelangen wir zum ersten Schritt, der die ganze Rekursion ausgelöst hat: Da 0!=1, ist 1! = 1 1 = 1 Da 1!=1, ist 2! = 2 1 = 2 Da 2!=2, ist 3! = 3 2 = 6 Da 3!=6, ist 4! = 4 6 = 24 Prof. Dr. Björn Dreher Programmieren 1 C 139 Programmieren 1 C Überblick: 12.4 Nicht-lineare Rekursion Prof. Dr. Björn Dreher Programmieren 1 C 140
Funktionsweise der Rekursion Soll ein rekursiver Algorithmus endlich sein, so muss es eine Terminierungsbedingung geben (s.o. bei der Fakultät) Bei Nichterfüllung wird ein Parameter gezielt verändert Dabei wird das Problem kleiner Dies geht solange, bis einmal die Terminierungsbedingung (degenerierter Fall) erfüllt ist Jedes Mal wird eine neue Rekursionsebene eröffnet, die jedoch - solange die Terminierungsbedingung noch nicht erfüllt ist - nicht sofort gelöst werden kann: Zunächst wird nur die nächste Ebene geöffnet Am Ende wird in umgekehrter Reihenfolge eine Ebene nach der anderen abgearbeitet Prof. Dr. Björn Dreher Programmieren 1 C 141 Funktionsweise der Rekursion Bei der Abarbeitung einer rekursiven Funktion f(x) mit dem Parameter x können also zwei Fälle auftreten: 1. x erfüllt die Terminierungsbedingung (0!=1 in obigem Beispiel). Dann erhält f(x) einen bestimmten Wert bed(x). 2. x erfüllt die Terminierungsbedingung nicht. Um diese zu erreichen muss reduziert werden. Dies geschieht nach dem Algorithmus red(x). Der nächste Selbstaufruf lautet dann f(red(x)). Schematisch: bed( x), f ( x) = f ( red( x)), falls Bedingung erfüllt sonst Prof. Dr. Björn Dreher Programmieren 1 C 142
Funktionsweise der Rekursion (fortgesetzt) bed( x), f ( x) = f ( red( x)), falls Bedingung erfüllt sonst Die Durchführbarkeit der Reduktion ist gegeben, wenn die Kette der einzelnen Reduktionen red(x) nach endlichen Schritten abbricht Die Komplexität der Reduktion hängt von der Komplexität der Funktion red(x) ab, aber auch - evtl. noch stärker - ob in einem Schritt nur ein Selbstaufruf erfolgt oder vielleicht noch mehrere Prof. Dr. Björn Dreher Programmieren 1 C 143 Rekursionsarten Lineare Rekursion, wie beim Beispiel Fakultät. Es erfolgt in jedem Schritt nur ein Selbstaufruf. Nichtlineare Rekursionen entstehen, wenn die Rekursionen in jedem Schritt vervielfältigt werden. Binäre Rekursion: Hier enthält jeder Schritt zwei Selbstaufrufe. Indirekte Rekursionen entstehen durch oft ungewollte gegenseitige Aufrufe von Unterprogrammen: void a(void) void b(void) { {...... b(); a(); } } Dies kann auch über mehrere Zwischenstationen geschehen. Es führt in der Regel zu einer Endlos-Schleife und sollte daher vermieden werden! Prof. Dr. Björn Dreher Programmieren 1 C 144
Programmieren 1 C Überblick: 12.4 Nicht-lineare Rekursion Prof. Dr. Björn Dreher Programmieren 1 C 145 Fakultät int fakultaet(int x) { if (x==0) return 1; else return ( x * fakultaet(x-1) ); } Bei jedem Aufruf von fakultaet wird ein weiterer Speicherplatz für den Werteparameter angelegt (übrigens auch für alle evtl. benötigten lokalen Parameter!). Berechnet man z.b. 5!, so existieren zum Schluss 6 verschachtelte Werteparameter (und lokale Umgebungen der Funktion). Prof. Dr. Björn Dreher Programmieren 1 C 146
Fakultät: Graphische Darstellung f = fakul(5); fakul(5)= Zwischenspeicherung in einem Stack 120 5 * fakul(4) fakul(4)= 5 * 24 4 * fakul(3) fakul(3)= 4 * 6 3 * fakul(2) fakul(2)= 3 * 2 2 * fakul(1) fakul(1)= int fakul(int x) { if (x==0) return 1; else return (x * fakul(x-1) ); } 2 * 1 1 * fakul(0) 1 * 1 fakul(0) = 1; Rekursionstiefe Prof. Dr. Björn Dreher Programmieren 1 C 147 Stack (Stapel), dt. Kellerspeicher Wird uns noch häufiger in der Informatik begegnen Ein zu verstauender Gegenstand wird oben drauf gelegt Er muss auch zuerst wieder weggenommen werden, wenn man an darunter liegende Gegenstände gelangen will (LIFO-Prinzip). Man denke auch an den Tablett-Stapel in einer Kantine. Zwei Grundoperationen: Hinzufügen Push Wegnehmen Pop Push kann solange durchgeführt werden, bis der Keller voll ist, Pop solange, bis er wieder leer ist. Weitere nützliche Operation: Abfrage, ob Stack leer ist Prof. Dr. Björn Dreher Programmieren 1 C 148
Ein anderes einfaches Beispiel: void spiegel(void); { char ch; } scanf("%c", &ch); if (ch!= ' ') spiegel(); printf("%c", ch); Was macht diese Funktion? Prof. Dr. Björn Dreher Programmieren 1 C 149 Programmieren 1 C Überblick: 12.4 Nicht-lineare Rekursion Prof. Dr. Björn Dreher Programmieren 1 C 150
12.4 Nicht-lineare Rekursion Beispiel einer binären Rekursion Berechnung der Fibonacci-Zahlen nach folgender Definition: fib( n 1) + fib( n 2) für n > 1 fib( n) = 1 für n = 1 0 für n = 0 fib(n) gibt für eine natürliche Zahl n die Summe der beiden vorangehenden Fibonacci-Zahlen der natürlichen Zahlen n-2 und n-1 an Ein weiteres klassisches Beispiel ist das Problem der Türme von Hanoi Prof. Dr. Björn Dreher Programmieren 1 C 151