![]() |
| | Themen-Optionen | Thema durchsuchen |
| | Nach oben #1 |
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
| Eine praktische Einführung in die Rekursion Inhalt
Vorwort Hallo. Hast du keinen Schimmer, was Rekursion ist? Oder hast du schonmal etwas darüber gehört und möchtest jetzt mal wissen was das eigentlich genau ist und wie man es anwendet? Dann bist du hier genau richtig. Denn mit diesem kleinen Tutorial versuche ich, dir die Welt der Rekursion ein wenig näher zu bringen. Erstens ist Rekursion nämlich gar nicht so schwer und zweitens kann sie einem manchmal das Leben sehr erleichtern. Es lohnt sich also auf jeden Fall hier rein zu schnuppern. Was lernst du hier? In 7 Kapiteln lernst du hier...
Wie benutze ich dieses Tutorial? In diesem Tutorial wird dir wahrscheinlich viel Unbekanntes begegnen. Solltest du einen Beispielcode nicht verstehen, gib nicht gleich auf. Meist habe ich bei etwas komplizierten Funktionen oder Konstrukten vorher eine entsprechende Info mit einem weiterführendem Link eingefügt. Es lohnt sich, diese Links im Zweifelsfall zu verfolgen. Die folgenden Symbole werden dir im Tutorial öfters begegnen:
Natürlich kannst du auch einfach nachfragen, wenn du mit deinem Problem nicht weiterkommst. Geändert von Ben (06.06.2007 um 23:58 Uhr). |
| | |
| | Nach oben #2 |
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
| 1. Was ist eigentlich Rekursion? Der Begriff Rekursion kommt von dem lateinischen Wort recurrere was soviel heißt wie zurückkommen, wiederkehren. Aber wie? Eine for-Schleife kehrt doch auch wieder, oder nicht? Das mag zwar stimmen, aber das besondere bei der Rekursion ist die Tatsache, dass ganze Funktionen, Methoden oder ähnliches wiederkehren. Und zwar in der entsprechenden Funktion oder Methode selbst. Funktion meine_funktion() ruft also in sich selbst meine_funktion() auf. Klingt komisch? Is aber so. Geändert von Ben (06.06.2007 um 23:59 Uhr). |
| | |
| | Nach oben #3 |
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
| 2. Wie sehen rekursive Funktionen aus? Eine sehr einfache rekursive Funktion ist beispielsweise diese hier: PHP-Code: Das ist zugegeben eine sehr sinnlose Funktion, denn sie macht ja nichts weiter, außer sich selbst aufzurufen. Doch diese ist nicht nur sinnlos, sondern auch problematisch. Denn wenn wir von einem PHP-Script aus diese Funktion aufrufen, wird das Script nie auf normalen Wege enden. Wir haben sozusagen eine Endlosschleife kreiert, denn ein Aufruf von meine_rekursive_funktion() ruft nur wieder diese Funktion auf, die dann wieder die Funktion aufruft, die dann wieder....... und so weiter und so fort. Wir sehen also: Ein anderes Beispiel: Eine Funktion die alle Zahlen von 0 bis zu einer übergebenen Zahl zusammenzählt. PHP-Code: Geändert von Jojo (15.03.2006 um 00:26 Uhr). |
| | |
| | Nach oben #4 | ||
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
| 3. Wie funktionieren nun rekursive Funktionen? Betrachten wir noch einmal unsere Summen-Funktion: PHP-Code: Nun. Zuerst wird überprüft, ob 3 ungleich 0 ist, was definitiv der Fall ist. Also wird eine Zahl zurückgegeben. Und was jetzt kommt ist wichtig! Die Zahl die zurückgegeben wird, muss zuerst noch zusammengesetzt werden. Zurückgegeben wird $zahl + summe($zahl - 1). $zahl ist unsere übergebene 3 und $zahl-1 ist dann 2. Also besteht der Rückgabewert aus 3 + summe(2). Also wird wiederum die Funktion summe() aufgerufen. Diesmal allerdings mit dem Parameter 2. Nun wird also wieder zuerst einmal überprüft, ob 2 ungleich 0 ist, was wieder der Fall ist. Also wird zurückgegeben 2 + summe(1), was den Parser wieder dazu anleitet, summe() mit dem Parameter 1 aufzurufen. 1 ist immernoch ungleich 0 und somit wird zurückgegeben 1 + summe(0). Und wieder ruft der Parser die Funktion summe() auf. Diesmal mit dem Argument 0. Und hier ist endlich unsere Abbruchbedingung erfüllt. 0 ist gleich 0 und somit wird lediglich 0 zurückgegeben, ohne dass ein weiterer Aufruf von summe() stattfindet. Und jetzt kommt der springende Punkt. Das heißt, summe(2) wird während summe(3) aufgerufen, summe(1) innerhalb von summe(2) undDas hat zur Folge, dass der Rückgabewert von summe(0) - also 0 - an summe(1) zurückgegeben wird,summe(0) in summe(1). der nun endlich seinen Rückgabewert 1 + summe(0) berechnen kann - 1 + 0 = 1. Und den kann er dann auch endlich an summe(2) geben, der dadurch 2 + summe(1) berechnen kann - 2 + 1 = 3. Und endlich, endlich bekommt der Funktionsaufruf der schon am längsten wartet, etwas zurückgeben zu können, nämlich summe(3), die heiß ersehnte Zahl summe(2), mit der er seinen Rückgabewert 3 + summe(2)bilden kann - 3 + 3 = 6. Und diese Zahl 6 bekomme wir nun zurück, wenn wir summe(3) aufrufen. ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------ Puhh.... Das war jetzt ganz schön schwer bis hier her. Erstmal durchatmen. Es war bis jetzt auch ganz schön theoretisch. Es geht aber auch anders. Schauen wir uns das ganze einmal etwas grafisch aufbereitet an. Dazu betrachtet wir einzeln die Rekursionsstufen der Funktion summe() mit dem Parameter 3. Was sind Rekursionsstufen, wirst du jetzt vielleicht fragen. Eine Rekursionsstufe ist der Grad der Rekursion, in der wir uns befinden. Sie sagt uns, wie tief wir in der Rekursion stecken. Die Rekursionsstufen fangen bei 1 an und erhöhen sich mit jedem rekursiven Aufruf der Funktion - ausgehend von der Stufe, in der der rekursive Aufruf statt findet. Beim Aufruf von summe(3) befinden wir uns noch in Stufe 1. Wenn dann summe(2) rekursiv aufgerufen wird, landen wir in Stufe 2, usw. Bis wir beim Aufruf von summe(0) in der höchsten Rekursionsstufe 4 landen. Hier geht es nicht tiefer. Nur zurück. Sollte beim Zurückrechnen auf Stufe 2 ein weiterer Aufruf von summe() fällig sein, ist die neue Stufe nicht 5, sondern - ausgehend von Stufe 2 - 3. Beim Aufruf von summe(3) ensteht also ein Bild mit folgenden Schritten, die von oben nach unten ausgeführt werden. Hier ist deutlich die Verschachtelung der einzelnen Stufen zu sehen Zitat:
Zitat:
So, noch da? Gut. Das war jetzt ein ganzer Haufen Information. Lehn dich jetzt erst einmal zurück und ordne deine Gedanken. Denn im nächsten Kapitel lernst du, wo du überall Rekursion einsetzen kannst und du lernst das Teile-und-Herrsche-Prinzip kennen. Geändert von Jojo (15.03.2006 um 00:26 Uhr). | ||
| | |
| | Nach oben #5 |
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
| 4. Wo setzt man rekursive Funktionen ein? Aber was bedeutet das? Das bedeutet, dass ein Problem, das wir rekursiv lösen möchten, die folgenden drei Bedingungen erfüllen muss:
Wenden wir uns einem anderen Beispiel zu, mit dem oft Informatikstudenten im 1. Semester an die Rekursion herangeführt werden: die Fibonacci-Reihe Die Fibonacci-Reihe ist die Folge der Zahlen, die sich aus den zwei vorherigen Zahlen zusammensetzen. Die 5. Fibonacci-Zahl z.B. wird aus der 4. und der 3. zusammengesetzt. Es gilt: fib(n) = fib(n-1) + fib(n-2) Zwei Fibonacci-Zahlen sind dazu schon vorher definiert: fib(0) = 0 und fib(1) = 1 Sonst könnte man nämlich gar nicht anfangen. Wenn wir die 2. Fibonacci-Zahl errechnen wollen, rechnen wir also 0 + 1 = 1. Die 3. wäre dann 1 + 1 = 2. Wenn wir so weitermachen, kommen wir auf die ersten 10 Fibonacci-Zahlen: 0.: 0 1.: 1 2.: 2 3.: 3 4.: 5 5.: 8 6.: 13 7.: 21 8.: 34 Unsere Problemstellung: Wir suchen eine Funktion, die uns das n-te Glied der Fibonacci-Folge liefert. Überprüfen wir nun mal, ob wir unser Problem rekursiv lösen können! Als Beispiel nehmen wir das 5. Glied. Um das 5. Glied zu bekommen benötige ich das 4. und das 3. Glied. Das deutet schon darauf hin, dass die erste unserer Bedingungen erfüllt ist. Denn fib(5) kann ich in zwei Teilprobleme unterteilen. Und diese Teilprobleme sind vom selben Typ wie unser Originalproblem: fib(4) + fib(3) Jedes dieser Teilprobleme kann ich wiederum in jeweils zwei Teilprobleme unterteilen. Und diese wieder und wieder bis das ganze dann so aussieht: ![]() Was ihr da seht ist ein sogenannter Rekursionsbaum. Und zwar einer für die 5. Fibonacci-Zahl. Dort sieht man schön, wie sich nach und nach die (Teil-)Probleme (Quadrate) aufteilen lassen, bis nur noch Probleme mit festgelegten Lösungen - fib(0) und fib(1) - übrig bleiben (Kreise). Und wir sehen am Rekursionsbaum auch schön, dass unsere 2. Bedingung erfüllt ist: Irgendwann müssen wir die Probleme nicht weiter unterteilen, da sie bereits eine nicht rekursive Lösung besitzen (das stellen die Kreise da). Unsere Abbruchbedingung ist also, dass die zu berechnende Fibonacci-Zahl die 0. oder die 1. ist. Denn für diese beiden Zahlen haben wir eine eindeutig definierte Lösung, nämlich 0 bzw. 1. Nun muss nur noch die dritte Bedingung erfüllt sein. Kann ich durch Lösen aller Teilprobleme das Originalproblem lösen? Am Rekursionsbaum ist das einfach zu sehen.
Nun aber endlich zum Programmieren! Geändert von Jann Hendrik (30.10.2007 um 15:36 Uhr). |
| | |
| | Nach oben #6 |
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
| 5. Wie konstruiere ich nun eine rekursive Funktion? Natürlich gibt es keinen universellen Plan, der für jede rekursive Funktion einen exakten Bauplan bereitstellt, aber ich kann dir ein paar Sachen zeigen, die mir persönlich die Konstruktion erleichtern.
Erstellen wir erst einmal unseren Funktionsbody: PHP-Code: PHP-Code: Nun aber zum rekursiven Part. Der ist recht simpel: PHP-Code: "Schön!", wirst du sagen. "Und was kann ich damit anfangen? Sowas benutze ich doch nie!". Um dir zu beweisen, dass man Rekursion auch gut in der Praxis anwenden kann, habe ich hier ein anderes Beispiel für dich, was du vielleicht auch in der Praxis anwenden kannst. Geändert von Jojo (15.03.2006 um 00:27 Uhr). |
| | |
| | Nach oben #7 |
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
| 6. Und wieso der ganze Umstand? Nun, eine berechtigte Frage, sicherlich. Das Schöne an der Rekursion aber ist, dass man dadurch viele Probleme einfach lösen kann, die bei einem iterativen Ansatz äußerst viel Planung beanspruchen würden, oder aber einen extrem aufwändigen Code zu Tage fördern würden. Eine rekursive Funktion zu basteln ist vielmals schlichtweg einfacher als eine iterative. Versuche einmal selbst eine iterative Lösung für das Problem der Fibonacci-Folge zu entwerfen. Sicherlich nicht unmöglich, aber doch schwieriger. Und länger, wenn man sie mit der rekursiven Funktion vergleicht, die ich mittels des ternären Operators auf eine Zeile im Body gekürzt habe: PHP-Code: Geändert von Ben (06.06.2007 um 23:59 Uhr). |
| | |
| | Nach oben #8 | ||
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
| 7. Die Nachteile der Rekursion Schönheit ist nicht alles. Geschwindigkeit zählt oft viel mehr. Und da liegt auch die große Schwäche der meisten Rekursionen gegenüber den Iterationen. Dadurch, dass beim rekursiven Aufruf einer Funktion jedesmal die gesamte Funktion neu gestartet werden muss, entsteht eine Menge zusätzlicher Ressourcenverbrauch. Wie schlimm sich das auswirkt, ist von Sprache zu Sprache unterschiedlich. Der Java-Compiler zum Beispiel versucht, Rekursionen intern zu Iterationen umzuformen, um damit die Anwendungen performanter zu machen. Um zu zeigen, wie sehr die Performance unter einer Rekursion leidet, brauchen wir keinen umständlichen Code. Wir gehen ein Stück zurück und greifen uns die Summenfunktion von vorhin. Mit diesem Code wollen wir die Performance "messen". Ich habe die rekursive Funktion übrigens (so wie oben) mit Hilfe des ternären Operators gekürzt. Bei der unteren Funktion lasse ich mit Hilfe einer for-Schleife alle Zahlen von 0 bis zu dieser Zahl zusammenzählen. PHP-Code: Wir (oder zumindest ich Zitat:
Dann erhöhen wir doch mal n auf 10000. Da sieht die Sache schon anders aus: Zitat:
Und jetzt stell dir mal den Code ein wenig komplizierter vor. Oder wie wärs, wenn du ihn mehrfach anwenden müsstest? Der Code würde immer langsamer und langsamer und laaaaaaangsaaaaa.... Naja, lassen wir das Du siehst: Eine gute Struktur des Algorithmus ist wichtig für die Performance. Die Struktur kann man zum Beispiel optimieren, indem man früh erkennt, wie viele Rekursionen überhaupt notwendig sind. So könnte man beispielsweise bei dem Fibonacci-Algorithmus testen, ob n-1 oder n-2 bereits 0 oder 1 sind und dann keinen rekursiven Aufruf starten. Du kannst ja mal überlegen, ob du selbst ein paar Optimierungen einbauen kannst. Geändert von Ben (07.06.2007 um 00:00 Uhr). | ||
| | |
| | Nach oben #9 |
| Johannes Schlichenmaier Registriert seit: 26.08.2005 Ort: Mannheim
Beiträge: 398
|
zu guter Letzt kommen wir nun zum Fazit Rekursionen sind sinnvoll! Gerade wenn es um komplizierte Probleme geht. Aber sie werden unperformant, wenn die Tiefe der Rekursion zunimmt. Durch Optimierung kann man viel verbessern, aber manchmal kann man kaum optimieren. Ich nutze Rekursionen immer dort, wo ich keine allzu vielen Rekursionsstufen zu erwarten habe, zum Beispiel beim rekursiven Erstellen von Ordnern. Ich hoffe, euch hat unser kleiner Ausflug in die Rekursion Spaß gemacht und ihr habt nebenbei etwas gelernt. Für Fragen und Anregungen stehe ich gerne im PHP-Forum zur Verfügung. Jojo Geändert von Jojo (12.03.2006 um 18:49 Uhr). |
| | |
![]() |
| Lesezeichen |
| Aktive Benutzer in diesem Thema: 1 (Registrierte Benutzer: 0, Gäste: 1) | |
| Themen-Optionen | Thema durchsuchen |
| |
Ähnliche Themen | ||||
| Thema | Autor | Forum | Antworten | Letzter Beitrag |
| Rekursion innerhalb einer Klasse... | code5 | PHP-Programmierung | 7 | 14.07.2006 10:26 |
| [Diskussion und Fragen] Einführung in die Spieltheorie | Ben | Literatur | 0 | 06.06.2006 12:25 |
| [PHP] OOP - eine Einführung | MrNiceGuy | Tutorials | 16 | 11.11.2005 00:05 |
| [PHP] Ein einfaches Template-System | MrNiceGuy | Tutorials | 0 | 09.10.2005 18:30 |