Iteration kann Rekursion ersetzen?


42

Ich habe da im ganzen Stack - Überlauf, zB hier , hier , hier , hier , hier und einige andere , die ich interessiere mich nicht zu erwähnen, dass „jedes Programm , das Verwendung Rekursion in ein Programm umgewandelt werden kann unter Verwendung von Iteration nur“.

Es gab sogar einen hochrangigen Thread mit einer hochrangigen Antwort , die sagte, dass es möglich ist.

Jetzt sage ich nicht, dass sie falsch liegen. Es ist nur so, dass diese Antwort meinem dürftigen Wissen und Verständnis über Computer widerspricht.

Ich glaube, dass jede iterative Funktion als Rekursion ausgedrückt werden kann, und Wikipedia hat eine entsprechende Aussage. Ich bezweifle jedoch, dass das Gegenteil der Fall ist. Zum einen bezweifle ich, dass nicht-primitive rekursive Funktionen iterativ ausgedrückt werden können.

Ich bezweifle auch, dass Hyperoperationen iterativ ausgedrückt werden können.

In seiner Antwort (die ich übrigens nicht verstehe) auf meine Frage sagte @YuvalFIlmus, dass es nicht möglich ist, eine Folge von mathematischen Operationen in eine Folge von Additionen umzuwandeln.

Wenn YFs Antwort in der Tat richtig ist (ich denke, es ist, aber seine Argumentation war über meinem Kopf), bedeutet dies dann nicht, dass nicht jede Rekursion in Iteration umgewandelt werden kann? Denn wenn es möglich wäre, jede Rekursion in eine Iteration umzuwandeln, wäre ich in der Lage, alle Operationen als Folge von Hinzufügungen auszudrücken.

Meine Frage lautet:

Kann jede Rekursion in eine Iteration umgewandelt werden und warum?

Bitte geben Sie eine Antwort, die ein kluger Highschooler oder ein Erstklässler verstehen wird. Danke.

PS Ich weiß nicht, was primitiv rekursiv ist (Ich weiß über die Ackermann-Funktion Bescheid und weiß, dass sie nicht primitiv rekursiv ist, aber dennoch berechenbar ist. Mein Wissen dazu stammt von der Wikipedia-Seite über die Ackermann-Funktion.)

PPS: Wenn die Antwort ja ist, könnten Sie zum Beispiel eine iterative Version einer nicht-primitiv-rekursiven Funktion schreiben. ZB Ackermann in der Antwort. Es wird mir helfen zu verstehen.


21
Alles, was Sie auf einer CPU ausführen, ist iterativ. Sie können es rekursiv in einer Sprache höherer Ordnung schreiben, aber es wird trotzdem zu einer Reihe iterativer Assembler-Anweisungen kompiliert. Im wahrsten Sinne des Wortes macht der Compiler genau das, wonach Sie fragen. Er wandelt all Ihre ausgefallenen Rekursionen in eine Reihe von iterativen Anweisungen für eine CPU um.
Davor

6
Auf einer niedrigen Ebene wissen die meisten von uns, dass Rekursion Iteration plus Stapel entspricht. Kontextfreie Grammatiken modellieren die Rekursion, während Pushdown-Automaten iterative "Programme" mit Stapelspeicher sind.
Hendrik Jan

2
Ich möchte nur darauf hinweisen, dass es im Allgemeinen eine gute Idee ist, mindestens 24 Stunden zu warten, um zu sehen, ob andere Antworten auftauchen. Die akzeptierte Frage erscheint mir offen gesagt ziemlich lang und verworren und scheint die Angelegenheit absichtlich mehr als notwendig zu komplizieren. Die grundlegende Antwort auf Ihre Frage lautet: "Der für die Rekursion verwendete Stack kann explizit iterativ implementiert werden", und es muss nicht viel komplizierter sein. Überlegungen, ob der Speicher unbegrenzt ist oder nicht, spielen keine Rolle, da diese Funktion sowieso auch für Rekursionsstapel erforderlich ist.
AnoE

Es ist möglich, den Turing-Maschinen-Emulator nur mit Iteration zu implementieren
Sarge Borsch

In einem vergleichenden Sprachkurs, den ich vor langer Zeit besucht habe, mussten wir Ackermanns Funktion in Assembler, in FORTRAN (nicht im modernen Fortran) und in einer rekursiven Sprache nach Wahl schreiben. Also ja, das ist in der Praxis möglich. Und natürlich ist es theoretisch möglich. Siehe zum Beispiel die Frage , ob Turingmaschinen und die Lambda-Rechnung gleichwertig sind .
David Hammen

Antworten:


52

Es ist möglich, die Rekursion durch Iteration und unbegrenzten Speicher zu ersetzen .

Wenn Sie nur eine Iteration (etwa while-Schleifen) und eine begrenzte Menge an Speicher haben, ist alles, was Sie haben, ein endlicher Automat. Mit einer endlichen Menge an Speicher hat die Berechnung eine endliche Anzahl möglicher Schritte, so dass es möglich ist, sie alle mit einem endlichen Automaten zu simulieren.

