Mit welchen Überlegungen können Sie feststellen, ob Sie ein Problem mithilfe der Rekursion lösen können?


10

Manchmal verwende ich in Interviews die Rekursion, um ein Problem zu lösen (z. B. das Hinzufügen 1zu einer Ganzzahl mit unendlicher Genauigkeit), oder wenn sich das Problem als geeignet für die Verwendung der Rekursion herausstellt. Manchmal kann es nur daran liegen, dass Rekursion häufig zur Problemlösung verwendet wird. Ohne viel nachzudenken, wird Rekursion verwendet, um das Problem zu lösen.

Was sind jedoch die Überlegungen, bevor Sie entscheiden können, ob die Rekursion zur Lösung eines Problems geeignet ist?


Einige Gedanken, die ich hatte:

Wenn wir die Rekursion für Daten verwenden, die jedes Mal halbiert werden, scheint es kein Problem zu sein, die Rekursion zu verwenden, da alle Daten, die in 16 GB RAM oder sogar eine 8-TB-Festplatte passen, durch eine Rekursion mit einer Tiefe von nur 42 Ebenen verarbeitet werden können. (also kein Stapelüberlauf (Ich denke, in einigen Umgebungen kann der Stapel 4000 Ebenen tief sein, weit mehr als 42, aber gleichzeitig hängt es auch davon ab, wie viele lokale Variablen Sie haben, da jeder Aufrufstapel mehr Speicher belegt Wenn es viele lokale Variablen gibt und es die Speichergröße und nicht die Ebene ist, die den Stapelüberlauf bestimmt)).

Wenn Sie Fibonacci-Zahlen mit reiner Rekursion berechnen, müssen Sie sich wirklich um die zeitliche Komplexität kümmern, es sei denn, Sie zwischenspeichern die Zwischenergebnisse.

Und wie wäre es 1mit einer Ganzzahl mit unendlicher Genauigkeit? Vielleicht ist es fraglich, ob Sie mit Zahlen arbeiten, die 3000 oder 4000 Stellen lang sind und so groß, dass es zu einem Stapelüberlauf kommen kann. Ich habe nicht daran gedacht, aber vielleicht lautet die Antwort nein, wir sollten keine Rekursion verwenden, sondern nur eine einfache Schleife, denn was ist, wenn in einer Anwendung die Zahl wirklich 4000 Stellen lang sein muss, um nach einigen zu suchen? Eigenschaften der Zahl, z. B. ob die Zahl eine Primzahl ist oder nicht.

Die letzte Frage lautet: Was sind die Überlegungen, bevor Sie sich für die Rekursion zur Lösung eines Problems entscheiden können?


7
Es ist eigentlich ziemlich einfach: "Ist die Lösung trivial, wenn ich davon ausgehen kann, dass die Lösung für ein etwas kleineres Problem bekannt ist?"
Kilian Foth

aber was ist mit der Fibonacci-Zahl oder der Addition 1einer Ganzzahl mit unendlicher Genauigkeit? Sie können sagen, ja, sie reduzieren sich auf ein kleineres Problem, aber reine Rekursion ist nicht dafür geeignet
Unpolarität

Sie können dies hilfreich finden - stackoverflow.com/questions/3021/…
Kishor Kundan

Antworten:


15

Eine Überlegung ist, ob Ihr Algorithmus eine abstrakte Lösung oder eine praktische ausführbare Lösung sein soll. Im ersteren Fall sind die Attribute, nach denen Sie suchen, Korrektheit und Verständnis für Ihre Zielgruppe 1 . Im letzteren Fall ist auch die Leistung ein Problem. Diese Überlegungen können Ihre Wahl beeinflussen.

Eine zweite Überlegung (für eine praktische Lösung) ist, ob die von Ihnen verwendete Programmiersprache (oder genauer gesagt ihre Implementierung) die Eliminierung von Tail-Calls bewirkt. Ohne Eliminierung von Tail-Calls ist die Rekursion langsamer als die Iteration, und eine tiefe Rekursion kann zu Problemen mit dem Stapelüberlauf führen.

Beachten Sie, dass eine (richtige) rekursive Lösung wird umgewandelt in eine äquivalente nicht-rekursive Lösung, so dass Sie nicht unbedingt eine harte Wahl zwischen den beiden Ansätzen müssen machen.

Schließlich wird die Wahl zwischen rekursiven und nicht rekursiven Formulierungen manchmal durch die Notwendigkeit motiviert, (im formalen Sinne) Eigenschaften eines Algorithmus zu beweisen. Rekursive Formulierungen ermöglichen direkter den Nachweis durch Induktion.


1 - Dies beinhaltet Überlegungen, ob die Zielgruppe ... und dies könnte Programmierer beinhalten, die praktischen Code lesen ... einen Lösungsstil als "natürlicher" als den anderen ansehen würden. Der Begriff "natürlich" variiert von Person zu Person, je nachdem, wie sie Programmierung oder Algorithmus gelernt haben. (I Herausforderung jeden, der „Natürlichkeit“ , wie die schlägt primäre Kriterien für die Entscheidung zur Verwendung Rekursion (oder nicht) zu definieren „Natürlichkeit“ objektiv, das heißt , wie man es messen würde.)


