„Rekursion“ – Versionsunterschied

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
[ungesichtete Version][gesichtete Version]
Inhalt gelöscht Inhalt hinzugefügt
Keine Bearbeitungszusammenfassung
K →‎Programmierbeispiel: Fakultätsberechnung: Leerzeichen, da sonst (allerdings nur im interaktiven Modus) "IndentationError: unexpected indent"
Zeile 173: Zeile 173:
def fakultaet_iterativ(n):
def fakultaet_iterativ(n):
ergebnis = faktor = 1
ergebnis = faktor = 1

while faktor <= n:
while faktor <= n:
ergebnis *= faktor
ergebnis *= faktor
faktor += 1
faktor += 1

return ergebnis
return ergebnis
</syntaxhighlight>
</syntaxhighlight>

Version vom 23. April 2019, 07:43 Uhr

Ein Beispiel von Rekursion: Rückkopplung im VLC media player bei anzeigen des eigenen Bildschirms.

Als Rekursion (lateinisch recurrere ‚zurücklaufen‘) bezeichnet man den abstrakten Vorgang, dass Regeln auf ein Produkt, das sie hervorgebracht haben, von neuem angewandt werden. Hierdurch entstehen potenziell unendliche Schleifen. Regeln bzw. Regelsysteme heißen rekursiv, wenn sie die Eigenschaft haben, Rekursion im Prinzip zuzulassen.

Rekursion ist ein zentraler Begriff in Mathematik, Logik und Informatik und hat vielfältige Anwendungen darüber hinaus; diese reichen bis in die Kunst, wo das Phänomen auch als mise en abyme bezeichnet worden ist.

Rekursion ist auch eine Problemlösungsstrategie. Komplexe Sachverhalte können oft mit rekursiv formulierten Regeln sehr elegant erfasst werden. Das Grundprinzip ist dabei dann das Zurückführen einer allgemeinen Aufgabe auf eine einfachere Aufgabe derselben Klasse. Beispielsweise ist die rekursive Programmierung Bestandteil vieler Programmiersprachen. Prozeduren oder Funktionen können sich dabei selbst aufrufen. Rekursion und Iteration sind im Wesentlichen gleichmächtige Sprachmittel.

In Mathematik, Logik und Informatik erscheint Rekursion spezieller in der Form, dass eine Funktion in ihrer Definition selbst nochmals aufgerufen wird (rekursive Definition). Wenn man mehrere Funktionen durch wechselseitige Verwendung voneinander definiert, spricht man von wechselseitiger Rekursion. Nicht jede rekursive Definition ist eine Definition im eigentlichen Sinn, denn die zu definierende Funktion braucht nicht wohldefiniert zu sein. Jeder Aufruf der rekursiven Funktion muss sich durch Entfalten der rekursiven Definition in endlich vielen Schritten auflösen lassen. Ist dies nicht erfüllt, so spricht man von einem infiniten Regress (in der Informatik auch als Endlosschleife bezeichnet).

Anwendungsbereiche und Beispiele für Rekursion

Rekursive Grafiken

Pythagoras-Baum
„Sprießender“ Pythagoras-Baum

Unter anderem können auch Punktmengen rekursiv definiert werden (dies ergibt die sogenannten Fraktale). Deren graphische Darstellung liefert ästhetisch ansprechende, natürlich aussehende Gebilde. Ein Beispiel ist der Pythagoras-Baum. Der dazugehörige Algorithmus sieht folgendermaßen aus; der dritte Teil enthält die Rekursion:

  • Errichte über zwei gegebenen Punkten ein Quadrat.
  • Auf der Oberseite zeichne ein Dreieck mit vorgegebenen Winkeln bzw. Höhe.
  • Wende die beiden obigen Schritte jeweils erneut auf die beiden Schenkel des neuentstandenen Dreieckes an.

Der Algorithmus wird dann bis zu einer vorgegebenen Rekursionstiefe entfaltet. Bei Rekursionstiefe eins entsteht ein Dreieck mit je einem Quadrat über den drei Seiten. Das sieht wie die Illustration zum Satz des Pythagoras aus – daher der Name. Je höher die Rekursionstiefe, desto mehr ähnelt das Gebilde einem Baum.

Rekursion in der Grammatik

