Um Rekursion zu verstehen, muss man vor allem Rekursion verstehen. http://www2.norwalk-city.k12.oh.us/wordpress/precalc/files/2009/05/mona-lisa-jmc.jpg Rekursive Funktionen OOPM, Ralf Lämmel
Was ist Rekursion? (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 2
Eine Illustration von Rekursion
4 Eine (rekursive) mathematische Definition von! (Fakultät) n! = 1, falls n = 0 n * (n-1)!, falls n > 1
5 Rekursive Berechnung von! 5! = 5 * 4! = 4 * 3! = 3 * 2! = 2 * - 1! = 1 * - 0! = 1
Rekursive Definition von! in Java Umsetzung in Java: // Assume n >= 0 public static int factorial(int n) { if (n == 0) return 1; else return n * factorial(n-1); (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package algorithm.factorial 6
7 Aufruf (nicht-) rekursiver Funktionen public class Program { public static int divby2(int x) { return x / 2; public static void main(string[] args) { int x = 5; int y = divby2(x); System.out.println(y);
8 Die Realisierung eines Funktionsaufrufs entspricht dem Entfalten der Funktionsdefinition. Dabei werden formale Parameter durch aktuelle Parameter ersetzt. int x = 5; int y = divby2(x); System.out.println(y); int x = 5; int y = x / 2; System.out.println(y); Die Entfaltung ist statisch möglich für nichtrekursive Funktionen. Die Entfaltung ist notwendigerweise dynamisch (zur Laufzeit) nötig für rekursive Funktionen.
9 Rekursive Funktionsdefinitionen Teile der Definition Basisfall Nichtrekursive Berechnung Rekursiver Fall Rekursive(r) Aufruf(e) Verwertung der (des) Aufrufe(s) Nichtrekursive Berechnung Die Auswahl der Teile erfolgt über Bedingung(en). Es kann auch mehre Fälle jeder Art geben.
10 Rekursive Definition von! // Assume n >= 0 public static int factorial(int n) { if (n == 0) return 1; else Abbruchbedingung Basisfall return n * factorial(n-1); Rekursiver Fall
11! mittels ternärem Operator // Assume n >= 0 Angenommene Vorbedingung (oft ausgelassen) public static int factorial(int n) { return (n == 0)? Abbruchbedingung 1 Basisfall : n * factorial(n-1); Rekursiver Fall
12 Die Fibonacci-Folge Anzahl neugeborener Kaninchen nach n Jahren: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,... Rekursive Funktionsdefinition: fib(n) = 0, falls n = 0 1, falls n = 1 fib(n-2) + fib(n-1), n >= 2
Die Fibonacci-Folge public static int fib(int n) { if (n == 0) return 0; else if (n == 1) return 1; 1. Basisfall 2. Basisfall else return fib(n - 2) + fib(n - 1); 2 rekursive Aufrufe (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package algorithm.fibonacci 13
14 Die Fibonacci-Folge (mit Verwendung des ternären Operators) public static int fib(int n) { return n == 0? 0 : n == 1? 1 : fib(n - 2) + fib(n - 1);
15 Anmerkung zur Wahl rekursiver Definitionen Folgende Attribute sind relativ: Eleganz In-/effizienz Beispiel - Die Fibonacci-Folge: Exponentielle Explosion von Aufrufen fib(n) benötigt nur 2 direkte Vorgänger. Effizientere Formulierung darum naheliegend Overhead Stichwort Compiler-Optimierungen
16 Umwandlung der ineffizienten, (binär-)rekursiven Fibonacci-Funktion in eine effiziente, entweder (linear-)rekursive oder iterative Darstellung Naive, rekursive Formulierung Exponentielle Explosion von Aufrufen Schlüsseleigenschaft fib(n) benötigt nur 2 direkte Vorgänger. Effiziente, rekursive Formulierung Vorgänger werden als Parameter gereicht. Effiziente, iterative Formulierung Vorgänger werden in Variablen gehalten.
17 Effiziente, rekursive Formulierung public static int fib(int n) { return fib(n,0,1); public static int fib(int n, int n1, int n2) { return n == 0? n1 : fib(n-1, n2, n1 + n2); Wir überladen den Funktionsnamen. (Wir verwenden verschiedene Argumenttypen.)
18 Effiziente, iterative Formulierung public static int fib(int n) { int n1 = 0; int n2 = 1; for (; n!= 0; n--) { int m1 = n1; int m2 = n2; n1 = m2; n2 = m1 + m2; return n1;
19 Türme von Hanoi Annahmen: N Scheiben verschiedener Durchmesser Stapelung mit abnehmendem Durchmesser 3 Plätze: A, B, C Ziel: Bewegen aller Scheiben von A nach C
20 Türme von Hanoi - Illustration Startkonfiguration Move 1 Move 2 Move 3 Move 4 Move 5 Move 6 Endkonfiguration
21 Türme von Hanoi - Ansatz Ziel: Verschiebung von N Scheiben von A nach C Annahme: Verschieben von N-1 Scheiben möglich Schritte: Verschiebe N-1 Scheiben von A nach B Verschiebe letzte (große) Scheibe von A nach C Verschiebe N-1 Scheiben von B nach C
Türme von Hanoi public class Program { public static void move( int n, String from, String temp, String to) { if (n == 0) return; move(n-1, from, to, temp); Move disc 1 from A to C Move disc 2 from A to B Move disc 1 from C to B Move disc 3 from A to C Move disc 1 from B to A Move disc 2 from B to C Move disc 1 from A to C System.out.println( "Move disc " + n + " from " + from + " to " + to); move(n-1, temp, from, to); public static void main(string[] args) { move(3,"a","b","c"); (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package algorithm.hanoi 22
23 Formen von Rekursion Primitive Rekursion Allgemeine Rekursion Endrekursion Lineare Rekursion Binäre Rekursion Indirekte Rekursion
24 Primitive Rekursion Funktion über induktive Struktur von nat. Zahlen Schemavariablen: g, h Schema: f(n) = g, falls n=0 h(n,f(n-1)), sonst Termination ist garantiert. Bestimme g und h! public static int factorial(int n) { if (n == 0) return 1; else return n * factorial(n-1);
25 Beispiel: even/odd (Primitiv-rekursive Variante) // Assume n >= 0 public static boolean even(int n) { return (n<=1)? (n==0) :!even(n-1); // Assume n >= 0 public static boolean odd(int n) { return (n<=1)? (n==1) :!odd(n-1); Eine einfache Verallgemeinerung der primitiven Rekursion mit einem Basisfall > 0
26 Allgemeine Rekursion Keine Einschränkungen an Rekursion Beispiel: McCarty 91-er Funktion: f(n) = n-10, falls x > 100 f(f(n+11)), sonst Eigenschaften: f(n) = 91 für alle n <= 101 f(n) = n - 10 für alle n > 101
27 Beispiel: McCarty 91-er Funktion public static int f(int n) { if (n > 100) return n - 10; else return f(f(n+11)); f(n) = 91 für alle n <= 101 f(n) = n - 10 für alle n > 101
28 Endrekursion Schemavariablen: g, p, r Schema: f(x) = g(x), falls p(x) f(r(x)), sonst Der rekursive Aufruf ist die letzte Operation auf jeder Ebene. http://calvinlawson.files.wordpress.com/2009/02/tail-recursion.jpg Bedeutung: iterative Implementation trivial Endrekursive Formulierung oft relativ einfach Beispiele:!, even/odd nach Anpassung
29 Beispiel: even/odd (Primitiv-rekursive Variante) // Assume n >= 0 public static boolean even(int n) { return (n<=1)? (n==0) :!even(n-1); // Assume n >= 0 public static boolean odd(int n) { return (n<=1)? (n==1) :!odd(n-1);
30 Beispiel: even/odd (End-rekursive Variante) // Assume n >= 0 public static boolean even(int n) { return (n<=1)? (n==0) : even(n-2); // Assume n >= 0 public static boolean odd(int n) { return (n<=1)? (n==1) : odd(n-2);
31 Umwandlung von Endrekursion in eine iterative Formulierung Rekursiv: f(x) = g(x), falls p(x) f(r(x)), sonst Iterativ: f(x) = Solange p(x) nicht gilt x = r(x) Gib g(x) zurück.
32 Beispiel: even/odd (End-rekursive Variante) public static boolean even(int n) { return (n<=1)? (n==0) : even(n-2); public static boolean even(int n) { while (!(n<=1)) n -= 2; return n==0;
33 Umwandlung von primitiver nach End- Rekursion am Beispiel der Fakultät (Verwendung eines Akkumulatorargumentes) Primitive Rekursion: n! = 1, falls n = 0 n * (n-1)!, sonst Endrekursion: n! = factorial(1,n) factorial(x,n) = x, falls n = 0 factorial(x*n, n-1), sonst
34 Beispiel: Fakultät (End-rekursive Variante) public static int factorial(int n) { return (n == 0)? 1 : n * factorial(n-1); public static int factorial(int n) { return factorial(1,n); Wir überladen den Funktionsnamen. (Wir verwenden verschiedene Argumenttypen.) public static int factorial(int x, int n) { return (n==0)? x : factorial(n*x,n-1);
35 Lineare Rekursion Verallgemeinerung von: Primitive Rekursion Endrekursion Schemavariablen: g, p, h, r Schema: f(x) = g(x), falls p(x) h(x,f(r(x))), sonst Einfache Verallgemeinerung: f kann mehrmals referenziert werden...... aber nur einmal ausgewertet werden.
36 Binäre Rekursion Verallgemeinerung von linearer Rekursion Zwei, verschiedenartige rekursive Aufrufe. Beide Aufrufe fallen auch dynamisch an. Beispiel: Die Fibonacci-Funktion
37 Indirekte Rekursion Eine Funktion ruft sich nicht (nur) direkt selbst auf. Stattdessen sind mehrere Funktionen beteiligt. Indirekte Rekursion erleichtert Modularisierung. Beispiel: even(n) = true, falls n = 0 odd(n-1), sonst odd(n) = false, falls n = 0 even(n-1), sonst
38 Beispiel: even/odd (Indirekt-rekursive Variante) // Assume n >= 0 public static boolean even(int n) { if (n==0) return true; else return odd(n-1); // Assume n >= 0 public static boolean odd(int n) { if (n==0) return false; else return even(n-1);
39 Beispiel: even/odd (Kompaktere indirekt-rekursive Variante) // Assume n >= 0 public static boolean even(int n) { return (n==0) odd(n-1); // Assume n >= 0 public static boolean odd(int n) { return!(n==0) && even(n-1);
40 MergeSort Teilung der Eingabe Linke Hälfte Rechte Hälfte Rekursives Sortieren Zwischenergbnis Zwischenergbnis Merge Sortiertes Ergebnis
MergeSort - Beispiel 8 2 4 9 7 8 2 4 9 7 2 4 7 8 9 2 8 4 7 9 8 2 4 9 7 8 2 4 7 9 9 7 9 7 commons.wikimedia.org (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau 41 commons.wikimedia.org
42 Mischen 2 8 4 7 9 2 4 7 8 9
MergeSort - Implementation public static void mergesort(int[] a, int[] temp, int min, int max) { // Cease on trivial sorting problem if (!(min<max)) return; // Divide int middle = ( min + max ) / 2 ; // Solve smaller problems recursively mergesort(a,temp,min,middle); mergesort(a,temp,middle+1,max); // Merge via temporary array merge(a,temp,min,middle,max); (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package algorithm.sorting 43
Merge - Implementation public static void merge(int[] a, int[] temp, int min, int middle, int max) { int i = min; // loop over left half int j = middle+1; // loop over right half int k = min; // loop over merged result while (k<=max) temp[k++] = (i <= middle && (j > max a[i] < a[j]))? a[i++] // copy from left half : a[j++]; // copy from right half // Commit temporary result for (k=min; k<=max; k++) a[k] = temp[k]; (C) Ralf Lämmel, OOPM, Universität Koblenz-Landau Siehe package algorithm.sorting 44
45
Zusammenfassung Rekursive Definitionen sind oft elegant. Rekursion kann Kosten verursachen. Konvertierung rekursiv nach iterativ Prinzipiell immer möglich Praktisch oft elegant möglich Ausblick Komplexität von Algorithmen Korrektheit von Programmen