V19 - Rekursive Funktionen (1)

Werbung
Algorithmen & Programmierung
Rekursive Funktionen (1)
Berechnung der Fakultät
Fakultät
Die Fakultät N! einer nichtnegativen ganzen Zahl N kann folgendermaßen definiert werden:
d.h. zur Berechnung werden alle Zahlen von 1 bis N miteinander multipliziert: 1·2·3·...·N
Fakultätsberechnung in C
int fak_iterativ(int N) {
int fk = 1; // = Fakultät von 0 und Fakultät von 1
for (int i = 2; i <= N; i++)
fk = fk * i;
return fk;
}
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
461
Berechnung der Fakultät
Alternative
Die Fakultät kann auch wie folgt definiert werden:
d.h. zur Bestimmung der Fakultät wird wiederum die Fakultät benötigt.
Rekursion
Der Begriff Rekursion bezeichnet die Definition einer Funktion durch sich selbst.
Konsequenz für rekursive Algorithmen
Damit ein rekursiver Algorithmus terminieren kann, muss es eine Abbruchbedingung
für die Rekursion geben und damit diese eintreten kann, müssen der bzw. die
Funktionsparameter bei jedem rekursiven Funktionsaufruf geändert werden.
Eine unendliche Wiederholung eines rekursiven Algorithmus ist praktisch unmöglich.
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
462
Abarbeitung rekursiver Funktionen
Funktionswert
Jeder (rekursive) Funktionsaufruf berechnet (irgendwann) einen individuellen
Funktionswert, der von den jeweils gültigen Parametern bzw. Variablenwerten zum
Aufrufzeitpunkt abhängt.
Rekursiver Abstieg
Zunächst bewirkt ein rekursiver Funktionsaufruf allerdings solange weitere rekursive
Funktionsaufrufe (mit jeweils neuen Parametern) bis die Abbruchbedingung erreicht
ist.
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
463
Abarbeitung rekursiver Funktionen
Rekursiver Aufstieg
Ein konkretes Funktionsergebnis wird zum ersten Mal bei Erreichen des
Abbruchkriteriums berechnet und zurückgegeben.
Mit Hilfe dieses Funktionswerts wird nun im Rahmen des vorletzten rekursiven
Funktionsaufrufs ein konkreter Funktionswert berechnet und an den drittletzten
Funktionsaufruf weitergereicht usw. …
Konsequenz
Für jeden Funktionsaufruf müssen alle Funktionsvariablen und Parameter
gespeichert werden!
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
464
Abarbeitung rekursiver Funktionen
Stack
Bei jedem Funktionsaufruf (egal ob rekursiv oder nicht) werden alle Variablen und Parameter
einer Funktion sowie die Rücksprungadresse und weitere Verwaltungsinformationen in einer
Datenstruktur namens Stack (Stapel) abgelegt, die durch das Laufzeitsystem verwaltet wird.
➔ Details in Vorlesung Datenstrukturen
Nach der vollständigen Durchführung einer Funktion wird der zu dieser Funktion
korrespondierende Teil des Stacks wieder gelöscht und ggf. nur das Funktionsergebnis auf
dem Stack abgelegt, um es einer aufrufenden Funktion zur Verfügung stellen zu können.
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
465
Abarbeitung rekursiver Funktionen
Rekursive Funktionen
Ruft sich eine Funktion selbst auf, werden für jeden Aufruf die entsprechenden
Funktionsparameter auf dem Stack abgelegt, der deshalb proportional zur
Rekursionstiefe wächst. (Deshalb sind unendliche Rekursionstiefen unmöglich)
Nach dem Erreichen der Abbruchbedingung werden die Berechnungen der
Funktionsrümpfe in der umgekehrten Reihenfolge ihres Aufrufs zu Ende
gebracht und die entsprechenden Teile des Stacks gelöscht, der dadurch
wieder schrumpft.
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
466
Abarbeitung rekursiver Funktionen
int f(int N) {
if (N == 0)
return 1;
return N * f(N - 1);
}
int main() {
int v = 0;
v = f(5);
printf("5! ist %d", v);
return 0;
}
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
467
4
3
2
1
Abstieg der Rekursion - Stack wächst
5
0
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
468
24
6
2
1
1
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
Aufstieg der Rekursion - Stack schrumpft
120
469
Formulierung rekursiver Algorithmen
Rekursion als Problemlösungsstrategie
Ein Algorithmus zur Lösung eines Problems muss so formuliert werden, dass die (mehrfache)
Selbstanwendung des Algorithmus auf seine Ergebnisse die Lösung des Problems liefern kann.
Dazu formuliert man in der Regel eine Menge elementarer Operationen, die in jedem
Rekursionsschritt mit genau einem Element der Problemdatenmenge ausgeführt werden können.
Anschließend wird die Problemdatenmenge verkleinert und der Algorithmus auf der verkleinerten
Problemdatenmenge wieder angewendet, bis die Lösung des Problems gefunden wurde.
Definition eines Abbruchkriteriums
Meist wird bei der Verkleinerung der Problemdatenmenge irgendwann ein besonderer Zustand
(Abbruchkriterium) erreicht, z.B. beim Erreichen einer leeren Problemdatenmenge.
Jeder Rekursionsschritt muss Bestandteile des Abbruchkriteriums modifizieren, so dass dieses
erreicht werden kann.
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
470
Formulierung rekursiver Algorithmen
Beispiel 1
Addition aller Elemente eines Feldes
Iterative Lösung
Die Summe aller Feldelemente lässt sich durch Iteration über alle Feldelemente bei gleichzeitiger
Addition des jeweils aktuellen Feldelements zu einer Summenvariablen bestimmen.
Rekursive Lösung
Die Summe aller Elemente eines Feldes lässt sich durch Addition des ersten Feldelements mit
der Summe des Restfeldes bestimmen.
Abbruchkriterium
• Das Restfeld enthält nur noch ein Element.
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
471
Formulierung rekursiver Algorithmen
Beispiel 2
Einlesen einer Zeichenfolge von der Tastatur und Ausgabe in umgekehrter Reihenfolge
Iterative Lösung
• Bereitstellen eines ausreichend großen Feldes
• Solange gelesenes Zeichen c ungleich '\n' lege c im Feld ab
• Iteriere vom letzten zum ersten Feldelement mit Ausgabe des jeweiligen Feldelements
Rekursive Lösung
• Lies Zeichen c ein und rufe Funktion wieder auf
• Gib c nach dem rekursiven Funktionaufruf aus
• Abbruchkriterium: c == '\n'
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
472
Formulierung rekursiver Algorithmen
Beispiel 2
Einlesen einer Zeichenfolge von der Tastatur und Ausgabe in umgekehrter Reihenfolge
Vergleich der Varianten
Offene Frage bei der iterativen Variante
• Wie stellen wir ein ausreichend großes Feld bereit?
Rekursion bietet eine implizite dynamische Speichermöglichkeit für nahezu beliebig viele Werte.
Felder stellen eine explizite Speichermöglichkeit mit im Vorhinein bekannter Elementanzahl dar.
Theoretisch bräuchten wir zur (iterativen) Lösung der Aufgabe ein dynamisches Feld, dessen
Elementanzahl in Abhängigkeit von der gelesenen Zeichenanzahl schrumpfen und wachsen kann ➔ Vorlesung Datenstrukturen
• Rekursion realisiert dynamische Größe
• Rekursiver Algorithmus ist zudem kürzer
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
473
Formulierung rekursiver Algorithmen
Beispiel 3
Ausgabe einer Dezimalzahl als Dualzahl
Iterative Lösung
• benötigt Feld zum Ablegen der Binärziffern, da die Berechnung „umgekehrt“ erfolgt
• Größe des benötigten Feldes lässt im Vorhinein bestimmen
• Algorithmus siehe Übung
Rekursive Lösung
• Rufe Funktion rekursiv mit halbiertem Argument auf
• Gib Rest der Division des Arguments durch 2 aus
• Abbruchkriterium: Argument ≤ 1
Dr. Frank Seifert
Vorlesung Algorithmen & Programmierung WS 2015/2016
474
Ende der Vorlesung
Herunterladen