Unbegrenztes Gedächtnis zu haben, verändert den Deal. Dieses unbegrenzte Gedächtnis kann viele Formen annehmen, die sich als äquivalente Ausdruckskraft herausstellen. Eine Turing-Maschine zum Beispiel macht es einfach: Es gibt ein einzelnes Band, und der Computer kann sich auf dem Band nur schrittweise vorwärts oder rückwärts bewegen - aber das reicht aus, um alles zu tun, was Sie mit rekursiven Funktionen tun können.

Eine Turing-Maschine kann als idealisiertes Modell eines Computers (Finite-State-Maschine) mit zusätzlichem Speicher angesehen werden, der bei Bedarf wächst. Beachten Sie, dass es nicht nur wichtig ist, dass das Band nicht begrenzt ist, sondern dass Sie auch bei der Eingabe nicht zuverlässig vorhersagen können, wie viel Band benötigt wird. Wenn Sie vorhersagen (dh berechnen) könnten, wie viel Band von der Eingabe benötigt wird, dann könnten Sie entscheiden, ob die Berechnung anhalten würde, indem Sie die maximale Bandgröße berechnen und dann das gesamte System, einschließlich des jetzt endlichen Bandes, als endliche Zustandsmaschine behandeln .

Eine andere Möglichkeit, eine Turing-Maschine mit Computern zu simulieren, ist die folgende. Simulieren Sie die Turing-Maschine mit einem Computerprogramm, das den Bandanfang im Speicher ablegt. Wenn die Berechnung das Ende des Teils des Bandes erreicht, der in den Speicher passt, ersetzen Sie den Computer durch einen größeren Computer und führen Sie die Berechnung erneut aus.

Angenommen, Sie möchten eine rekursive Berechnung mit einem Computer simulieren. Die Techniken zum Ausführen von rekursiven Funktionen sind gut bekannt: jeder Funktionsaufruf ein Stück Speicher, ein sogenannten Stapelrahmen . Entscheidend ist, dass rekursive Funktionen Informationen durch mehrere Aufrufe verbreiten können, indem sie Variablen weitergeben. In Bezug auf die Implementierung auf einem Computer bedeutet dies, dass ein Funktionsaufruf möglicherweise auf den Stack-Frame eines (grand-) * Elternaufrufs zugreift .

Ein Computer ist ein Prozessor - eine Finite-State-Maschine (mit einer großen Anzahl von Zuständen, aber wir machen hier eine Berechnungstheorie, es kommt also nur darauf an, dass sie endlich ist) - gekoppelt mit einem endlichen Speicher. Der Mikroprozessor führt eine Riesen-While-Schleife aus: "Lies bei eingeschalteter Stromversorgung einen Befehl aus dem Speicher und führe ihn aus". (Echte Prozessoren sind viel komplexer als das, aber es hat keinen Einfluss darauf, was sie berechnen können, nur darauf, wie schnell und bequem sie es tun.) Ein Computer kann mit genau dieser while-Schleife rekursive Funktionen ausführen, um Iteration und den Mechanismus dafür bereitzustellen Zugriff auf den Speicher, einschließlich der Möglichkeit, den Speicher nach Belieben zu vergrößern.

Wenn Sie die Rekursion auf die primitive Rekursion beschränken, können Sie die Iteration auf eine begrenzte Iteration beschränken . Das heißt, anstatt while-Schleifen mit unvorhersehbarer Laufzeit zu verwenden, können Sie for-Schleifen verwenden, bei denen die Anzahl der Iterationen am Anfang der Schleife bekannt ist¹. Die Anzahl der Iterationen ist zu Beginn des Programms möglicherweise nicht bekannt: Sie können selbst durch vorherige Schleifen berechnet worden sein.

Ich werde hier nicht einmal einen Beweis skizzieren, aber es gibt eine intuitive Beziehung zwischen dem Übergang von der primitiven zur vollständigen Rekursion und dem Übergang von for-Schleifen zu while-Schleifen: In beiden Fällen müssen Sie nicht im Voraus wissen, wann Sie sich befinden halt. Bei vollständiger Rekursion wird dies mit dem Minimierungsoperator durchgeführt, in dem Sie so lange weitermachen, bis Sie einen Parameter finden, der die Bedingung erfüllt. Bei while-Schleifen wird dies so lange durchgeführt, bis die Schleifenbedingung erfüllt ist.

forwhilen


Akzeptieren für die detaillierte Erklärung, auf dem gewünschten Niveau gehalten.
Tobi Alafin

12
Ich denke, es ist erwähnenswert, als in echten Computern, Rekursion verwendet auch Speicher (wachsende Aufrufliste). In praktischen Anwendungen ist daher kein unbegrenzter Speicher erforderlich (da alles gleichermaßen davon begrenzt ist). Und Rekursion benötigt oft mehr Speicher als Iteration.
Agent_L

@Agent_L Ja, die Rekursion benötigt unbegrenzten Speicher, um alle Stapelrahmen zu speichern. Bei einem Rekursionsansatz ist ein unbegrenzter Speicher erforderlich, der sich jedoch nicht intuitiv von der Sequenzierung von Operationen wie bei Iterationen trennen lässt.
Gilles 'SO - hör auf böse zu sein'

