Wann werden die Strategien zum Durchlaufen des binären Suchbaums für Vorbestellungen, Nachbestellungen und Inorder verwendet?

97

Ich habe kürzlich festgestellt, dass ich, obwohl ich in meinem Leben viel von BST verwendet habe, noch nie darüber nachgedacht habe, etwas anderes als Inorder Traversal zu verwenden (obwohl ich weiß und weiß, wie einfach es ist, ein Programm für die Verwendung von Pre- / Post-Order-Traversal anzupassen).

Als ich dies bemerkte, zog ich einige meiner alten Lehrbücher für Datenstrukturen heraus und suchte nach Gründen für die Nützlichkeit von Durchläufen vor und nach der Bestellung - sie sagten jedoch nicht viel.

Was sind einige Beispiele für die praktische Verwendung von Vorbestellung / Nachbestellung? Wann ist es sinnvoller als in Ordnung?

John Humphreys - w00te
quelle

Antworten:

135

Verwendung der Vorbestellungs-, In-Order- und Nachbestellungs-Traversal-Strategie

Bevor Sie verstehen können, unter welchen Umständen Vorbestellung, Reihenfolge und Nachbestellung für einen Binärbaum verwendet werden müssen, müssen Sie genau verstehen, wie jede Durchquerungsstrategie funktioniert. Verwenden Sie den folgenden Baum als Beispiel.

Die Wurzel des Baums ist 7 , der Knoten ganz links ist 0 , der Knoten ganz rechts ist 10 .

Geben Sie hier die Bildbeschreibung ein

Durchlauf vorbestellen :

Zusammenfassung: Beginnt an der Wurzel ( 7 ) und endet am äußersten rechten Knoten ( 10 )

Durchlaufsequenz: 7, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10

In-Order-Traversal :

Zusammenfassung: Beginnt am äußersten linken Knoten ( 0 ) und endet am äußersten rechten Knoten ( 10 )

Durchlaufsequenz: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

Nachbestellungsdurchlauf :

Zusammenfassung: Beginnt mit dem Knoten ganz links ( 0 ) und endet mit der Wurzel ( 7 )

Durchlaufsequenz: 0, 2, 4, 6, 5, 3, 1, 8, 10, 9, 7

Wann ist Pre-Order, In-Order oder Post-Order zu verwenden?

Die vom Programmierer ausgewählte Durchlaufstrategie hängt von den spezifischen Anforderungen des zu entwerfenden Algorithmus ab. Das Ziel ist die Geschwindigkeit. Wählen Sie also die Strategie, mit der Sie die Knoten erhalten, die Sie am schnellsten benötigen.

  1. Wenn Sie wissen, dass Sie die Wurzeln erforschen müssen, bevor Sie Blätter untersuchen, wählen Sie eine Vorbestellung, da Sie vor allen Blättern auf alle Wurzeln stoßen.

  2. Wenn Sie wissen, dass Sie alle Blätter vor Knoten untersuchen müssen, wählen Sie Nachbestellung , da Sie keine Zeit damit verschwenden, Wurzeln bei der Suche nach Blättern zu untersuchen.

  3. Wenn Sie wissen, dass der Baum eine inhärente Sequenz in den Knoten hat und Sie den Baum wieder in seine ursprüngliche Sequenz reduzieren möchten, sollte eine Durchquerung in der richtigen Reihenfolge verwendet werden. Der Baum würde auf die gleiche Weise abgeflacht, wie er erstellt wurde. Bei einem Durchlauf vor oder nach der Bestellung wird der Baum möglicherweise nicht in die Reihenfolge zurückgespult, in der er erstellt wurde.

Rekursive Algorithmen für Pre-Order, In-Order und Post-Order (C ++):

struct Node{
    int data;
    Node *left, *right;
};
void preOrderPrint(Node *root)
{
  print(root->name);                                  //record root
  if (root->left != NULL) preOrderPrint(root->left);  //traverse left if exists
  if (root->right != NULL) preOrderPrint(root->right);//traverse right if exists
}

void inOrderPrint(Node *root)
{
  if (root.left != NULL) inOrderPrint(root->left);   //traverse left if exists
  print(root->name);                                 //record root
  if (root.right != NULL) inOrderPrint(root->right); //traverse right if exists
}

void postOrderPrint(Node *root)
{
  if (root->left != NULL) postOrderPrint(root->left);  //traverse left if exists
  if (root->right != NULL) postOrderPrint(root->right);//traverse right if exists
  print(root->name);                                   //record root
}
Eric Leschinski
quelle
3
Was ist mit nicht rekursiven Durchquerungen? Es scheint mir, dass es viel einfacher ist, einen Baum nicht rekursiv in der Vorreihenfolge zu durchlaufen, als in der Reihenfolge / Nachbestellung, da es nicht erforderlich ist, zu vorherigen Knoten zurückzukehren.
bluenote10
@ bluenote10 Kannst du näher erläutern, was du meinst? In der Vorbestellung "kehren" Sie immer noch zu einem Knoten zurück, um sein rechtes Kind zu verarbeiten, nachdem Sie sein linkes Kind verarbeitet haben. Sicher, Sie könnten eine Warteschlange mit "noch nicht besuchten Knoten" verwenden, aber das ist wirklich nur der Austausch von implizitem (Stapel-) Speicher gegen eine explizite Warteschlange. Bei allen Traversal-Methoden müssen sowohl das linke als auch das rechte Kind verarbeitet werden. Dies bedeutet, dass Sie nach dem Ausführen eines dieser Verfahren zum Elternteil "zurückkehren" müssen.
Joshua Taylor
@JoshuaTaylor: Ja, sie sind alle dieselbe Komplexitätsklasse, aber wenn Sie sich typische Implementierungen ansehen , ist die Nachbestellung wahrscheinlich etwas schwieriger.
bluenote10
2
Die Traverse vor der Bestellung gibt die Knotenwerte in einer Einfügefolge an. Wenn Sie eine Kopie des Baums erstellen möchten, müssen Sie den Quellbaum auf diese Weise durchlaufen. In-Order-Traverse gibt die sortierten Knotenwerte an. Bei der Nachbestellungsüberquerung können Sie mit dieser Methode den gesamten Baum löschen, da dieser zuerst die Blattknoten besucht.
Albin
Ich denke, es ist wahr, auch wenn der Baum nicht richtig geordnet ist. Ich meine, in der Reihenfolge würde die sortierte Sequenz nicht angegeben, wenn die Sequenz zuerst nicht sortiert wäre.
CodeYogi
29

