Einführung in die Informatik I Fortgeschrittene Rekursion Prof. Dr. Nikolaus Wulff Problematische Rekursion • Mittels Rekursion lassen sich Spezifikationen recht elegant und einfach implementieren. • Leider sind jedoch die so erhaltenen Lösungen meist nicht sehr effizient, was den Speicherverbrauch und die Laufzeit betrifft. • Da sich jede primitive Rekursion durch eine iterative Lösung mittels Schleifen darstellen lässt, gilt es einen strukturierten Weg zu finden, um eine rekursive Lösung in eine Iterative zu überführen. • Ein erster Schritt hierzu die Endrekursion, die eine effizientere Implementierung gestattet. Prof. Dr. Nikolaus Wulff Informatik I 2 Endrekursion • Eine Rekursion heißt endrekursiv, wenn der Aufruf der Rekursion die letzte Aktion zur Berechnung der rekursiven Funktion f ist. • Endrekursive Funktionen zeigen ein besseres Verhalten hinsichtlich des Speicherbedarfs für lokale Variablen und den Stack, als normal rekursive Funktionen, die nach der Rekursion noch weitere Berechnungen vornehmen. • Am Beispiel der rekursiven Fakultätsberechnung soll das Prinzip verdeutlicht werden. Prof. Dr. Nikolaus Wulff Informatik I 3 Rekursive Fakultät int factorial(int n) { if(n<=1) return 1; return n*factorial(n-1); } • Diese Implementierung der Fakultät ist nicht endrekursiv, da nach der Rekursion noch eine Multiplikation mit dem Argument n erfolgt. • Sie ergibt sich z.B. für n=4 die Aufruffolge 4!: f(4) = 4·f(3) => f(4)=4·6=24 f(3)= 3·f(2) => f(3)=3·2=6 f(2) = 2·f(1) => f(2)=2·1=2 LIFO f(1)=1 Berechnung Prof. Dr. Nikolaus Wulff Informatik I 4 Endrekursion mittels Hilfsfunktion int g_factorial(int n, int fac) { if (n<=1) return fac; return g_factorial(n-1, fac*n); } int factorial(int n) { return g_factorial(n, 1); } • Durch Einführen der Hilfsfunktion g_factorial wird die nachträgliche Multiplikation vermeiden. • Die Rekursion ist nun endrekursiv. f(4) = g(4,1) = g(3,1·4) g(3,4) = g(2,4·3) g(2,12) = g(1,12·2) = 24 Prof. Dr. Nikolaus Wulff Informatik I 5 Nichtlineare Rekursion • Bei der Endrekursion entfallen viele der Zwischenergebnisse, die auf dem Stack vorgehalten werden und die Funktion kann am Ende der Rekursion direkt terminieren. • An dem einfachen Beispiel der Fakuktät kommt dieser Vorteil noch nicht so deutlich zum Tragen, da hier die Rekursion linear verläuft, anders wird dies bei nichtlinearen Rekursionen. • Eine nichtlineare Rekursion liegt vor, wenn im Rumpf der Definition von f mehr als ein rekursiver Aufruf ist. Prof. Dr. Nikolaus Wulff Informatik I 6 Nichtlineare Rekursion • Ein besonders schlechtes Lauftzeitverhalten hat die nichtlineare Rekursion, wie sie z.B. bei den Fibonacci Zahlen: 1, 1, 2, 3, 5, 8, 13, 21, 34, ... vorkommt. f n := f n−1 f n−2 f 1 := f 2 :=1 • Die Aufruffolge zeigt, dass viele Fibonacci Zahlen mehrfach berechnet werden und ~2n Auswertungen fn notwendig sind: fn-1 fn-2 fn-3 Prof. Dr. Nikolaus Wulff fn-2 fn-3 fn-3 fn-4 ... fn-4 Informatik I 7 Auflösen der Rekursion • Es ist offensichtlich, dass diese rekursive Lösung nicht effizient ist und viele der Zahlen fk unnötig oft berechnet werden. • Zum Berechnen von fk werden lediglich die direkten Vorgängerzahlen fk-1 und fk-2 benötigt. Wenn der Algorithmus diese bei der Berechnung nur einmal berechnet und ab dann wiederverwendet, so wird sich die Rekursion wesentlich verkürzen. • Hierzu wird eine Hilfsfunktion g eingeführt, welche die Fibonacci-Zahl fk aus den beiden vorhergehenden Funktionswerten mit linearer Endrekursion berechnet. Prof. Dr. Nikolaus Wulff Informatik I 8 Linearisierung der Rekursion int g_fibonacci(int n, int fk1, int fk2) { if (n<=1) return fk1+fk2; return g_fibonacci(n-1, fk2+fk1, fk1); } int fibonacci(int n) { return g_fibonacci(n-1,1,0); } • Aus der nichtlinearen Rekursion ist mittels der Hilfsfunktion g eine Endrekursion geworden. • Diese Lösung hat eine lineare Laufzeit ~n im Vergleich zur ursprünglichen rekursiven Lösung ~2n Prof. Dr. Nikolaus Wulff Informatik I 9 Endrekursion > Iterative Lösung • Ist erst einmal eine endrekursive Lösung gefunden worden, so ist es meistens leicht, daraus eine iterative Lösung zu entwickeln, die hinsichtlich der Laufzeit immer die bessere Variante darstellt. • Die generische Struktur einer linearen Rekursion ist: { g n falls T n f n= h n , f n−1 sonst • Hierbei ist T(n) die terminierende Bedingung und im Spezialfall h(n,y)=y=f(n) ergibt sich die Endrekursion, die sich durch ein LOOP Programm, d.h eine einfache n-fache for-Schleife berechnen lässt. Prof. Dr. Nikolaus Wulff Informatik I 10 Iterative Lösung unsigned fibonacci(unsigned n) { unsigned f, fk1=1, fk2=0; while (--n) { f = fk1 + fk2; fk2 = fk1, fk1 = f; } return fk1; } • Diese iterative Lösung hat die Hilfsfunktion g durch eine Schleife ersetzt und besitzt sowohl eine optimale Laufzeit als auch den minimalen Speicherverbrauch. Prof. Dr. Nikolaus Wulff Informatik I 11 Wechselseitige Rekursion unsigned even(unsigned n) { if (n==0) return 1; return odd(n-1); } unsigned odd(unsigned n) { if(n==0) return 0; return even(n-1); } • Indirekte Rekursion liegt vor, wenn eine Funktion f eine Funktion h aufruft, die direkt oder indirekt wiederum eine Rekursion der Funktion f aufruft, wie hier am Beispiel der Funktionen even und odd, die berechnen ob eine Zahl n gerade oder ungerade ist. Prof. Dr. Nikolaus Wulff Informatik I 12 Weitere Rekursionen • Das letzte Beispiel ist ziemlich künstlich, ob eine Zahl n gerade oder ungerade ist, lässt sich mit der Modulo Operation n%2 == 1 viel einfacher und schneller herausbekommen. • Das Bisektionsverfahren zur Nullstellensuche oder dem Zahlenraten lässt sich rekursiv durchführen. • Rekursion wird nicht nur für rein mathematische Aufgaben verwendet, sondern auch bei Strategiespielen, die sich im weitesten Sinne auf Rekursion zurückführen lassen: – Wolf, Schaf und Kohl mit Floss – Das acht Damen Problem Prof. Dr. Nikolaus Wulff Informatik I 13 Graphische Rekursionen • Mittels Rekursion lassen sich graphische Algorithmen entwickeln wie z.B. die fraktale Koch Kurve (1904). • Eine Strecke wird in drei gleichlange Teile zerlegt. Das mittlere Teilstück wird verdoppelt und die beiden werden mit einem 60° Winkel eingefügt. Anschließend wird diese Vorschrift rekursiv auf alle Teilstücke angewandt. 1. Rekursion 0. Rekursion Prof. Dr. Nikolaus Wulff Informatik I 14 Entwicklung der Koch Kurve Rekursionstiefe: 1 2 3 4 5 Prof. Dr. Nikolaus Wulff Informatik I 15 Hilbert Kurve • Die Kurve entsteht durch vier Zeichenfunktionen, die sich wechselseitig rekursiv aufrufen... Prof. Dr. Nikolaus Wulff Informatik I 16 Entwicklung der Hilbert Kurve 1 4 2 3 5 6 • Mit zunehmender Rekursionstiefe wird die Kurve immer filigraner und berührt im Limes jeden Punkt der Fläche und füllt diese vollständig aus... Prof. Dr. Nikolaus Wulff Informatik I 17