Slide 1
Problemlösen mit Rekursion
Klaus Becker
2009
Slide 2
2
Problemlösen mit Rekursion
Inhalte:
Problemlösen durch Problemreduktion
Selbstähnliche Figuren
Rekursive Verarbeitung von Listen
Rekursive Verarbeitung natürlicher Zahlen
Rekursion und Berechnungsaufwand
Rekursion und Iteration
Slide 3
3
Teil 1
Problemlösen durch Problemreduktion
Slide 4
4
Einstieg - Türme von Hanoi
Einer Geschichte zufolge soll im Tempel zu Benares - das ist eine "heilige Stadt" in Indien - ein
Turm aus 64 goldenen, der Größe nach geordneten Scheiben stehen. Die Mönche des Tempels
erhalten die Aufgabe, die Scheiben an einen anderen Ort zu bringen. Dabei müssen sie einige
Regeln beachten: Es darf immer nur eine Scheibe transportiert werden. Scheiben können auf
einem (einzigen) Hilfsstapel zwischenzeitlich abgelegt werden. Auch auf dem (teilweise
abgebauten) Ausgangsturm können Scheiben zwischenzeitlich abgelegt werden. Es darf aber
nie eine größere Scheibe auf eine kleinere gelegt werden. Wenn der neue Turm fertig ist, dann
ist das Ende der Zeit erreicht.
Slide 5
5
Einstieg - Aufgabe (siehe 9.1.1)
Versuchen Sie, einen Turm mit 5 Scheiben nach den vorgegebenen Regeln umzustapeln. Wenn
das nicht klappt, dann versuchen Sie erst einmal, Türme mit 3 bzw. 4 Scheiben umzustapeln.
Ausgangszustand
Zielzustand
Benutzen Sie Münzen unterschiedlicher Größe oder ein Simulationsprogramm.
z. B.: http://www.mpg-trier.de/d7/prog/hanoi/hanoi.htm
Slide 6
6
Einstieg - Aufgabe (siehe 9.1.1)
Überlegen Sie sich auch eine Strategie, mit der man Türme mit 6, 7, ... Scheiben umstapeln
kann.
Ausgangszustand
Zielzustand
Slide 7
7
Lösungsidee
transportiere einen 5-Scheiben-Turm von A über B nach C
Ausgangszustand
transportiere einen 4-Scheiben-Turm von A über C nach B
Zwischenzustand
transportiere eine Scheibe von A nach C
Zwischenzustand
transportiere einen 4-Scheiben-Turm von B über A nach C
Zielzustand
Slide 8
8
Verallgemeinerung
transportiere einen n-Scheiben-Turm von X über Y nach Z
Ausgangszustand
transportiere einen (n-1)-Scheiben-Turm von X über Z nach Y
Zwischenzustand
transportiere eine Scheibe von X nach Z
Zwischenzustand
transportiere einen (n-1)-Scheiben-Turm von Y über X nach Z
Zielzustand
Slide 9
Algorithmus
9
Algorithmus: transportiere einen n-Scheiben-Turm von X über Y nach Z
wenn n > 1:
transportiere einen (n-1)-Scheiben-Turm von X über Z nach Y
transportiere eine Scheibe von X nach Z
transportiere einen (n-1)-Scheiben-Turm von Y über X nach Z
sonst:
transportiere eine Scheibe von X nach Z
Slide 10
Rekursive Problemreduktion
10
Rekursive Problemreduktion ist eine Problemlösestrategie, bei der ein Problem auf ein
strukturgleiches Problem (in verkleinerter Form) zurückgeführt wird.
Algorithmus: transportiere einen n-Scheiben-Turm von X über Y nach Z
wenn n > 1:
transportiere einen (n-1)-Scheiben-Turm von X über Z nach Y
transportiere eine Scheibe von X nach Z
transportiere einen (n-1)-Scheiben-Turm von Y über X nach Z
sonst:
transportiere eine Scheibe von X nach Z
Ein rekursiver Algorithmus ruft sich (eventuell über Umwege) selbst auf und nutzt sich so
selbst zur Beschreibung der Lösung des gegebenen Problems.
Um Rekursion als Problemlösestrategie nutzen zu können, benötigt man ein Ausführsystem,
das in der Lage ist, rekursive Algorithmen wiederholt aufzurufen und auf diese Weise die
eigentliche Lösung zu generieren.
Slide 11
11
Ausführung des Algorithmus
Algorithmus: transportiere einen n-Scheiben-Turm von X über Y nach Z
wenn n > 1:
transportiere einen (n-1)-Scheiben-Turm von X über Z nach Y
transportiere eine Scheibe von X nach Z
transportiere einen (n-1)-Scheiben-Turm von Y über X nach Z
sonst:
transportiere eine Scheibe von X nach Z
transportiere einen 3-Scheiben-Turm von A über B nach C:
transportiere einen 2-Scheiben-Turm von A über C nach B
transportiere eine Scheibe von A nach C
transportiere einen 2-Scheiben-Turm von B über A nach C
Ausführungstiefe: 1
Slide 12
12
Ausführung des Algorithmus
Algorithmus: transportiere einen n-Scheiben-Turm von X über Y nach Z
wenn n > 1:
transportiere einen (n-1)-Scheiben-Turm von X über Z nach Y
transportiere eine Scheibe von X nach Z
transportiere einen (n-1)-Scheiben-Turm von Y über X nach Z
sonst:
transportiere eine Scheibe von X nach Z
transportiere einen 3-Scheiben-Turm von A über B nach C:
transportiere einen 2-Scheiben-Turm von A über C nach B:
transportiere einen 1-Scheiben-Turm von A über B nach C
transportiere eine Scheibe von A nach B
transportiere einen 1-Scheiben-Turm von C über A nach B
transportiere eine Scheibe von A nach C
transportiere einen 2-Scheiben-Turm von B über A nach C:
transportiere einen 1-Scheiben-Turm von B über C nach A
transportiere eine Scheibe von B nach C
transportiere einen 1-Scheiben-Turm von A über B nach C
Ausführungstiefe: 2
Slide 13
13
Ausführung des Algorithmus
Algorithmus: transportiere einen n-Scheiben-Turm von X über Y nach Z
wenn n > 1:
transportiere einen (n-1)-Scheiben-Turm von X über Z nach Y
transportiere eine Scheibe von X nach Z
transportiere einen (n-1)-Scheiben-Turm von Y über X nach Z
sonst:
transportiere eine Scheibe von X nach Z
transportiere einen 3-Scheiben-Turm von A über B nach C:
transportiere einen 2-Scheiben-Turm von A über C nach B:
transportiere einen 1-Scheiben-Turm von A über B nach C:
transportiere eine Scheibe von A nach C
transportiere eine Scheibe von A nach B
transportiere einen 1-Scheiben-Turm von C über A nach B:
transportiere eine Scheibe von C nach B
transportiere eine Scheibe von A nach C
transportiere einen 2-Scheiben-Turm von B über A nach C:
transportiere einen 1-Scheiben-Turm von B über C nach A:
transportiere eine Scheibe von B nach A
transportiere eine Scheibe von B nach C
transportiere einen 1-Scheiben-Turm von A über B nach C:
transportiere eine Scheibe von A nach C
Ausführungstiefe: 3
Slide 14
14
Ausführung des Algorithmus
transportiere einen 3-Scheiben-Turm von A über B nach C:
transportiere einen 2-Scheiben-Turm von A über C nach B:
transportiere einen 1-Scheiben-Turm von A über B nach C:
transportiere eine Scheibe von A nach C
transportiere eine Scheibe von A nach B
transportiere einen 1-Scheiben-Turm von C über A nach B:
transportiere eine Scheibe von C nach B
transportiere eine Scheibe von A nach C
transportiere einen 2-Scheiben-Turm von B über A nach C:
transportiere einen 1-Scheiben-Turm von B über C nach A:
transportiere eine Scheibe von B nach A
transportiere eine Scheibe von B nach C
transportiere einen 1-Scheiben-Turm von A über B nach C:
transportiere eine Scheibe von A nach C
Basisaktionen
Slide 15
15
Implementierung in Python
Algorithmus: transportiere einen n-Scheiben-Turm von X über Y nach Z
wenn n > 1:
transportiere einen (n-1)-Scheiben-Turm von X über Z nach Y
transportiere eine Scheibe von X nach Z
transportiere einen (n-1)-Scheiben-Turm von Y über X nach Z
sonst:
transportiere eine Scheibe von X nach Z
def transportiereTurm(n, x, y, z):
if n > 1:
transportiereTurm(n-1, x, z, y)
print "transportiere eine Scheibe von ", x, " nach ", z
transportiereTurm(n-1, y, x, z)
else:
print "transportiere eine Scheibe von ", x, " nach ", z
Algorithmus
Python-Programm
Slide 16
16
Übungen (siehe 9.1.4)
Bearbeiten Sie die Aufgaben 1, 2.
Slide 17
17
Teil 2
Selbstähnliche Figuren
Slide 18
18
Einstieg - Selbstähnliche Figur
Eine Figur ist selbstähnlich, wenn sie sich in Teile zerlegen lässt, die zur ihr ähnlich sind.
Slide 19
19
Einstieg - Selbstähnliche Figur
Eine Figur ist selbstähnlich, wenn sie sich in Teile zerlegen lässt, die zur ihr ähnlich sind.
zeichne_Baum(200):
gehe_vorwaerts(200)
drehe_dich_nach_rechts(45)
zeichne_Baum(100)
drehe_dich_nach_links(90)
zeichne_Baum(100)
drehe_dich_nach_rechts(45)
gehe_rueckwaerts(200)
rekursive
Problemreduktion
ALG zeichne_Baum(x):
wenn x >-> 2:
gehe_vorwaerts(x)
drehe_dich_nach_rechts(45)
zeichne_Baum(x/2)
drehe_dich_nach_links(90)
zeichne_Baum(x/2)
drehe_dich_nach_rechts(45)
gehe_rueckwaerts(x)
rekursiver
Algorithmus
Slide 20
Exkurs - Turtle-Grafik
20
Turtle-Grafik basiert auf der Vorstellung, dass eine Schildkröte mit bestimmten Anweisungen
auf einer Zeichenfläche bewegt wird und dass die Schildkröte dabei eine Spur hinterlässt.
vorwaerts(100)
Turtle-Befehle
zeichne_Quadrat(laenge):
wiederhole 4 mal:
gehe_vorwaerts(laenge)
drehe_dich_nach_links(90)
Turtle-Algorithmus
stift_hoch
stift_runter
gehe_vorwaerts(betrag)
gehe_rueckwaerts(betrag)
drehe_dich_nach_links(winkel)
drehe_dich_nach_rechts(winkel)
gehe_zu_punkt(punkt)
...
Slide 21
21
Exkurs - Turtle-Grafik in Python
Turtle-Grafik basiert auf der Vorstellung, dass eine Schildkröte mit bestimmten Anweisungen
auf einer Zeichenfläche bewegt wird und dass die Schildkröte dabei eine Spur hinterlässt.
zeichne_Quadrat(laenge):
wiederhole 4 mal:
gehe_vorwaerts(laenge)
drehe_dich_nach_links(90)
stift_hoch
stift_runter
gehe_vorwaerts(betrag)
gehe_rueckwaerts(betrag)
drehe_dich_nach_links(winkel)
drehe_dich_nach_rechts(winkel)
gehe_zu_punkt(punkt)
...
Turtle-Programm
# -*- coding: iso-8859-1 -*from turtle import *
# Deklaration einer
Zeichenprozedur
def quadrat(laenge):
for i in range(4):
t.forward(laenge)
t.left(90)
# Erzeugung eines Turtle-Objekts
t = Turtle()
# Test der Zeichenprozedur
quadrat(100)
Turtle-Klasse
t.forward(100)
Slide 22
22
Exkurs - Turtle-Grafik in Python
# -*- coding: iso-8859-1 -*from turtle import *
# Deklaration einer
Zeichenprozedur
def baum(stamm):
if stamm >= 2:
t.forward(stamm)
t.right(45)
baum(stamm/2)
t.left(90)
baum(stamm/2)
t.right(45)
t.backward(stamm)
# Erzeugung eines Turtle-Objekts
t = Turtle()
# Test der Zeichenprozedur
t.left(90)
baum(200)
ALG zeichne_Baum(x):
wenn x >= 2:
gehe_vorwaerts(x)
drehe_dich_nach_rechts(45)
zeichne_Baum(x/2)
drehe_dich_nach_links(90)
zeichne_Baum(x/2)
drehe_dich_nach_rechts(45)
gehe_rueckwaerts(x)
Slide 23
23
Übungen (siehe 9.2.3)
Wählen Sie eine der folgenden selbstähnlichen Figuren aus. Entwickeln Sie mit Hilfe einer
rekursive Problemreduktion einen rekursiven Algorithmus zum Zeichnen der Figur. Testen Sie
den Algorithmus mit einer Python-Implementierung.
Slide 24
24
Übungen (siehe 9.2.3)
Wählen Sie eine der folgenden selbstähnlichen Figuren aus. Entwickeln Sie mit Hilfe einer
rekursive Problemreduktion einen rekursiven Algorithmus zum Zeichnen der Figur. Testen Sie
den Algorithmus mit einer Python-Implementierung.
Slide 25
25
Übungen (siehe 9.2.3)
Wählen Sie eine der folgenden selbstähnlichen Figuren aus. Entwickeln Sie mit Hilfe einer
rekursive Problemreduktion einen rekursiven Algorithmus zum Zeichnen der Figur. Testen Sie
den Algorithmus mit einer Python-Implementierung.
Slide 26
26
Übungen (siehe 9.2.3)
Wählen Sie eine der folgenden selbstähnlichen Figuren aus. Entwickeln Sie mit Hilfe einer
rekursive Problemreduktion einen rekursiven Algorithmus zum Zeichnen der Figur. Testen Sie
den Algorithmus mit einer Python-Implementierung.
Slide 27
27
Übungen (siehe 9.2.3)
Wählen Sie eine der folgenden selbstähnlichen Figuren aus. Entwickeln Sie mit Hilfe einer
rekursive Problemreduktion einen rekursiven Algorithmus zum Zeichnen der Figur. Testen Sie
den Algorithmus mit einer Python-Implementierung.
Slide 28
28
Übungen (siehe 9.2.3)
Wählen Sie eine der folgenden selbstähnlichen Figuren aus. Entwickeln Sie mit Hilfe einer
rekursive Problemreduktion einen rekursiven Algorithmus zum Zeichnen der Figur. Testen Sie
den Algorithmus mit einer Python-Implementierung.
Slide 29
29
Teil 3
Rekursive Verarbeitung von Listen
Slide 30
30
Einstieg - Geschachtelte Listen
Eine Gästeliste soll mit einem Programm verwaltet und verarbeitet werden.
gaeste = ["Ursula", "Winfried", "Ulrike", "Klaus", ...]
def ausgabe(liste):
i=0
while i < len(liste):
element = liste[i]
print element
i=i+1
iterativ
# Test
ausgabe(gaeste)
def ausgabe(liste):
if len(liste) == 0:
pass
else:
erstesElement = liste[0]
restListe = liste[1:]
print erstesElement
ausgabe(restListe)
rekursiv
Slide 31
31
Einstieg - Geschachtelte Listen
Eine Gästeliste soll mit einem Programm verwaltet und verarbeitet werden.
gaeste = \
[\
["Ursula", "Winfried"], \
["Ulrike", "Klaus"], \
["Christiane", "Tim"], \
["Andreas"], \
["Ulrike", "Peter", ["Kea", "Lena", "Paula"]], \ def ausgabe(liste):
...
if len(liste) == 0:
]
pass
else:
erstesElement = liste[0]
restListe = liste[1:]
?
if type(erstesElement) == list:
ausgabe(erstesElement)
else:
print erstesElement
ausgabe(restListe)
iterativ
# Test
ausgabe(gaeste)
rekursiv
Slide 32
32
Einstieg - Geschachtelte Listen
ausgabe([["Ursula", "Winfried"], ["Ulrike", "Klaus"], ["Christiane", "Tim"], ...])
ausgabe(["Ursula", "Winfried"])
print "Ursula"
ausgabe(["Winfried"])
print "Winfried"
ausgabe([])
pass
ausgabe([["Ulrike", "Klaus"], ["Christiane", "Tim"], ...]
ausgabe(["Ulrike", "Klaus"])
print "Ulrike"
ausgabe(["Klaus"])
print "Klaus"
def ausgabe(liste):
ausgabe([])
if len(liste) == 0:
pass
pass
ausgabe([["Christiane", "Tim"], ...])
else:
...
erstesElement = liste[0]
restListe = liste[1:]
Ausführung
if type(erstesElement) == list:
ausgabe(erstesElement)
else:
print erstesElement
rekursiver Algorithmus
ausgabe(restListe)
Slide 33
33
Liste als rekursive Datenstruktur
Eine Liste ist entweder eine leere Liste, oder besteht aus einem ersten Element und einer
(Rest-)Liste.
def ausgabe(liste):
if len(liste) == 0:
# liste == []
pass
else:
# liste == [erstesElement] + restListe
erstesElement = liste[0]
restListe = liste[1:]
print erstesElement
ausgabe(restListe)
def ausgabe(liste):
if len(liste) == 0:
# liste == []
pass
else:
# liste == [erstesElement] + restListe
erstesElement = liste[0]
restListe = liste[1:]
if type(erstesElement) == list:
ausgabe(erstesElement)
else:
print erstesElement
ausgabe(restListe)
Slide 34
34
Entwicklung rekursiver Algorithmen
Problem: Es soll gezählt werden, wie oft ein Element in einer Liste vorkommt.
Fall 1: Bearbeite eine leere Liste
anzahl('b', []) ->
0
Reduktionsanfang:
Löse das Problem direkt
Fall 2: Bearbeite eine nicht-leere Liste
anzahl('b', ['b', 'b', 'd', 'a', 'c', 'b']) ->
1 + anzahl('b', ['b', 'd', 'a', 'c', 'b'])
Rekursionsschritt:
Löse ein entsprechendes Problem
anzahl('b', ['a', 'b', 'b', 'd', 'a', 'c', 'b']) ->
anzahl('b', ['b', 'b', 'd', 'a', 'c', 'b'])
Rekursive Problemreduktion: Reduziere des Problems auf ein entsprechendes, aber
„verkleinertes“ Problem.
Slide 35
35
Entwicklung rekursiver Algorithmen
Problem: Es soll gezählt werden, wie oft ein Element in einer Liste vorkommt.
anzahl('b', []) ->
0
anzahl('b', ['b', 'b', 'd', 'a', 'c', 'b']) ->
1 + anzahl('b', ['b', 'd', 'a', 'c', 'b'])
anzahl('b', ['a', 'b', 'b', 'd', 'a', 'c', 'b']) ->
anzahl('b', ['b', 'b', 'd', 'a', 'c', 'b'])
(rekursive) Reduktionsschritte
def anzahl(element, liste):
if len(liste) == 0:
return 0
else:
if liste[0] == element:
return (1 + anzahl(element, liste[1:]))
else:
return anzahl(element, liste[1:])
(rekursive) Reduktionsregeln
Rekursive Problemreduktion: Reduziere des Problems auf ein entsprechendes, aber
„verkleinertes“ Problem.
Slide 36
36
Entwicklung rekursiver Algorithmen
Problem: Es soll gezählt werden, wie oft ein Element in einer Liste vorkommt.
anzahl('b', ['a', 'b', 'd', 'a', 'b']) ->
anzahl('b', ['b', 'd', 'a', 'b']) ->
1 + anzahl('b', ['d', 'a', 'b']) ->
1 + anzahl('b', ['a', 'b']) ->
1 + anzahl('b', ['b']) ->
1 + (1 + anzahl('b', [])) ->
1 + (1 + 0) ->
2
Reduktionskette
def anzahl(element, liste):
if len(liste) == 0:
return 0
else:
if liste[0] == element:
return (1 + anzahl(element, liste[1:]))
else:
return anzahl(element, liste[1:])
(rekursive) Reduktionsregeln
>>> anzahl('b', ['a', 'b', 'd', 'a', 'b'])
2
Slide 37
37
Übungen (siehe 9.2.3)
Rekursionsgymnastik: Bearbeiten Sie die Aufgaben des Abschnitts 9.2.3.
Slide 38
38
Fallstudie - geometrische Objekte
In den folgenden Aufgaben (siehe 9.3.4) geht es um die Verwaltung und Verarbeitung
geometrischer Objekte. Wir betrachten vereinfachend nur geometrische Objekte, die aus
Streckenzügen mit Punkten mit ganzzahligen Koordinaten bestehen. Die folgende Abbildung
(Logo des Fachbereichs Informatik der TU Kaiserslautern) ist aus solchen geometrischen
Objekte aufgebaut.
stuetzelinks = [[0, 0],[20, 0],[50, 100],[30, 100],[0, 0]]
blockunten = [[90, 10],[110, 10],[110, 30],[90, 30],[90, 10]]
blockoben = [[90, 70],[110, 70],[110, 90],[90, 90],[90, 70]]
raute = [[80, 50],[100, 40],[120, 50],[100, 60],[80, 50]]
verbindung1 = [[100, 110], [100, 90]]
verbindung2 = [[100, 70], [100, 60]]
verbindung3 = [[100, 40], [100, 30]]
verbindung4 = [[100, 10], [100, 0]]
verbindung5 = [[80, 50], [70, 50], [70, 100], [100, 100]]
stuetzerechts = [verbindung1, blockoben, verbindung2, raute, \
verbindung5, verbindung3, blockunten, verbindung4]
dach = [[10, 110],[130, 110],[130, 125],[70, 140],[10, 125],[10, 110]]
tor = [stuetzelinks, stuetzerechts, dach]
rahmen = [[0, 0],[140, 0],[140, 140],[0, 140],[0, 0]]
logo = [tor, rahmen]
Bearbeiten Sie die Aufgaben aus Abschnitt 9.3.4.
Slide 39
39
Teil 4
Rekursive Verarbeitung natürlicher Zahlen
Slide 40
40
Einstieg - Wege im Galton-Brett
Ein Galton-Brett besteht aus Stäben, die in Reihen untereinander versetzt angeordnet sind.
Wenn man eine Kugel ein solches Galton-Brett herunterrollen lässt, dann trifft es auf jeweils
auf einen Stab und rollt dann entweder links oder rechts davon weiter herunter.
Slide 41
41
Einstieg - Aufgabe (siehe 9.4.1)
Die Stäbe in der Abbildung oben sind bereits mit Stab-Koordinaten versehen. Mit diesen
Koordinaten könnte man einen möglichen Kugelweg so beschreiben:
(0,0), (1, 0), (2, 1), (3, 2), (4, 3), (5, 3).
Wie viele Wege gibt es im Galton-Brett bis zum Stab (m, n)? Bestimmen Sie für alle Stäbe
erst einmal die jeweilige Anzahl. Zur Kontrolle: Bis zum Stab (5, 3) gibt es 10 Wege.
Slide 42
42
Einstieg - Aufgabe (siehe 9.4.1)
Die Funktion galton(m, n) beschreibe die Anzahl der Wege im Galton-Brett bis zum Stab
(m, n). Begründen Sie die unten formulierten Eigenschaften der Funktion galton.
galton(2, 0)
galton(n, 0) -> 1
galton(n, n) -> 1
galton(m, n) -> galton(m-1, n-1) + galton(m-1, n), falls m > 0 und 0 < n < m gilt.
Slide 43
43
Einstieg - Aufgabe (siehe 9.4.1)
Testen Sie die folgenden Funktionsdefinitionen. Worin unterscheiden sie sich? Welche ist
korrekt?
def galton(m, n):
if m < n:
def galton(m, n):
return None
if m < n:
else:
return 0
if (n == 0) or (m == n):
else:
return 1
if n == 0:
else:
return 1
return (galton(m-1, n-1) + galton(m-1, n))
else:
return (galton(m-1, n-1) + galton(m-1, n))
def galton(m, n):
if m == 0:
if n == 0:
return 1
else:
return 0
else:
if n == 0:
return 1
else:
return (galton(m-1, n-1) + galton(m-1, n))
Slide 44
44
Rekursive Struktur natürlicher Zahlen
Eine natürliche Zahl ist entweder eine Null oder Nachfolger einer natürlichen Zahl.
Problem: Die Summe der ersten n natürlichen Zahlen soll berechnet werden.
Fall 1: Bearbeite die Zahl 0.
summe(0) ->
0
Reduktionsanfang:
Löse das Problem direkt
Fall 2: Bearbeite den Nachfolger einer natürlichen Zahl
summe(5) ->
5 + summe(4)
Rekursionsschritt:
Löse ein entsprechendes Problem
Rekursive Problemreduktion: Reduziere des Problems auf ein entsprechendes, aber
„verkleinertes“ Problem.
Slide 45
45
Entwicklung rekursiver Algorithmen
Problem: Die Summe der ersten n natürlichen Zahlen soll berechnet werden.
summe(0) ->
0
summe(5) ->
5 + summe(4)
(rekursive) Reduktionsschritte
def summe(zahl):
if zahl == 0:
return 0
else:
return zahl + summe(zahl-1)
(rekursive) Reduktionsregeln
Rekursive Problemreduktion: Reduziere des Problems auf ein entsprechendes, aber
„verkleinertes“ Problem.
Slide 46
46
Entwicklung rekursiver Algorithmen
Problem: Die Summe der ersten n natürlichen Zahlen soll berechnet werden.
summe(5) ->
5 + summe(4) ->
5 + (4 + summe(3)) ->
5 + (4 + (3 + summe(2))) ->
5 + (4 + (3 + (2 + summe(1)))) ->
5 + (4 + (3 + (2 + (1 + summe(0))))) ->
5 + (4 + (3 + (2 + (1 + 0)))) ->
5 + (4 + (3 + (2 + 1))) ->
5 + (4 + (3 + 3)) ->
5 + (4 + 6) ->
5 + 10 ->
15
Reduktionskette
def summe(zahl):
if zahl == 0:
return 0
else:
return zahl + summe(zahl-1)
(rekursive) Reduktionsregeln
>>> summe(5)
15
Slide 47
47
Übungen (siehe 9.4.3)
Rekursionsgymnastik: Bearbeiten Sie die Aufgaben des Abschnitts 9.4.3.
Slide 48
48
Fallstudie - natürliche Zahlen
Operationen auf natürlichen Zahlen lassen sich alle aus einer einzigen Grundoperationen
entwickeln. Man benötigt hierzu nur die Nachfolger-Operation s: N -> N, die jeder natürlichen
Zahl n ihren Nachfolger s(n) zuordnet.
add(x,0) -> x
add(x,s(y)) -> s(add(x,y))
(rekursive) Reduktionsregeln
def s(x):
return x+1
def p(x):
return x-1
def add(x, y):
if y == 0:
return x
else:
return s(add(x, p(y)))
Implementierung in Python
Bearbeiten Sie die Aufgaben aus Abschnitt 9.4.4.
Slide 49
49
Teil 5
Rekursion und Berechnungsaufwand
Slide 50
50
Einstieg - Wege im Galton-Brett
Warum handelt es sich hier um ein ineffizientes Berechnungsverfahren?
>>> galton(5, 3)
galton( 5 , 3 )
galton( 4 , 2 )
galton( 3 , 1 )
galton( 2 , 0 )
galton( 2 , 1 )
galton( 1 , 0 )
galton( 1 , 1 )
galton( 3 , 2 )
galton( 2 , 1 )
galton( 1 , 0 )
galton( 1 , 1 )
galton( 2 , 2 )
galton( 4 , 3 )
galton( 3 , 2 )
galton( 2 , 1 )
galton( 1 , 0 )
galton( 1 , 1 )
galton( 2 , 2 )
galton( 3 , 3 )
10
def galton(m, n):
print "galton(", m, ",", n, ")"
if m < n:
return None
else:
if (n == 0) or (m == n):
return 1
else:
return (galton(m-1, n-1) + galton(m-1, n))
Slide 51
51
Ackermann-Funktion
"Die Ackermannfunktion ist eine 1926 von Wilhelm Ackermann gefundene, extrem schnell
wachsende mathematische Funktion, mit deren Hilfe in der theoretischen Informatik Grenzen
von Computer- und Berechnungsmodellen aufgezeigt werden können. Heute gibt es eine
ganze Reihe von Funktionen, die als Ackermannfunktion bezeichnet werden. Diese weisen
alle ein ähnliches Bildungsgesetz wie die ursprüngliche Ackermannfunktion auf und haben
auch ein ähnliches Wachstumsverhalten." (wikipedia)
ack(0, y) -> y+1
ack(x, 0) -> ack(x-1, 1), falls x > 0
ack(x, y) -> ack(x-1, ack(x, y-1)), falls y > 0
Slide 52
52
Auswertung der Ackermann-Funktion
Setzen Sie die Reduktionskette einige Schritte weiter fort. Wollen Sie es zu Ende rechnen?
ack(0, y) -> y+1
ack(x, 0) -> ack(x-1, 1), falls x > 0
ack(x, y) -> ack(x-1, ack(x, y-1)), falls y > 0
ack(3,
ack(2,
ack(2,
ack(2,
2) ->
ack(3, 1)) ->
ack(2, ack(3, 0))) ->
ack(2, ack(2, 1))) ->
Slide 53
53
Implementierung d. Ackermann-Funkt.
Implementieren Sie die Ackermann-Funktion in Python so, dass jeder Funktionsaufruf mit den
aktuellen Parameterwerten auf dem Bildschirm ausgegeben wird (vgl. galton). Testen Sie
verschiedene Funktionsaufrufe wie z. B. ack(2, 3) und ack(3, 2). Was fällt auf?
ack(0, y) -> y+1
ack(x, 0) -> ack(x-1, 1), falls x > 0
ack(x, y) -> ack(x-1, ack(x, y-1)), falls y > 0
Slide 54
54
Eigenschaften der Ackermann-Funktion
Informieren Sie sich (z. B. bei Wikipedia) über das Wachstumsverhalten der AckermannFunktion.
Quelle: http://de.wikipedia.org/wiki/Ackermannfunktion
Slide 55
55
Berechnungsaufwand
Warum stößt man bei der Berechnung der Ackermann-Funktion sehr schnell auf Grenzen sowohl hinsichtlich der Rechenzeit als auch hinsichtlich des Speicherbedarfs?
ack(4, 3) ->
ack(3, ack(4, 2)) ->
ack(3, ack(3, ack(4, 1))) ->
ack(3, ack(3, ack(3, ack(4, 0)))) ->
ack(3, ack(3, ack(3, ack(3, 1)))) ->
ack(3, ack(3, ack(3, ack(2, ack(3, 0))))) ->
ack(3, ack(3, ack(3, ack(2, ack(2, 1))))) ->
ack(3, ack(3, ack(3, ack(2, ack(1, ack(2, 0)))))) ->
ack(3, ack(3, ack(3, ack(2, ack(1, ack(1, 1)))))) ->
ack(3, ack(3, ack(3, ack(2, ack(1, ack(0, ack(1, 0))))))) ->
ack(3, ack(3, ack(3, ack(2, ack(1, ack(0, ack(0, 1))))))) ->
ack(3, ack(3, ack(3, ack(2, ack(1, ack(0, 2)))))) ->
ack(3, ack(3, ack(3, ack(2, ack(1, 3))))) ->
ack(3, ack(3, ack(3, ack(2, ack(0, ack(1, 2)))))) ->
ack(3, ack(3, ack(3, ack(2, ack(0, ack(0, ack(1, 1))))))) ->
ack(3, ack(3, ack(3, ack(2, ack(0, ack(0, ack(0, ack(1, 0)))))))) ->
ack(3, ack(3, ack(3, ack(2, ack(0, ack(0, ack(0, ack(0, 1)))))))) ->
ack(3, ack(3, ack(3, ack(2, ack(0, ack(0, ack(0, 2)))))) ->
ack(3, ack(3, ack(3, ack(2, ack(0, ack(0, 3))))) ->
ack(3, ack(3, ack(3, ack(2, ack(0, 4))))) ->
ack(3, ack(3, ack(3, ack(2, 5)))) ->
...
ack(3, ack(3, ack(3, 13))) ->
...
ack(3, ack(3, 65533)) ->
...
Slide 56
56
Teil 6
Rekursion und Iteration Umwandlung rekursiver Algorithmen in
iterative Algorithmen
Slide 57
57
Äquivalente Algorithmen
Zur rekursiv definierten Fakultätsfunktion lässt sich leicht ein äquivalenter iterativer
Berechnungsalgorithmus angeben:
def fak(n):
if n == 0:
return 1
else:
return n * fak(n-1)
Rekursion
def fak(n):
i=n
erg = 1
while not i == 0:
erg = erg * i
i=i-1
return erg
Iteration
Slide 58
58
Umwandlungsverfahren
Ein allgemeines Umwandlungsverfahren erhält man, indem man die Reduktionsschritte bei
der Auswertung von Termen simuliert. Dabei benutzt man zwei Stapel, um den aktuellen
Berechnungszustand darzustellen.
def ack(m, n):
if m == 0:
return n+1
else:
if n == 0:
return ack(m-1, 1)
else:
return ack(m-1, ack(m, n-1))
ack(2,
ack(1,
ack(1,
ack(1,
ack(1,
ack(1,
ack(1,
...
3) ->
ack(2,
ack(1,
ack(1,
ack(1,
ack(1,
ack(1,
2)) ->
ack(2,
ack(1,
ack(0,
ack(0,
ack(0,
0))) ->
1))) ->
ack(1, 0)))) ->
ack(0, 1)))) ->
2))) ->
Auswertung durch
Reduktionsschritte
Simulation mit Hilfe von
Stapeln
Slide 59
Umwandlungsverfahren
59
t
s
while t.size() > 0:
e = t.top()
t.pop()
if type(e) == int:
s.push(e)
else:
m = s.top()
s.pop()
n = s.top()
s.pop()
if m == 0:
t.push(n+1)
else:
if n == 0:
t.push("a")
t.push(m-1)
t.push(1)
else:
t.push("a")
t.push(m-1)
t.push("a")
t.push(m)
t.push(n-1)
def ack(m, n):
if m == 0:
return n+1
else:
if n == 0:
return ack(m-1, 1)
else:
return ack(m-1, ack(m, n-1))
Slide 60
Umwandlungsverfahren
60
t
s
while t.size() > 0:
e = t.top()
t.pop()
if type(e) == int:
s.push(e)
else:
m = s.top()
s.pop()
n = s.top()
s.pop()
if m == 0:
t.push(n+1)
else:
if n == 0:
t.push("a")
t.push(m-1)
t.push(1)
else:
t.push("a")
t.push(m-1)
t.push("a")
t.push(m)
t.push(n-1)
def auswerten(term):
t = Stapel()
t.setStapel(term)
s = Stapel()
while t.size() > 0:
# siehe links
return s.top()
def ack(m, n):
term = [n, m, "a"]
return auswerten(term)
äquivalenter iterativerAlgorithmus
def ack(m, n):
rekursiver
if m == 0:
Algorithmus
return n+1
else:
if n == 0:
return ack(m-1, 1)
else:
return ack(m-1, ack(m, n-1))
Slide 61
Umwandlungsverfahren
61
t
s
Beachte: Die iterative Ausführung rekursiver Algorithmen ist deshalb
von besonderer Bedeutung, weil rekursive Algorithmen (derzeit)
immer auf sequentiell arbeitenden Maschinen ausgeführt werden. Das
Umwandlungsverfahren zeigt exemplarisch, dass eine solche iterative
Ausführung immer möglich ist.
Slide 62
62
Übungen (siehe 9.6.1)
Testen Sie die Implementierung des Umwandlungsverfahrens (siehe 9.6.1).
Übertragen Sie das Umwandlungsverfahren auch auf die Funktion galton (siehe Aufgabe 2).
Slide 63
63
Teil 7
Rekursion und Iteration Umwandlung iterativer Algorithmen in
rekursive Algorithmen
Slide 64
64
Äquivalente Algorithmen
Zur iterativ definierten Potenzfunktion lässt sich leicht ein äquivalenter rekursiver
Berechnungsalgorithmus angeben:
def pot(a, n):
p=1
while n > 0:
p=p*a
n=n-1
return p
def pot(a, n):
if n == 0:
return 1
else:
return a * pot(a, n-1)
Iteration
Rekursion
Slide 65
65
Umwandlungsverfahren
Ein allgemeines Umwandlungsverfahren erhält man, indem man die Auswertungsschritte bei
der Abarbeitung des Algorithmus simuliert.
def pot(a, n):
p=1
while n > 0:
p=p*a
n=n-1
return p
Abarbeitung des
Algorithmus
{a -> 2; n ->
p=1
while n > 0:
p=p*a
n=n-1
{a -> 2; n ->
while n > 0:
p=p*a
n=n-1
{a -> 2; n ->
p=p*a
n=n-1
while n > 0:
p=p*a
n=n-1
{a -> 2; n ->
...
{a -> 2; n ->
3}
3; p -> 1}
3; p -> 1}
2; p -> 2}
0; p -> 8}
Slide 66
66
Umwandlungsverfahren
Das folgende Ablaufprotokoll zeigt, wie die Daten mit Hilfe von Listen, Tupeln etc. dargestellt
werden sollen.
[('a', 2), ('n', 3)]
[('=', 'p', 1), ('while', ('>', 'n', 0), [('=', 'p',
('*', 'p', 'a')), ('=', 'n', ('-', 'n', 1))])]
[('a', 2), ('n', 3), ('p', 1)]
[('while', ('>', 'n', 0), [('=', 'p', ('*', 'p', 'a')),
('=', 'n', ('-', 'n', 1))])]
[('a', 2), ('n', 3), ('p', 1)]
[('=', 'p', ('*', 'p', 'a')), ('=', 'n', ('-', 'n', 1)),
('while', ('>', 'n', 0), [('=', 'p', ('*', 'p', 'a')),
('=', 'n', ('-', 'n', 1))])]
[('a', 2), ('n', 3), ('p', 2)]
[('=', 'n', ('-', 'n', 1)), ('while', ('>', 'n', 0),
[('=', 'p', ('*', 'p', 'a')), ('=', 'n', ('-', 'n', 1))])]
...
[('a', 2), ('n', 0), ('p', 8)]
[]
{a -> 2; n ->
p=1
while n > 0:
p=p*a
n=n-1
{a -> 2; n ->
while n > 0:
p=p*a
n=n-1
{a -> 2; n ->
p=p*a
n=n-1
while n > 0:
p=p*a
n=n-1
{a -> 2; n ->
...
{a -> 2; n ->
3}
3; p -> 1}
3; p -> 1}
3; p -> 2}
0; p -> 8}
Slide 67
67
Umwandlungsverfahren
Zur Erzeugung des Ablaufprotokolls werden folgende Hilfsfunktionen benutzt:
>>> VariablenWert('y', [('x', 4), ('y', 3), ('z', 7)])
3
>>> VariablenWert('a', [('x', 4), ('y', 3), ('z', 7)])
'?'
>>> NeuerZustand('y', 6, [('x', 4), ('y', 3), ('z', 7)])
[('x', 4), ('y', 6), ('z', 7)]
>>> NeuerZustand('a', 0, [('x', 4), ('y', 3), ('z', 7)])
[('x', 4), ('y', 3), ('z', 7), ('a', 0)]
>>> TermWert(('+', 'z', 4), [('x', 4), ('y', 3), ('z', 7)])
11
>>> TermWert(('+', 'z', ('+', 'x', 'x')), [('x', 4), ('y', 3), ('z', 7)])
15
>>> BooleWert(('>', 'x', 4), [('x', 4), ('y', 3), ('z', 7)])
False
>>> BooleWert(('==', 'x', 4), [('x', 4), ('y', 3), ('z', 7)])
True
Slide 68
68
Umwandlungsverfahren
Zur Erzeugung des Ablaufprotokolls werden folgende Hilfsfunktionen benutzt:
>>> AnweisungenAusfuehren([('=', 'x', 2), ('if', ('>', 'x', 3), [('=', 'y', '0')], [('=', 'y', 1)])], [])
[('x', 2), ('y', 1)]
>>> AnweisungenAusfuehren([('while', ('>', 'u', 0), [('=', 'u', ('-', 'u', 1))])], [('u', 3)])
[('u', 0)]
>>> AnweisungenAusfuehren([('=', 'p', 1), ('while', ('>', 'n', 0), [('=', 'p', ('*', 'p', 'a')), ('=', 'n',
('-', 'n', 1))])], [('a', 2), ('n', 3)])
[('a', 2), ('n', 0), ('p', 8)]
Slide 69
69
Umwandlungsverfahren
def pot(a, n):
p=1
while n > 0:
p=p*a
n=n-1
return p
iterativer Algorithmus
def potenz(a, n):
return VariablenWert('p', \
AnweisungenAusfuehren(\
[\
('=', 'p', 1), \
('while', ('>', 'n', 0), \
[\
('=', 'p', ('*', 'p', 'a')), \
('=', 'n', ('-', 'n', 1))\
])\
], \
[('a', a), ('n', n)]))
äquivalenter rekursiver
Algorithmus
Slide 70
70
Übungen (siehe 9.6.2)
Testen Sie die Implementierung des Umwandlungsverfahrens (siehe 9.6.2).
Übertragen Sie das Umwandlungsverfahren auch auf eine andere Definition der
Potenzfunktion (siehe Aufgabe 1/3).