Rekursion versus Iteration

108

Ist es richtig zu sagen, dass überall dort, wo Rekursion verwendet wird, eine forSchleife verwendet werden könnte? Und wenn die Rekursion normalerweise langsamer ist, was ist der technische Grund dafür, sie jemals über eine forSchleifeniteration zu verwenden?

Und wenn es immer möglich ist, eine Rekursion in eine forSchleife umzuwandeln, gibt es eine Faustregel dafür?

Breako Breako
quelle
3
recursionvs iteration? iteration = for loopMeiner Ansicht nach.
Gongzhitaao
4
Tom Moertels Blog hat vier ausgezeichnete Beiträge zum Konvertieren von rekursivem Code in iterativen Code: blog.moertel.com/tags/recursion.html
cjohnson318

Antworten:

146

Die Rekursion ist normalerweise viel langsamer, da alle Funktionsaufrufe in einem Stapel gespeichert werden müssen, damit die Aufruferfunktionen wieder aufgerufen werden können. In vielen Fällen muss Speicher zugewiesen und kopiert werden, um die Bereichsisolierung zu implementieren.

Einige Optimierungen, wie die Tail-Call-Optimierung , beschleunigen Rekursionen, sind jedoch nicht immer möglich und nicht in allen Sprachen implementiert.

Die Hauptgründe für die Verwendung der Rekursion sind

  • dass es in vielen Fällen intuitiver ist, wenn es unsere Herangehensweise an das Problem nachahmt
  • dass einige Datenstrukturen wie Bäume mithilfe der Rekursion leichter zu erkunden sind (oder in jedem Fall Stapel benötigen würden)

Natürlich jede Rekursion kann als eine Art Schleife modelliert werden: das ist , was die CPU letztlich tun wird. Und die Rekursion selbst bedeutet direkter, die Funktionsaufrufe und Bereiche in einem Stapel abzulegen. Das Ändern Ihres rekursiven Algorithmus in einen Schleifenalgorithmus erfordert jedoch möglicherweise viel Arbeit und macht Ihren Code weniger wartbar: Wie bei jeder Optimierung sollte nur versucht werden, wenn eine Profilerstellung oder ein Beweis dies als notwendig erweist.

Denys Séguret
quelle
10
Hinzu kommt, dass die Rekursion eng mit dem Begriff der Reduktion verbunden ist, der in vielen Algorithmen und in CS im Allgemeinen eine zentrale Rolle spielt.
SomeWittyUsername
3
Können Sie mir bitte ein Beispiel geben, bei dem die Rekursion den Code wartbarer macht? Nach meiner Erfahrung ist es immer umgekehrt. Vielen Dank
Yeikel
@Yeikel Schreibe eine Funktion f(n), die die n-te Fibonacci-Zahl zurückgibt .
Matt
54

Ist es richtig zu sagen, dass überall dort, wo Rekursion verwendet wird, eine for-Schleife verwendet werden könnte?

Ja, da die Rekursion in den meisten CPUs mit Schleifen und einer Stapeldatenstruktur modelliert wird.

Und wenn die Rekursion normalerweise langsamer ist, was ist der technische Grund für die Verwendung?

Es ist nicht "normalerweise langsamer": Es ist eine falsch angewendete Rekursion, die langsamer ist. Darüber hinaus können moderne Compiler einige Rekursionen gut in Schleifen konvertieren, ohne danach zu fragen.

Und wenn es immer möglich ist, eine Rekursion in eine for-Schleife umzuwandeln, gibt es eine Faustregel dafür?

Schreiben Sie iterative Programme für Algorithmen, die am besten verstanden werden, wenn sie iterativ erklärt werden. Schreiben Sie rekursive Programme für Algorithmen, die am besten rekursiv erklärt werden.

Beispielsweise wird das Durchsuchen von Binärbäumen, das Ausführen von Quicksort und das Parsen von Ausdrücken in vielen Programmiersprachen häufig rekursiv erläutert. Diese werden am besten auch rekursiv codiert. Andererseits sind das Berechnen von Fakultäten und das Berechnen von Fibonacci-Zahlen durch Iterationen viel einfacher zu erklären. Rekursion für sie zu verwenden ist wie Fliegen mit einem Vorschlaghammer zu schlagen: Es ist keine gute Idee, selbst wenn der Vorschlaghammer wirklich gute Arbeit leistet + .


+ Ich habe mir die Vorschlaghammer-Analogie aus Dijkstras "Disziplin der Programmierung" ausgeliehen.

