8. Rekursion - Bertram Hafner

Werbung
Informatik 11
[email protected]
Seite 35
8. Rekursion
Nach einer großen Party muss das Geschirr gespült werden. Du läufst an der Küche vorbei und jemand
sagt zu dir: „Spüle das Geschirr!“. Du bist faul,
-
spülst ein einziges Teil und
sagst zu einem anderen: „Spüle das Geschirr!“
Wenn wir annehmen, dass derjenige genauso faul ist und die anderen auch, dann haben wir eine rekursive
Lösung für das Geschirr-Spülen gefunden. Diese Methode ist sehr angenehm (besonders für den letzten),
allerdings nicht unbedingt die effektivste. Manch schwieriges Problem lässt sich aber durch Rekursion so
einfach und verständlich lösen wie dieses Geschirr-Problem.
Der rekursive Ansatz kann so formuliert werden:
„Spüle das Geschirr!“
- Wenn das Spülbecken leer ist, ist nichts zu tun.
- Wenn es nicht leer ist, dann
- spüle ein einziges Teil und
- sage zu einem anderen: „Spüle das Geschirr!“
Prozedur Spüle das Geschirr
ist Spülbecken leer
wahr
falsch
Spüle ein Teil
Spüle das Geschirr
Eine Prozedur dieser Art, die einen Teil der Aufgabe löst und dann den Rest erledigt, indem sie sich
selbst aufruft, wird rekursive Prozedur genannt.
(das Spül-Beispiel nach Wolfram Burgard, http://ais.informatik.uni-freiburg.de/lehre/ws02/info1/material/recursion/10recurs.pdf)
Die Fakultät
In der Kombinatorik (Teil der Wahrscheinlichkeitsrechnung) braucht man häufig folgendes Produkt, das
man Fakultät nennt:
n! = n · (n-1) · (n-2) · . . . · 3 · 2 · 1
So berechnet man z.B. die Zahl der Möglichkeiten, wie man 6 Sprinter auf die 6 Bahnen verteilen kann:
Für die erste Bahn hat man 6 Personen zur Auswahl, für die zweite jeweils noch 5, für die dritte jeweils
4 usw. Insgesamt gibt es 6! = 6 · 5 · 4 · 3 · 2 · 1 = 720 mögliche Aufstellungen. n! wächst unheimlich
schnell, wenn man n größer macht. 20! hat bereits 19 Stellen, 30! hat 33 Stellen, und 100! ist wesentlich
mehr, als es Elementarteilchen im gesamten Weltall (≈ 1070) gibt. Wenn das kein Grund ist, so wie beim
Spülen nur einen Schritt zu berechnen und für den Rest einen Assistenten zu beauftragen!
Wir wollen 10! berechnen. Wir sind faul und sagen zum Assistenten „Berechne 9!“ Wenn er das Ergebnis
hat, multiplizieren wir es noch mit 10 und sind fertig. Der Assistent seinerseits durchschaut die List und
beauftragt einen zweiten mit „Berechne 8!“, um dessen Ergebnis mit 9 zu multiplizieren. Du siehst, wie
die Sache weiter läuft. Die Prozedur können wir folgendermaßen rekursiv angeben:
„Berechne Fakultät(n)“
- Wenn n = 1 ist, dann ist das Ergebnis 1.
- Wenn n > 1 ist, dann
- sage einem Assistenten „Berechne Fakultät(n-1)“
- das Ergebnis ist n mal das des Assistenten.
Prozedur Fakultät(n)
ist n=1
wahr
Ergebnis := 1
falsch
Ergebnis :=
Fakultät(n-1) � n
[email protected]
Informatik 11
Seite 36
Diese Prozedur ist eine gültige Rekursion, weil sie
-
einen Aufruf zu sich selbst enthält
das Problem bei jedem Aufruf verkleinert
überprüft, ob die Arbeit erledigt ist.
►
Schreibe dazu ein Delphi-Programm, das zur Berechnung von n! eine rekursive Funktion
enthält mit dem Kopf:
function Fakultaet(n:integer): integer;
In einem Edit soll die Zahl n eingegeben werden, ein zweites Edit soll das Ergebnis anzeigen. Bedenke beim Test deines Programms, dass Integer-Zahlen in Computer-Programmen nur mit 32 Bit gespeichert werden. Deshalb ist die größte Integer-Zahl 2 147 483 647.
Will man mit noch größeren ganzen Zahlen rechnen, dann muss man dem PC das „schrifliche“ Multiplizieren beibringen wie einem Grundschüler.
►
Entwickle eine Rekursion zur Berechnung der Potenz xn , wobei x und n nur natürliche
Zahlen sind. Wie bei der Fakultät soll diese Rekursion in einem Delphi-Programm angewandt werden.
►
Euklid (300 v. Chr.) hat ein Verfahren zur ggT-Berechnung zweier natürlicher Zahlen a und
b gefunden, das ohne Faktor-Zerlegung auskommt. Seine Überlegung: Jeder Teiler von a
und b ist auch Teiler von der Differenz a-b. Deswegen kann man rechnen:
ggT(48,30) = ggT(30,18) = ggT(18,12) = ggT(12,6) = ggT(6,6) = 6
Formuliere dafür einen rekursiven Algorithmus und setze ihn in ein Delphi-Programm
um.
Permutationen einer Menge
(Permutation = Vertauschung)
Bestimme alle Permutationen der Menge {A,B,C}. Das sind die Tupel
(ABC), (ACB), (BAC), (BCA), (CAB) und (CBA).
Es gibt 6 (= 3!) mögliche Permutationen der 3-Menge. In einer 4-Menge sind es 4! = 24 Permutationen.
Bei den 6 Sprintern sind es bereits 720 mögliche Anordnungen. Wie man sie zählt, wissen wir also schon,
aber wie kann man alle Permutationen anschreiben?
►
Bevor wir das Problem allgemein lösen: Mache dir die kleine Mühe, alle Permutationen
von {A,B,C,D} anzuschreiben. Überlege dir ein System.
Wir versuchen wieder einen rekursiven Ansatz, indem wir einen Assistenten einsetzen, der ein reduziertes
Problem zu lösen bekommt:
Eine Idee wäre: Ich halte der Reihe nach jeden Buchstaben von ’ABCD’ fest, sage jeweils zu einem Assistenten „permutiere den Rest“ und hänge dann die Ergebnisse des Assistenten an meinen Buchstaben
dran. Das ist leider nicht durchführbar, weil jetzt mein Assistent nicht ein Ergebnis liefert sondern viele,
die er in einem Array speichern müsste, bevor ich sie an den festgehaltenen Buchstaben dranhängen kann.
Ebenso jeder weitere Assistent.
Ich kann aber dem Assistenten mitteilen, welchen Buchstaben ich festgehalten habe, damit er jedes seiner
Ergebnisse gleich an meinen Buchstaben dranhängen kann, um das Endergebnis auszugeben. Der Assistent aber hält wieder der Reihe nach jeden Buchstaben fest, muss deshalb seinem Assistenten mitteilen,
was ich und er festgehalten haben, damit dieser seine Permutationen dranhängen kann ...
Das sieht dann so aus:
Informatik 11
[email protected]
Seite 37
Ich sage nacheinander zu meinem Assistenten:
Permutiere (’A’, ’BCD’)
(damit sich ergeben soll:
’A’+’BCD’
’A’+’BDC’
’A’+’CBD’ usw.)
Permutiere (’B’,’ACD’)
(damit sich ergeben soll:
’B’+’ACD’
’B’+’ADC’
’B’+’CAD’ usw.)
Permutiere (’C’,’ABD’)
(damit sich ergeben soll:
’C’+’ABD’
’C’+’ADB’
’C’+’BAD’ usw.)
Permutiere (’D’,’ABC’)
(damit sich ergeben soll:
’D’+’ABC’
’D’+’ACB’
’D’+’BAC’ usw.)
Die Länge des String ’ABCD’ bestimmt also, wie oft ich meinen Assistenten bemühe. Was macht dieser
bei meinem ersten Auftrag Permutiere (’A’, ’BCD’) ? Er gibt drei Aufträge an seinen Assistenten:
Permutiere (’AB’, ’CD’)
Permutiere (’AC’, ’BD’)
Permutiere (’AD’, ’BC’)
Er hält also nacheinander jeden der drei Buchstaben von ’BCD’ fest, hängt ihn an meinen Festteil dran
und gibt den neuen Festteil und den neuen Rest weiter. Der Festteil wächst bei jedem Aufruf an, der Rest
nimmt ab, bis er zuletzt ein Leerstring ist.
Wie du siehst, brauchen wir folgende Prozedur, die sich rekursiv aufruft:
procedure permutiere(festteil, rest: string);
Diese stellt fest, ob der Rest bereits der Leerstring ist. Wenn ja, wird der Festteil als ein Ergebnis ausgegeben. Wenn nein, nimmt sie nacheinander jeden Buchstaben von Rest, hängt ihn an Festteil dran, bestimmt
den neuen Rest und gibt den Auftrag rekursiv weiter.
►
Erstelle das Struktogramm des Algorithmus.
►
Realisiere deine Lösung in einem Delphi-Programm. Die Menge kannst du dir in einem
Edit-Objekt als String ’ABCD’ vorgeben. Mit den Funktionen length() und copy() kannst
du den String zerlegen, mit der Operation ’+’ kannst du den neuen Festteil wieder zusammensetzen (sh. S.11 oder die Delphi-Hilfe). Um alle Permutationen aufzulisten, eignet sich
am besten eine Listbox.
Beim allerersten Aufruf der Prozedur muss Festteil ein Leerstring sein.
Quicksort
Wenn du an diesen Beispielen Spass gefunden hast, dann kannst du dich jetzt mit Hilfe des Programms
’Sortieren Lernen’ an das Problem Quicksort wagen.
Herunterladen