Die Grammatik natürlicher Sprachen wird in der Linguistik u. a. mit Hilfe von sogenannten Phrasenstrukturregeln beschrieben.[1] Nach Ansicht der meisten Linguisten zeigen dabei alle menschlichen Sprachen[2] die Eigenschaft, rekursiv aufgebaut zu sein (im Gegensatz zu Signalsystemen im Tierreich). Dies ergibt sich, weil in der Zerlegung einer grammatischen Einheit, die mit einer Kategorie etikettiert wird, dieselbe Kategorie erneut auftauchen kann. Ein Beispiel ist das Phänomen der Nebensätze, das hier mit folgender stark vereinfachter Produktionsregel beschrieben ist:

  1. S → NP VP (ein Satz besteht aus einer Nominalphrase (als Subjekt) und einer Verbalphrase)
  2. VP → V NP* (eine Verbalphrase besteht aus einem Verb und null bis vielen Nominalphrasen als Objekten des Verbs)
  3. VP → V S (eine Verbalphrase besteht aus einem Verb und einem Nebensatz als Objekt des Verbs)

Diese Grammatik lässt die Wahl, ob die Ausbuchstabierung von „VP“ mit Regel 2 oder 3 erfolgen soll. Für den Fall, dass die Schritte 1 und dann 3 aufgerufen werden, ergibt sich eine Rekursion: Als Produkt von Regel 3 erscheint das Symbol S, das wiederum den Start für Regel 1 darstellt.

Rekursive Verfahren in der Mathematik

Summenbildung

Die Funktion soll zu jeder Zahl die Summe der natürlichen Zahlen bis einschließlich berechnen. Sie ist folgendermaßen definiert:

.

Gleichwertig zu dieser Darstellung ist das Verfahren, eine rekursive Definition der Summenfunktion zu geben. Hierzu bestimmen wir zunächst den einfachen Fall, den Rekursionsanfang. Im Beispiel handelt es sich um den Funktionswert für .

Übrig bleibt der schwierige Fall, also hier der Funktionswert für beliebige . Den schwierigen Fall führen wir auf einen einfacheren Fall zurück, nämlich auf den Fall . Dieser einfachere Fall wird unser rekursiver Aufruf. Die entsprechende Vorschrift heißt Rekursionsschritt. Beispielsweise lässt sich die Summe der natürlichen Zahlen bis einschließlich berechnen, indem man die Summe der natürlichen Zahlen bis einschließlich berechnet und dazu die Zahl addiert:

Diese beiden Gleichungen lassen sich zu einer rekursiven Definition der Summenfunktion zusammenfassen:

Es handelt sich hierbei um eine lineare Rekursion, denn in jedem der beiden Fälle (Rekursionsanfang und Rekursionsschritt) gibt es höchstens einen sum-Aufruf. Es ist sogar eine primitive Rekursion. Bei der dargestellten Definition handelt es sich um keine Endrekursion, denn nach dem rekursiven Aufruf muss noch addiert werden.

Die Summe der Zahlen von bis berechnet sich dann wie folgt:

Die Aufrufkette dazu sieht so aus:

Es gibt auch eine Charakterisierung der Summenfunktion ohne Rekursion: die Gaußsche Summenformel besagt, dass

.

Die Fibonacci-Folge

Ein anderes klassisches Beispiel für eine rekursive Funktion ist die Fibonacci-Folge

Die Fibonacci-Funktion , die jedem die -te Fibonacci-Zahl zuordnet, hat die einfachen Fälle und und genügt der Rekursionsgleichung

für .

Es ergibt sich die rekursive Definition:

Diese rekursive Definition ist kaskadenförmig. Die dritte Fibonacci-Zahl wird folgendermaßen berechnet:

Die Berechnung für wird hier mehrfach durchgeführt. Das deutet an, dass es Potential für Optimierungen gibt. Auch für die Fibonacci-Funktion gibt es einen gleichwertigen geschlossenen Ausdruck.

Rekursive Definition einer Funktion und Untertypen von Rekursion

Mathematische Definition

(Hinweis vorab: Rekursionsverfahren und rekursive Definitionen sind nicht auf Funktionen natürlicher Zahlen beschränkt. Hier sei auf das verallgemeinerte Rekursionsschema verwiesen.)

