2048 Stimmen

Was ist Tail-Rekursion?

Als ich anfing, Lisp zu lernen, stieß ich auf den Begriff tail-recursive . Was bedeutet das genau?

22 Stimmen

Vielleicht ist es spät, aber dies ist ein ziemlich guter Artikel über Schwanz rekursiv: programmerinterview.com/index.php/rekursion/schwanz-rekursion

8 Stimmen

Einer der großen Vorteile der Identifizierung einer tail-rekursiven Funktion ist, dass sie in eine iterative Form umgewandelt werden kann und somit der Algorithmus vom Methoden-Stack-Overhead befreit wird. Vielleicht möchten Sie die Antwort von @Kyle Cronin und einigen anderen unten lesen

0 Stimmen

Dieser Link von @yesudeep ist die beste und ausführlichste Beschreibung, die ich gefunden habe - lua.org/pil/6.3.html

86voto

Pat Punkte 35602

In der Jargon-Datei steht Folgendes über die Definition der Tail-Rekursion:

Schwanzrekursion /n./

Falls Sie es noch nicht satt haben, siehe Schwanzrekursion.

1 Stimmen

Interessanterweise ist der Eintrag in der Jargon-Datei tatsächlich ein gültiges Beispiel für eine Schwanzrekursion. Sobald Sie die Bedingung erfüllt haben, dass Sie die Tail-Rekursion satt haben, müssen Sie nicht mehr den Stapel der früheren Aufrufe der Definition der Tail-Rekursion durchgehen, um das Verständnis der Definition zu vervollständigen. Es steht Ihnen frei, die Definition direkt zu verlassen und mit Ihrem Leben weiterzumachen.

73voto

Kyle Cronin Punkte 74993

Anstatt es mit Worten zu erklären, hier ein Beispiel. Dies ist eine Scheme-Version der faktoriellen Funktion:

(define (factorial x)
  (if (= x 0) 1
      (* x (factorial (- x 1)))))

Hier ist eine Version von factorial, die tail-rekursiv ist:

(define factorial
  (letrec ((fact (lambda (x accum)
                   (if (= x 0) accum
                       (fact (- x 1) (* accum x))))))
    (lambda (x)
      (fact x 1))))

Sie werden in der ersten Version feststellen, dass der rekursive Aufruf von fact in den Multiplikationsausdruck eingespeist wird und daher der Zustand auf dem Stack gespeichert werden muss, wenn der rekursive Aufruf erfolgt. In der tail-rekursiven Version gibt es keinen anderen S-Ausdruck, der auf den Wert des rekursiven Aufrufs wartet, und da es keine weitere Arbeit zu tun gibt, muss der Zustand nicht auf dem Stack gespeichert werden. In der Regel verbrauchen tail-rekursive Funktionen von Scheme konstanten Stack-Speicherplatz.

6 Stimmen

+1 für die Erwähnung des wichtigsten Aspekts von Tail-Rekursionen, dass sie in eine iterative Form umgewandelt werden können und dadurch zu einer Form mit O(1) Speicherkomplexität werden.

2 Stimmen

@KGhatak nicht genau; die Antwort spricht korrekt von "konstantem Stapelplatz", nicht von Speicher im Allgemeinen. nicht um pingelig zu sein, nur um sicherzustellen, dass es kein Missverständnis gibt. z.B. tail-recursive list-tail-mutating list-reverse Prozedur wird in konstantem Stackspace ausgeführt, erzeugt und vergrößert aber eine Datenstruktur auf dem Heap. Ein Tree Traversal könnte einen simulierten Stack in einem zusätzlichen Argument verwenden. usw.

55voto

Peter Meyer Punkte 25181

Tail-Rekursion bedeutet, dass der rekursive Aufruf in der letzten logischen Anweisung des rekursiven Algorithmus zuletzt erfolgt.

