1
Teil VI : Rekursive Algorithmen
1. Rekursive Prozeduren und Funktionen
2. „Teilen-und-Herrschen“
3. Türme von Hanoi
Zusammenfassung
Klaus Murmann, Heiko Neumann & Helmuth Partsch, Fakultät für Informatik, Universität Ulm, 2002/03
RekursionRekursion
RekursionRekursion
RekursionRekursion
2
1. Rekursive Prozeduren und Funktionen
• Definition rekursiver Funktionen• Struktur rekursiver Algorithmen• Verschiedene Rekursionsarten• Termination rekursiver Funktionen/Prozeduren
Einordnung
§ Bisher bereits : Rekursive Definition von Syntaxdiagrammen !
Definition rekursiver Funktionen
§ Neu : Eine Prozedur, die sich selbst aufruft – möglicherweise indirekt über andere Prozeduren – heißt rekursiv
.9|8|7|6|5|4|3|2|1|0
.|
.|
.|
→
−+→
→
→
Ziffer
Vorzeichen
ZahlZifferZifferZahl
ZahlVorzeichenZahlGanzeZahl
BNF : Syntax für ganze Dezimalzahlen
3
Struktur
1. Rekursivität direkt
PROCEDURE alpha;
BEGIN
:
alpha;
:
END alpha;
PROCEDURE cesar;
BEGIN
:
beta;
:
END cesar;
PROCEDURE beta;
BEGIN
:
cesar;
:
END beta;
2. Rekursivität indirekt
Beispiel 1 – Fakultät-Funktion
Definition
oder
( )! 1:!
1:!0
−⋅=
=
nnn 1, ≥n
( )( )
>
=
−⋅=
0,
0,
1
1
n
n
nfactnnfact
Berechnung der Fakultät (rekursiver Algorithmus)
PROCEDURE fact(n : INTEGER) : INTEGER;
BEGIN
IF n = 0 THEN
RETURN 1
ELSE
RETURN n * fact (n–1)
END
END fact;
4
Ergebnis
Berechnung von 4! – Phasen eines rekursiven Programms
( ) ( )( )( )
( )( )( )( )( )( )( )
( )( )( )( )( )
( )
24
64
234
1234
11234
01234
1234
234
344
=
⋅=
⋅⋅=
⋅⋅⋅=
⋅⋅⋅⋅=
⋅⋅⋅⋅=
⋅⋅⋅=
⋅⋅=
⋅=
fact
fact
fact
factfactAbstieg
Aufstieg
Ende
n = 4, n = 3
n = 2
n = 1
n = 0
Intuitive Erkenntnisse
§ Während des Berechnungsablaufs wird die Funktion fact immer wieder (rekursiv) mit jeweils kleinerem Argument (Parameter) aufgerufen
§ Dabei werden verschiedene Inkarnationen erzeugt (Abstieg) – die Anzahl (hier: 5) hängt vom Algorithmus und vom Parameterwert ab.
§ Das Ende wird durch die Abbruchbedingung
fact(0) = 1
bedingt
§ Während des Aufstiegs werden die Zwischenergebnisse verknüpft und die Inkarnationen (ab-)geschlossen und wieder zerstört
5
Verarbeitung rekursiver Prozeduren mittels Formular-Maschine
Schritte :
(1) Erstelle ein Formular für das rekursive Problem
(2) Übertrage die Argumentwerte in das Formular
(3) Werte die Bedingung für Fallunterscheidung aus
(4) Wähle den Zweig der Fallunterscheidung
(5) Werte nur diesen Zweig aus
(6) Bei (nicht direkt auswertbaren) rekursiven Aufrufen: neues Formular über das aktuelle legen; weiter bei (2)
(7) Ist ein Formular vollständig ausgefüllt, so ist dieses zugleich wegzuwerfen und dessen Ergebnis auf das ggf. darunter l iegende Formular zu übertragen; weiter bei (5)
Ausnahme (= Terminierung) : Das vollständig ausgefüllte Formular ist das letzte; eine Ergebnis-Übertragung ist dann nicht (mehr) möglich ⇒ das Ergebnis ist das gesuchte Endergebnis !
Schritt (1) : Erstelle ein Formular für das rekursive Problem (hier: Fakultät)
Formular für n! = fact(n)
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
6
Inkarnation 1; n = 4
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
Schritte (2) – (5)
4
44
4
3
false
?
hier kommen wir nicht weiter, d.h., wir „lagern“ dieses Formular jetzt „im Keller“, indem wir ein weiteres fact-Prozedur-Formular darüber le-gen, um die Rechnung fortzusetzen,
diesmal eines für n = 3 bzw. fact(3)
Abstieg
X
Schritte (6), (2) – (5)
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
2
false
?
3
33
3
Abstieg
X
7
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 3; n = 2
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
1
false
?
2
22
2
Abstieg
Schritte (6), (2) – (5)
X
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 3; n = 2
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 4; n = 1
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
0
false
?
1
11
1
Abstieg
X
Schritte (6), (2) – (5)
8
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 3; n = 2
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 4; n = 1
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 5; n = 0
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
true
11
0
00
0
Abstieg
X
Schritte (6), (2) – (5)
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 3; n = 2
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 4; n = 1
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 5; n = 0
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
-1
true
11
0
00
0
Ende
X
Ende des Abstiegs …
9
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 3; n = 2
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 4; n = 1
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 5; n = 0
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
-1
true
11
0
00
0
Aufstieg
X
Schritt (7)
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 3; n = 2
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 4; n = 1
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
1
11
1
0
false 1
1
11
Aufstieg
X
Schritte (5), (7)
10
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
Inkarnation 3; n = 2
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
2
22
2
1
false 2
1
22
Aufstieg
X
Schritte (5), (7)
Inkarnation 1; n = 4
1
0
1
n
-
fac(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false
?
Inkarnation 2; n = 3
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
3
33
3
2
false 6
2
66
Aufstieg
X
Schritte (5), (7)
11
Inkarnation 1; n = 4
1
0
1
n
-
fact(n-1)nn
*=n
fact(n)if
thenelse≡≡
4
44
4
3
false 24
6
2424
Aufstieg
Gesamtergebnis
X
Schritte (5), (7, diesmal Terminierung)
Beobachtungen
§ Inkarnationen werden „stapel-artig“ (neudeutsch: „stack-artig“, ‚stack‘ = Stapel), auch „keller-artig“ auf- und abgebaut( → vgl. Tellerstapel in der Mensa)
§ (noch) nicht abgeschlossene Inkarnationen benötigen Speicher(-platz) für ihre Parameter und lokale Variable
Bezeichnungen : „pulsierender Speicher“, Kellerspeicher, ‚stack‘
Beachte:
§ es gibt Programmiersprachen, die keine Rekursion erlauben, z.B. FORTRAN !
12
Beispiel 2 – Fibonacci-Zahlen
Ursprung: Beispiel zur mathematischen Populationsdynamik (→ Biomathematik)
„Das Weibchen eines Kaninchenpaars wirft von der Vollendung des 2. Lebensmonats an allmonatlich ein neues Kaninchenpaar. Man berechne die Anzahl F(n) der Kaninchenpaare im Monat n, wenn im [nach] Monat 0 genau ein neugeborenes Kaninchenpaar vorhanden ist.“
(Aufgabe von Leonardo von Pisa (= Fibonacci), ca. 1180 – ca. 1250; aus K. Jacobs. Einführung in die Kombinatorik, 1983)
Generiert die Folge :
n
F(n)
0
0
1
1
2
1
3
2
4
3
5
5
6
8
7
13
8
21
9
34
10
55
11
89
12
144
Rekursion für die Folge F(0), F(1), ...
Def.: ( )( )( ) ( ) ( )21
11
00
−+−=
=
=
nFnFnF
F
F
2, ≥n
(manchmal auch = 1, siehe oben !)Anzahl der Kaninchenpaare des letzten Monats
Anzahl neugeborener Paare = Anzahl Kaninchenpaare,
die ≥ 2 Monate alt sind
Berechnung durch rekursiven Algorithmus (hier: fib(n))
PROCEDURE fib(n : CARDINAL) : CARDINAL;
BEGIN
IF n <= 1 THEN
RETURN n (* Ergebnis: 0 oder 1 *)
ELSE
RETURN fib(n–1) + fib(n-2)
END
END fib;
Aufrufe der Prozedur (Beispiel: n = 5) :fib(5)
fib(4) fib(3)
fib(3) fib(2) fib(2) fib(1)
fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
fib(1) fib(0)
= 1
= 1 = 0= 1 = 0
= 1 = 0
= 1
Summation: fib(5) = 5
13
Struktur :
7532 4 6n
F(n) ≡≡ fib(n)
Fibonacci
Aufrufe
Blätter
13521 3 8
...1442 8 24
...832 5 13
⇒⇒ Programm mit exponentiellem Zeitbedarf !
Iteratives Programm, das als Funktion (hier: fibonacci(n)) direkt den Fibonacci-Wert bestimmt :
PROCEDURE fibonacci(n : CARDINAL) : CARDINAL;
VAR
i, a, b, fibo : CARDINAL;
BEGIN
IF n = 0 THEN
fibo := 0
ELSE
a := 0;
b := 1;
FOR i := 1 TO n DO
a := a + b;
b := a – b (* ergibt das „alte“ a ! *)
END;
fibo := a
END;
RETURN fibo
END fibonacci;
Schema :Wert_1‘ := Wert_1 + Wert_2
Wert_2‘ := Wert_1
neuer Wert (nach Zuweisung)
14
Ergebnisberechnung :
0
a b fibonacci(n)n
0
1 101
2 111
3 212
4 323
5 535
6 858
7 13813
8 211321
Beispiel-Implementierungen – Vergleich der Laufzeiten
Rekursive Lösung Iterative Lösung
Parameterbeispiele : n = 35, 36, 37, ..., 40
15
Vorbemerkung
Viele Aufgabenstellungen, Funktions-Definitionen, etc. sind von „Natur“ aus rekursiv , so daß die Formulierung einer zugehörigen rekursiven Prozedur oft die eleganteste und klarste Lösung der Aufgabe darstellt !
Struktur rekursiver Algorithmen
PROCEDURE recProc(...);
:
BEGIN
IF < Rekursionsende erreicht > THEN
< nicht-rekursiver Teil >
ELSE
:
recProc(...); (* ein oder mehrere rekursive Aufrufe *)
:
END;
:
END recProc;
Typischer Aufbau einer rekursiven Prozedur
zur Erinnerung : Berechnung der Fakultät
PROCEDURE fact(n : INTEGER) : INTEGER;
BEGIN
IF n = 0 THEN
RETURN 1
ELSE
RETURN n * fact(n–1)
END
END fact;
Terminations-Bedingung – Rekursionsende
Rekursionsende : Ergebnis bei Ende des Rekursions-Abstiegs
• Rekursion – rekursiver Abstieg• Verknüpfung der Teilresultate
16
Konstruktion rekursiver Algorithmen (allgemeine Hinweise)
1. Spezifikation der Aufgabe wie bei anderen Vorhaben auch
„Schnittstellen-Beschreibung“ : Identifikation der Rolle der Parameter
2. Konstruktion
a) Anfang Fallunterscheidung zur Bestimmung des Rekursionsendes, in diesem Teil findet kein rekursiver Aufruf statt
b) Rekursiver Zweig
„Denkweise“: zu formulierende rekursive Prozedur existiert bereits als „Black Box“ (wie ein Bibliotheks-Programm)
Wirkung eindeutig durch Schnittstellen-Beschreibung gegeben !
Black Box
Konstruktion rekursiver Algorithmen
Beispiel : Das Jeep-Problem (1)
§ Gegeben
• Tankkapazität des Jeeps: c (in dm3)
• Treibstoff-Verbrauch des Jeeps: v (in dm3/ 100km)
• Anzahl der Tankfüllungen am Ausgangspunkt: k
§ Gesucht
Maximal zurücklegbare Entfernung durch geschicktes Anlegen von Zwischen-depots,
dabei ist die Entfernung zwischen benachbarten Depots so zu wählen, dass genau 1 Tankfüllung verbraucht wird, um den gesamten Treibstoff zu transportieren.
Der Treibstoff kann aus Sicherheitsgründen nur im (Original-) Tank des Jeeps transportiert werden; für die Depots seien geeignete Lagerbehälter vorhanden.
17
§ Lösungsstrategie
• von jedem Depot aus jeweils ein neues Tanklager anlegen
• Entfernung des Depots so bestimmen, dass eine Tankfüllung reicht, um das restliche Benzin in das neue Depot zu schaffen
• im neuen Depot alles Benzin – bis ggf. auf den Bedarf für die Rückfahrt in das vorherige Depot – aus dem Tank in den Depot-Lagerbehälter ausladen
Depot-Nr:
Start
0 1 2 3
Tankladungen: k k-1 k-2 k-3 0
maximale Entfernung maxent(k,v,c) mit k Tankfüllungen
maximale Entfernung maxent(k-1,v,c) mit k-1 Tankfüllungen
Distanz dist(k-2,v,c) zwischen Depot 2 und 3
Ziel
k
§ Entwicklung der Lösung
• maxent(i,v,c) ist die maximale Reichweite, die der Jeep mit i (i ≥ 1) Tank-füllungen, Verbrauch v und Tankkapazität c zurücklegen kann
• offensichtlich gilt:maximale Reichweite mit 1 Tankfüllung: maxent(1,v,c) = 100 · c/v
• die Strecke dist(i,v,c) zwischen zwei Depots A und B wird wie folgt befahren:
Hinweg A →→ B : i malRückweg B →→ A : i - 1 mal
insgesamt also 2i - 1 mal und es gilt :
• mit diesem dist(i,v,c) ergibt sich als Rekursionsbeziehung:
• Auslademenge aus(i,v,c) = c – {2 mal Verbrauch auf dist(i,v,c)}
v1)(2i
100c)dist(i,v,c
⋅−
⋅=
1,v,c)maxent(i)dist(i,v,c,c)maxent(i,v −+=
1002
v)dist(i,v,ccaus(i,v,c) ⋅⋅−=
18
1. Repetitive oder iterative Rekursion („tail recursion“)
§ Der rekursive Aufruf ist die (zeitlich) letzte Anweisung in der Prozedur
§ (Bereits) die Terminierung liefert das Gesamtergebnis; d.h. de facto findet nur ein Abstieg statt. Der Aufstieg besteht lediglich in der Schließung der Inkarnationen
§ Beispiel : Variante der Fakultät : fact(n,m) = fact(n-1,m·n); fact(0,m) = m
Verschiedene Rekursionsarten
PROCEDURE fact(
n,m : CARDINAL): CARDINAL;
BEGIN
IF n = 0 THEN
RETURN m
ELSE
RETURN fact(n–1, m*n)
END
END fact;
Formular für fact(n, m)
0
m
*
fact(n-1,m·n)
n
=n
if
thenelse≡≡
fact(n,m)
n
m
1n
-
m
Spezialfall : fact(n,1) = n!
Bemerkungen
§ Hier werden die noch nicht beendeten Inkarnationen eigentlich nicht gebraucht, da beim Aufstieg aus der Rekursion nur noch Ergebnisübertragungenstattfinden ⇒⇒ effizientere Lösung : Formular überschreiben
• Man benötigt nur ein Formular und einen Radiergummi
• Iterative Lösung mit WHILE-Schleife
• Speicherersparnis der iterativen Lösung gegenüber der rekursiven (nur einFormular!)
Formular für fact(n,m)
0
m
*
fact(n-1,m·n)
n
=n
if
thenelse≡≡
fact(n,m)
n
m
1n
-
m
Beispiel: fact(4,1) =
fact(3,4) =
fact(2,12) =
fact(1,24) =
fact(0,24) = 24
Formular für fact(n,m)
0
m
*
fact(n-1,m·n)
n
=n
if
thenelse≡≡
fact(n,m)
n
m
1n
-
m4 1
4
4 41
false
X1
3 4
?
Formular für fact(n,m)
0
m
*
fact(n-1,m·n)
n
=n
if
thenelse≡≡
fact(n,m)
n
m
1n
-
m3 4
3
3 34
false
X4
2 12
?
Formular für fact(n,m)
0
m
*
fact(n-1,m·n)
n
=n
if
thenelse≡≡
fact(n,m)
n
m
1n
-
m2 12
2
2 212
false
X12
1 24
?
Formular für fact(n,m)
0
m
*
fact(n-1,m·n)
n
=n
if
thenelse≡≡
fact(n,m)
n
m
1n
-
m1 24
1
1 124
false
X24
0 24
?
Formular für fact(n,m)
0
m
*
fact(n-1,m·n)
n
=n
if
thenelse≡≡
fact(n,m)
n
m
1n
-
m0 24
0
0 024
true
X24
2424
19
Iterative Lösung für fact(n, m)
PROCEDURE fact(n,m : CARDINAL) : CARDINAL;
VAR
vn, vm : CARDINAL;
BEGIN
vn := n;
vm := m;
WHILE vn <> 0 DO
vm := vm * vn;
vn := vn - 1
END;
RETURN vm
END fact;
2. Lineare Rekursion
§ jeder rekursive Aufruf führt zu höchstens einem weiteren Aufruf(während des Abstiegs)
§ das Resultat eines Aufrufs ist Operand einer umfassenden Operation(während des Aufstiegs)
§ Beispiel : Fakultät (Hinweis: Fibonacci ist kein Beispiel für eine lineare Rekursion !)
Ergebnis
( ) ( )( )( )
( )( )( )( )( )( )( )
( )( )( )( )( )
( )
24
64
234
1234
11234
01234
1234
234
344
=
⋅=
⋅⋅=
⋅⋅⋅=
⋅⋅⋅⋅=
⋅⋅⋅⋅=
⋅⋅⋅=
⋅⋅=
⋅=
fact
fact
fact
factfactAbstieg
Aufstieg
Ende
n = 4, n = 3
n = 2
n = 1
n = 0
Jeweils ein Aufrufpro Rekursions-stufe während des Abstiegs
Operation während des Aufstiegs ist die Multiplikation
20
3. Baumartige Rekursion
§ Jeder nichtterminierende rekursive Aufruf führt zu mindestens zweiweiteren Aufrufen (während des Abstiegs)
§ Beispiel : Binomialkoeffizient(Hinweis: Fibonacci ist auch ein Beispiel für eine baumartige Rekursion !)
n Werte der Binomialkoeffizienten(Pascal‘sches Dreieck)
1
11
1 12
4
1
1
13 3
6 4 1
105 10 5 11
2015 15 6 161
...
0
1
6
2
3
4
5
:
k = 0 →→ n
k
n
10
=
n1=
n
n
nkkfallsk
n
k
n
k
n<∧>
−+
−
−=
0
1
1
1
10515
2
5
1
5
2
6:
+=
+
=
Beispiel
Aufrufbaum für , 10
=
n1=
n
n, 0
1
1
1nkkfalls
k
n
k
n
k
n<∧>
−+
−
−=
am Beispiel n = 4, k = 2 :
bin(4,2)
bin(3,2)bin(3,1)
bin(2,2)bin(2,1)bin(2,1)bin(2,0)
bin(1,1)bin(1,0)bin(1,1)bin(1,0)
= 1
= 1 = 1 = 1 = 1
= 1
PROCEDURE bin(
n,k : CARDINAL) : CARDINAL;
BEGIN
IF (k = 0) OR (k = n)
THEN RETURN 1
ELSE bin(n-1,k-1) + bin(n-1,k)
END
END bin;
Summation : bin(4, 2) = 6
21
4. Geschachtelte Rekursion
§ Argument eines rekursiven Aufrufs ist selbst rekursiver Aufruf
§ Beispiel : Modulo
5. Verschränkte Rekursion
§ Prozedur f ruft Prozedur g und sich selbst auf
§ Prozedur g ruft Prozedur f und sich selbst auf
Bemerkungen zu 4. und 5.
§ Auch diese Arten sind nicht ohne Weiteres mittels einfacher Schleifen zu ersetzen
§ Hinweis : Beliebige Rekursionsarten lassen sich immer dadurch in eine iterativeForm bringen, dass die „kellerartige“ Verarbeitung der Rekursion ausprogrammiert
wird.
PROCEDURE modulo(a,b: CARDINAL): CARDINAL;
BEGIN
IF a < b THEN RETURN a
ELSIF a < 2b THEN RETURN a-b
ELSE RETURN modulo(modulo(a,2*b),b)
END modulo;
Termination rekursiver Funktionen / Prozeduren
§ Intuitiv
zu zeigen:
§ jeder rekursive Aufruf führt zu „kleineren“ Argumenten§ nach einer endlichen Anzahl von Aufrufen wird Terminationsargument erreicht
§ formal
f inde eine (streng monoton fallende) Abbildung
t: „Parameterbereich“ →→ N0
mitt(Argument im rekursiven Aufruf) < t(formalen Argument)
für alle rekursiven Aufrufe.
bin(n,k) terminiert
§ Beispiel : Binomialkoeffizient bin(n,k)
wähle: t: N0 x N0 →→ N0 als t(n,k) = n + k
dann: t(n-1,k-1) = n+k-2 < n+k = t(n,k)t(n-1,k) = n+k-1 < n+k = t(n,k)
22
2. „Teilen-und-Herrschen“
• Prinzip „Teilen-und-Herrschen“ (“Divide-and-conquer”)
• Beispiel – Markieren eines Lineals
Konzept
Prinzip „Teilen-und-Herrschen“ („Divide-and-conquer“)
Eigenschaften
§ Lösungen (Algorithmen) nach dem „Teile-und-Herrsche“-Prinzip sind baumartige Rekursionen und lassen sich daher i.a. nicht direkt in iterative Verfahren abbilden !
§ Bei „Teilen-und-Herrschen“-Verfahren finden meist keine redundanten Berechnungen statt, da die Eingabedatenmenge in nicht-überlappende Teilmengen zerlegt wird :
( ) ( ) ( )∅=∩
+=∪
BA
BcardAcardBAcard oder
Häufig bieten sich rekursive Lösungen für ein Problem an, in denen die
Eingabemenge in 2 (etwa gleich große) Teile zerlegt wird, die jeweils
durch rekursive Aufrufe des Lösungs-Algorithmus bearbeitet werden !
Je nach Aufgabenstellung kann die Zerlegung auch in mehr als 2 Teile
erfolgen !
siehe
Binomial-koeffizient
oder
Fibonacci
23
Beispiel – Markieren eines Lineals
0 1
Markierung bei
Markierung bei und
Markierung bei und und und
[ ]321
2/1
1:02
1
434214/1
2
1:0
2
1
434214/3
1:2
1
2
1
434218/1
4
1:0
2
1
434218/3
2
1:
4
1
2
1
434218/5
4
3:
2
1
2
1
434218/7
1:4
3
2
1
Aufgabe :
Strategie : mehrstufige Intervallhalbierung
Strategie der rekursiven Lösung
1. Maximale Markierung festlegen
2. Das Lineal wird sukzessive halbiert
3. Die Mitte wird markiert
(Länge der Markierung = Anzahl der letzt-gröberen Markierung - 1)
4. Für jede (aktuelle) Lineal-Hälfte werden die Schritte 2. und 3.
Halbierung + Markierung
wiederholt
5. Ende : Länge der Markierung = 0 oder gleichbedeutendLänge des aktuellen Teil-Lineals = 0
Algorithmus – Entwurfsentscheidungen
1. Das Lineal ist als l ineares Feld aus INTEGER / CARDINAL-Elementen realisiert
2. Die Werte der Elemente (des Feldes) bezeichnen die Längen der Markierungen
Initialisierung (angenommen) : ( ) [ ] 0 :10, =−≤≤∀ iANii
24
:
CONST
N = < cardinal >;
VAR
ruler : ARRAY [0..N] OF CARDINAL;
begin, end, mheight : CARDINAL;
PROCEDURE mark(index, height : CARDINAL);
BEGIN
ruler[index] := height
END mark;
PROCEDURE rule(i_begin, i_end, mheight : CARDINAL);
VAR
i_middle : CARDINAL;
BEGIN
IF mheight > 0 AND (i_end – i_begin) > 0 THEN
i_middle := (i_begin + i_end) DIV 2;
mark(i_middle, mheight);
rule(i_begin, i_middle, mheight-1);
rule(i_middle, i_end, mheight-1)
END
END rule;
Rekursionsende hier mit „leerer“ Aktion !
Zahl, z.B. 64
BEGIN (* -- Hauptprogramm *)
:
mheight := ...;
begin := 0;
end := N;
rule(begin, end, mheight)
END ...
3. Türme von Hanoi
• Problem• Strategie• Rekursiver Algorithmus• Formaler Beweis der Korrektheit• Aufwandsabschätzungen (rekursive Lösung)• Einfacher nicht-rekursiver Algorithmus
25
Kupfer GoldSilber
Nach einer alten Legende standen vor langer Zeit vor einem Tempel in Hanoi drei Säulen :
• eine aus Kupfer , • eine aus Silber,• eine aus Gold .
Problem
4
1
2
3
Auf der kupfernen Säule befanden sich hundert verschieden große Scheiben aus Porphyr (vulkanisches Gestein), wobei die Scheiben in ihrer Größe nach oben hin immer kleiner wurden :
Ein alter Mönch hatte sich die Aufgabe gestellt, alle Scheiben von der kupfernenzur goldenen Säule zu tragen. Da die Porphyrscheiben sehr schwer waren, konnte der Mönch immer nur eine Scheibe gleichzeitig transportieren. Da die Säule ihm gleichzeitig als Treppe diente, durfte bei der Umschichtung nie eine größere auf eine kleine Scheibe gelegt werden.
Wenn der Mönch – so die Legende – seine Aufgabe erfüllt habe, so werde das Ende der Welt kommen.
Kupfer GoldSilber
4
1
2
3
26
Der Mönch bemerkte sehr schnell, daß er beim Transport der Scheiben auch die silberne Säule benötigte , da er ja immer nur eine Scheibe gleichzeitig tragenkonnte. Nach einigen Tagen Meditation bekam er auf einmal die Erleuchtung:
Strategie
Beim Betrachten dieses Schemas bemerkte der Mönch, daß Teil 1 und Teil 3 außerordentlich mühsam sein würden. Da er nicht nur ein alter, sondern auch ein weiser Mönch war, entschloß er sich, Teil 1 von seinem ältesten Schüler ausführen zu lassen. Wenn dieser mit der Arbeit fertig wäre, würde der Mönch selbst die große Scheibe von der kupfernen zur goldenen Säule tragen – und dann nochmals die Dienste seines ältesten Schülers in Anspruch nehmen.
Die Aufgabe kann in drei Teilaufgaben zerlegt werden:
Teil 1 : Transportiere den Turm – bestehend aus 99 oberen Scheiben von der kupfernen zur silbernen Säule
Teil 2 : Transportiere die übriggebliebene 100ste Scheibe (ganz unten) von der kupfernen zur goldenen Säule
Teil 3 : Transportiere den Turm mit den 99 Scheiben von der silbernenzur goldenen Säule
Kupfer GoldSilber
4
1
2
3
Kupfer GoldSilber
4
1
2
3
Start (Quelle) ZielSchema :
27
Kupfer GoldSilber
4
1
2
3
Kupfer GoldSilber
4
1
2
3
Ziel
Um seinem ältesten Schüler, der selbst schon in den Jahren war, nicht zu viel Arbeit zu machen, wollte er ihm diesen Plan mitteilen, damit auch er es sich leicht machen könnte.
Der Algorithmus , den der Mönch am nächsten Tag an die Tempeltür nagelte, ist aus dem Alt-Vietnamesischen übersetzt :
Anleitung , um einen Turm von n Scheiben von der einen zu der anderen Säule – unter Verwendung einer weiteren (dritten) Säule –zu transportieren :
Wenn der Turm aus mehr als einer Scheibe besteht,
• dann bitte Deinen ältesten Schüler, einen Turm von n-1 Scheiben von der ersten zur dritten Säule zu transportieren;
• trage selbst eine Scheibe von der ersten zur anderen Säule;
• bitte Deinen ältesten Schüler, einen Turm von n-1 Scheiben von der dritten zur anderen Säule zu transportieren.
28
Als der Mönch dieses Dokument festgenagelt hatte, fragte er sich, was jetzt zu tun sei – er musste einen Turm von 100 Scheiben von der kupfernen zur goldenen Säule transportieren. Weil er nach der schweren Denkarbeit der vorherigen Tage etwas zerstreut war, wußte er nicht mehr genau, wie er das machen sollte; als er vor der Tempeltür eine Traube von Menschen sah, die offenbar etwas lasen, erinnerte er sich wieder. Entschlossen drängelte er sich zur Tempeltür und las, wie zu verfahren sei.
Und so rief er seinen ältesten Schüler zu sich und bat ihn, einen Turm von 99 Scheiben von der kupfernen zur silbernen Säule (unter Verwendung der goldenen) zu transportieren – und sich danach wieder bei ihm zu melden ...
(nach G. Hommel, C.A.H. Koster. Algorithmen, TU Berlin, 1977/78)
1links
3rechts
2mitte
Grobstruktur
geg. : Turmhöhe : hoehe (einlesbar)
Ausgangssäule : 1 (links)
Zielsäule : 3 (rechts)
Rekursiver Algorithmus
Nummerierung
29
MODULE TuermeVonHanoi;
FROM InOut IMPORT WriteString,WriteLn,ReadCard;
TYPE saeule = (1, 2, 3); (* Saeulenpositionen links, mitte, rechts *)
VAR hoehe : CARDINAL; (* Höhe des Turms = Anzahl der Scheiben *)
PROCEDURE drucke(x : saeule) (* druckt die Säulenposition aus *)
BEGIN
CASE x OF
1: WriteString(’ links’)|
2: WriteString(’ mitte’)|
3: WriteString(’ rechts’)
END
END drucke;
PROCEDURE TurmBewegung(hoehe : CARDINAL; A, B : saeule)
(* bewegt einen Turm der Höhe ‚hoehe‘ von Säule A nach Säule B *)
BEGIN
IF hoehe > 0 THEN
TurmBewegung(hoehe-1, A, 6-A-B);
WriteString(’Oberste Scheibe von ’);
drucke(A); WriteString(’ nach’); drucke(B);
TurmBewegung(hoehe-1, 6-A-B, B)
END
END TurmBewegung;
BEGIN
WriteString(’Turmhoehe = ’); ReadCard(hoehe); WriteLn;
TurmBewegung(hoehe, 1, 3)
END TuermeVonHanoi.
Rekursionsende bei hoehe = 0
Rekursion
a = Position Ausgangssäuleb = Position Zielsäule6 - a - b = Position Hilfssäule
„BewegeScheibe(A, B)“
„Handsimulation“ von TuermeVonHanoi
1 32123
hoehe = 3
TurmBewegung(3, 1, 3)
hoehe = 3 > 0:
TurmBewegung(2, 1, 6-1-3=2)
hoehe = 2 > 0:
TurmBewegung(1, 1, 6-1-2=3)
hoehe = 1 > 0:
TurmBewegung(0, 1, 6-1-3=2)
hoehe = 0 ⇒⇒ EXIT
BewegeScheibe(1, 3)
TurmBewegung(0, 3, 6-1-3=2)
hoehe = 0 ⇒⇒ EXIT
BewegeScheibe(1, 2)
TurmBewegung(1, 6-1-2=3, 2)
hoehe = 1 > 0:
TurmBewegung(0, 3, 6-3-2=1)
hoehe = 0 ⇒⇒ EXIT
BewegeScheibe(3, 2)
TurmBewegung(0, 6-3-2=1, 2)
BewegeScheibe(1, 3)
TurmBewegung(2, 6-1-3=2, 3)
: < →→ nächste Seite >
Start :
1 32
231
1 322 31
1 3223
1
1 3223
1
30
1 3212
3:
BewegeScheibe(1, 3)
TurmBewegung(2, 6-1-3=2, 3)
hoehe = 2 > 0:
TurmBewegung(1, 2, 6-2-3=1)
hoehe = 1 > 0:
TurmBewegung(0, 2, 6-2-1=3)
hoehe = 0 ⇒⇒ EXIT
BewegeScheibe(2, 1)
TurmBewegung(0, 6-2-1=3, 1)
hoehe = 0 ⇒⇒ EXIT
BewegeScheibe(2, 3)
TurmBewegung(1, 6-2-3=1, 3)
hoehe = 1 > 0:
TurmBewegung(0, 1, 6-1-3=2)
hoehe = 0 ⇒⇒ EXIT
BewegeScheibe(1, 3)
TurmBewegung(0, 6-3-2=1, 2)
hoehe = 0 ⇒⇒ EXIT
TERMINATION
1 3223 1
1 32
23 1
1 32
23
1
Finale : 1 32
23
1Programmende
Frage : Liefert der Algorithmus stets die korrekte Lösung ?
→ Vollständige Induktion
Formaler Beweis der Korrektheit
1 3 2
B. Induktionsannahme : Der Algorithmus arbeitet für n Scheiben korrekt
1 3 2
n
C. Induktionsschritt : Arbeitet der Algorithmus auch für n + 1 Scheiben korrekt ?
Bewege n + 1 Scheiben von 1 nach 3 (Hilfssäule ist 2)
BewegeScheibe(1, 3)
Bewege 1 Scheibe von 1 nach 3
A. Induktionsbeginn : n = 1
31
TurmBewegung(n+1, 1, 3)
1. hoehe = n+1 > 0:
2. TurmBewegung(n, 1, 2)
3. BewegeScheibe(1, 3)
4. TurmBewegung(n, 2, 3)
2. gilt laut Induktionsannahme , da hoehe = n und damit
Bewege n Scheiben von 1 nach 2 (Hilfssäule : 3)
3. gilt laut Induktionsbeginn , mit
Bewege 1 Scheibe von 1 nach 3
4. gilt laut Induktionsannahme , da hoehe = n und damit
Bewege n Scheiben von 2 nach 3 (Hilfssäule : 1)
außerdem gilt die Transitivität der Transportoperation BewegeScheibe(., .) :
BewegeScheibe(1, 2) ∧∧ BewegeScheibe(2, 3) ⇒⇒ BewegeScheibe(1, 3)
⇒ Induktionsschluß : Der Algorithmus arbeitet auch für n + 1 Scheiben !
Frage : Wie oft müssen Scheiben hin- und hergetragen werden ?
Aufwandsabschätzung (rekursive Lösung)
Vermutung : Bei Betrachtung der Zahlenfolge 1, 3, 7, 15, ... liegt die Vermutung nahe, dass bei n Scheiben 2n – 1 Trageoperationen notwendig sind !
Überprüfung : Trageoperationen bei n = 1 + 2·Trageoperationen bei (n – 1)
( )
12
221
122112 1
−=
−+=
−⋅+=− −
n
n
nn ?
?⇒ Die Anzahl der Trageoperationen
nimmt exponentiell mit n zu !
1
2
3
4
:
n
Scheibenanzahl Trageoperationen
1
1 + 1 + 1 = 3
3 + 1 + 3 = 7
7 + 1 + 7 = 15
:
1 + 2·Trageoperationen bei (n-1)
Ausgangssituation: Auf der kupfernen Säule befinden sich n ≥ 0 Scheiben
32
Zurück zum Anfang . . .
. . . das Ende der Welt ist dann wahrscheinlich (in der Tat) nicht mehr fern !
Wenn der Mönch – so die Legende – seine Aufgabe erfüllt habe, so werde das Ende der Welt kommen.
)(!104.2
.min1026765.1.min1224
30100
Jahre⋅≈
⋅≈− (1 Jahr = 525600 min.)
Wenn alle Mönche für den Transport der 100 Scheiben sehr fleißig arbeiten und jede Minute eine Scheibe transportieren, dann benötigen sie für den Transport des Turms
Struktur
→ Es wird keine „Ziel-Säule“ angegeben !
Einfacher nicht-rekursiver Algorithmus 1
3 2
(nach D. Harel. The science of computing. Addison-Wesley, 1989, p.107)
:
LOOP
< bewege die kleinste Scheibe (= oberste Scheibe der Anfangs-Säule)um eine Position „im Uhrzeigersinn“ (also 1 →→ 2 oder 2 →→ 3 oder 3 →→ 1) >;
IF < alle Scheiben korrekt auf einer anderen Säule (= Zielsäule) gestapelt > THENEXIT
END; (* IF *)
< bewege die kleinste Scheibe nicht, mache den einzig möglichen momentan verbleibenden Zug >
END; (* LOOP *)
:
33
2
1
3
Ablauf für einen Turm mit 4 Scheiben
2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3 2
1
3
Fertig !
Ablauf
Transport einer geraden Anzahl von Scheiben, z.B. 4 – 3 – 2 – 1 , Zielstapel auf nächster Säule im Gegenzeigersinn
4
1
2
3
Transport einer ungeraden Anzahl von Scheiben, z.B. 5 – 4 – 3 – 2 – 1 , Zielstapel auf nächster Säule im Uhrzeigersinn
4
1
2
3
5
2
1
3
3 2
1
Start
Zielstapel
Start
Zielstapel
34
§ Rekursive Funktionen / Prozeduren rufen sich selbst auf – direkt oder indirekt
§ Die Verarbeitung rekursiver Prozeduren lässt sich anschaulich durch die „Formular-Maschine“ darstellen. Zur Verwaltung der bei jedem Aufruf (Inkarnation) angelegten Variablen und übergebenen Parameter wird (vom System) ein pulsierender Speicher (auch: „Stack“, Kellerspeicher) verwendet
§ Verarbeitungs-Phasen eines rekursiven Programms :• Abstieg• Ende (Terminationsbedingung) • Aufstieg (ggf. mit Zusammenfügen der Teilergebnisse)
§ Es werden verschiedene Rekursionsarten unterschieden: repetitive, lineare, baumartige, geschachtelte, verschränkte Rekursion
§ Die Terminierung rekursiver Funktionen ist ein wesentlicher Aspekt. Sie wird mit einer streng monoton fallenden Abb. t:(Parameterbereich) → N untersucht.
§ Verfahren nach dem Prinzip „Teilen-und-Herrschen“ zerlegen die Datenmenge in etwa gleiche Teile und wenden den Algorithmus jeweils auf den Teilmengen an.
§ Das „Türme-von-Hanoi“-Problem stellt ein klassisches Beispiel dar, eine Aufgabe rekursiv in einfacher Weise zu lösen , eine gleichwertige iterative Lösung ist dagegen sehr viel schwerer durchschaubar; rekursive Lösungen dieser Art benötigen exponentiellen Aufwand
Zusammenfassung