Vorbestellung: Zum Erstellen einer Kopie eines Baums. Wenn Sie beispielsweise eine Replik eines Baums erstellen möchten, platzieren Sie die Knoten in einem Array mit einer Vorbestellungsdurchquerung. Führen Sie dann für jeden Wert im Array einen Einfügevorgang für einen neuen Baum aus. Am Ende erhalten Sie eine Kopie Ihres ursprünglichen Baums.

In der Reihenfolge :: Wird verwendet, um die Werte der Knoten in einer BST in nicht abnehmender Reihenfolge abzurufen.

Post-Bestellung: : Gebrauchte einen Baum von Blatt zu root löschen

Viraj Nimbalkar
quelle
2
Dies ist eine großartige, präzise Antwort, die mir geholfen hat, Anwendungsfälle vor und nach der Bestellung zu verstehen. Es mag zwar offensichtlich sein, dass die Frage dies direkt erwähnt, aber beachten Sie, dass dies bei binären Suchbäumen der Fall ist und nicht unbedingt für allgemeine binäre Bäume funktioniert. Beispielsweise können Sie nicht unbedingt die Vorbestellungsdurchquerung verwenden, um einen allgemeinen Binärbaum zu kopieren, da die Einfügelogik während des Kopiervorgangs nicht funktioniert.
Markckim
7
In der Reihenfolge
::
26

Wenn ich einfach das hierarchische Format des Baums in einem linearen Format ausdrucken möchte, würde ich wahrscheinlich die Vorbestellungsdurchquerung verwenden. Beispielsweise:

- ROOT
    - A
         - B
         - C
    - D
         - E
         - F
             - G
Oliver Charlesworth
quelle
4
Oder in einer TreeViewKomponente in einer GUI-Anwendung.
Svick
4

Vor- und Nachbestellung beziehen sich auf rekursive Top-Down- bzw. Bottom-Up-Algorithmen. Wenn Sie einen bestimmten rekursiven Algorithmus iterativ auf Binärbäume schreiben möchten, tun Sie dies im Wesentlichen.

Beachten Sie außerdem, dass Sequenzen vor und nach der Bestellung zusammen den vorliegenden Baum vollständig spezifizieren und eine kompakte Codierung ergeben (zumindest für spärliche Bäume).

Raphael
quelle
1
Ich denke, Sie versuchen, etwas Wichtiges zu sagen. Können Sie bitte die erste Hälfte erklären?
CodeYogi
@CodeYogi Was genau müssen Sie erklären?
Raphael
1
"Vor- und Nachbestellung beziehen sich auf rekursive Top-Down- und Bottom-Up-Algorithmen" Ich denke, Sie möchten sagen, dass im ersten Fall der Knoten verarbeitet wird, bevor eine der rekursiven Methoden aufgerufen wird, und umgekehrt in der letzteren, rechts ?
CodeYogi
@ CodeYogi Ja, im Grunde.
Raphael
2

Es gibt unzählige Orte, an denen dieser Unterschied eine echte Rolle spielt.

Eine großartige, auf die ich hinweisen werde, ist die Codegenerierung für einen Compiler. Betrachten Sie die Aussage:

x := y + 32

Die Art und Weise, wie Sie Code dafür generieren würden, besteht (natürlich naiv) darin, zuerst Code zum Laden von y in ein Register, zum Laden von 32 in ein Register und dann zum Generieren einer Anweisung zum Hinzufügen der beiden zu generieren. Da sich etwas in einem Register befinden muss, bevor Sie es manipulieren (nehmen wir an, Sie können immer konstante Operanden ausführen, aber was auch immer), müssen Sie dies auf diese Weise tun.

Im Allgemeinen reduzieren sich die Antworten auf diese Frage im Wesentlichen darauf: Der Unterschied ist wirklich wichtig, wenn eine gewisse Abhängigkeit zwischen der Verarbeitung verschiedener Teile der Datenstruktur besteht. Sie sehen dies beim Drucken der Elemente, beim Generieren von Code (der externe Status macht den Unterschied, Sie können dies natürlich auch monadisch anzeigen) oder wenn Sie andere Arten von Berechnungen über die Struktur durchführen, die Berechnungen in Abhängigkeit von den zuerst verarbeiteten untergeordneten Elementen beinhalten .

Kristopher Micinski
quelle