Ich bin neu auf dieser Seite und diese Frage ist sicherlich nicht auf Forschungsniveau - aber na ja. Ich habe einen kleinen Hintergrund in Software-Engineering und fast keinen in CSTheory, aber ich finde es attraktiv. Um es kurz zu machen, ich möchte eine detailliertere Antwort auf Folgendes, wenn diese Frage auf dieser Website akzeptabel ist.
Ich weiß also, dass jedes rekursive Programm ein iteratives Analogon hat, und ich verstehe die beliebte Erklärung, die dafür angeboten wird, indem ich etwas Ähnliches wie den "Systemstapel" behalte und Umgebungseinstellungen wie die Absenderadresse usw. drücke. Ich finde diese Art von Handwellen .
Da ich etwas konkreter bin, möchte ich (formal) sehen, wie man diese Aussage in Fällen beweist, in denen Sie eine Funktion haben, die die Kette . Außerdem was ist, wenn es einige bedingten Anweisungen , die einen führen könnten einen Anruf zu einem gewissen machen ? Das heißt, der potenzielle Funktionsaufrufgraph enthält einige stark verbundene Komponenten.F i F j
Ich würde gerne wissen, wie mit diesen Situationen umgegangen werden kann, indem wir einige rekursive zu iterativen Konverter sagen. Und ist die Handwellenbeschreibung, auf die ich mich zuvor bezogen habe, wirklich genug für dieses Problem? Ich meine, warum finde ich es dann in einigen Fällen einfach, die Rekursion zu entfernen? Insbesondere das Entfernen der Rekursion aus der Vorbestellungsdurchquerung eines Binärbaums ist wirklich einfach - es ist eine Standard-Interviewfrage, aber das Entfernen der Rekursion im Falle einer Nachbestellung war für mich immer ein Albtraum.
Was ich wirklich stelle, sind Fragen
(1) Gibt es wirklich einen formelleren (überzeugenden?) Beweis dafür, dass Rekursion in Iteration umgewandelt werden kann?
(2) Wenn diese Theorie wirklich da draußen ist, warum finde ich es dann beispielsweise einfacher , die Vorbestellung einfacher und die Nachbestellung so schwer zu iterieren? (außer meiner begrenzten Intelligenz)
quelle
Antworten:
Wenn ich das richtig verstehe, ist Ihnen klar, dass Sie Funktionen konvertieren, die keine anderen Funktionsaufrufe als sich selbst enthalten.
So nehmen wir eine "Call - Kette" haben . Wenn wir weiterhin davon aus, dass sind sich nicht rekursiv (weil wir sie bereits umgesetzt haben), können wir inline alle diese Anrufe in die Definition von , die auf diese Weise eine direkt rekursive Funktion wird können wir bereits beschäftigen.F→F1→⋯→Fn→F F1,…,Fn F
Dies schlägt fehl, wenn einige selbst eine rekursive Aufrufkette haben, in der auftritt, dh . In diesem Fall haben wir eine gegenseitige Rekursion, die einen weiteren Trick erfordert, um sie loszuwerden. Die Idee ist, beide Funktionen gleichzeitig zu berechnen. Zum Beispiel im trivialen Fall:Fj F Fj→⋯→F→⋯→Fj
mit
f'
undg'
nicht rekursiven Funktionen (oder zumindest unabhängig vonf
undg
) wirdDies erstreckt sich natürlich auf mehr beteiligte Funktionen und kompliziertere Funktionen.
quelle
f
undg
verschiedene Arten von Typen zu akzeptieren, ein allgemeinere Trick benötigt.f
undg
bis sie ein gemeinsames Eingabecodierungs- und Rekursionsschema haben (wir können nicht eines haben, das und das andere ).Ja, es gibt überzeugende Gründe zu der Annahme, dass Rekursion in Iteration umgewandelt werden kann. Dies tut jeder Compiler, wenn er Quellcode in die Maschinensprache übersetzt. Theoretisch sollten Sie den Vorschlägen von Dave Clarke folgen. Wenn Sie tatsächlichen Code sehen möchten, der Rekursion in nicht rekursiven Code konvertiert, sehen Sie sich
machine.ml
die MiniML-Sprache in meinem PL Zoo an (beachten Sie, dass dieloop
Funktion unten, die tatsächlich Code ausführt, schwanzrekursiv ist und dies auch kann trivial in eine tatsächliche Schleife umgewandelt werden).Eine Sache noch. MiniML unterstützt keine gegenseitig rekursiven Funktionen. Das ist aber kein Problem. Wenn Sie eine gegenseitige Rekursion zwischen Funktionen haben
Die Rekursion kann in Form einer einzelnen rekursiven Karte ausgedrückt werden
quelle
Vielleicht möchten Sie sich die SECD-Maschine ansehen . Eine funktionale Sprache (obwohl es sich um eine beliebige Sprache handeln kann) wird in eine Reihe von Anweisungen übersetzt, die beispielsweise das Setzen von Argumenten von Stapeln, das "Aufrufen" neuer Funktionen usw. verwalten, die alle von einer einfachen Schleife verwaltet werden.
Rekursive Aufrufe werden nie aufgerufen. Stattdessen werden die Anweisungen des Hauptteils der aufgerufenen Funktion zum Ausführen auf den Stapel gelegt.
Ein verwandter Ansatz ist die CEK-Maschine .
Beide gibt es schon lange, daher gibt es viel zu tun. Und natürlich gibt es Beweise dafür, dass sie funktionieren, und das Verfahren zum "Kompilieren" eines Programms in SECD-Anweisungen ist in der Größe des Programms linear (es muss nicht über das Programm nachdenken).
Der Punkt meiner Antwort ist, dass es ein automatisches Verfahren gibt, um das zu tun, was Sie wollen. Leider wird die Transformation nicht unbedingt in Bezug auf Dinge erfolgen, die für einen Programmierer sofort leicht zu interpretieren sind. Ich denke, der Schlüssel ist, dass Sie, wenn Sie ein Programm iterieren möchten, auf dem Stapel speichern müssen, was das Programm tun muss, wenn Sie von einem iterierten Funktionsaufruf zurückkehren (dies wird als Fortsetzung bezeichnet). Für einige Funktionen (wie z. B. rekursive Funktionen) ist die Fortsetzung trivial. Für andere ist die Fortsetzung möglicherweise sehr komplex, insbesondere wenn Sie sie selbst codieren müssen.
quelle
F : "Gibt es wirklich einen formaleren (überzeugenderen?) Beweis dafür, dass Rekursion in Iteration umgewandelt werden kann?"
A : Die Vollständigkeit einer Turingmaschine :-)
Abgesehen von Witzen entspricht das RASP-Maschinenmodell ( Turing Equivalent Random Access Stored Program) in etwa der Funktionsweise realer Mikroprozessoren, und sein Befehlssatz enthält nur einen bedingten Sprung (keine Rekursion). Die Möglichkeit, den Code dinamisch selbst zu ändern, erleichtert die Implementierung von Unterprogrammen und rekursiven Aufrufen.
Ich denke, dass Sie viele Artikel / Artikel über die " rekursive zu iterative Konvertierung " finden können (siehe Daves Antwort oder nur Google die Schlüsselwörter), aber vielleicht ist ein weniger bekannter (und praktischer ) Ansatz die neueste Forschung zur Hardware-Implementierung rekursiver Algorithmen ( Verwenden der VHDL-Sprache , die direkt in eine Hardware "kompiliert" wird). Siehe zum Beispiel V.Sklyarovs Artikel " FPGA-basierte Implementierung rekursiver Algorithmen " ( Der Artikel schlägt eine neuartige Methode zur Implementierung rekursiver Algorithmen in Hardware vor. ... Zwei praktische Anwendungen rekursiver Algorithmen im Bereich der Datensortierung und -komprimierung wurden untersucht im Detail .... ).
quelle
Wenn Sie mit Sprachen vertraut sind, die Lambdas unterstützen, besteht eine Möglichkeit darin, die CPS-Transformation zu untersuchen. Das Entfernen der Verwendung des Aufrufstapels (und insbesondere der Rekursion) ist genau das, was die CPS-Transformation bewirkt. Es wandelt ein Programm mit Prozeduraufrufen in ein Programm mit nur Endaufrufen um (Sie können sich diese als gotos vorstellen, was ein iteratives Konstrukt ist).
Die CPS-Umwandlung hängt eng mit der expliziten Aufbewahrung eines Aufrufstapels in einem herkömmlichen Array-basierten Stapel zusammen. Statt eines Arrays wird der Aufrufstapel jedoch mit verknüpften Abschlüssen dargestellt.
quelle
Meiner Meinung nach geht diese Frage auf die Ursprünge der Definitionen von Berechnungen zurück und wurde vor langer Zeit rigoros bewiesen, als gezeigt wurde, dass der kirchliche Lambda-Kalkül (der das Konzept der Rekursion in hohem Maße erfasst) Turing-Maschinen entspricht und enthalten ist in der noch verwendeten Terminologie "rekursive Sprachen / Funktionen". anscheinend ist eine spätere Schlüsselreferenz in dieser Richtung wie folgt
ein großer Teil der BKD hierzu finden Sie in dieser Wikipedia - Seite Kirche-Turing - These . Ich bin mir der genauen Einzelheiten nicht sicher, aber der Wikipedia-Artikel scheint darauf hinzudeuten, dass es Rosser (1939) war, der diese Äquivalenz zwischen Lambda-Kalkül und Turingmaschinen zum ersten Mal bewiesen hat. Vielleicht / Vermutlich hat sein Papier einen stapelartigen Mechanismus zum Konvertieren der (möglicherweise rekursiven) Lambda-Aufrufe in die TM-Konstruktion?
Rosser, JB (1939). "Eine informelle Darstellung von Beweisen des Satzes von Godel und des Satzes der Kirche". Das Journal of Symbolic Logic (Das Journal of Symbolic Logic, Band 4, Nr. 2) 4 (2): 53–60. doi: 10.2307 / 2269059. JSTOR 2269059.
Anmerkung natürlich für jedermann in den Prinzipien der modernen interessiert Lisp Sprache und die Variante Scheme haben absichtlich eine starke Ähnlichkeit mit dem Lambda - Kalkül. Das Studium des Interpreter-Codes für die Bewertung von Ausdrücken führt zu Ideen, die ursprünglich in Abhandlungen zur Vollständigkeit der Lambda-Berechnung enthalten waren.
quelle