Die Tail-Call-Optimierung ist in vielen Sprachen und Compilern vorhanden. In dieser Situation erkennt der Compiler eine Funktion der Form:
int foo(n) {
...
return bar(n);
}
Hier kann die Sprache erkennen, dass das zurückgegebene Ergebnis das Ergebnis einer anderen Funktion ist, und einen Funktionsaufruf mit einem neuen Stapelrahmen in einen Sprung umwandeln.
Beachten Sie, dass die klassische Fakultätsmethode:
int factorial(n) {
if(n == 0) return 1;
if(n == 1) return 1;
return n * factorial(n - 1);
}
ist wegen der bei der rückgabe notwendigen inspektion nicht schwanzrufoptimierbar. ( Beispiel für Quellcode und kompilierte Ausgabe )
Um diesen Tail Call zu optimieren,
int _fact(int n, int acc) {
if(n == 1) return acc;
return _fact(n - 1, acc * n);
}
int factorial(int n) {
if(n == 0) return 1;
return _fact(n, 1);
}
Kompilieren Sie diesen Code mit gcc -O2 -S fact.c
(-O2 ist erforderlich, um die Optimierung im Compiler zu ermöglichen, aber mit mehr Optimierungen von -O3 wird es für einen Menschen schwer zu lesen ...)
_fact(int, int):
cmpl $1, %edi
movl %esi, %eax
je .L2
.L3:
imull %edi, %eax
subl $1, %edi
cmpl $1, %edi
jne .L3
.L2:
rep ret
( Beispiel für Quellcode und kompilierte Ausgabe )
Man kann in dem Segment sehen .L3
, die jne
eher als ein call
(das macht einen Subroutinen - Aufruf mit einem neuen Stapelrahmen).
Beachten Sie, dass dies mit C durchgeführt wurde. Die Optimierung von Tail-Aufrufen in Java ist schwierig und hängt von der JVM-Implementierung ab (ich habe jedoch noch keine davon gesehen, da dies schwierig ist und Auswirkungen auf das erforderliche Java-Sicherheitsmodell hat, das Stack-Frames erfordert - was TCO vermeidet) - Tail-Recursion + Java und Tail-Recursion + Optimierung sind gute Tag-Sets zum Durchsuchen. Sie können andere JVM Sprachen sind in der Lage zu optimieren Endrekursion finden besser (try clojure (die die erfordert recur zu Endrekursion optimize) oder scala).
Das gesagt,
Es ist eine gewisse Freude zu wissen, dass Sie etwas richtig geschrieben haben - auf die ideale Art und Weise, wie es getan werden kann.
Und jetzt hole ich mir einen Scotch und ziehe eine deutsche Electronica an ...
Zur allgemeinen Frage "Methoden zur Vermeidung eines Stapelüberlaufs in einem rekursiven Algorithmus" ...
Ein anderer Ansatz besteht darin, einen Rekursionszähler einzuschließen. Dies dient eher zum Erkennen von Endlosschleifen, die durch Situationen verursacht werden, auf die man keinen Einfluss hat (und durch schlechte Codierung).
Der Rekursionszähler hat die Form von
int foo(arg, counter) {
if(counter > RECURSION_MAX) { return -1; }
...
return foo(arg, counter + 1);
}
Bei jedem Anruf erhöhen Sie den Zähler. Wenn der Zähler zu groß wird, tritt ein Fehler auf (hier nur eine Rückgabe von -1, in anderen Sprachen möchten Sie möglicherweise eine Ausnahme auslösen). Damit soll verhindert werden, dass bei einer Rekursion, die viel tiefer als erwartet ist und wahrscheinlich eine Endlosschleife darstellt, schlimmere Ereignisse (Speicherfehler) auftreten.
Theoretisch sollte man das nicht brauchen. In der Praxis habe ich schlecht geschriebenen Code gesehen, der dies aufgrund einer Vielzahl von kleinen Fehlern und schlechten Codierungspraktiken getroffen hat (Multithread-Probleme, bei denen etwas außerhalb der Methode geändert wird, wodurch ein anderer Thread in eine Endlosschleife rekursiver Aufrufe gerät).
Verwenden Sie den richtigen Algorithmus und lösen Sie das richtige Problem. Speziell für die Collatz-Vermutung scheint es , dass Sie versuchen, sie auf die xkcd- Weise zu lösen :
Du beginnst bei einer Zahl und machst eine Baumquerung. Dies führt schnell zu einem sehr großen Suchraum. Ein schneller Durchlauf zur Berechnung der Anzahl der Iterationen für die richtige Antwort führt zu etwa 500 Schritten. Dies sollte kein Problem für die Rekursion mit einem kleinen Stapelrahmen sein.
Das Wissen um die rekursive Lösung ist zwar keine schlechte Sache, man sollte jedoch auch erkennen, dass die iterative Lösung um ein Vielfaches besser ist . Eine Reihe von Möglichkeiten, einen rekursiven Algorithmus in einen iterativen zu konvertieren, finden Sie unter Stapelüberlauf auf dem Weg von der Rekursion zur Iteration .