it-swarm.com.de

Gibt es Vorteile für die Verwendung von Rekursion gegenüber Iteration - außer manchmal Lesbarkeit und Eleganz?

Ich bin dabei, zwei Annahmen zu treffen. Bitte korrigieren Sie mich, wenn sie falsch sind:

  1. Es gibt keinen rekursiven Algorithmus ohne iteratives Äquivalent.
  2. Iteration ist in Bezug auf die Leistung immer billiger als Rekursion (zumindest in Allzwecksprachen wie Java, C++, Python usw.)).

Wenn es stimmt, dass Rekursion immer teurer ist als Iteration und dass sie immer durch einen iterativen Algorithmus ersetzt werden kann (in Sprachen, die dies zulassen), dann sind die beiden verbleibenden Gründe für die Verwendung von Rekursion meiner Meinung nach: Eleganz und Lesbarkeit.

Einige Algorithmen werden mit Rekursion eleganter ausgedrückt. Z.B. Scannen eines Binärbaums.

Gibt es jedoch abgesehen davon Gründe, die Rekursion über die Iteration zu verwenden? Hat Rekursion andere Vorteile als Iteration als manchmal Eleganz und Lesbarkeit?

8
Aviv Cohn

Nun, es ist normalerweise weniger Code.

Und in dem Maße, in dem es weniger Code gibt, ist es weniger fehleranfällig.

Insbesondere ist die Rekursion sehr vorteilhaft, wenn die iterativen Lösungen erfordern, dass Sie Rekursion simulieren mit einem Stapel. Durch die Rekursion wird bestätigt, dass der Compiler bereits einen Stapel verwaltet, um genau das zu erreichen, was Sie benötigen. Wenn Sie anfangen, Ihre eigenen zu verwalten, werden Sie wahrscheinlich nicht nur den Funktionsaufruf-Overhead wieder einführen, den Sie vermeiden wollten. Aber Sie erfinden ein Rad neu (mit viel Platz für Fehler), das bereits in einer ziemlich fehlerfreien Form existiert.

IMO, vermeiden Sie Rekursionen nur, wenn dies auf natürliche und einfache Weise ohne Stapel möglich ist. (Oder eine andere nicht skalare Status- und/oder Bereichsverwaltungsstruktur.)

12
svidgen

Erstens ist es zwar wahr, dass es immer ein äquivalentes iteratives Äquivalent zu einem rekursiven Algorithmus gibt, aber es ist nicht unbedingt der Fall, dass das iterative Äquivalent für eine vernünftige Definition von "besser" besser ist.

Bei einigen Algorithmen simuliert das iterative Äquivalent lediglich den rekursiven Algorithmus, einschließlich simulierter Parameter und Stapeln und Entstapeln lokaler Variablen. Betrachten Sie Ackermanns Funktion . Betrachten Sie Huffman-Codierung . In diesen Fällen wird durch (erneutes) Schreiben der expliziten Stapeloperationen nur sehr wenig gewonnen.

Zweitens ist die Rekursion nicht unbedingt immer teurer als die Iteration. Betrachten Sie Schwanzrekursion und lesen Sie die "Lambda: The Ultimate ..." - Papiere.

(Ja, ich weiß, dass dies erweitert werden muss. Ich muss jetzt zum Arzt gehen.)

OK, es gibt einige Dinge, die gesagt werden können.

Tony Hoare beschrieb Quicksort ursprünglich iterativ und berichtete, dass es sehr schwer zu erklären sei. Rekursiv erklärt, ist es EINFACH. Siehe Quicksort in Haskell für Details. Wenn das Array eine Länge von eins hat, sind Sie fertig, andernfalls müssen Sie es tatsächlich sortieren. Zunächst wählen Sie ein Pivot-Element aus. Traditionell ist dies das erste Element, aber jedes Element kann verwendet werden. Als nächstes durchlaufen Sie das Array, um drei Subarrays zu bilden. Das erste sind alle Elemente, die kleiner als der Drehpunkt sind, das zweite sind alle Elemente, die gleich dem Drehpunkt sind, und das dritte sind alle Elemente, die größer als der Drehpunkt sind. (Wenn die Array-Elementwerte eindeutig sein müssen, ist die Länge des zweiten Subarrays gleich eins.) Sortieren Sie nun das erste Subarray und dann das zweite Subarray.

Die binäre Suche ist am einfachsten zu verstehen, wenn sie rekursiv dargestellt wird, und sie ist tatsächlich schwanzrekursiv. Sie prüfen das mittlere Element des Arrays. Wenn es dem Wert entspricht, den Sie suchen, sind Sie fertig. Wenn das mittlere Element größer als der gesuchte Wert ist, wissen Sie, dass der gewünschte Wert "links" liegen muss, und Sie suchen das Subarray vor dem mittleren Element, andernfalls suchen Sie das Subarray nach dem mittleren Element. In beiden Fällen wissen Sie, dass das mittlere Element nicht das Ziel ist, also lassen Sie es weg. Entweder finden Sie Ihr Ziel oder Ihnen geht das Array für die Suche aus. An diesem Punkt steigen Sie den ganzen Weg aus und sind fertig. Im Jahr 2014, egal 2019, weiß jeder Compiler mit Selbstachtung, wie man eine Tail-Rekursionsoptimierung durchführt, selbst Java Compiler. (Hinweis: Der klassische Java) Die virtuelle Maschine unterstützt keine allgemeine Tail-Call-Optimierung, da keine allgemeine GOTO-Operation vorhanden ist. Dies hat jedoch keinen Einfluss auf die Tail-Rekursionsoptimierung.)

Das eigentliche Problem, viele Orte, ist, dass die Leute, die die Show leiten, nie etwas über die Optimierung der Schwanzrekursion gelernt haben und daher jede Rekursion verbieten. Ich bin bei Nortel Networks darauf gestoßen und musste eine ganze Seite mit Kommentaren schreiben, die es und einige verwandte Konzepte [~ # ~] und [~ # ~] Zeigen Sie ihnen die Assembler-Listen, die bewiesen haben, dass der Compiler KEINE rekursiven Aufrufe generiert hat. Nortel ist jetzt weg, aber diese Manager existieren immer noch an vielen Orten.

Hoffe das hilft.

4
John R. Strohm

Die Rekursion verwendet den Aufrufstapel zum Speichern von Funktionsaufrufrückgaben. Der Funktionsstatus wird zwischen den Aufrufen gespeichert.

Die Iteration muss auch einen Stapel oder einen ähnlichen Mechanismus verwenden, um Zwischenzustände zu speichern, außer dass Sie den Stapel selbst erstellen. Es sei denn, Sie finden natürlich einen Ersatzalgorithmus, für den kein solcher Statusspeicher erforderlich ist.

Rekursion ist nur dann teurer, wenn Sie den Stapel überlaufen. In gewissem Sinne wird die Iteration teurer sein (bei den Algorithmen, die sich für eine Rekursion eignen), da Sie den von der Rekursion bereits bereitgestellten Statusspeichermechanismus neu erstellen.

3
Robert Harvey