Wie zu vermeiden stack-überlauf einer rekursiven Funktion?
Zum Beispiel, wenn wir traversaling ein ziemlich großer Baum, der durch die folgende Funktion ist es möglich, dass wir, um stack-überlauf.
void inorder(node* n)
{
if(n == null) return;
inorder(n->l);
n->print();
inorder(n->r);
}
Wie eine Bedingung hinzufügen, oder etwas in der Funktion zu verhindern, dass ein solcher überlauf passiert?
- Wie groß ist der Baum?
- Das ist nicht wirklich eine Frage, die Sie sollten sorgen zu machen, es sei denn, Sie sind der Umgang mit einige ziemlich große Bäume.
- Sein Vorschlag wäre, zu senden, extra param
counter
es zu verfolgen. - Dies ist nur ein Problem wenn Ihr Baum ist wirklich tief. Wenn das der Fall ist, die Sie haben, möchten Sie vielleicht zu prüfen, das umwandeln Ihrer rekursive Funktion, um eine nicht-rekursive einer.
- Ist es ein binärer such-Baum, oder ist es nur ein Baum (z.B. ein azyklischer graph mit nicht mehr als zwei ausgehende Kanten und eine eingehende Kante)?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Gibt es keine portable Lösung außer durch ersetzen der Rekursion
mit expliziten Verwaltung der Stapel (mit
std::vector<Node*>
). Nicht-portabel, können Sie verfolgen dieTiefe die Verwendung einer statischen variable; wenn Sie die maximale stack
Größe und wie viel stack jede Rekursion braucht, dann kann man
überprüfen, dass die Tiefe nicht übersteigen.
Viele Systeme, wie Linux und Solaris kann man nicht wissen, die
maximale stack-Tiefe vorne, da der stack ist reserviert
dynamisch. Zumindest unter Linux und Solaris, aber sobald
Speicher zugewiesen wurde, um den Stapel, wird es weiterhin zugeteilt bleibt
und bleiben die betroffenen auf den Stapel. So können Sie recurse ziemlich
tief am Anfang des Programms (möglicherweise Absturz, aber
bevor er etwas getan hat), und überprüfen Sie dann gegen diesen Wert
später:
Werden Sie feststellen, dass es gibt ein paar Fälle von
undefiniert/nicht spezifiziert Verhalten Umlauf im code, aber
Ich habe es erfolgreich auf einem paar von Gelegenheiten (unter
Solaris auf Sparc, aber Linux auf dem PC funktioniert genau in diesem
Bezug). Es wird in der Tat, arbeiten auf fast jedem system, in dem:
- der stack wächst nach unten, und
- lokale Variablen auf dem Stapel reserviert.
So funktioniert es auch auf Windows, aber wenn es nicht
reservieren Sie den ersten Stapel, die Sie haben zu verknüpfen, anstatt
führen Sie einfach das Programm in dem Augenblick, wo es weniger Aktivität auf
die box (oder ändern
ulimits
) (da die stack-Größe auf Windowsist behoben, zur link-Zeit).
EDIT:
Einen Kommentar über die Verwendung einer expliziten stack: einige
Systeme (einschließlich Linux, standardmäßig) overcommit, was bedeutet, dass
dass Sie nicht zuverlässig ein "out of memory" - Fehlermeldung, wenn
Erweiterung einer
std::vector<>
; das system wird Ihnen sagen,std::vector<>
dass die Erinnerung ist da, und dann geben dieProgramm ein segment Verletzung, wenn er versucht, darauf zuzugreifen.
betrachten die iteration über die Rekursion , wenn das ist wirklich ein Problem.
http://en.wikipedia.org/wiki/Tree_traversal
sehen, die psedo-code für die iteration
iterativeInorder
iterativePreorder
iterativePostorder
Basdically verwenden Sie Ihre eigene Liste als stack-Daten-Struktur in einer while-Schleife , können Sie effektiv ersetzen-Funktion die Rekursion.
Ist die Sache mit der Rekursion ist, dass man nie Garantie, dass es nie overflow auf dem stack, , es sei denn Sie können einige Grenzen auf, sowohl die (Mindest -) Größe des Speichers und die (maximale) Größe der Eingabe. Was Sie kann tun, jedoch ist die Garantie, dass es wird overflow den stack, wenn Sie eine unendliche Schleife...
Ich die "if() return;" beenden Zustand, so sollten Sie es vermeiden, Endlosschleifen, so lange jeder Zweig von Ihrem Baum endet in einer null. Eine Möglichkeit ist also fehlerhafte Eingabe, wo einige Zweig des Baumes erreicht nie den Wert null. (Dies würde auftreten, z.B., wenn Sie eine Schleife in Ihrem Baum-Datenstruktur.)
Die einzige andere Möglichkeit, die ich sehe, ist, dass Ihre Struktur der Struktur der Daten ist einfach zu groß für die Menge der stack-Speicher zur Verfügung. (N. B. dies ist virtueller Speicher und swap-Speicherplatz verwendet werden kann, es ist also nicht unbedingt ein Problem von zu wenig RAM.) Wenn das der Fall ist, müssen Sie möglicherweise zu kommen mit einigen anderen algorithmischen Ansatz, der nicht rekursiv ist. Obwohl Ihre Funktion hat einen kleinen memory-footprint, also, es sei denn, Sie haben ausgelassen, einige zusätzliche Verarbeitung, dass es funktioniert, Ihr Baum würde wirklich WIRKLICH TIEF für diese ein Problem sein. (N. B. es ist die maximale Tiefe, das ist ein Problem hier, nicht die Anzahl der Knoten.)
Könnten Sie erhöhen die stack-Größe für Ihr Betriebssystem. Dieser ist normalerweise so konfiguriert, durch
ulimit
wenn Sie auf einem Unix-ähnlichen Umgebung.E. g. unter Linux kann man tun
ulimit -s unlimited
wird die Größe des stack auf 'unbegrenzt', obwohl IIRC gibt es eine Feste Grenze, und man nicht widmen Sie Ihre gesamte Speicher zu einem Prozess (obwohl die Antworten in den links weiter unten erwähnt, eine unbegrenzte Menge).Meine Vorschläge ausführen
ulimit -s
die Ihnen die aktuelle Größe des Stacks und wenn Sie noch immer einen stack-überlauf-Doppel, das limit, bis du glücklich bist.Haben Sie einen Blick hier, hier und hier für eine ausführlichere Diskussion über die Größe des stack und wie Sie zu aktualisieren.
ulimit -s
ist nicht die einzige Grenze auf stack-Größe. Wie Sie sagen, müssen Sie den (virtuellen) Speicher für den stack (und die meisten Unix-nur verteilen Sie träge). Und wie viel virtueller Speicher Sie zur Verfügung haben, hängt davon ab, was sonst passiert auf der Maschine auf den moment, in dem Sie den Speicher benötigen.Wenn Sie einen sehr großen Baum, und Sie laufen in Probleme mit der überschreitung Ihrem Stapel mithilfe der rekursiven traversalen, das problem ist wahrscheinlich, dass Sie nicht über eine ausgewogene Struktur. Der erste Vorschlag ist dann, zu versuchen, einen ausgeglichenen binären Baum, wie rot-schwarz-Baum oder AVL-Baum, oder ein Baum mit mehr als 2 Kinder pro Knoten wie ein B+ - Baum. Die C++ - Bibliothek bietet
std::map<>
undstd::set<>
die in der Regel umgesetzt mit einem ausgewogenen Baum.Jedoch eine einfache Möglichkeit, zu vermeiden, rekursive in-order traversals ist die Anpassung des Baum eingefädelt werden. Das ist, verwenden Sie die richtigen Zeiger vom Blattknoten zeigen an den nächsten Knoten. Die Traversierung eines solchen Baumes würde in etwa so Aussehen:
Können Sie eine statische variable zu verfolgen, die mal die Funktion aufgerufen wird. Wenn es immer in der Nähe, was Sie denken, würde das system zum Absturz bringen, führen Sie einige routine, um Benachrichtigen der Benutzer über den Fehler.
Einen kleinen Prototyp der änderungen, die gemacht werden können, indem eine weitere int-variable mit der rekursiven Funktion.Könnten Sie übergeben der variable als argument an die Funktion beginnend mit null-Wert standardmäßig auf die Wurzel-und Dekrement es, wie u gehen, den Baum hinunter ...
Nachteil: diese Lösung geht auf Kosten eines mehraufwands bei einer int-Variablen zugewiesen, die für jeden Knoten.
berücksichtigen, für die weitere Forschung:
Obwohl das problem möglicherweise nicht mit traversal-wenn nur die Rekursion ist zu berücksichtigen. Und könnte vermieden werden, mit besseren Baum-Erstellung und-Aktualisierung. überprüfen Sie das Konzept der ausgewogenen Bäume, wenn nicht bereits berücksichtigt sind.