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

11voto

Hier ist ein Common Lisp-Beispiel, das mit Hilfe von Tail-Rekursion Faktorisierungen durchführt. Aufgrund der stapellosen Natur könnte man wahnsinnig große faktorielle Berechnungen durchführen ...

(defun ! (n &optional (product 1))
    (if (zerop n) product
        (! (1- n) (* product n))))

Und dann könnten Sie zum Spaß versuchen (format nil "~R" (! 25))

10voto

coding_ninza Punkte 325

Eine Tail-Rekursion ist eine rekursive Funktion, bei der die Funktion sich selbst am Ende ("tail") der Funktion aufruft, in der nach der Rückkehr des rekursiven nach der Rückkehr des rekursiven Aufrufs durchgeführt wird. Viele Compiler optimieren, um einen rekursiven Aufruf in einen tail-rekursiven oder einen iterativen Aufruf zu verwandeln.

Betrachten Sie das Problem der Berechnung der Fakultät einer Zahl.

Ein direkter Ansatz wäre:

  factorial(n):

    if n==0 then 1

    else n*factorial(n-1)

Angenommen, Sie rufen factorial(4) auf. Der Rekursionsbaum würde lauten:

       factorial(4)
       /        \
      4      factorial(3)
     /             \
    3          factorial(2)
   /                  \
  2                factorial(1)
 /                       \
1                       factorial(0)
                            \
                             1    

Die maximale Rekursionstiefe ist im obigen Fall O(n).

Betrachten Sie jedoch das folgende Beispiel:

factAux(m,n):
if n==0  then m;
else     factAux(m*n,n-1);

factTail(n):
   return factAux(1,n);

Der Rekursionsbaum für factTail(4) wäre:

factTail(4)
   |
factAux(1,4)
   |
factAux(4,3)
   |
factAux(12,2)
   |
factAux(24,1)
   |
factAux(24,0)
   |
  24

Auch hier ist die maximale Rekursionstiefe O(n), aber keiner der Aufrufe fügt eine zusätzliche Variable auf dem Stack hinzu. Daher kann der Compiler auf einen Stack verzichten.

9voto

justyy Punkte 5603

Kurz gesagt, eine Tail-Rekursion hat den rekursiven Aufruf als zuletzt Anweisung in der Funktion, damit sie nicht auf den rekursiven Aufruf warten muss.

Es handelt sich also um eine Tail-Rekursion, d.h. N(x - 1, p * x) ist die letzte Anweisung in der Funktion, bei der der Compiler schlau genug ist, um herauszufinden, dass sie zu einer for-Schleife (Fakultät) optimiert werden kann. Der zweite Parameter p enthält den Wert des Zwischenprodukts.

function N(x, p) {
   return x == 1 ? p : N(x - 1, p * x);
}

Dies ist die nicht-rekursive Art, die obige faktorielle Funktion zu schreiben (obwohl einige C++-Compiler sie möglicherweise trotzdem optimieren können).

function N(x) {
   return x == 1 ? 1 : x * N(x - 1);
}

aber das ist es nicht:

function F(x) {
  if (x == 1) return 0;
  if (x == 2) return 1;
  return F(x - 1) + F(x - 2);
}

Ich habe einen langen Beitrag mit dem Titel " Verstehen der Tail-Rekursion - Visual Studio C++ - Baugruppenansicht "

enter image description here

1 Stimmen

Wie ist Ihre Funktion N tail-recursive ?

0 Stimmen

N(x-1) ist die letzte Anweisung in der Funktion, bei der der Compiler schlau ist und herausfindet, dass sie zu einer for-Schleife optimiert werden kann (Fakultät)

0 Stimmen

Meine Sorge ist, dass Ihre Funktion N genau die Funktion recsum aus der akzeptierten Antwort dieses Themas ist (außer, dass es eine Summe und kein Produkt ist), und dass recsum nicht tail-rekursiv sein soll?

8voto

Brad Gilbert Punkte 33120

Hier ist eine Perl 5 Version des tailrecsum Funktion, die bereits erwähnt wurde.

sub tail_rec_sum($;$){
  my( $x,$running_total ) = (@_,0);

  return $running_total unless $x;

  @_ = ($x-1,$running_total+$x);
  goto &tail_rec_sum; # throw away current stack frame
}

8voto

ayushgp Punkte 4365

Dies ist ein Auszug aus Struktur und Interpretation von Computerprogrammen über Schwanzrekursion.

Bei der Gegenüberstellung von Iteration und Rekursion müssen wir darauf achten, dass wir den Begriff eines rekursiven Prozesses nicht mit dem Begriff eines rekursiven Prozedur. Wenn wir ein Verfahren als rekursiv bezeichnen, beziehen wir uns auf die syntaktische Tatsache, dass die Prozedurdefinition (entweder direkt oder indirekt) auf die Prozedur selbst verweist. Wenn wir aber einen Prozess als einem Muster folgend beschreiben, das z. B. linear rekursiv ist rekursiv ist, sprechen wir darüber, wie sich der Prozess entwickelt, nicht über die Syntax, wie eine Prozedur geschrieben ist. Es mag beunruhigend erscheinen, dass wir eine rekursive Prozedur wie fact-iter als Erzeugung eines iterativen Prozess bezeichnen. Der Prozess ist jedoch tatsächlich iterativ: Sein Zustand wird vollständig durch seine drei Zustandsvariablen erfasst, und ein Interpreter muss nur drei Variablen im Auge behalten, um den Prozess den Prozess auszuführen.

Ein Grund dafür, dass die Unterscheidung zwischen Prozess und Verfahren verwirrend sein kann, ist, dass die meisten Implementierungen gängiger Sprachen (einschließlich Ada, Pascal und C) so konzipiert sind, dass die Interpretation jeder rekursiven Prozedur Prozedur eine Menge an Speicher verbraucht, die mit der Anzahl der Prozeduraufrufe wächst, auch wenn der beschriebene Prozess im Prinzip, iterativ ist. Infolgedessen können diese Sprachen iterative iterative Prozesse nur durch den Rückgriff auf spezielle "Schleifenkonstrukte" wie do, repeat, until, for und while. Die Umsetzung von Scheme weist diesen Fehler nicht auf. Sie führt einen iterativen Prozess im konstanten Raum aus, selbst wenn der iterative Prozess durch eine rekursive Prozedur beschrieben wird. Eine Implementierung mit dieser Eigenschaft wird tail-recursive genannt. Mit einem tail-recursive Implementierung kann die Iteration durch den gewöhnlichen Prozeduraufrufmechanismus ausgedrückt werden, so dass spezielle Iterations Konstrukte nur als syntaktischer Zucker nützlich sind.

1 Stimmen

Ich habe alle Antworten hier durchgelesen, und doch ist dies die klarste Erklärung, die den wirklich tiefen Kern dieses Konzepts berührt. Sie erklärt es auf eine so direkte Art und Weise, die alles so einfach und so klar erscheinen lässt. Verzeihen Sie bitte meine Unhöflichkeit. Irgendwie habe ich das Gefühl, dass die anderen Antworten den Nagel nicht auf den Kopf treffen. Ich denke, das ist der Grund, warum SICP so wichtig ist.

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