Binärer Suchbaum

In der Informatik ist ein binärer Suchbaum (BST), der manchmal auch einen bestellten genannt werden kann oder binären Baum sortiert hat, eine knotenbasierte binäre Baumdatenstruktur

der die folgenden Eigenschaften hat:

  • Der linke Subbaum eines Knotens enthält nur Knoten mit Schlüsseln weniger als der Schlüssel des Knotens.
  • Der richtige Subbaum eines Knotens enthält nur Knoten mit Schlüsseln, die größer sind als der Schlüssel des Knotens.
  • Sowohl der verlassene als auch die richtigen Subbäume müssen auch binäre Suchbäume sein.

Allgemein ist die durch jeden Knoten vertretene Information eine Aufzeichnung aber nicht ein einzelnes Datenelement. Jedoch, zu sequencing Zwecken, werden Knoten gemäß ihren Schlüsseln aber nicht jedem Teil ihrer verbundenen Aufzeichnungen verglichen.

Der Hauptvorteil von binären Suchbäumen über andere Datenstrukturen besteht darin, dass die zusammenhängenden Sortieren-Algorithmen und Suchalgorithmen solcher als, um Traversal sehr effizient sein kann.

Binäre Suchbäume sind eine grundsätzliche Datenstruktur, die verwendet ist, um abstraktere Datenstrukturen wie Sätze, Mehrsätze und assoziative Reihe zu bauen.

Operationen

Operationen auf einem binären Suchbaum verlangen Vergleiche zwischen Knoten. Diese Vergleiche werden mit Anrufen zu einem comparator gemacht, der ein Unterprogramm ist, das den Gesamtbezug (geradlinige Ordnung) auf irgendwelchen zwei Werten schätzt. Dieser comparator kann abhängig von der Sprache ausführlich oder implizit definiert werden, auf der der BST durchgeführt wird.

Suche

Die Suche eines binären Suchbaums für einen spezifischen Wert kann ein rekursiver oder wiederholender Prozess sein. Diese Erklärung bedeckt eine rekursive Methode.

Wir beginnen, indem wir den Wurzelknoten untersuchen. Wenn der Baum ungültig ist, besteht der Wert, nach dem wir suchen, im Baum nicht. Sonst, wenn der Wert der Wurzel gleichkommt, ist die Suche erfolgreich. Wenn der Wert weniger ist als die Wurzel, suchen Sie den linken Subbaum. Ähnlich, wenn es größer ist als die Wurzel, suchen Sie den richtigen Subbaum. Dieser Prozess wird wiederholt, bis der Wert gefunden wird oder der angezeigte Subbaum ungültig ist. Wenn der gesuchte Wert nicht gefunden wird, bevor ein ungültiger Subbaum erreicht wird, dann muss der Artikel nicht im Baum da sein.

Hier ist der Suchalgorithmus auf der Pythonschlange-Sprache:

  1. 'Knoten' bezieht sich auf den Elternteilknoten in diesem Fall

def search_binary_tree (Knoten, Schlüssel):

wenn Knoten Niemand ist:

geben Sie Niemanden # Schlüssel nicht gefundener zurück

wenn Schlüssel

geben Sie search_binary_tree (node.rightChild, Schlüssel) zurück

sonst: # ist Schlüssel dem Knotenschlüssel gleich

geben Sie node.value # gefundener Schlüssel zurück

</Quelle>

… oder gleichwertiger Haskell:

searchBinaryTree _ NullNode = Nichts

SearchBinaryTree-Schlüssel (Knoten nodeKey nodeValue (leftChild, rightChild)) =

Fall vergleicht Schlüssel nodeKey von

LEUTNANT-> searchBinaryTree Schlüssel leftChild

GT-> searchBinaryTree Schlüssel rightChild

EQ-> Gerade nodeValue

</Quelle>

Diese Operation verlangt O (loggen Sie n) Zeit mit dem durchschnittlichen Fall, aber braucht O (n) Zeit mit dem Grenzfall, wenn der unausgeglichene Baum einer verbundenen Liste (degenerierter Baum) ähnelt.

