Es scheint , dass ich einen generellen Weg gefunden zu konvertieren jede rekursive Prozedur bis zum Schwanz-Rekursion:
- Definieren Sie eine Hilfsteilprozedur mit einem zusätzlichen "Ergebnis" -Parameter.
- Wenden Sie auf diesen Parameter an, was auf den Rückgabewert der Prozedur angewendet wird.
- Rufen Sie diese Hilfsprozedur auf, um loszulegen. Der Anfangswert für den Parameter "result" ist der Wert für den Austrittspunkt des rekursiven Prozesses, sodass der resultierende iterative Prozess dort beginnt, wo der rekursive Prozess zu schrumpfen beginnt.
Hier ist zum Beispiel die ursprüngliche rekursive Prozedur, die konvertiert werden soll ( SICP-Übung 1.17 ):
(define (fast-multiply a b)
(define (double num)
(* num 2))
(define (half num)
(/ num 2))
(cond ((= b 0) 0)
((even? b) (double (fast-multiply a (half b))))
(else (+ (fast-multiply a (- b 1)) a))))
Hier ist die konvertierte, endrekursive Prozedur ( SICP-Übung 1.18 ):
(define (fast-multiply a b)
(define (double n)
(* n 2))
(define (half n)
(/ n 2))
(define (multi-iter a b product)
(cond ((= b 0) product)
((even? b) (multi-iter a (half b) (double product)))
(else (multi-iter a (- b 1) (+ product a)))))
(multi-iter a b 0))
Kann jemand dies beweisen oder widerlegen?
algorithms
logic
recursion
lisp
nalzok
quelle
quelle
b
Eine Potenz von 2 zu wählen, zeigt, dass die anfängliche Einstellungproduct
auf 0 nicht ganz richtig ist. Aber wennb
es ungerade ist, funktioniert es nicht, es auf 1 zu setzen . Benötigen Sie 2 verschiedene Speicherparameter?Antworten:
Ihre Beschreibung Ihres Algorithmus ist zu vage, um sie an dieser Stelle zu bewerten. Aber hier sind einige Dinge zu beachten.
CPS
Tatsächlich gibt es eine Möglichkeit, jeden Code in ein Formular umzuwandeln , das nur Tail-Calls verwendet. Dies ist die CPS-Transformation. CPS ( Continuation-Passing Style ) ist eine Form, Code auszudrücken, indem jeder Funktion eine Fortsetzung übergeben wird. Eine Fortsetzung ist ein abstrakter Begriff, der "den Rest einer Berechnung" darstellt. In Code in CPS Form ausgedrückt, die auf natürliche Weise reify eine Fortsetzung ist als eine Funktion , die einen Wert annimmt. In CPS wird statt einer Funktion, die einen Wert zurückgibt, die Funktion angewendet, die die aktuelle Fortsetzung darstellt, um von der Funktion "zurückgegeben" zu werden.
Betrachten Sie beispielsweise die folgende Funktion:
Dies könnte in CPS wie folgt ausgedrückt werden:
Es ist hässlich und oft langsam, hat aber einige Vorteile:
TCO
Es scheint mir, dass der einzige Grund, sich mit Tail-Recursion (oder Tail-Calls im Allgemeinen) zu befassen, die Tail-Call-Optimierung (TCO) ist. Ich denke also, eine bessere Frage zu stellen ist: "Ist mein Transformationsertragcode für Tail-Calls optimierbar?".
Wenn wir noch einmal CPS betrachten, ist eine seiner Eigenschaften, dass der in CPS ausgedrückte Code nur aus Tail-Calls besteht. Da es sich bei allem um einen Tail-Call handelt, müssen wir keinen Rücksprungpunkt auf dem Stack speichern. Also alle Code in CPS Form muss Tail-Call - optimiert sein, nicht wahr?
Nicht ganz. Sie sehen, obwohl es den Anschein hat, als hätten wir den Stapel entfernt, haben wir lediglich die Art und Weise geändert, wie wir ihn darstellen. Der Stapel ist nun Teil des Verschlusses, der eine Fortsetzung darstellt. CPS optimiert also nicht auf magische Weise alle unsere Code-Tail-Calls.
Also, wenn CPS nicht alle TCO machen kann, gibt es eine Transformation speziell für die direkte Rekursion, die das kann? Nein, im Allgemeinen nicht. Einige Rekursionen sind linear, andere nicht. Nichtlineare (z. B. Baum-) Rekursionen müssen einfach irgendwo eine variable Zustandsgröße beibehalten.
quelle