Das Grundprinzip der rekursiven Definition einer Funktion f ist: Der Funktionswert f(n+1) einer Funktion f : N0N0 ergibt sich durch Verknüpfung bereits berechneter Werte f(n), f(n-1), … Falls die Funktionswerte von f für hinreichend viele Startargumente bekannt sind, kann jeder Funktionswert von f berechnet werden. Bei einer rekursiven Definition einer Funktion f ruft sich die Funktion so oft selbst auf, bis eine durch den Aufruf der Funktion veränderte Variable einen vorgegebenen Zielwert erreicht oder Grenzwert überschritten hat (Terminierung, Abbruchbedingung).

Die Definition von rekursiv festgelegten Funktionen ist eine grundsätzliche Vorgehensweise in der funktionalen Programmierung. Ausgehend von einigen gegebenen Funktionen (wie z. B. der Summenfunktion) werden neue Funktionen definiert. Mit diesen können weitere Funktionen definiert werden.

Ein Spezialfall der Rekursion ist die primitive Rekursion, die stets durch eine Iteration ersetzt werden kann. Bei einer solchen Rekursion enthält der Aufrufbaum keine Verzweigungen, das heißt, er ist eine Aufrufkette: das ist immer dann der Fall, wenn eine rekursive Funktion sich selbst jeweils nur einmal aufruft. Der Aufruf kann dabei am Anfang (Head Recursion, siehe Infiniter Regress) oder am Ende (Tail Recursion oder Endrekursion) der Funktion erfolgen. Umgekehrt kann jede Iteration durch eine primitive Rekursion ersetzt werden, ohne dass sich dabei die Komplexität des Algorithmus ändert.

Formen der Rekursion

Die häufigste Rekursionsform ist die lineare Rekursion, bei der in jedem Fall der rekursiven Definition höchstens ein rekursiver Aufruf vorkommen darf. Die Berechnung verläuft dann entlang einer Kette von Aufrufen.

Die primitive Rekursion ist ein Spezialfall der linearen Rekursion. Hier definiert man Funktionen auf den natürlichen Zahlen, wobei in jedem rekursiven Aufruf dessen erster Parameter um Eins ab- oder zunimmt. Jede primitiv-rekursive Definition kann unter Zuhilfenahme eines Stapels durch eine Schleife (Programmierung) (z. B. For-Schleife oder While-Schleife) ersetzt werden.

Die endständige oder repetitive Rekursion (Tail Recursion oder Endrekursion) bezeichnet den Spezialfall der linearen Rekursion, bei der jeder rekursive Aufruf die letzte Aktion des rekursiven Aufrufs ist. Endrekursionen lassen sich unmittelbar durch While-Schleifen ersetzen und umgekehrt.

Unter verschachtelter Rekursion versteht man eine Rekursion, bei welcher rekursive Aufrufe in Parameterausdrücken rekursiver Aufrufe vorkommen. Diese Rekursionsform gilt als außerordentlich schwer zu durchschauen.

Kaskadenförmige Rekursion bezeichnet den Fall, in dem mehrere rekursive Aufrufe nebeneinander stehen. Die rekursiven Aufrufe bilden dann einen Baum. Kaskadenförmige Rekursion gilt als elegant, kann aber ohne weitere Maßnahmen einen exponentiellen Berechnungsaufwand nach sich ziehen. Sie wird gerne als Ausgangspunkt für die Ableitung einer anderen effizienteren Formulierung gebraucht.

Die wechselseitige Rekursion bezeichnet die Definition mehrerer Funktionen durch wechselseitige Verwendung voneinander. Sie lässt sich auf die gewöhnliche Rekursion einer tupelwertigen Funktion zurückführen.

Weitere Anwendungen: Rekursion in der Programmierung

Allgemeines: Iterative und rekursive Implementierung

Im Fall von primitiv-rekursiven Funktionen steht es dem Programmierer frei, eine iterative oder eine rekursive Implementierung zu wählen. Dabei ist die rekursive Umsetzung meist eleganter, während die iterative Umsetzung effizienter ist (insbesondere weil der Stack weniger beansprucht wird und der Overhead für den wiederholten Funktionsaufruf fehlt); siehe auch das Programmierbeispiel unten.

Manche Programmiersprachen (zum Beispiel in der Funktionalen Programmierung) erlauben keine Iteration, sodass immer die rekursive Umsetzung gewählt werden muss. Solche Sprachen setzen zur Optimierung häufig primitive Rekursionen ein, die intern als Iterationen umgesetzt sind (einige Interpreter für LISP und Scheme verfahren so).