Typischerweise haben Sie in der Rekursion eine Basisfall wodurch die rekursiven Aufrufe gestoppt werden und der Aufrufstapel geleert wird. Ein klassisches Beispiel, wenn auch eher in C als in Lisp, ist die Funktion factorial zur Veranschaulichung der Tail-Rekursion. Der rekursive Aufruf erfolgt nach die Überprüfung der Base-Case-Bedingung.

factorial(x, fac=1) {
  if (x == 1)
     return fac;
   else
     return factorial(x-1, x*fac);
}

Der erste Aufruf von factorial würde lauten factorial(n) donde fac=1 (Standardwert) und n ist die Zahl, für die die Fakultät berechnet werden soll.

0 Stimmen

Ich fand Ihre Erklärung am einfachsten zu verstehen, aber wenn es nach mir geht, dann ist die Tail-Rekursion nur für Funktionen mit einer Anweisung als Basisfall nützlich. Betrachten Sie eine Methode wie die folgende postimg.cc/5Yg3Cdjn . Hinweis: Die äußere else ist der Schritt, den man als "Basisfall" bezeichnen könnte, erstreckt sich aber über mehrere Zeilen. Verstehe ich Sie falsch oder ist meine Annahme richtig? Tail-Rekursion ist nur für Einzeiler gut?

3 Stimmen

@IWantAnswers - Nein, der Körper der Funktion kann beliebig groß sein. Alles, was für einen Tail-Call erforderlich ist, ist, dass der Zweig, in dem er sich befindet, die Funktion als allerletzte Aktion aufruft und das Ergebnis des Funktionsaufrufs zurückgibt. Die factorial Beispiel ist nur das klassische, einfache Beispiel, das ist alles.

0 Stimmen

Peter Meyer, muss die Laufzeitumgebung für Ihr Beispiel wirklich einen Aufrufstapel verwalten? @FlySwat

29voto

Chris Smith Punkte 17414

Das bedeutet, dass Sie den Befehlszeiger nicht mehr auf den Stack legen müssen, sondern einfach an den Anfang einer rekursiven Funktion springen und die Ausführung fortsetzen können. So können Funktionen unbegrenzt rekursiv ausgeführt werden, ohne dass der Stack überläuft.

Ich schrieb eine blog mit grafischen Beispielen, die zeigen, wie die Stack Frames aussehen.

24voto

Abhiroop Sarkar Punkte 2107

Der beste Weg für mich zu verstehen tail call recursion ist ein Spezialfall der Rekursion, bei dem die letzter Aufruf (oder der Tail Call) ist die Funktion selbst.

Vergleich der in Python bereitgestellten Beispiele:

def recsum(x):
 if x == 1:
  return x
 else:
  return x + recsum(x - 1)

^RECURSION

def tailrecsum(x, running_total=0):
  if x == 0:
    return running_total
  else:
    return tailrecsum(x - 1, running_total + x)

^SCHWANZ-REKURSION

Wie Sie in der allgemeinen rekursiven Version sehen können, lautet der letzte Aufruf im Codeblock x + recsum(x - 1) . Nach dem Aufruf der recsum Methode gibt es einen weiteren Vorgang, der x + .. .

In der Tail-Rekursiv-Version ist der letzte Aufruf (oder der Tail-Aufruf) im Codeblock jedoch tailrecsum(x - 1, running_total + x) was bedeutet, dass der letzte Aufruf an die Methode selbst erfolgt und danach keine Operation mehr.

Dieser Punkt ist wichtig, weil die Tail-Rekursion, wie hier zu sehen, den Speicher nicht wachsen lässt, denn wenn die zugrunde liegende VM sieht, dass eine Funktion sich selbst an einer Tail-Position aufruft (der letzte Ausdruck, der in einer Funktion ausgewertet wird), beseitigt sie den aktuellen Stack-Frame, was als Tail Call Optimization (TCO) bekannt ist.

EDIT

NB. Bitte bedenken Sie, dass das obige Beispiel in Python geschrieben ist, dessen Laufzeitumgebung TCO nicht unterstützt. Dies ist nur ein Beispiel, um den Punkt zu erklären. TCO wird in Sprachen wie Scheme, Haskell usw. unterstützt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X