4 Rekursionen Viele Algorithmen besitzen sowohl eine iterative als auch eine rekursive Lösung. Sie unterscheiden sich darin, dass die iterative Version meist einen etwas längeren Kode besitzt, während die rekursive Version mehr Speicher benötigt, um alle Rekursionsschritte zwischenzuspeichern. 4.1 Erstes Beispiel Der Algorithmus soll die Fakultät n! berechnen. 4.1.1 Iterative Version FAKULTAET_ITERATIV(n) 1: f 1 2: FOR i 1 to n 3: f f*i 4: RETURN f Die Laufzeit beträgt T (n) = O(n), da nur eine Schleife durchlaufen werden muss. Der Speicherbedarf ist mit O(1) unabhängig von der Inputgröÿe. 4.1.2 Rekursive Version FAKULTAET_REKURSIV(n) 1: IF n=1 THEN 2: RETURN 1 4: RETURN (n*fakultaet_rekursiv(n-1)) In Zeile 1 bendet sich die Abbruchbedingung für die Rekursion. Zur Analyse benötigt man die Laufzeit für den Fall, wenn abgebrochen wird. Hier benötigt der Algorithmus im Abbruchfall n = 1 eine Laufzeit von O(1). Im Normalfall (während der Rekursionen) kann man folgende Rekursionsgleichung aufstellen: T (n) = O(1) + T (n 1) }{{}}{{} 1 2 1. Beinhaltet die konstante Zeit, die die Zeilen 1-3 benötigen 2. Ist die Laufzeit um ein Element verringert, da FAKULTAET_REKURSIV mit (n-1) aufgerufen wird. Die Inputgröÿe ist hier die Zahl n selbst, von der die Fakultät n! berechnet werden soll. Für den Speicherverbrauch gilt ebenfalls S(n) = O(1) + S(n 1). Der Speicherverbrauch von rekursiven Algorithmen hängt im Allgemeinen von der maximalen Rekursionstiefe ab (= Anzahl der gleichzeitig oenen Funktionsaufrufe). 1
4.2 Methoden zur Lösung von Rekursionsgleichungen 4.2.1 Lösen durch Einsetzen Es wird ein Rekursionsschritt tiefer T (n 1) = O(1) + T (n 2) in die Orginalgleichung T (n) = O(1) + T (n 1) eingesetzt: T (n) = O(1) + O(1) + T (n 2) Analog erhält man durch weiteres Einsetzen eine allgemeine Formel in Abhängigkeit der Rekursionstiefe k T (n) = O(1) + O(1) + T (n 2) = O(1) + O(1) + O(1) + T (n 3) =... = k O(1) + T (n k) Die Rekursion verläuft bis zur Abbruchbedingung n = 1, dass heiÿt, bis T (1) auftritt. Man will nun eine Lösung unabhängig von k. Mit T (1) = T (n k) bekommt man n k = 1 k = n 1 und setzt in die Gleichung ein: T (n) = (n 1) O(1) + T (n n + 1) = (n 1) O(1) + T (1) = (n 1) O(1) + O(1) T (n) = O(n) Für den Speicherverbrauch S(n) = O(1) + S(n 1) folgt analog S(n) = O(n). 4.2.2 Lösen durch Induktion Zur Lösen der Rekursionsgleichung durch Induktion muss zuerst eine Lösung geschätzt und diese Annahme durch Induktion bewiesen werden. 4.3 Zweites Beispiel - Fibonacci-Zahlen Die Denition der Fibonacci-Zahlen: f n = f n 1 + f n 2 ; n 3 ; f 1 = 1, f 2 = 1 Die ersten Zahlen lauten 1, 1, 2, 3, 5, 8, 13, 21,... Die Folge ist monton wachsend und besitzt folgende Schranken: f n = f n 1 + f n 2 = f n 2 + f n 3 }{{} f n 2 +f n 2 { 2 fn 2 f n = Ω(2 n 2 ) 3 f n 2 f n = O(3 n 2 ) 2
Mit den Anfangswerten f 1 = f 2 = 1 folgt: 2 n 1 2 f n 3 n 1 2 c 1 ( 2) n f n c 2 ( 3) n f n = Θ (c n ) 4.3.1 Iterative Version Es wird die n-te Fibonacci-Zahl gesucht: FIBONACCI(n) 1: fib 1 2: fib_1 1 3: FOR i 3 to n 4: fib_2 fib_1 5: fib_1 fib 6: fib fib_1 + fib_2 7: RETURN fib 4.3.2 Rekursive Version Es wird die n-te Fibonacci-Zahl gesucht: FIBONACCI_R(n) 1: IF n 2 THEN 2: RETURN 1 4: RETURN (FIBONACCI_R(n-1)+FIBONACCI_R(n-2)) Die Rekursiongleichung lautet T (n) = O(1) + T (n 1) + T (n 2) mit T (2) = T (1) = O(1). Analog zu den Fibonacci-Zahlen kann folgende Abschätzung gemacht werden: T (n) = O(1) + T (n 1) + T (n 2) = 2O(1) + 2T (n 2) + T (n 3) }{{} T (n 2) und daher T (n) = { 2T (n 2) n T (n) = Ω(2 2 ) 3T (n 2) T (n) = O(3 n 2 ) Die Lösung ist T (n) = Θ(c n ). Der Speicheraufwand beträgt S(n) = O(1) + max{s(n 1), S(n 2)} = O(1) + S(n 1) = O(n). Die rekursive Version ist einfacher zu implementieren, besitzt jedoch exponentielle Laufzeit und linearen Speicherbedarf. 3
4.4 Die Ackermann-Funktion Es gibt zahlreiche Arten dieser rekursiven Funktion. Zum Beispiel: ACKER(n,x,y) 1: IF n=0 THEN 2: RETURN x+1 IF y=0 THEN 4: IF n=1 THEN RETURN x 5: IF n=2 THEN RETURN 0 6: IF n=3 THEN RETURN 1 7: IF n 4 THEN RETURN 2 8: RETURN ACKER(n-1, ACKER(n,x,y-1),x) 4.5 Türme von Hanoi Regeln: 3 Stäbe A, B und C Am Stab A liegen zu Beginn n unterschiedliche groÿe Scheiben Diese sollen am Ende auf Stab B liegen Es darf jeweils nur eine Scheibe bewegt werden Es darf nie eine gröÿere auf einer kleineren Scheibe liegen Zum Beispiel mit 7 Scheiben: Rekursiver Algorithmus zur Lösung: HANOI(n,x,y,z) 1: IF n=1 THEN 2: Lege Scheibe von 'x' nach 'y' 4: HANOI(n-1,x,z,y) 5: HANOI(1,x,y,z) 6: HANOI(n-1,z,y,x) 4
Der Aufruf erfolgt zum Beispiel mit HANOI (7,'A','B','C') und fordert den Algorithmus auf die 7 obersten Scheiben von A auf B unter Verwendung von C zu legen. Für die Anzahl der Züge Z(n) (n ist hier die Anzahl der Scheiben) gilt mit Z(1) = 1 Z(n) = Z(n 1) + 1 + Z(n 1) = 1 + 2 Z(n 1) = 1 + 2 [1 + 2 Z(n 2)] = 1 + 2 + 4 Z(n 2) = 1 + 2 + 4 + 8 Z(n 2) = k 1 = 2 i + 2 k Z(n k) Die Abbruchbedingung ist erreicht, wenn das Argument n k = 1 ist, also k = n 1. Damit ist n 2 n 1 Z(n) = 2 i + 2 n 1 1 = 2 i = 2 n 1 = O(2 n ). Für die Laufzeit gilt mit T (1) = O(1) analog T (n) = 1 + 2 T (n 1) =... = O(2 n ). In diesem Beispiel ist die exponentielle Laufzeit des rekursiven Algorithmus optimal. Für den Speicherverbrauch gilt mit S(1) = O(1) S(n) = O(1) + max{s(n 1), S(1), S(n 1)} = O(1) + S(n 1) = O(n). 5