12. Rekursion Grundlagen der Programmierung 1 (Java)
Transcription
12. Rekursion Grundlagen der Programmierung 1 (Java)
12. Rekursion Grundlagen der Programmierung 1 (Java) Fachhochschule Darmstadt Haardtring 100 D-64295 Darmstadt Prof. Dr. Bernhard Humm FH Darmstadt, 24. Januar 2006 Einordnung im Kontext der Vorlesung 1. Einführung 10. Gutes Programmieren 2. Einfache Programme 11. Komponenten 3. Kontrollstrukturen 12. Rekursion 4. Objekt-Orientierung I 13. Algorithmen und Datenstrukturen II 5. Algorithmen und Datenstrukturen I 14. Objektorientierung II 6. Interfaces 15. Design 7. Pakete 16. Die Java Klassenbibliothek I 8. Parametrisierte Typen (Generics) 17. Die Java Klassenbibliothek II 9. Fehler und Ausnahmen Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 2 Agenda Definition Definition Beispiele Eigenschaften rekursiver Algorithmen Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 3 Definition von Rekursion Beispiel: Fakultät Eine Methode m() heißt rekursiv, wenn sie sich selbst aufruft m(…) ⇒ m(…) m(…) ⇒ n(…) ⇒ m(…) direkt rekursiv indirekt rekursiv Beispiel: Berechnung der Fakultät (n!) n! = 1 * 2 * 3 * ... * (n-1) * n (n-1)! rekursive Definition n! = (n-1)! * n 1! = 1 Rekursive Methode zur Berechnung der Fakultät long fact (long n) { if (n == 1) return 1; else return fact(n-1) * n; } Allgemeines Muster if (Problem klein genug) nichtrekursiver Zweig; else rekursiver Zweig Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 4 Ablauf einer rekursiven Methode Beispiel: Fakultät n=4 24 long fact (long n) { if (n == 1) return 1 else return fact(n-1) * n; } Jede Aktivierung von fact hat ihr eigenes n und rettet es über den rekursiven Aufruf hinweg n=3 6 long fact (long n) { if (n == 1) return 1 else return fact(n-1) * n; } n=2 2 long fact (long n) { if (n == 1) return 1 else return fact(n-1) * n; } 1 n=1 long fact (long n) { if (n == 1) return 1 else return fact(n-1) * n; } Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 5 Agenda Definition Beispiele Beispiele Eigenschaften rekursiver Algorithmen Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 6 Beispiel: binäres Suchen rekursiv z.B. Suche von 17 (Array muss sortiert sein) a 0 1 2 3 2 3 5 7 low a 4 5 6 11 13 17 19 m 0 1 2 3 2 3 5 7 7 • Index m des mittleren Element bestimmen • 17 > a[m] ⇒ in rechter Hälfte weitersuchen high 4 5 6 7 11 13 17 19 low m high static int search (int elem, int[] a, int low, int high) { if (low > high) return -1; // empty int m = (low + high) / 2; if (elem == a[m]) return m; if (elem < a[m]) return search(elem, a, low, m-1); return search(elem, a, m+1, high); } Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 nichtrekursiver Zweig rekursiver Zweig 24.1.2006, Seite 7 Ablauf des rekursiven binären Suchens 6 elem = 17, low = 0, high = 7 static int search (int elem, int[] a, int low, int high) { if (low > high) return -1; int m = (low + high) / 2; if (elem == a[m]) return m; if (elem < a[m]) return search(elem, a, low, m-1); return search(elem, a, m+1, high); } low = 4, high = 7 0 2 1 3 2 5 3 4 5 6 7 7 11 13 17 19 m=3 low m high 6 static int search (int elem, int[] a, int low, int high) { if (low > high) return -1; int m = (low + high) / 2; if (elem == a[m]) return m; if (elem < a[m]) return search(elem, a, low, m-1); return search(elem, a, m+1, high); } low = 6, high = 7 0 2 m=5 1 3 2 5 3 4 5 6 7 7 11 13 17 19 low m high 6 static int search (int elem, int[] a, int low, int high) { if (low > high) return -1; int m = (low + high) / 2; if (elem == a[m]) return m; if (elem < a[m]) return search(elem, a, low, m-1); return search(elem, a, m+1, high); } Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 0 2 1 3 2 5 3 4 5 6 7 7 11 13 17 19 m=6 low high m 24.1.2006, Seite 8 Beispiel: größter gemeinsamer Teiler rekursiv static int gcd (int x, int y) { int rest = x % y; if (rest == 0) return y; else return gcd(y, rest); } iterativ static int gcd (int x, int y) { int rest = x % y; while (rest != 0){ x = y; y = rest; rest = x % y; } return y; } Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 9 Beispiel: Die Türme von Hanoi Gegeben sind 3 Pfosten mit n Scheiben (unterschiedlicher Größe) Grundstellung: alle Scheiben nach Größe geordnet auf Pfosten A A B C Ziel: Lege alle n Scheiben von A nach C Restriktion 1: jeweils nur eine Scheibe darf bewegt werden Restriktion 2: niemals darf eine größere auf einer kleineren Scheibe liegen Lösungsstrategie: Problem allg. für Turm der Höhe n lösen; folgende Fälle sind zu unterscheiden – n = 0: gar nichts machen – n > 0: (1) Turm der Höhe n-1 von A nach B bewegen (mittels C) (2) Scheibe von A nach C legen (3) Turm der Höhe n - 1 von B nach C bewegen (mittels A) Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 10 Beispiel: Türme von Hanoi Rekursiver Algorithmus import java.io.*; public class Hanoi { A B C public static void verlegeTurm(int hoehe, int von, int nach, int ueber) { if (hoehe > 0) { verlegeTurm(hoehe-1, von, ueber, nach); System.out.println("von "+von + " nach "+ nach); verlegeTurm(hoehe-1, ueber, nach, von); } } public static void main(String[] args) throws IOException { BufferedReader in = Text.open(System.in); int hoehe = Text.readInt(in); verlegeTurm(hoehe, 1, 2, 3); } } Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 11 Beispiel: Fibonacci-Funktion Rekursiver Algorithmus – Fibonacci-Funktion fib(n) = 1 für n= 1, 2 und fib(n) = fib(n-1) + fib(n-2) für n > 2 – Rekursiver Algorithmus zur Berechnung der Fibonacci-Zahlen public class Fib { public static int fib(int n) { if (n <= 2) return 1; else return fib(n-1) + fib(n-2); } – Algorithmus heißt effektiv, wenn er nach endlich vielen Schritten das korrekte Ergebnis liefert – Algorithmis heißt effizient, wenn das Ergebnis mit einem Aufwand erreicht wird, der innerhalb vorgegebener Grenzen liegt Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 12 Effizienzuntersuchung Fibonacci: Rekursive Implementierung – da bei Aufrufen der fib()-Methode stets die gleichen Schritte ausgeführt werden, ist Aufwand (mit c bezeichnet) proportional zur Anzahl der Aufrufe der Methode – Abschätzung Anzahl rekursiver Aufrufe von fib in Abhängigkeit von n es gilt: c1 = c2 = 1 für n > 2: cn = 1 + cn-1 + cn-2 bei n > 3: cn-1= 1 + cn-2 + cn-3, also cn-2 = cn-1 - 1 - cn-3. Einsetzen von cn-1 in Gleichung cn cn = 2 + 2cn-2 + cn-3 > 2cn-2 > 22cn-4 > 23cn-6 > ...> 2n DIV2-1c2 also cn > 2n DIV2-1 (ist eine Untergrenze für cn) cn = 1 + cn-1 + cn-2 = 2cn-1 - cn-3 < 2cn-1< 22cn-2 <...< 2n-1c1 also cn < 2n-1 (ist eine Obergrenze für cn) – Fibonacci-Methode erfordert mit n exponentiell wachsenden Aufwand, folglich ist Implementierung nicht effizient für große n. Gibt es effizientere Implementierungen? Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 13 Effizienzuntersuchung Fibonacci-Funktion: Iterative Implementierung public static long fibIt(int n) { long fibN = 0; long fibN1 = 1; // fuer Fib(n-1); long fibN2 = 1; // fuer Fib(n-2); if (n == 1) else if (n == 2) return 1; return 1; for (int i=3; i <= n; i++) { fibN = fibN1 + fibN2; fibN2 = fibN1; fibN1 = fibN; } System.out.println("Fib "+n+ " = "+fibN); return fibN; } • Abschätzung der Anzahl der ausgeführte Schritte (Anweisungen) cn = 5 + (n-2)*3 + 1 • iterative Fibonacci-Algorithmus erfordert mit n linear wachsenden Aufwand Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 14 Beispiel: Ackermann-Funktion ack(n,m) = m + 1 falls n=0 ack(n-1,1) falls m=0 ack(n-1,ack(n,m-1)) sonst Vorsicht: Funktion wächst sehr stark: • ack(4,2) besitzt 19729 Stellen • ack(4,4) ist größer als 10 hoch 10 hoch 10 hoch 1900 public static int ack(int n, int m) { if (n == 0) return m + 1; else if (m == 0) return ack(n-1,1); else return ack(n-1,ack(n,m-1)); } Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 15 Agenda Definition Beispiele Eigenschaften rekursiver Algorithmen Eigenschaften rekursiver Algorithmen Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 16 Vor- und Nachteile rekursiver Algorithmen Anmerkung zu jedem rekursiv formulierten Algorithmus gibt es einen äquivalenten iterativen Algorithmus 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" Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 17 Rekursion in Programmiersprachen Nicht alle Programmiersprachen unterstützen rekursive Algorithmen Rekursion erlaubt: ALGOL-Familie (ALGOL 60, Simula 67, ALGOL 68, PASCAL, MODULA-2, Ada) PL/1 C und C++ Java C# Rekursion nicht möglich: FORTRAN COBOL LISP arbeitet überwiegend mit rekursiven Algorithmen (gilt i.d.R. auch für PROLOG) Bernhard Humm: „Grundlagen der Programmierung I (Java)“. FH Darmstadt, WS 2005/2006 24.1.2006, Seite 18