1
Vielleicht von Interesse wäre die Church-Turing-These. Turing-Maschinen sind hochgradig iterative Maschinen ohne (inhärentes) Konzept der Rekursion. Lambda-Kalkül ist eine Sprache, die entwickelt wurde, um Berechnungen rekursiv auszudrücken. Die Church-Turing-These zeigte, dass alle Lambda-Ausdrücke auf einer Turing-Maschine ausgewertet werden konnten, wobei Rekursion und Iteration miteinander verbunden wurden.
Cort Ammon

1
@BlackVegetable Wenn eine rekursive Methode keine internen Variablen hat und der einzige Speicher, den sie verwendet, der Aufrufstapel ist (was durch TCO optimiert werden kann), hat die iterative Version höchstwahrscheinlich auch keine internen Variablen und verwendet nur eine konstante Menge an Speicher ( Variablen), die für alle Iterationen verwendet werden, z. B. counter.
Agent_L

33

Jede Rekursion kann in eine Iteration konvertiert werden, wie Ihre CPU bezeugt, die beliebige Programme mit einer unendlichen Iteration zum Abrufen und Ausführen ausführt. Dies ist eine Form des Böhm-Jacopini-Theorems . Darüber hinaus haben viele Turing-vollständige Rechenmodelle keine Rekursion, zum Beispiel Turing-Maschinen und Zählmaschinen .

Primitive rekursive Funktionen entsprechen Programmen mit beschränkter Iteration, dh, Sie müssen vorab die Anzahl der Iterationen angeben, die eine Schleife ausgeführt wird. Eine gebundene Iteration kann im Allgemeinen keine Rekursion simulieren, da die Ackermann-Funktion nicht primitiv rekursiv ist. Eine unbegrenzte Iteration kann jedoch jede teilweise berechenbare Funktion simulieren.


25

a(n,m)

s

push(s,x)xxpop(s)empty(s)

Ackermann(n0,m0):

  • s= (leeren Stack initialisieren)
  • push(s,n0)
  • push(s,m0)
  • while(true):
    • mpop(s)
    • if(empty(s)):
      • return m (dies beendet die Schleife)
    • npop(s)
    • if(n=0):
      • push(s,m+1)
    • else if(m=0):
      • push(s,n1)
      • push(s,1)
    • else:
      • push(s,n1)
      • push(s,n)
      • push(s,m1)

Ich habe dies in Ceylon implementiert, Sie können es im WebIDE ausführen , wenn Sie möchten. (Es gibt den Stapel zu Beginn jeder Iteration der Schleife aus.)

Dies hat natürlich nur den impliziten Aufrufstapel von der Rekursion in einen expliziten Stapel mit den Parametern und Rückgabewerten verschoben.


15
Ich denke, das ist ein wichtiger Punkt. Sie haben ganz explizit gezeigt, dass Rekursion nichts Besonderes ist und dass sie entfernt werden kann, indem Sie den Aufrufstapel selbst nachverfolgen, anstatt es dem Compiler zu überlassen. Rekursion ist nur eine Compiler-Funktion, die das Schreiben solcher Programme erleichtert.
David Richerby

4
Vielen Dank, dass Sie sich die Mühe gemacht haben, das gewünschte Programm zu schreiben.
Tobi Alafin

16

Es gibt bereits einige großartige Antworten (mit denen ich nicht einmal konkurrieren kann), aber ich möchte diese einfache Erklärung abgeben.

Rekursion ist nur die Manipulation des Laufzeitstacks. Recursing fügt einen neuen Stack-Frame hinzu (für den erneuten Aufruf der rekursiven Funktion) und Return entfernt einen Stack-Frame (für die gerade abgeschlossene Neuerung der rekursiven Funktion). Durch die Rekursion wird eine bestimmte Anzahl von Stack-Frames hinzugefügt / entfernt, bis schließlich alle beendet werden (hoffentlich!) Und das Ergebnis an den Aufrufer zurückgegeben wird.

Was würde nun passieren, wenn Sie Ihren eigenen Stack erstellen, rekursive Funktionsaufrufe durch Pushing auf den Stack ersetzen, die Rückkehr von rekursiven Funktionen durch Poppen des Stacks ersetzen und eine while-Schleife ausführen, bis der Stack leer ist?


2

Soweit ich das beurteilen kann, können Sie nach meiner Erfahrung jede Rekursion als Iteration implementieren. Wie oben erwähnt, verwendet die Rekursion den Stapel, der konzeptionell unbegrenzt, aber praktisch begrenzt ist (haben Sie jemals eine Stapelüberlaufnachricht erhalten?). In meinen Anfängen als Programmierer (im dritten Viertel des letzten Jahrhunderts des letzten Jahrtausends) verwendete ich nicht-rekursive Sprachen, die rekursive Algorithmen implementierten, und hatte keine Probleme. Ich bin mir jedoch nicht sicher, wie man es beweisen würde.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.