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