web-dev-qa-db-ger.com

Knotensuche im Binärbaum-Überlaufstapel

Ich verwende die folgende Methode, um einen binären Baum mit 300 000 Ebenen zu durchlaufen:

Node* find(int v){
   if(value==v)
      return this;
   else if(right && value<v)
      return right->find(v); 
   else if(left && value>v)
      return left->find(v);
}

Ich erhalte jedoch einen Segmentierungsfehler aufgrund eines Stapelüberlaufs. Gibt es irgendwelche Ideen, wie der tiefe Baum ohne den Aufwand rekursiver Funktionsaufrufe durchlaufen werden kann?

* Mit "Durchqueren" meine ich "Suche nach einem Knoten mit dem angegebenen Wert", nicht durch den ganzen Baum.

18
PerelMan

Ja! Für einen Baum mit 300 000 Ebenen Rekursion vermeiden. Durchlaufen Sie Ihren Baum und finden Sie den Wert iterativ mithilfe einer Schleife.

Binäre Suchbaumdarstellung

           25             // Level 1
        20    36          // Level 2
      10 22  30 40        // Level 3
  .. .. .. .. .. .. .. 
.. .. .. .. .. .. .. ..   // Level n

Nur um das Problem weiter zu klären. Ihr Baum hat eine Tiefe von n = 300.000 Ebenen. Im schlimmsten Fall muss ein Binary Search Tree (BST) ALL der Knoten des Baums besuchen. Dies ist eine schlechte Nachricht, da Worst-Case eine algorithmische O(n) - Zeitkomplexität aufweist. Ein solcher Baum kann Folgendes haben:

2 ~ 300.000 Knoten = ungefähr 9.9701e + 90308 Knoten.


9.9701e + 90308 Knoten ist eine exponentiell massive Anzahl von zu besuchenden Knoten. Mit diesen Nummern wird klar, warum der Call Stack überläuft.


Lösung (iterativer Weg):

Ich gehe davon aus, dass Ihre Node classstruct-DEKLARATION EINE KLASSISCHE STANDARD-GANZZAHL _/_ BST ist. Dann könnten Sie es anpassen und es wird funktionieren:

struct Node {
    int data;
    Node* right;
    Node* left;
};

Node* find(int v) {
    Node* temp = root;  // temp Node* value copy to not mess up tree structure by changing the root
    while (temp != nullptr) {
        if (temp->data == v) {
            return temp;
        }
        if (v > temp->data) {
            temp = temp->right;
        }
        else {
            temp = temp->left;
        }
    }
    return nullptr;
}

Durch diesen iterativen - Ansatz wird Rekursion vermieden, sodass Sie sich nicht die Mühe machen müssen, den Wert in einem so großen Baum rekursiv in Ihrem Programmaufrufstapel zu finden. 

27
Santiago Varela

Eine einfache Schleife, bei der Sie eine Variable vom Typ Node * haben, die Sie auf den nächsten Knoten setzen, und dann erneut eine Schleife ausführen ...
Vergessen Sie nicht den Fall, dass der von Ihnen gesuchte Wert nicht existiert!

9
Rene

Sie können die Rekursion implementieren, indem Sie nicht den Aufrufstapel verwenden, sondern einen benutzerdefinierten Stapel oder etwas Ähnliches. Dies könnte über die vorhandene Vorlage stack erfolgen. Der Ansatz wäre eine while-Schleife, die wiederholt wird, bis der Stapel leer ist. Da die vorhandene Implementierung die Tiefensuche verwendet, können Sie die rekursiven Aufrufe eliminieren hier .

7
Codor

Wenn der Baum, den Sie haben, ein Binärer Suchbaum ist und Sie nur nach einem Knoten suchen möchten, der einen bestimmten Wert hat, dann sind die Dinge einfach: Es ist keine Rekursion erforderlich. Sie können dies auch tun eine einfache Schleife, wie andere darauf hingewiesen haben.

Im allgemeineren Fall, wenn Sie einen Baum haben, der nicht notwendigerweise ein Binary Search Tree ist, und einen full-Durchlauf durchführen wollen davon -, ist der einfachste Weg die Verwendung der Rekursion, aber wie Sie bereits wissen, wenn Der Baum ist sehr tief, dann funktioniert die Rekursion nicht.