Das Annehmen, das eine Klasse mit einer Mitglied-Funktion und einem Zeigestock zum Wurzelknoten, der Algorithmus ist, wird auch in Bezug auf eine wiederholende Annäherung leicht durchgeführt. Der Algorithmus geht in eine Schleife ein, und entscheidet ob zum Zweig verlassen oder direkt abhängig vom Wert des Knotens an jedem Elternteilknoten.

bool BinarySearchTree:: Suchen Sie (interne Nummer val)

{\

Knoten *next = das-> Wurzel ;

während (als nächstes! = UNGÜLTIG) {\

wenn (val == als nächstes-> Wert ) {\

kehren Sie wahr zurück;

} sonst wenn (val

als nächstes = als nächstes-> verlassen ;

} sonst {\

als nächstes = als nächstes-> Recht ;

}\

}

//nicht gefundener

kehren Sie falsch zurück;

} </Quelle>

Einfügung

Einfügung beginnt, wie eine Suche beginnen würde; wenn die Wurzel dem Wert nicht gleich ist, suchen wir den verlassenen oder die richtigen Subbäume wie zuvor. Schließlich werden wir einen Außenknoten erreichen und den Wert als sein richtiges oder linkes Kind abhängig vom Wert des Knotens hinzufügen. Mit anderen Worten untersuchen wir die Wurzel und fügen rekursiv den neuen Knoten zum linken Subbaum ein, wenn der neue Wert weniger ist als die Wurzel oder der richtige Subbaum, wenn der neue Wert größer oder gleich der Wurzel ist.

Hier ist, wie eine typische binäre Suchbaumeinfügung in C ++ durchgeführt werden könnte:

/* Fügt den Knoten ein, der auf durch "newNode" in den Subbaum angespitzt ist, der an "treeNode" * / eingewurzelt ist

leerer InsertNode (Node* &treeNode, Knoten *newNode)

{\

wenn (treeNode == UNGÜLTIG)

treeNode = newNode;

sonst, wenn (newNode-> Schlüssel

InsertNode (treeNode->, ist newNode abgereist);

sonst

InsertNode (treeNode-> Recht, newNode);

}\

</Quelle>

Die obengenannte zerstörende Verfahrensvariante modifiziert den Baum im Platz. Es verwendet nur unveränderlichen Raum, aber die vorherige Version des Baums wird verloren. Wechselweise, als im folgenden Pythonschlange-Beispiel, können wir alle Vorfahren des eingefügten Knotens wieder aufbauen; jede Verweisung auf die ursprüngliche Baumwurzel bleibt gültig, den Baum eine beharrliche Datenstruktur machend:

def binary_tree_insert (Knoten, Schlüssel, Wert):

wenn Knoten Niemand ist:

geben Sie TreeNode (Niemand, Schlüssel, Wert, Niemand) zurück

wenn Schlüssel == node.key:

geben Sie TreeNode (node.left, Schlüssel, Wert, node.right) zurück

wenn Schlüssel

Der Teil, der Gebrauch Θ wieder aufgebaut wird (loggen n), der Raum im durchschnittlichen Fall und O (n) im Grenzfall (sieh große-O Notation).

In jeder Version verlangt diese Operation Zeit, die zur Höhe des Baums im Grenzfall proportional ist, der O ist (loggen Sie n) Zeit mit dem durchschnittlichen Fall über alle Bäume, aber O (n) Zeit mit dem Grenzfall.

Eine andere Weise, Einfügung zu erklären, besteht darin, dass, um einen neuen Knoten in den Baum einzufügen, sein Wert im Vergleich zum Wert der Wurzel erst ist. Wenn sein Wert weniger ist als die Wurzel, ist es dann im Vergleich zum Wert des linken Kindes der Wurzel. Wenn sein Wert größer ist, ist es im Vergleich zum richtigen Kind der Wurzel. Dieser Prozess geht weiter, bis der neue Knoten im Vergleich zu einem Blatt-Knoten ist, und dann es als das Recht dieses Knotens oder linkes Kind abhängig von seinem Wert hinzugefügt wird.

Es gibt andere Weisen, Knoten in einen binären Baum einzufügen, aber das ist die einzige Weise, Knoten an den Blättern einzufügen und zur gleichen Zeit die BST Struktur zu bewahren.

Hier ist eine wiederholende Annäherung an das Einfügen in einen binären Suchbaum in Java:

privater Knoten m_root;

öffentlicher leerer Einsatz (int Daten) {\

wenn (m_root == ungültig) {\

m_root = neuer TreeNode (Daten, ungültig, ungültig);

kehren Sie zurück;

}\

Knotenwurzel = m_root;

während (Wurzel! = ungültig) {\

//Nicht derselbe Wert zweimal

wenn (Daten == root.getData ) {\

kehren Sie zurück;

} sonst wenn (Daten

Unten ist eine rekursive Annäherung an die Einfügungsmethode.

privater Knoten m_root;

öffentlicher leerer Einsatz (int Daten) {\

wenn (m_root == ungültig) {\

m_root = TreeNode (Daten, ungültig, ungültig)

;

} sonst {\

internalInsert (m_root, Daten);

}\

}\

private statische Leere internalInsert (Knotenknoten, int Daten) {\

//Nicht derselbe Wert zweimal

wenn (Daten == node.getValue ) {\

kehren Sie zurück;

} sonst wenn (Daten

Auswischen

Es gibt drei mögliche Fälle, um in Betracht zu ziehen:

  • Das Löschen eines Blattes (Knoten ohne Kinder): Das Löschen eines Blattes ist leicht, weil wir es einfach vom Baum entfernen können.
  • Das Löschen eines Knotens mit einem Kind: Entfernen Sie den Knoten und ersetzen Sie ihn durch sein Kind.
  • Das Löschen eines Knotens mit zwei Kindern: Nennen Sie den Knoten, der N zu löschen ist. Löschen Sie N nicht. Wählen Sie statt dessen entweder seinen um Nachfolger-Knoten oder seinen um Vorgänger-Knoten, R. Ersetzen Sie den Wert von N mit dem Wert von R, dann löschen Sie R.

Als mit allen binären Bäumen, ein Knoten, um Nachfolger ganz links Kind seines richtigen Subbaums, und ein Knoten ist, um Vorgänger das niedrigstwertige Kind seines linken Subbaums ist. In jedem Fall wird dieser Knoten Null oder Kinder haben. Löschen Sie es gemäß einem der zwei einfacheren Fälle oben.

Durchweg mit, um Nachfolger oder, um der Vorgänger für jedes Beispiel des Zwei-Kinder-Falls zu einem unausgeglichenen Baum, so gute Durchführungen führen kann, Widersprüchlichkeit zu dieser Auswahl hinzufügt.

Laufzeit-Analyse:

Obwohl diese Operation den Baum unten zu einem Blatt nicht immer überquert, ist das immer eine Möglichkeit; so im Grenzfall verlangt man zur Höhe des Baums proportionale Zeit. Es verlangt mehr nicht, selbst wenn der Knoten zwei Kinder hat, da es noch einem einzelnen Pfad folgt und keinen Knoten zweimal besucht.

Hier ist der Code in der Pythonschlange:

def findMin (selbst):

Findet das kleinste Element, das ein Kind von *self * ist

current_node = selbst

während current_node.left_child:

current_node = current_node.left_child

geben Sie current_node zurück

def replace_node_in_parent (selbst, new_value=None):

Entfernt die Verweisung auf *self* von *self.parent* und ersetzt es durch *new_value*.

wenn self.parent:

wenn selbst == selbst parent.left_child:

selbst parent.left_child = new_value

sonst:

selbst parent.right_child = new_value

wenn new_value:

new_value.parent = self.parent

def binary_tree_delete (selbst, Schlüssel):

wenn Schlüssel

selbst right_child.binary_tree_delete (Schlüssel)

sonst: # löschen den Schlüssel hier

wenn selbst left_child und selbst right_child: #, wenn beide Kinder anwesend sind

# bekommen den kleinsten Knoten es ist größer als *self *

Nachfolger = selbst right_child.findMin

self.key = successor.key

#, wenn *successor* ein Kind hat, ersetzen Sie ihn dadurch

# an diesem Punkt kann es nur einen *right_child * haben

#, wenn es keine Kinder hat, wird *right_child* "Niemand" sein

Nachfolger replace_node_in_parent (Nachfolger right_child)

elif selbst left_child oder selbst right_child: #, wenn der Knoten nur ein Kind hat

wenn selbst left_child:

selbst replace_node_in_parent (selbst left_child)

sonst:

selbst replace_node_in_parent (selbst right_child)

sonst: # hat dieser Knoten keine Kinder

selbst replace_node_in_parent (Niemand)

</Quelle>

Quellcode in C ++ (von http://www.algolist.net/Data_structures/Binary_search_tree). Diese URL-ADRESSE erklärt auch die Operation nett mit Diagrammen.

bool BinarySearchTree:: Entfernen Sie (int Wert) {\

wenn (wurzeln == UNGÜLTIG ein)

, kehren Sie falsch zurück;

sonst {\

wenn (Wurzel-> getValue == Wert) {\

BSTNode auxRoot (0);

auxRoot.setLeftChild (Wurzel);

BSTNode* removedNode = Wurzel-> zieht (Wert, &auxRoot) um;

wurzeln Sie = auxRoot.getLeft ein;

wenn (removedNode! = UNGÜLTIG) {\

löschen Sie removedNode;

kehren Sie wahr zurück;

} sonst

kehren Sie falsch zurück;

} sonst {\

BSTNode* removedNode = Wurzel-> zieht (Wert, UNGÜLTIG) um;

wenn (removedNode! = UNGÜLTIG) {\ löschen Sie removedNode; kehren Sie wahr zurück; } sonst kehren Sie falsch zurück; }\

}\

}\

BSTNode* BSTNode:: Entfernen Sie (int Wert, BSTNode *parent) {\

wenn (Wert

wenn (verlassen! = UNGÜLTIG)

kehren Sie nach links> zurück ziehen (Wert, das) um;

sonst

kehren Sie UNGÜLTIG zurück;

} sonst wenn (Wert> das-> Wert) {\

wenn (Recht! = UNGÜLTIG)

kehren Sie zurück Recht-> ziehen (Wert, das) um;

sonst kehren Sie UNGÜLTIG zurück;

} sonst {\

wenn (verlassen! = UNGÜLTIG && Recht! = UNGÜLTIG) {\

das-> schätzt = Recht-> minValue ;

kehren Sie zurück Recht-> ziehen (das-> Wert, das) um;

} sonst wenn (ist Elternteil-> == das abgereist), {\

Elternteil-> ist = abgereist (verlassen! = UNGÜLTIG)? verlassen: Recht;

geben Sie das zurück;

} sonst wenn (Elternteil-> Recht == das) {\

Elternteil-> Recht = (verlassen! = UNGÜLTIG)? verlassen: Recht;

geben Sie das zurück; }\ }\}\

int BSTNode:: minValue {\

wenn (verlassen == UNGÜLTIG)

geben Sie Wert zurück;

sonst

kehren Sie nach links> minValue zurück;

}\</Quelle>

Traversal

Sobald der binäre Suchbaum geschaffen worden ist, können seine Elemente wiederbekommen werden, um durch das rekursive Überqueren des linken Subbaums des Wurzelknotens, das Zugreifen auf den Knoten selbst, dann rekursiv das Überqueren des richtigen Subbaums des Knotens, das Fortsetzen dieses Musters mit jedem Knoten im Baum, weil darauf rekursiv zugegriffen wird. Als mit allen binären Bäumen kann man ein Vorordnungstraversal oder ein Postordnungstraversal führen, aber keiner wird wahrscheinlich für binäre Suchbäume nützlich sein.

Der Code dafür, um das Traversal in der Pythonschlange unten gegeben wird. Es wird Rückrufaktion nach jedem Knoten im Baum nennen.

def traverse_binary_tree (Knoten, Rückrufaktion):

wenn Knoten Niemand ist:

geben Sie zurück

traverse_binary_tree (node.leftChild, Rückrufaktion)

Rückrufaktion (node.value)

traverse_binary_tree (node.rightChild, Rückrufaktion)

</Quelle>

Traversal verlangt O (n) Zeit, da es jeden Knoten besuchen muss. Dieser Algorithmus ist auch O (n), so ist es asymptotisch optimal.

Der Code dafür, um das Traversal auf der Sprache C unten gegeben wird.

leerer InOrderTraversal (struct Knoten *n)

{\

Struct-Knoten *Cur, *Pre;

wenn (n == UNGÜLTIG)

kehren Sie zurück;

Köter = n;

während (Köter! = UNGÜLTIG)

{\

wenn (Köter-> lptr == UNGÜLTIG)

{\

printf (" \t%d", Köter-> val);

Köter = Köter-> rptr;

}\

sonst

{\

Pre = Köter-> lptr;

während (Prä-> rptr! =NULL && Prä-> rptr! = Köter)

Pre = Prä-> rptr;

wenn (Prä-> rptr == UNGÜLTIG)

{\

Prä-> rptr = Köter;

Köter = Köter-> lptr;

}\

sonst

{\

Prä-> rptr = UNGÜLTIG;

printf (" \t%d", Köter-> val);

Köter = Köter-> rptr;

}\ }\

}\

}\</Quelle>

Sorte

Ein binärer Suchbaum kann verwendet werden, um einen einfachen, aber effizienten Sortieren-Algorithmus durchzuführen. Ähnlich heapsort fügen wir alle Werte ein, die wir in sortieren möchten, strukturieren neue bestellte Daten einen binären Suchbaum in diesem Fall - und überqueren ihn dann in der Ordnung, unser Ergebnis bauend:

def build_binary_tree (Werte):

Baum = Niemand

für v in Werten:

Baum = binary_tree_insert (Baum, v)

geben Sie Baum zurück

def get_inorder_traversal (Wurzel):

Gibt eine Liste zurück, die alle Werte im Baum enthält, an *root* anfangend.

Überquert den Baum um (leftChild, Wurzel, rightChild).

resultieren Sie = []

traverse_binary_tree (Wurzel, Lambda-Element: result.append (Element))

geben Sie Ergebnis zurück

</Quelle>

Die Grenzfall-Zeit dessen ist - wenn Sie sie eine sortierte Liste von Werten füttern, kettet sie sie in eine verbundene Liste ohne linke Subbäume. Zum Beispiel, gibt den Baum nach.

Es gibt mehrere Schemas, um diesen Fehler mit einfachen binären Bäumen zu überwinden; das allgemeinste ist der selbstbalancierende binäre Suchbaum. Wenn dieses dasselbe Verfahren mit solch einem Baum getan wird, ist die gesamte Grenzfall-Zeit O (nlog n), der für eine Vergleich-Sorte asymptotisch optimal ist. In der Praxis hat die schlechte Leistung des geheimen Lagers und oben rechtzeitig beigetragen, und Raum für eine baumbasierte Sorte (besonders für die Knotenzuteilung) machen es untergeordnet anderen asymptotisch optimalen Sorten wie heapsort für das statische Listensortieren. Andererseits ist es eine der effizientesten Methoden des zusätzlichen Sortierens, Sachen zu einer Liste mit der Zeit hinzufügend, während es die Liste sortiert zu jeder Zeit hält.

Typen

Es gibt viele Typen von binären Suchbäumen. AVL Bäume und rot-schwarze Bäume sind beide Formen, binäre Suchbäume zu selbsterwägen. Ein gespreizter Baum ist ein binärer Suchbaum, der sich automatisch bewegt, oft hat auf Elemente näher zur Wurzel zugegriffen. In einem treap (Baumhaufen) hält jeder Knoten auch (zufällig gewählt) Vorrang und der Elternteilknoten haben höheren Vorrang als seine Kinder. Tango-Bäume sind für schnelle Suchen optimierte Bäume.

Zwei andere Titel, die binäre Suchbäume beschreiben, sind die eines ganzen und degenerierten Baums.

Ein ganzer Baum ist ein Baum mit n Niveaus, wo für jedes Niveau d. Das bedeutet, dass alle möglichen Knoten an diesen Niveaus bestehen. Eine zusätzliche Voraussetzung für einen ganzen binären Baum ist, dass für das n-te Niveau, während jeder Knoten nicht bestehen muss, sich die Knoten, die wirklich bestehen, vom linken bis Recht füllen müssen.

Ein degenerierter Baum ist ein Baum wo für jeden Elternteilknoten, es gibt nur einen verbundenen Kinderknoten. Was das bedeutet, ist, dass in einer Leistungsmessung sich der Baum im Wesentlichen wie eine verbundene Listendatenstruktur benehmen wird.

Leistungsvergleiche

D. A. Heger (2004) hat einen Leistungsvergleich von binären Suchbäumen präsentiert. Wie man fand, hatte Treap die beste durchschnittliche Leistung, während, wie man fand, rot-schwarzer Baum den kleinsten Betrag von Leistungsschwankungen hatte.

Optimale binäre Suchbäume

Wenn wir beim Ändern eines Suchbaums nicht planen, und wir genau wissen, wie oft auf jeden Artikel zugegriffen wird, können wir einen optimalen binären Suchbaum bauen, der ein Suchbaum ist, wo die durchschnittlichen Kosten, einen Artikel (die erwarteten Suchkosten) nachzuschlagen, minimiert werden.

Selbst wenn wir nur Schätzungen der Suchkosten haben, kann solch ein System lookups durchschnittlich beträchtlich beschleunigen. Zum Beispiel, wenn Sie einen BST von englischen in einem Rechtschreibprüfprogramm verwendeten Wörtern haben, könnten Sie den Baum erwägen, der auf der Wortfrequenz in der Textkorpora gestützt ist, Wörter wie die Nähe die Wurzel und Wörter wie agerasia in der Nähe von den Blättern legend. Solch ein Baum könnte im Vergleich zu Bäumen von Huffman sein, die sich ähnlich bemühen, oft verwendete Sachen in der Nähe von der Wurzel zu legen, um eine dichte Informationsverschlüsselung zu erzeugen; jedoch versorgen Bäume von Huffman nur Datenelemente in Blättern, und diese Elemente brauchen nicht bestellt zu werden.

Wenn wir die Folge nicht wissen, in der auf die Elemente im Baum im Voraus zugegriffen wird, können wir gespreizte Bäume verwenden, die asymptotisch so gut sind wie jeder statische Suchbaum, den wir für jede besondere Folge von lookup Operationen bauen können.

Alphabetische Bäume sind Bäume von Huffman mit der zusätzlichen Einschränkung auf Bestellung, oder, gleichwertig, Suchbäume mit der Modifizierung, dass alle Elemente in den Blättern versorgt werden. Schnellere Algorithmen bestehen für optimale alphabetische binäre Bäume (OABTs).

Beispiel:

Verfahren-Optimum-Suchbaum (f, f', c):

für j = 0 zu n tun

c [j, j] = 0, F [j, j] = f'j

für d = 1 zu n tun

weil ich = 0 zu (n  d) tue

j = ich + d

F [ich, j] = F [ich, j  1] + f' + f'j

c [ich, j] = MINUTE (ich

Siehe auch

Weiterführende Literatur

Links


Bücher von Chroniken / Binärer Baum
Impressum & Datenschutz