Problemlösen mit Rekursion
Klaus Becker
2009
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
3 Teil 1
Problemlösen durch Problemreduktion
4 Einstieg - Türme von HanoiEiner 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.
5 Einstieg - Aufgabe (siehe 9.1.1)
Ausgangszustand
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.
Zielzustand
Benutzen Sie Münzen unterschiedlicher Größe oder ein Simulationsprogramm.z. B.: http://www.mpg-trier.de/d7/prog/hanoi/hanoi.htm
6 Einstieg - Aufgabe (siehe 9.1.1)
Ausgangszustand
Überlegen Sie sich auch eine Strategie, mit der man Türme mit 6, 7, ... Scheiben umstapeln kann.
Zielzustand
7 Lösungsidee
transportiere einen 4-Scheiben-Turm von A über C nach B
transportiere eine Scheibe von A nach C
transportiere einen 4-Scheiben-Turm von B über A nach C
Ausgangszustand
Zielzustand
Zwischenzustand
Zwischenzustand
transportiere einen 5-Scheiben-Turm von A über B nach C
8 Verallgemeinerung
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
Ausgangszustand
Zielzustand
Zwischenzustand
Zwischenzustand
transportiere einen n-Scheiben-Turm von X über Y nach Z
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
Algorithmus
10 Rekursive Problemreduktion
Rekursive Problemreduktion ist eine Problemlösestrategie, bei der ein Problem auf ein strukturgleiches Problem (in verkleinerter Form) zurückgeführt wird.
Ein rekursiver Algorithmus ruft sich (eventuell über Umwege) selbst auf und nutzt sich so selbst zur Beschreibung der Lösung des gegebenen Problems.
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
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.
11
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
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 eine Scheibe von A nach C transportiere einen 2-Scheiben-Turm von B über A nach C
Ausführungstiefe: 1
12
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
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 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
13
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
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
Ausführungstiefe: 3
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
15 Implementierung in Python
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: 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
Algorithmus
Python-Programm
16 Übungen (siehe 9.1.4)Bearbeiten Sie die Aufgaben 1, 2.
17 Teil 2
Selbstähnliche Figuren
18 Einstieg - Selbstähnliche Figur
Eine Figur ist selbstähnlich, wenn sie sich in Teile zerlegen lässt, die zur ihr ähnlich sind.
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)
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)
rekursive Problemreduktion
rekursiver Algorithmus
20 Exkurs - Turtle-Grafik
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-Algorithmus
Turtle-Befehle
vorwaerts(100)
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
Turtle-Klasse
# -*- coding: iso-8859-1 -*-from turtle import *# Deklaration einer Zeichenprozedurdef quadrat(laenge): for i in range(4): t.forward(laenge) t.left(90)# Erzeugung eines Turtle-Objektst = Turtle()# Test der Zeichenprozedurquadrat(100)
t.forward(100)
22 Exkurs - Turtle-Grafik in Python
# -*- coding: iso-8859-1 -*-from turtle import *# Deklaration einer Zeichenprozedurdef 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-Objektst = Turtle()# Test der Zeichenprozedurt.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)
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.
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.
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.
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.
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.
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.
29 Teil 3
Rekursive Verarbeitung von Listen
30 Einstieg - Geschachtelte ListenEine Gästeliste soll mit einem Programm verwaltet und verarbeitet werden.
def ausgabe(liste): i = 0 while i < len(liste): element = liste[i] print element i = i + 1
def ausgabe(liste): if len(liste) == 0: pass else: erstesElement = liste[0] restListe = liste[1:] print erstesElement ausgabe(restListe)
gaeste = ["Ursula", "Winfried", "Ulrike", "Klaus", ...]
# Testausgabe(gaeste)
rekursiv
iterativ
31 Einstieg - Geschachtelte ListenEine Gästeliste soll mit einem Programm verwaltet und verarbeitet werden.
?
gaeste = \ [ \ ["Ursula", "Winfried"], \ ["Ulrike", "Klaus"], \ ["Christiane", "Tim"], \ ["Andreas"], \ ["Ulrike", "Peter", ["Kea", "Lena", "Paula"]], \ ... ]
# Testausgabe(gaeste)
iterativ
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)
rekursiv
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" ausgabe([]) pass ausgabe([["Christiane", "Tim"], ...])
...
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)rekursiver
Algorithmus
Ausführung
33
Eine Liste ist entweder eine leere Liste, oder besteht aus einem ersten Element und einer (Rest-)Liste.
Liste als rekursive Datenstruktur
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)
def ausgabe(liste): if len(liste) == 0: # liste == [] pass else: # liste == [erstesElement] + restListe erstesElement = liste[0] restListe = liste[1:] print erstesElement ausgabe(restListe)
34 Entwicklung rekursiver AlgorithmenProblem: Es soll gezählt werden, wie oft ein Element in einer Liste vorkommt.
anzahl('b', []) -> 0
Reduktionsanfang: Löse das Problem direkt
Rekursive Problemreduktion: Reduziere des Problems auf ein entsprechendes, aber „verkleinertes“ Problem.
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'])
Rekursionsschritt: Löse ein entsprechendes
Problem
Fall 2: Bearbeite eine nicht-leere Liste
Fall 1: Bearbeite eine leere Liste
35 Entwicklung rekursiver AlgorithmenProblem: 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'])
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) Reduktionsschritte
(rekursive) Reduktionsregeln
Rekursive Problemreduktion: Reduziere des Problems auf ein entsprechendes, aber „verkleinertes“ Problem.
36 Entwicklung rekursiver AlgorithmenProblem: 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
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:])
Reduktionskette (rekursive) Reduktionsregeln
>>> anzahl('b', ['a', 'b', 'd', 'a', 'b'])2
37 Übungen (siehe 9.2.3)Rekursionsgymnastik: Bearbeiten Sie die Aufgaben des Abschnitts 9.2.3.
38 Fallstudie - geometrische ObjekteIn 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.
39 Teil 4
Rekursive Verarbeitung natürlicher Zahlen
40 Einstieg - Wege im Galton-BrettEin 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.
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.
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(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.
galton(2, 0)
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: return 0 else: if n == 0: return 1 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))
def 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))
44
Rekursive Struktur natürlicher Zahlen
Problem: Die Summe der ersten n natürlichen Zahlen soll berechnet werden.
summe(0) -> 0
Reduktionsanfang: Löse das Problem direkt
Rekursive Problemreduktion: Reduziere des Problems auf ein entsprechendes, aber „verkleinertes“ Problem.
summe(5) -> 5 + summe(4)
Rekursionsschritt: Löse ein entsprechendes
Problem
Fall 2: Bearbeite den Nachfolger einer natürlichen Zahl
Fall 1: Bearbeite die Zahl 0.
Eine natürliche Zahl ist entweder eine Null oder Nachfolger einer natürlichen Zahl.
45 Entwicklung rekursiver Algorithmen
summe(0) -> 0
summe(5) -> 5 + summe(4)
def summe(zahl): if zahl == 0: return 0 else: return zahl + summe(zahl-1)
(rekursive) Reduktionsschritte
(rekursive) Reduktionsregeln
Rekursive Problemreduktion: Reduziere des Problems auf ein entsprechendes, aber „verkleinertes“ Problem.
Problem: Die Summe der ersten n natürlichen Zahlen soll berechnet werden.
46 Entwicklung rekursiver Algorithmen
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
(rekursive) Reduktionsregeln
>>> summe(5)15
Problem: Die Summe der ersten n natürlichen Zahlen soll berechnet werden.
def summe(zahl): if zahl == 0: return 0 else: return zahl + summe(zahl-1)
47 Übungen (siehe 9.4.3)Rekursionsgymnastik: Bearbeiten Sie die Aufgaben des Abschnitts 9.4.3.
48 Fallstudie - natürliche ZahlenOperationen 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.
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)))
add(x,0) -> xadd(x,s(y)) -> s(add(x,y))
(rekursive) Reduktionsregeln
Implementierung in Python
Bearbeiten Sie die Aufgaben aus Abschnitt 9.4.4.
49 Teil 5
Rekursion und Berechnungsaufwand
50 Einstieg - Wege im Galton-BrettWarum handelt es sich hier um ein ineffizientes Berechnungsverfahren?
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))
>>> 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
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 > 0ack(x, y) -> ack(x-1, ack(x, y-1)), falls y > 0
52
Auswertung der Ackermann-Funktion
Setzen Sie die Reduktionskette einige Schritte weiter fort. Wollen Sie es zu Ende rechnen?
ack(3, 2) -> ack(2, ack(3, 1)) ->ack(2, ack(2, ack(3, 0))) ->ack(2, ack(2, ack(2, 1))) ->
ack(0, y) -> y+1 ack(x, 0) -> ack(x-1, 1), falls x > 0ack(x, y) -> ack(x-1, ack(x, y-1)), falls y > 0
53
Implementierung d. Ackermann-Funkt.
ack(0, y) -> y+1 ack(x, 0) -> ack(x-1, 1), falls x > 0ack(x, y) -> ack(x-1, ack(x, y-1)), falls y > 0
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?
54
Eigenschaften der Ackermann-Funktion
Informieren Sie sich (z. B. bei Wikipedia) über das Wachstumsverhalten der Ackermann-Funktion.
Quelle: http://de.wikipedia.org/wiki/Ackermannfunktion
55 BerechnungsaufwandWarum 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)) ->...
56 Teil 6
Rekursion und Iteration - Umwandlung rekursiver Algorithmen in iterative Algorithmen
57 Äquivalente AlgorithmenZur 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)
def fak(n): i = n erg = 1 while not i == 0: erg = erg * i i = i - 1 return erg
Rekursion Iteration
58 UmwandlungsverfahrenEin 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, 3) -> ack(1, ack(2, 2)) ->ack(1, ack(1, ack(2, 0))) ->ack(1, ack(1, ack(1, 1))) ->ack(1, ack(1, ack(0, ack(1, 0)))) ->ack(1, ack(1, ack(0, ack(0, 1)))) ->ack(1, ack(1, ack(0, 2))) ->...
Auswertung durch Reduktionsschritte
Simulation mit Hilfe von Stapeln
59 Umwandlungsverfahren
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)
t s
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))
60 Umwandlungsverfahren
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)
t s
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))
def ack(m, n): term = [n, m, "a"] return auswerten(term)
def auswerten(term): t = Stapel() t.setStapel(term) s = Stapel() while t.size() > 0: # siehe links return s.top()
rekursiver Algorithmus
äquivalenter iterativerAlgorithmus
61 Umwandlungsverfahrent 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.
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).
63 Teil 7
Rekursion und Iteration - Umwandlung iterativer Algorithmen in rekursive Algorithmen
64 Äquivalente AlgorithmenZur 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
65 UmwandlungsverfahrenEin 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
{a -> 2; n -> 3}p = 1while 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 * an = n - 1while n > 0: p = p * a n = n - 1{a -> 2; n -> 2; p -> 2}...{a -> 2; n -> 0; p -> 8}
Abarbeitung des Algorithmus
66 UmwandlungsverfahrenDas folgende Ablaufprotokoll zeigt, wie die Daten mit Hilfe von Listen, Tupeln etc. dargestellt werden sollen.
{a -> 2; n -> 3}p = 1while 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 * an = n - 1while n > 0: p = p * a n = n - 1{a -> 2; n -> 3; p -> 2}...{a -> 2; n -> 0; p -> 8}
[('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)][]
67 UmwandlungsverfahrenZur 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
68 UmwandlungsverfahrenZur 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)]
69 Umwandlungsverfahren
def potenz(a, n): return VariablenWert('p', \ AnweisungenAusfuehren(\ [\ ('=', 'p', 1), \ ('while', ('>', 'n', 0), \ [\ ('=', 'p', ('*', 'p', 'a')), \ ('=', 'n', ('-', 'n', 1))\ ])\ ], \ [('a', a), ('n', n)]))
def pot(a, n): p = 1 while n > 0: p = p * a n = n - 1 return p
iterativer Algorithmus
äquivalenter rekursiver Algorithmus
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).
Top Related