Ein reddit-Thread warf eine anscheinend interessante Frage auf:
Schwanzrekursive Funktionen können trivial in iterative Funktionen umgewandelt werden. Andere können mithilfe eines expliziten Stapels transformiert werden. Kann jede Rekursion in Iteration umgewandelt werden?
Das (Zähler?) Beispiel im Beitrag ist das Paar:
(define (num-ways x y)
(case ((= x 0) 1)
((= y 0) 1)
(num-ways2 x y) ))
(define (num-ways2 x y)
(+ (num-ways (- x 1) y)
(num-ways x (- y 1))
choose
x = (x + y)! / (X! Y!), Die keine Rekursion benötigt.Antworten:
Können Sie eine rekursive Funktion immer in eine iterative umwandeln? Ja, absolut, und die Church-Turing-These beweist es, wenn das Gedächtnis dient. In Laien ausgedrückt heißt es, dass das, was durch rekursive Funktionen berechenbar ist, durch ein iteratives Modell (wie die Turing-Maschine) berechenbar ist und umgekehrt. Die These sagt Ihnen nicht genau, wie Sie die Konvertierung durchführen sollen, aber sie besagt, dass dies definitiv möglich ist.
In vielen Fällen ist das Konvertieren einer rekursiven Funktion einfach. Knuth bietet verschiedene Techniken in "The Art of Computer Programming" an. Und oft kann eine rekursiv berechnete Sache durch einen völlig anderen Ansatz in weniger Zeit und Raum berechnet werden. Das klassische Beispiel hierfür sind Fibonacci-Zahlen oder Sequenzen davon. Sie haben dieses Problem sicherlich in Ihrem Studienplan getroffen.
Auf der anderen Seite dieser Medaille können wir uns sicherlich ein Programmiersystem vorstellen, das so fortschrittlich ist, dass es eine rekursive Definition einer Formel als Aufforderung behandelt, sich frühere Ergebnisse zu merken, und so den Geschwindigkeitsvorteil bietet, ohne dem Computer genau sagen zu müssen, zu welchen Schritten er vorgehen soll Folgen Sie der Berechnung einer Formel mit einer rekursiven Definition. Dijkstra hat sich mit ziemlicher Sicherheit ein solches System vorgestellt. Er hat lange versucht, die Implementierung von der Semantik einer Programmiersprache zu trennen. Andererseits sind seine nicht deterministischen und mehrverarbeitenden Programmiersprachen in einer Liga über dem praktizierenden professionellen Programmierer.
Letztendlich sind viele Funktionen in rekursiver Form einfach einfacher zu verstehen, zu lesen und zu schreiben. Sofern es keinen zwingenden Grund gibt, sollten Sie diese Funktionen wahrscheinlich nicht (manuell) in einen explizit iterativen Algorithmus konvertieren. Ihr Computer wird diesen Job korrekt ausführen.
Ich kann einen zwingenden Grund sehen. Angenommen, Sie haben ein Prototypsystem in einer Sprache auf höchstem Niveau wie [ Asbestunterwäsche anziehen ], Lisp, Haskell, OCaml, Perl oder Pascal. Angenommen, die Bedingungen sind so, dass Sie eine Implementierung in C oder Java benötigen. (Vielleicht ist es Politik.) Dann könnten Sie sicherlich einige Funktionen rekursiv schreiben lassen, die aber wörtlich übersetzt Ihr Laufzeitsystem explodieren lassen würden. Beispielsweise ist in Schema eine unendliche Schwanzrekursion möglich, aber dieselbe Redewendung verursacht ein Problem für vorhandene C-Umgebungen. Ein weiteres Beispiel ist die Verwendung von lexikalisch verschachtelten Funktionen und statischen Bereichen, die Pascal unterstützt, C jedoch nicht.
Unter diesen Umständen könnten Sie versuchen, den politischen Widerstand gegen die Originalsprache zu überwinden. Sie könnten feststellen, dass Sie Lisp schlecht umsetzen, wie im zehnten Gesetz von Greenspun (ironisch). Oder Sie finden einfach einen völlig anderen Lösungsansatz. Aber auf jeden Fall gibt es einen Weg.
quelle
Ja. Ein einfacher formaler Beweis besteht darin, zu zeigen, dass sowohl die µ-Rekursion als auch ein nicht rekursiver Kalkül wie GOTO vollständig sind. Da alle vollständigen Turing-Kalküle in ihrer Ausdruckskraft streng gleichwertig sind, können alle rekursiven Funktionen durch den nicht rekursiven Turing-vollständigen Kalkül implementiert werden.
Leider kann ich online keine gute, formale Definition von GOTO finden. Hier ist eine:
Ein GOTO-Programm ist eine Folge von Befehlen P, die auf einer Registermaschine ausgeführt werden, so dass P einer der folgenden ist:
HALT
, was die Ausführung anhältr = r + 1
Wor
ist ein Register?r = r – 1
Wor
ist ein Register?GOTO x
Wox
ist ein Etikett?IF r ≠ 0 GOTO x
Wor
ist ein Register undx
ist ein EtikettDie Konvertierung zwischen rekursiven und nicht rekursiven Funktionen ist jedoch nicht immer trivial (außer durch sinnlose manuelle Neuimplementierung des Aufrufstapels).
Weitere Informationen finden Sie in dieser Antwort .
quelle
Die Rekursion wird als Stapel oder ähnliche Konstrukte in den tatsächlichen Interpreten oder Compilern implementiert. Sie können also eine rekursive Funktion in ein iteratives Gegenstück konvertieren, da dies immer (wenn auch automatisch) erfolgt . Sie werden die Arbeit des Compilers nur ad-hoc und wahrscheinlich auf sehr hässliche und ineffiziente Weise duplizieren.
quelle
Grundsätzlich ja, im Wesentlichen müssen Sie am Ende Methodenaufrufe (die den Status implizit auf den Stapel übertragen) durch explizite Stapel-Pushs ersetzen, um sich daran zu erinnern, wo der 'vorherige Aufruf' angekommen war, und dann die 'aufgerufene Methode' ausführen. stattdessen.
Ich würde mir vorstellen, dass die Kombination einer Schleife, eines Stapels und einer Zustandsmaschine für alle Szenarien verwendet werden kann, indem die Methodenaufrufe im Grunde simuliert werden. Ob dies "besser" sein wird oder nicht (entweder schneller oder in gewissem Sinne effizienter), kann man im Allgemeinen nicht wirklich sagen.
quelle
Der Ausführungsfluss rekursiver Funktionen kann als Baum dargestellt werden.
Dieselbe Logik kann von einer Schleife ausgeführt werden, die eine Datenstruktur verwendet, um diesen Baum zu durchlaufen.
Die Tiefendurchquerung kann mithilfe eines Stapels erfolgen, die Breitendurchquerung kann mithilfe einer Warteschlange erfolgen.
Die Antwort lautet also: Ja. Warum: https://stackoverflow.com/a/531721/2128327 .
quelle
Ja, es ist immer möglich, eine nicht rekursive Version zu schreiben. Die triviale Lösung besteht darin, eine Stapeldatenstruktur zu verwenden und die rekursive Ausführung zu simulieren.
quelle
Ja, explizit einen Stapel verwenden (aber Rekursion ist meiner Meinung nach viel angenehmer zu lesen).
quelle
Im Prinzip ist es immer möglich, die Rekursion zu entfernen und durch Iteration in einer Sprache zu ersetzen, die sowohl für Datenstrukturen als auch für den Aufrufstapel einen unendlichen Zustand hat. Dies ist eine grundlegende Konsequenz der Church-Turing-These.
Bei einer tatsächlichen Programmiersprache ist die Antwort nicht so offensichtlich. Das Problem ist, dass es durchaus möglich ist, eine Sprache zu haben, in der die Menge des im Programm zuweisbaren Speichers begrenzt ist, die Menge des verwendbaren Aufrufstapels jedoch unbegrenzt ist (32-Bit-C, wobei die Adresse der Stapelvariablen angegeben ist) ist nicht zugänglich). In diesem Fall ist die Rekursion einfach deshalb leistungsfähiger, weil sie über mehr Speicher verfügt, den sie verwenden kann. Es ist nicht genügend explizit zuweisbarer Speicher vorhanden, um den Aufrufstapel zu emulieren. Eine ausführliche Diskussion hierzu finden Sie in dieser Diskussion .
quelle
Alle berechenbaren Funktionen können von Turing-Maschinen berechnet werden, und daher sind die rekursiven Systeme und Turing-Maschinen (iterative Systeme) äquivalent.
quelle
Manchmal ist das Ersetzen der Rekursion viel einfacher. Rekursion war in den 90er Jahren die Mode, die in CS gelehrt wurde, und so dachten viele durchschnittliche Entwickler aus dieser Zeit, wenn Sie etwas mit Rekursion lösten, war es eine bessere Lösung. Also würden sie Rekursion verwenden, anstatt sich rückwärts zu drehen, um die Reihenfolge umzukehren, oder dumme Dinge wie diese. Manchmal ist das Entfernen von Rekursion eine einfache Art von Übung, die "duh, das war offensichtlich".
Dies ist jetzt weniger problematisch, da sich die Mode in Richtung anderer Technologien verlagert hat.
quelle
Das Entfernen der Rekursion ist ein komplexes Problem und unter genau definierten Umständen möglich.
Die folgenden Fälle gehören zu den einfachen:
quelle
Abgesehen vom expliziten Stapel ist die Verwendung eines Trampolins ein weiteres Muster für die Umwandlung von Rekursion in Iteration.
Hier geben die Funktionen entweder das Endergebnis zurück oder schließen den Funktionsaufruf, den er sonst ausgeführt hätte. Dann ruft die Initiierungsfunktion (Trampolin) die zurückgegebenen Verschlüsse so lange auf, bis das Endergebnis erreicht ist.
Dieser Ansatz funktioniert für gegenseitig rekursive Funktionen, aber ich fürchte, er funktioniert nur für Tail-Calls.
http://en.wikipedia.org/wiki/Trampoline_(computers)
quelle
Ich würde ja sagen - ein Funktionsaufruf ist nichts anderes als eine Goto- und eine Stack-Operation (grob gesagt). Alles, was Sie tun müssen, ist, den Stapel zu imitieren, der beim Aufrufen von Funktionen erstellt wurde, und etwas Ähnliches wie goto zu tun (Sie können gotos mit Sprachen imitieren, die dieses Schlüsselwort nicht explizit haben).
quelle
Schauen Sie sich die folgenden Einträge auf Wikipedia an. Sie können sie als Ausgangspunkt verwenden, um eine vollständige Antwort auf Ihre Frage zu finden.
Folgt einem Absatz, der Ihnen einen Hinweis geben kann, wo Sie anfangen sollen:
Schauen Sie sich auch den letzten Absatz dieses Eintrags an .
quelle
Weitere Details: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
quelle
Tazzego, Rekursion bedeutet, dass sich eine Funktion selbst aufruft, ob Sie es mögen oder nicht. Wenn Leute darüber sprechen, ob Dinge ohne Rekursion gemacht werden können oder nicht, meinen sie dies und Sie können nicht "Nein, das ist nicht wahr, weil ich mit der Definition von Rekursion nicht einverstanden bin" als gültige Aussage sagen.
In diesem Sinne ist fast alles, was Sie sagen, Unsinn. Das einzige andere, was Sie sagen, das ist kein Unsinn, ist die Idee, dass Sie sich das Programmieren ohne Callstack nicht vorstellen können. Dies wurde jahrzehntelang getan, bis die Verwendung eines Callstacks populär wurde. In alten Versionen von FORTRAN fehlte ein Callstack, und sie funktionierten einwandfrei.
Übrigens gibt es Turing-vollständige Sprachen, die nur Rekursion (z. B. SML) als Mittel zum Schleifen implementieren. Es gibt auch Turing-vollständige Sprachen, die Iteration nur als Mittel zum Schleifen implementieren (z. B. FORTRAN IV). Die Church-Turing-These beweist, dass alles, was in rekursiven Sprachen möglich ist, in einer nicht rekursiven Sprache und umgekehrt durchgeführt werden kann, indem beide die Eigenschaft der Turing-Vollständigkeit haben.
quelle
Hier ist ein iterativer Algorithmus:
quelle