2
Einige Probleme werden einfach natürlicher durch Rekursion ausgedrückt. Zum Beispiel Baumdurchquerung.
Frank Hileman

Aktualisierte meine Antwort, um diesen Punkt anzusprechen,
Stephen C

1
In Bezug auf "Natürlichkeit": Baumdurchquerung ohne Rekursion führt beispielsweise tendenziell zu größerem, weniger allgemeinem Code. Verwenden Sie beispielsweise polymorphe Aufrufe zum Durchlaufen des Baums mit unterschiedlichem Verhalten für Blatt- und zusammengesetzte Knoten. Dies ist ohne Rekursion nicht möglich.
Frank Hileman

1) Haben Sie meine Herausforderung angenommen, "natürlich" zu definieren? 2) Da es möglich ist, eine Rekursion unter Verwendung einer Stapeldatenstruktur zu simulieren, ist es auch möglich, eine Baumdurchquerung auf diese Weise zu implementieren. Es ist vielleicht nicht der effizienteste Weg ... und es gibt Ihnen nicht den am besten lesbaren Code ... aber es ist definitiv möglich und praktisch, dies zu tun.
Stephen C

Übrigens hat die erste Programmiersprache, die ich gelernt habe (FORTRAN 4), die Rekursion überhaupt nicht unterstützt.
Stephen C

1

Als C / C ++ - Programmierer ist die Leistung meine wichtigste Überlegung. Mein Entscheidungsprozess ist ungefähr so:

  1. Was ist die maximale Tiefe des Aufrufstapels? Wenn zu tief, entfernen Sie die Rekursion. Wenn flach, gehen Sie zu 2.

  2. Ist diese Funktion wahrscheinlich ein Engpass in meinem Programm? Wenn ja, fahren Sie mit Schritt 3 fort. Wenn nein, behalten Sie die Rekursion bei. Wenn Sie sich nicht sicher sind, führen Sie einen Profiler aus.

  3. Wie viel Prozent der CPU-Zeit wird für rekursive Funktionsaufrufe aufgewendet? Wenn Funktionsaufrufe erheblich weniger Zeit in Anspruch nehmen als der Rest des Funktionskörpers, kann die Rekursion verwendet werden.


0

Was sind jedoch die Überlegungen, bevor Sie entscheiden können, ob die Rekursion zur Lösung eines Problems geeignet ist?

Wenn ich Funktionen in Schema schreibe, finde ich es natürlich, rekursive Schwanzfunktionen zu schreiben, ohne zu viel nachzudenken.

Wenn ich Funktionen in C ++ schreibe, diskutiere ich, bevor ich eine rekursive Funktion verwende. Die Fragen, die ich mir stelle, sind:

  • Kann die Berechnung mit einem iterativen Algorithmus durchgeführt werden? Wenn ja, verwenden Sie einen iterativen Ansatz.

  • Kann die Rekursionstiefe um die Größe des Modells zunehmen? Ich bin kürzlich auf einen Fall gestoßen, in dem die Rekursionstiefe aufgrund der Größe des Modells auf fast 13000 angewachsen ist. Ich musste die Funktion konvertieren, um nach der Eile einen iterativen Algorithmus zu verwenden.

    Aus diesem Grund würde ich nicht empfehlen, einen Tree-Traversal-Algorithmus mit rekursiven Funktionen zu schreiben. Sie wissen nie, wann der Baum für Ihre Laufzeitumgebung zu tief wird.

  • Kann die Funktion durch Verwendung eines iterativen Algorithmus zu kompliziert werden? Wenn ja, verwenden Sie eine rekursive Funktion. Ich habe nicht versucht, qsortmit einem iterativen Ansatz zu schreiben , aber ich habe das Gefühl, dass die Verwendung einer rekursiven Funktion für sie natürlicher ist.


0

Für Fibonacci-Zahlen ist die naive "Rekursion" einfach total dumm. Das liegt daran, dass das gleiche Teilproblem immer wieder gelöst wird.

Es gibt tatsächlich eine triviale Variation der Fibonacci-Zahlen, bei der die Rekursion sehr effizient ist: Berechnen Sie bei einer Zahl n ≥ 1 sowohl fib (n) als auch fib (n-1). Sie benötigen also eine Funktion, die zwei Ergebnisse zurückgibt. Rufen Sie diese Funktion fib2 auf.

Die Implementierung ist ganz einfach:

function fib2 (n) -> (fibn, fibnm1) {
    if n ≤ 1 { return (1, 1) }
    let (fibn, fibnm1) = fib2 (n-1)
    return (fibn + fibnm1, fibn)
}

Denken Sie, Sie können das Programm in einer gemeinsamen Sprache schreiben? und Sie geben fib2ein Zahlenpaar zurück, und Sie fib2()passen nicht zur Schnittstelle von fib(), die bei einer gegebenen Zahl eine Zahl zurückgibt . Es scheint, dass Sie fib(n)zurückkehren sollen, fib2(n)[0]aber bitte seien Sie genau
Unpolarität
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.