Es ist zu beachten, dass eine naive Implementierung bei manchen Funktionen (z. B. den Fibonacci-Zahlen) bedingt, dass Teillösungen mehrfach berechnet werden. Abhilfe schafft in diesem Beispiel die Memoisation, die auf der Wiederverwendung bereits berechneter Zwischenlösungen beruht. Die Rekursion ist ein wesentlicher Bestandteil einiger Entwurfsstrategien für effiziente Algorithmen, insbesondere der Teile-und-herrsche-Strategie (Divide and Conquer). Andere Ansätze (zum Beispiel sogenannte Greedy-Algorithmen) verlangen ein iteratives Vorgehen. Rekursion und primitiv-rekursive Funktionen spielen eine große Rolle in der theoretischen Informatik, insbesondere in der Komplexitätstheorie und Berechenbarkeitstheorie (siehe auch Lambda-Kalkül und Ackermannfunktion).

Im Compilerbau ist der rekursive Abstieg (Recursive Descent) eine Technik, bei der eine Sprache rekursiv geparst wird.

Programmierbeispiel: Methodik rekursiver Implementierung

Iterative Implementierung

Im folgenden Beispiel wird eine Zeichenkette von Offset 0 bis Offset n implementativ iterativ durchlaufen. Die Abbruchbedingung ist erfüllt, wenn der Iterator auf den Nullterminator der Zeichenkette stößt.

 char* strKette = "foobar",
     * pTemp    = strKette;

 while( *pTemp != 0x0 )
 {
   // z. B. Buchstabe für Buchstabe ausgeben
   putchar(*pTemp);
   ++pTemp;
 };

Rekursive Implementierung

Jedoch lässt sich die Iteration einer Zeichenkette auch implementativ rekursiv darstellen, auch wenn die Aufgabenstellung nicht implizit rechnerisch rekursiv ist.

 void fnTraverse( char* pString )
 {
   if( *pString == 0x0 )
     return;
   else
     // z. B. Buchstabe für Buchst. ausgeben
     putchar(*pString);
   fnTraverse( ++pString );
 }

Programmierbeispiel: Fakultätsberechnung

Das Beispiel zeigt eine beliebte und einfache Implementierung der Fakultätsberechnung mittels Python-Code. Der rekursiven Variante wird hier zur Verdeutlichung eine iterative Variante gegenübergestellt. Dabei ist die Zahl, deren Fakultät berechnet werden soll.

def fakultaet_rekursiv(n):
    return 1 if n <= 1 else n * fakultaet_rekursiv(n - 1)

Die Rekursion kommt nach dem else zum Ausdruck, wo die Funktion sich selbst mit einem um 1 verringerten Argument aufruft. Im nächsten Beispiel wird die Funktion nur einmal aufgerufen und arbeitet dann linear den gegebenen Algorithmus ab:

def fakultaet_iterativ(n):
    ergebnis = faktor = 1
    
    while faktor <= n:
        ergebnis *= faktor
        faktor += 1
    
    return ergebnis

Lösen von Rekursionen

Beim Lösen einer Rekursion sucht man zum einen den Laufzeitaufwand, zum anderen die explizite Form der Rekursion.

Der Aufwand kann als asymptotische Θ- bzw. Ο-Schranke mittels Mastertheorem bzw. Substitutionsmethode bestimmt werden. Auch das geschickte Raten mit anschließender Induktion bietet eine Möglichkeit, eine Oberschranke der Laufzeit zu ermitteln.

Die explizite Form (oder auch geschlossene Form genannt) der Rekursionsgleichung lässt sich beispielsweise durch die Erzeugende-Funktion finden. Eine zweite Möglichkeit bietet das Ableiten durch Differenzenbildung aufeinanderfolgender Funktionswerte der Rekurrenz.

Siehe auch

Wiktionary: Rekursion – Bedeutungserklärungen, Wortherkunft, Synonyme, Übersetzungen
Wiktionary: rekursiv – Bedeutungserklärungen, Wortherkunft, Synonyme, Übersetzungen

Einzelnachweise und Anmerkungen

  1. Siehe z. B. Andrew Carnie: Constituent Structure. Second edition. Oxford University Press, 2010. Zum Thema Rekursivität v. a. S. 84ff.
  2. Lediglich für die Sprache Pirahã ist die These vorgebracht worden, dass sie keine Rekursion in der Grammatik kennen würde, da es keine Nebensätze gebe. Diese Analyse ist umstritten, für Details siehe den verlinkten Artikel.