Abmeldungen ohne Angabe triftiger Gründe können bis zu einer Woche vor dem Prüfungstermin vorgenommen werden.
Bei Krankheit ist die Vorlage einer ärztlichen Bescheinigung mit Angabe der Krankheitssymptome bis spätestens eine Woche nach der Prüfung erforderlich.
Abmeldungen ohne Angabe triftiger Gründe können bis zu einer Woche vor dem Prüfungstermin vorgenommen werden.
Bei Krankheit ist die Vorlage einer ärztlichen Bescheinigung mit Angabe der Krankheitssymptome bis spätestens eine Woche nach der Prüfung erforderlich.
- Studiengänge ET (Diplom+Bachelor) und WING ( - Bachelor)
melden sich über PAUL für die Klausur an. Information:
<http://www.upb.de/studium/paul/hilfe-fuer-studierende/tutorials/prue-
fungsanmeldung/>.- Studiengänge WING (Diplom) und BK Lehramt (Bachelor)
melden Sie sich bitte
bis zum 12. Februar 2013per E-Mail
bei Herrn Padberg (Padberg@adt.upb.de) unter Angabe des NAMENS und der MATRIKELNUMMER.
- Zugelassene Hilfsmittel zur Klausur
1 DIN A4-Blatt beidseitig in eigener Handschrift beschrieben, das in eine Klarsichtfolie eingeheftet und mit Name und Matrikelnummer lesbar
versehen ist.
- Studiengänge ET (Diplom+Bachelor) und WING (Bachelor)
melden sich über PAUL für die Klausur an. Information:
<ht
>.
jeden Dienstag, 17:00-18:30 Uhr
• Beginn: 27. Nov. 2012
• Ort: Poolraum der E-Technik (P7.2.02.1)
• Inhalt: Wiederholung, Frage-Antwort
• In der vorlesungsfreien Zeit
– vom 24.12.2012 bis 06.01.2013 finden keine Tutorien statt,
– vom 04.02. bis 26.02.2013 führen wir die Tutorien bei Bedarf weiter.
13. Rekursion
Ziele dieser Einheit:
- Einführung des Begriffs Rekursivität für Algorithmen.
- Damit die Vorbereitung auf
-
rekursive Datenstrukturen und-
Verallgemeinerung „Abstrakte Datentypen“.- Halteproblem; Besonderheiten von/Vorsicht bei
rekursiven Strukturen, Terminierungskriterien.
Rekursive Algorithmen und ihre Realisierung in C
– Rekursive, d.h.
sich selbst aufrufendeAlgorithmen, ermöglichen eine elegante Implementierung von Rekursionsrelationen. Beispiel:
– In dieser Relation wird n! (sprich:
n-Fakultät)
durch
(n-1)!(also wieder eine Fakultät) definiert, die wiederum
durch
(n-2)!definiert wird, die wiederum
...
Der Vorgang endet, wenn
0!erreicht wird, was gleich eins ist.
– Rekursive Lösungen sind nützlich auch für Betriebssystem- konstruktion, Datenbankentwurfes, Warteschlangenverwaltung etc.
n! = n * (n-1)! mit 0! = 1 .
Ausweg/ Realisierung:
Die meisten höheren Programmiersprachen (auch C) lassen daher zu, dass ein Unterprogramm sich selbst aufruft.
Bedingung:
Die Rekursivität muss nach einer endlichen Anzahl von Aufrufen
(„Inkarnationen“) durch eine
Terminierungsbedingungbeendet
werden.
13.1 Realisierung der Rekursion
Beispiel:
int fakultaet(int x) {
if (x <= 0) return 1;
else return x * fakultaet(x-1);
}
Preisfrage: Was passiert bei einem Aufruf fakultaet(3)?
Durch jeden Aufruf des formalen Parameter x wird eigener Speicherplatz angelegt. Wir nummerieren daher die x´ens durch.
1. Erster (externer) Aufruf der fakultaet. x1=3 wird angelegt. Da x1>0, erfolgt der Aufruf fakultaet(2).
2. Zweiter Aufruf der fakultaet. x2=2 wird angelegt. Da x2>0, erfolgt der Aufruf fakultaet(1)
3. Dritter Aufruf der fakultaet. x3=1 wird angelegt. Da x3>0, erfolgt der Aufruf fakultaet(0).
4. Vierter Aufruf der fakultaet. x4=0, wird angelegt. Da x4<=0 wird 1 zurückgegeben. Die Funktion wird in der innersten Ebene beendet.
3. Das Ergebnis des 4. Aufrufs (=1) mit x3=1 multipliziert. Das Ergebnis ist 1. Die Funktion wird in der dritten Ebene beendet.
2. Das Ergebnis des 3. Aufrufs (=1) mit x2=2 multipliziert. Das Ergebnis ist 2.
Die Funktion wird in der zweiten Ebene beendet.
1. Das Ergebnis des 2. Aufrufs (=2) mit x1=3 multipliziert. Das Ergebnis ist 6. Die Funktion wird in der äußersten Ebene beendet.
13.2 „Keller“-Struktur (Stack/LIFO)
- Jeder rekursive Aufruf erzeugt eine neue Instanz des formalen Parameters
x, die unabhängig von den Instanzen der vorangegangenenAufrufe ist .
- Bei dem „innersten“ (d.h.
4ten) Aufruf existieren gleichzeitig vierverschiedene
x(x1 bis
x4), von denen jedoch nur auf x4zugegriffen werden kann.
- Diese Struktur ähnelt einem Keller, bei dem
der nächste Gegenstand zuoberst „gekellert“ werden kann (PUSH), bis der Keller voll ist und
jeweils nur der zuletzt hineingepackte Gegenstand herausgeholt
werden (POP) kann, bis der Keller leer ist.
Keller (Stack), LIFO-Prinzip („Last-In-First-Out“)
Rekursionstiefe
- gibt die Anzahl der noch nicht abgeschlossenen Aufrufe an.
- bestimmt den Zeit- und Speicheraufwand der Ausführung des Unterprogramms.
- Für das Beispiel „Fakultät“ ist die maximale Rekursionstiefe 4.
Objekt x4 Objekt x3 Objekt x2 Objekt x1
TOP (drüber: VOLL („OVERFLOW“))
BOTTOM (drunter: LEER („EMPTY“)
≈ ... ≈ Objekt xn
POP-Operation PUSH-Operation
- Spezieller Datentyp, zusammen mit den Operationen bildet einen „abstrakten Datentypen“.
blah# → #halb
Beispiel: Lies ein Wort, das mit einem „#“ abgeschlossen wird und drucke es gespiegelt aus.
void spieglein () {
char zeichen;
scanf(" %c", &zeichen);
if (zeichen != '#') spieglein ();
printf("%c", zeichen);
}
Funktionsweise dieses Beispiels anhand eines Beispielwortes.
h a l b
h a l b
# #
Einige Palindrome : - Otto, Anna, Ebbe, ...
- Siams Keksmais - Rolf Flor
- Annasusanna
- Otter-Billett-Astor-Negro-Mosse- Esso-Morgenrot-Sattel-Libretto
- Legnet‘s Stengel?
- Die Liebe ist Sieger, rege ist sie bei Leid.
- Ella rüffelte Detlef für alle.
- Erika feuert nur untreu Fakire.
- Madam, I‘m Adam.
- I‘m running‘ , Nurmi.
13.3 Arten der Rekursion
- Lineare Rekursion
if (Terminierungsbed. erfüllt>)
<basta>
else <tue etwas, dann Selbstaufruf>
- Binäre Rekursion
if (<Terminierungsbed. erfüllt>)
<basta>
else <tue etwas,dann Selbstaufruf Noch ein Selbstaufruf>
- Nicht-Lineare Rekursion
for(idx=1; idx<=n; idx++) {
if (<terminierungsbed. erfüllt>)
<basta>
else <tue etwas, dann Selbstaufruf, noch ein Selbstaufruf>
} ...
void dies()
{ ...
jenes();
} ...
void jenes() { ...
dies();
} ...
- Indirekte Rekursion
: durch gegenseitigen Aufruf von Unterprogrammenint euklid (int zhl1, int zhl2) {
if((zhl2 % zhl1) == 0) {
return zhl1;
}
else return euklid (zhl2 % zhl1, zhl1);
}
16.4 Klassische Beispiele
Lineare Rekursion
Beispiel: Euklidscher (ca. 360 v. Chr. bis ca. 280 v. Chr.) Algorithmus zur Berechnung des größten gemeinsamen Teilers zweier ganzer Zahlen zhl1, zhl2 mit zhl1 < zhl2:
- LEONARDO VON PISA (genannt FIBONACCI, 1180-1201) liebte mathematische Textaufgaben.
- In Liber abaci (das Buch des Abakus, erschienen 1202 ),
sperrt er gedanklich ein Kaninchenpaar in einen geschlossenen Raum und stellt folgende Aufgabe.
- Pro Monat wirft das Paar ein neues Kaninchenpaar, wobei es erst im dritten Monat gebiert, und jedes neu geworfene Kaninchenpaar macht es ebenso, wobei kein Kanin- chen stirbt.
- Wie viele Kaninchen, fragt Fibonacci, hat man nach einem Jahr, nach 2 Jahren, …?
Fibonacci stellt seine Reihe auf: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 und so weiter.
- Die Lösung nach einem Jahr: 144.
- Die Aufgabe und ihre Lösung klingt albern, ihre Wirkung für die Mathematik aber ist fulminant.
- Die Quotienten aufeinanderfolgender Zahlen der Reihe - also die dritte geteilt durch die zweite, die vierte geteilt durch die dritte, die fünfte geteilt durch die vierte etc. - nähern sich, ins Unendliche fortgesetzt, der Zahl 1,618033988749894848 ...
- Und das ist der Goldene Schnitt, der seit der Antike für Ästhetik und Harmonie steht.
- Also jenes perfekte Verhältnis bestimmter Maße oder Größen zueinander, das in der Kunst eine ebenso wichtige Rolle spielt wie in der Natur.
Binäre Rekursion
Das „Kaninchenproblem“
Wir wollen wissen, wie viele Nachkommen ein Kaninchenpaar in einem bestimmten Zeitraum bekommt, wenn folgendes gilt:
– Jedes Kaninchenpaar wird im Alter von zwei Monaten gebärfähig.
– Jedes Paar bringt (von da an) jeden Monat ein neues Paar zur Welt.
– Alle Kaninchen leben ewig.
f1 = 1, f2 =1, f3 = 2, f4 = 3, f5 = 5, f6 = 8, ...
fn = Anzahl der Kaninchenpaare im n-ten Monat fn+1 = Anzahl der Kaninchenpaare im (n+1)-ten Monat
f
n+2 = Anzahl der Paare im (n+1)-ten Monatf + f
Monat
Anfang (1. Monat) x
2. Monat o
3. Monat x o
4. Monat o x o
5. Monat x o o x o
6. Monat o x o x o o x o
.. .
x : nicht gebärfähig o : gebärfähig
fn : Anzahl der Kaninchenpaare, die im n-ten Monat leben.
Ein Briefträger steigt täglich eine lange Treppe nach folgendem Muster empor:
- Die erste Stufe betritt er in jedem Fall.
- Von da an nimmt er jeweils nur eine Stufe oder aber zwei Stufen auf einmal.
Auf wie viele verschiedene Arten kann der Briefträger die n-te Stufe erreichen?
fn = Anzahl der Möglichkeiten, mit denen die n-te Stufe erreicht werden kann.
f1 =1; f2=1; ...
f
n+2= Anzahl der Möglichkeiten, dass der Briefträger sich auf der (n+1)-ten Stufe befindet+
Anzahl der Möglichkeiten, dass der Briefträger sich auf der n-ten Stufe befindet
Fibonacci-Zahlen
Berechne die „Fibonacci-Zahlen“ nach der Formel:
fib(n) = fib(n-1) + fib(n-2) für n>1 fib(1) = 1; fib(0) = 0
int fib (int n) {
if (n <= 1) return n;
else return fib(n-1) + fib(n-2);
} ...
fib(20) führt zu 21981 Aufrufen!
13.5 Beispiele zu nicht-linearer Rekursion
„Türme von Hanoi“
(nach Edouard Lucas, 1883, und Rouse Ball, 1884; „Tempel Banares“, bzw. „Turm der Brahmanen“)
Aufgabenstellung
n Scheiben mit aufsteigendem Durchmesser, die aufeinander auf einer (Start-) Säule liegen, sind auf eine (Ziel-)Säule zu verlegen. Dabei kann eine weitere (Hilfs-)Säule als „Zwischenstation“ einzelner Scheiben benutzt werden.
Regeln
– Es darf immer nur eine Scheibe (die oberste) auf einmal transportiert werden.
Lösungsvorschlag „Türme von Hanoi“
- n = 0 : Hier ist nichts zu machen.
- n = 1 : Transportiere die Scheibe von Säule 1 (Startsäule) nach Säule 3 Zielsäule)
- n > 1 : Nimm an, das Problem sei für n-1 Scheiben gelöst. Dann führe folgende Schritte durch:
Verlege n-1 Scheiben (Scheibenturm der Höhe n-1) von Startsäule nach Hilfssäule.
Transportiere die verbleibende (unterste), eine Scheibe von Startsäule nach Zielsäule.
Verlege die n-1 Scheiben auf Hilfssäule nach Zielsäule.
#include <stdio.h>
void verlegeturm(int hoehe, int start, int ziel, int hilf) {
if (hoehe > 0) {
verlegeturm(hoehe-1,start,hilf,ziel); /* 1. Rek. */
printf("ausgangssaeule: %d zielsaeule %d \n", start, ziel);
verlegeturm(hoehe-1,hilf,ziel,start); /* 2. Rek. */
} }
void main() {
int hoehe;
scanf("%d", &hoehe);
if (hoehe > 0)
verlegeturm(hoehe,1,2,3);
Beispiel 1:
hoehe = 2,
start=1, ziel=2, hilf=3 ergibt :
AUSGANGSSÄULE : 1 ZIELSÄULE : 3 AUSGANGSSÄULE : 1 ZIELSÄULE : 2 AUSGANGSSÄULE : 3 ZIELSÄULE : 2
AUSGANGSSÄULE : 2 ZIELSÄULE : 3 AUSGANGSSÄULE : 1 ZIELSÄULE : 2 AUSGANGSSÄULE : 1 ZIELSÄULE : 3 AUSGANGSSÄULE : 1 ZIELSÄULE : 2 AUSGANGSSÄULE : 3 ZIELSÄULE : 1 AUSGANGSSÄULE : 3 ZIELSÄULE : 2 AUSGANGSSÄULE : 1 ZIELSÄULE : 2 Beispiel 2:
hoehe = 3,
start=1, ziel=2, hilf=3 ergibt :
2 1 2 3 hoehe
start ziel hilf
1 1 3 2
0 - - -
0 - - - 1
3 2 1
1: 1=>3
0 - - -
3: 3=>2
2: 1=>2
0 - - -
Rek.-Tiefe
Aufruf (Tiefe 0):Aufruf
AUSGANGSSÄULE : 1 ZIELSÄULE : 2 AUSGANGSSÄULE : 1 ZIELSÄULE : 3 AUSGANGSSÄULE : 2 ZIELSÄULE : 3 AUSGANGSSÄULE : 1 ZIELSÄULE : 3
AUSGANGSSÄULE : 1 ZIELSÄULE : 3 AUSGANGSSÄULE : 2 ZIELSÄULE : 1 AUSGANGSSÄULE : 2 ZIELSÄULE : 3 AUSGANGSSÄULE : 1 ZIELSÄULE : 2 AUSGANGSSÄULE : 3 ZIELSÄULE : 2 AUSGANGSSÄULE : 3 ZIELSÄULE : 1 AUSGANGSSÄULE : 2 ZIELSÄULE : 1 AUSGANGSSÄULE : 3 ZIELSÄULE : 2 AUSGANGSSÄULE : 1 ZIELSÄULE : 3 AUSGANGSSÄULE : 1 ZIELSÄULE : 2 AUSGANGSSÄULE : 3 ZIELSÄULE : 2 Beispiel 3:
hoehe = 4,
start=1, ziel=2, hilf=3 ergibt :
Rek.-Tiefe
Aufruf (Tiefe 0):
• Tiefe 1:
hoehe 3 2 1 0 0 1 0 0 2 1 0 0 1 0 0
start 1 1 1 / / 2 / / 3 3 / / 1 / /
ziel 2 3 2 / / 3 / / 2 1 / / 2 / /
hilf 3 2 3 / / 1 / / 1 2 / / 3 / /
1:1=>2 3:2=>3 5:3=>1 7:1=>2
2:1=>3
4:1=>2
6:3=>2
13.6 Rekursion oder Iteration?
Obwohl so elegant, sind die rekursiven Unterprogramme speicherplatz- und zeitaufwendig. Daher ist eine Auflösung in ein iteratives Unterprogramm wünschenswert. Die Umwandlung ist
– einfach, wenn der rekursive Aufruf am Ende der Prozedur erfolgt, oder wenn es sich um lineare oder binäre Rekursivitäten handelt.
– schwer, wenn es sich um nichtlineare oder indirekte Rekursivitäten handelt.
Rekursionsauflösung
– Indirekte Rekursivität vermeiden.
– Rekursive Unterprogramme bieten natürliche Lösungen für rekursive Datenstrukturen an, z.B. Baumstrukturen.
Beispiel: Umwandlung der fakultaet in iterative Form:
int fakul ( int zhl) {
int fakultaet = 1;
while (zhl > 0) {
fakultaet = zhl * fakultaet;
zhl = zhl-1;
}
return fakultaet;
}
Regeln zur Anwendung der rekursiven Unterprogramme