Um eine Rekursion zu vermeiden, müssen Sie einen Stapel auf dem C++ - Heapspeicher implementieren. Sie müssen eine neue StackElement-Klasse deklarieren, die ein Element für jede lokale Variable enthält, die Ihre ursprüngliche rekursive Funktion hatte, und ein Element für jeden Parameter, den Ihre ursprüngliche rekursive Funktion akzeptierte. (Möglicherweise können Sie mit weniger Member-Variablen auskommen, Sie können sich jedoch Sorgen machen, nachdem Sie Ihren Code zum Laufen gebracht haben.)

Sie können Instanzen von StackElement in einer Stack-Collection speichern oder Sie können einfach jede von ihnen einen Zeiger auf das übergeordnete Element enthalten, um den Stack vollständig selbst zu implementieren. 

Anstatt Ihre Funktion selbst rekursiv aufzurufen, besteht sie einfach aus einer Schleife. Ihre Funktion tritt in die Schleife ein, wobei die current StackElement mit Informationen über den Stammknoten Ihres Baums initialisiert wird. Der übergeordnete Zeiger ist null, was bedeutet, dass der Stapel leer ist.

An allen Stellen, an denen die rekursive Version Ihrer Funktion sich selbst aufgerufen hat, wird Ihre neue Funktion eine neue Instanz von StackElement zuweisen, sie initialisieren und die Schleife mit dieser neuen Instanz als current -Element wiederholen.

An jeder Stelle, an der die rekursive Version Ihrer Funktion zurückkehrte, wird Ihre neue Funktion die current StackElement freigeben, die auf der obersten Ebene des Stapels platzierte Funktion wird zum neuen current - Element und Wiederholen der Schleife.

Wenn Sie den gewünschten Knoten gefunden haben, brechen Sie einfach die Schleife ab.

Wenn der Knoten Ihres vorhandenen Baums a) eine Verknüpfung zu seinem "übergeordneten" Knoten und b) Benutzerdaten (in denen Sie ein "besuchte" Flag speichern können) unterstützt, müssen Sie alternativ keinen eigenen Stack implementieren Durchlaufen Sie einfach den Baum an Ort und Stelle: In jeder Iteration Ihrer Schleife prüfen Sie zuerst, ob der aktuelle Knoten der Knoten ist, nach dem Sie gesucht haben. Wenn nicht, dann zählen Sie durch Kinder auf, bis Sie eines finden, das noch nicht besucht wurde, und dann besuchen Sie es. Wenn Sie ein Blatt oder einen Knoten erreichen, dessen Kinder alle besucht wurden, gehen Sie zurück, indem Sie dem Link zum übergeordneten Element folgen. Wenn Sie die Freiheit haben, den Baum während des Durchlaufs zu zerstören, brauchen Sie nicht einmal das Konzept der "Benutzerdaten": Sobald Sie mit einem Kindknoten fertig sind, geben Sie ihn frei und machen ihn auf Null.

6
Mike Nakis

Nun, es kann rekursiv für den Preis einer einzelnen zusätzlichen lokalen Variablen und einiger Vergleiche gemacht werden:

Node* find(int v){
  if(value==v)
    return this;
  else if(!right && value<v)
    return NULL;
  else if(!left && value>v)
    return NULL;
  else {
    Node *tmp = NULL;
    if(value<v)
      tmp = right;
    else if(value>v)
      tmp = left;
    return tmp->find(v);
  }
}
3
user2793784

Das Durchlaufen eines binären Baums ist ein rekursiver Prozess, bei dem Sie so lange weitergehen, bis der Knoten, an dem Sie sich gerade befinden, nirgendwohin zeigt.

Es ist erforderlich, dass Sie eine geeignete Basisbedingung benötigen. Etwas, das aussieht:

if (treeNode == NULL)
   return NULL;

Im Allgemeinen erfolgt das Durchlaufen eines Baums auf folgende Weise (in C):

void traverse(treeNode *pTree){
  if (pTree==0)
    return;
  printf("%d\n",pTree->nodeData);
  traverse(pTree->leftChild);
  traverse(pTree->rightChild);
}
0
user4063679