dasblinkenlight
quelle
7
Rekursion ist normalerweise teurer (langsamer / mehr Speicher), da Stapelrahmen und dergleichen erstellt werden. Der Unterschied mag bei korrekter Anwendung für ein ausreichend komplexes Problem gering sein, ist aber immer noch teurer. Es gibt mögliche Ausnahmen wie die Optimierung der Schwanzrekursion.
Bernhard Barker
Ich bin mir nicht sicher, ob es in jedem Fall eine einzige for-Schleife gibt . Betrachten Sie eine komplexere Rekursion oder eine Rekursion mit mehr als einer Variablen
SomeWittyUsername
@dasblinkenlight Es ist theoretisch möglich, mehrere Schleifen auf eine einzige zu reduzieren, ist sich aber nicht sicher.
SomeWittyUsername
@icepack Ja, das ist möglich. Es mag nicht schön sein, aber es ist möglich.
Bernhard Barker
Ich bin mir nicht sicher, ob ich Ihrer ersten Aussage zustimme. CPUs selbst modellieren die Rekursion überhaupt nicht wirklich. Es sind die Anweisungen, die auf der CPU ausgeführt werden, die die Rekursion modellieren. Zweitens hat eine Schleifenstruktur (notwendigerweise) keinen dynamisch wachsenden und schrumpfenden Datensatz, wobei ein rekursiver Algorithmus normalerweise für jede Ebene, in der die Rekursion ausgeführt werden muss, einen Schritt macht.
Trompetenlicks
28

Frage:

Und wenn die Rekursion normalerweise langsamer ist, was ist der technische Grund, sie jemals für die Schleifeniteration zu verwenden?

Antworten :

Weil es in einigen Algorithmen schwierig ist, es iterativ zu lösen. Versuchen Sie, die Tiefensuche sowohl rekursiv als auch iterativ zu lösen. Sie werden auf die Idee kommen, dass es einfach schwierig ist, DFS mit Iteration zu lösen.

Eine weitere gute Sache zum Ausprobieren: Versuchen Sie, Merge sort iterativ zu schreiben. Es wird einige Zeit dauern.

Frage:

Ist es richtig zu sagen, dass überall dort, wo Rekursion verwendet wird, eine for-Schleife verwendet werden könnte?

Antworten :

Ja. Dieser Thread hat eine sehr gute Antwort darauf.

Frage:

Und wenn es immer möglich ist, eine Rekursion in eine for-Schleife umzuwandeln, gibt es eine Faustregel dafür?

Antworten :

Vertrau mir. Versuchen Sie, Ihre eigene Version zu schreiben, um die Tiefensuche iterativ zu lösen. Sie werden feststellen, dass einige Probleme leichter rekursiv zu lösen sind.

Tipp: Rekursion ist gut, wenn Sie ein Problem lösen, das durch Teilen und Erobern gelöst werden kann.

Thanakron Tandavas
quelle
3
Ich schätze den Versuch, eine maßgebliche Antwort zu geben, und ich bin sicher, dass der Autor intelligent ist, aber "Vertrau mir" ist keine hilfreiche Antwort auf eine aussagekräftige Frage, deren Antwort nicht sofort offensichtlich ist. Es gibt sehr einfache Algorithmen für eine iterative Tiefensuche. Im Beispiel am Ende dieser Seite finden Sie eine Beschreibung eines Algorithmus im Pseudocode: csl.mtu.edu/cs2321/www/newLectures/26_Depth_First_Search.html
jdelman
3

Die Rekursion ist nicht nur langsamer, sondern kann auch zu Stapelüberlauffehlern führen, je nachdem, wie tief sie reicht.

G. Steigert
quelle
3

Um eine äquivalente Methode mit Iteration zu schreiben, müssen wir explizit einen Stapel verwenden. Die Tatsache, dass die iterative Version einen Stapel für ihre Lösung benötigt, zeigt, dass das Problem so schwierig ist, dass es von einer Rekursion profitieren kann. In der Regel eignet sich die Rekursion am besten für Probleme, die nicht mit einer festen Speichermenge gelöst werden können und daher bei iterativer Lösung einen Stapel erfordern. Rekursion und Iteration können jedoch dasselbe Ergebnis zeigen, während sie unterschiedlichen Mustern folgen. Um zu entscheiden, welche Methode besser funktioniert, müssen Sie von Fall zu Fall entscheiden.

So finden Sie beispielsweise die n-te Dreieckszahl der Dreiecksfolge: 1 3 6 10 15… Ein Programm, das einen iterativen Algorithmus verwendet, um die n-te Dreieckszahl zu ermitteln:

Verwenden eines iterativen Algorithmus:

//Triangular.java
import java.util.*;
class Triangular {
   public static int iterativeTriangular(int n) {
      int sum = 0;
      for (int i = 1; i <= n; i ++)
         sum += i;
      return sum;
   }
   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                            iterativeTriangular(n));
   }
}//enter code here

Verwenden eines rekursiven Algorithmus:

//Triangular.java
import java.util.*;
class Triangular {
   public static int recursiveTriangular(int n) {
      if (n == 1)
     return 1;  
      return recursiveTriangular(n-1) + n; 
   }

   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                             recursiveTriangular(n)); 
   }
}
Shirin
quelle
1

Die meisten Antworten scheinen davon auszugehen, dass iterative= for loop. Wenn Ihre for-Schleife nicht eingeschränkt ist ( a la C, Sie können mit Ihrem Schleifenzähler alles tun, was Sie wollen), ist das richtig. Wenn es sich um eine echte for Schleife handelt (z. B. in Python oder den meisten funktionalen Sprachen, in denen Sie den Schleifenzähler nicht manuell ändern können), ist sie nicht korrekt.

Alle (berechenbaren) Funktionen können sowohl rekursiv als auch mithilfe von whileSchleifen (oder bedingten Sprüngen, die im Grunde dasselbe sind) implementiert werden. Wenn Sie sich wirklich darauf beschränken for loops, erhalten Sie nur eine Teilmenge dieser Funktionen (die primitiven rekursiven, wenn Ihre elementaren Operationen sinnvoll sind). Zugegeben, es ist eine ziemlich große Teilmenge, die zufällig jede einzelne Funktion enthält, auf die Sie in der Praxis wahrscheinlich stoßen werden.

Viel wichtiger ist, dass viele Funktionen sehr einfach rekursiv und schrecklich schwer iterativ zu implementieren sind (die manuelle Verwaltung Ihres Aufrufstapels zählt nicht).

Jbeuh
quelle
1

Ja, wie gesagt durch Thanakron Tandavas ,

Rekursion ist gut, wenn Sie ein Problem lösen, das durch Teilen und Erobern gelöst werden kann.

Zum Beispiel: Türme von Hanoi

  1. N Ringe in zunehmender Größe
  2. 3 Pole
  3. Ringe beginnen auf Pol 1 gestapelt. Ziel ist es, Ringe so zu bewegen, dass sie auf Pol 3 gestapelt sind ... Aber
    • Kann jeweils nur einen Ring bewegen.
    • Ein größerer Ring kann nicht auf einen kleineren gelegt werden.
  4. Die iterative Lösung ist „mächtig und doch hässlich“. rekursive Lösung ist "elegant".
Ramesh Mukkera
quelle
Ein interessantes Beispiel. Ich denke, Sie kennen das Papier von MC Er "Die Türme von Hanoi und binäre Ziffern". Auch in einem fantastischen Video von 3brown1blue behandelt.
Andrestand
0

Ich erinnere mich an meinen Informatikprofessor, der damals sagte, dass alle Probleme mit rekursiven Lösungen auch iterative Lösungen haben. Er sagt, dass eine rekursive Lösung normalerweise langsamer ist, aber sie werden häufig verwendet, wenn sie leichter zu überlegen und zu codieren sind als iterative Lösungen.

Bei fortgeschritteneren rekursiven Lösungen glaube ich jedoch nicht, dass sie immer mit einer einfachen forSchleife implementiert werden können .

Vivian River
quelle
Es ist immer möglich, einen rekursiven Algorithmus in einen iterativen Algorithmus umzuwandeln (unter Verwendung von Stapeln). Möglicherweise erhalten Sie keine besonders einfache Schleife, aber es ist möglich.
Bernhard Barker
-4

Rekursion + Auswendiglernen könnte zu einer effizienteren Lösung führen als ein rein iterativer Ansatz. Überprüfen Sie dies beispielsweise: http://jsperf.com/fibonacci-memoized-vs-iterative-for-large-n

Reza Afzalan
quelle
3
Jeder rekursive Code kann mithilfe von Stapeln in funktionsidentischen iterativen Code konvertiert werden. Der Unterschied, den Sie zeigen, ist der Unterschied zwischen zwei Ansätzen zur Lösung des gleichen Problems, nicht der Unterschied zwischen Rekursion und Iteration.
Bernhard Barker
-6

Kurze Antwort: Der Kompromiss ist, dass die Rekursion schneller ist und für Schleifen in fast allen Fällen weniger Speicherplatz benötigt. Normalerweise gibt es jedoch Möglichkeiten, die for-Schleife oder die Rekursion zu ändern, damit sie schneller ausgeführt wird

Jessica Shu
quelle