Suchbäume kann man sich als eine dynamische Version eines sortierten Arrays vorstellen.
Aber sortierte Arrays unterstützen keine schnelle Implementierung von Insert
und Delete
.
Search
: Für einen Schlüssel $k$, gib einen Zeiger zu einem Objekt in der Datenstruktur zurück, das den Schlüssel $k$ hat (oder melde, dass kein solches Objekt existiert). Implementiert wird dies auf einem sortierten Array mittelsBinarySearch
.
Min
(Max
): Gib einen Zeiger zu einem Objekt in der Datenstruktur zurück, das den kleinesten (größten) Schlüssel hat. Implementiert wird dies auf einem sortierten Array mittels "gib erstes (letztes) Element zurück".
Predecessor
(Successor
) d.h. Vorgänger(Nachfolger): Für einen Zeiger zu einem Objekt in der Datenstruktur gib einen Zeiger zu einem Objekt zurück, das den nächst-kleineren (nächst-größeren) Schlüssel besitzt. Wenn das gegebene Objekt den kleinsten (größten) Schlüssen hat, gib "none" ( bzw. Nullzeiger etc.) zurück. Implementiert wird dies auf einem sortierten Array mittelsSearch
und anschließend das vorherige (nächste) Element des Arrays zurückgeben.
OutputSorted
: Gib die Objekte in der Datenstruktur nacheinander aus in der Ordnung Ihrer Schlüssel. Implementiert wird dies auf einem sortierten Array indem einfach die Elemente in ihrer Reihenfolge zurückgegeben werden.
Select
: Für eine Zahl $i$ zwischen $1$ und der Anzahl der Objekte in der Datenstruktur, gib das Objekt zurück, das den $i$-kleinsten Schlüssel besitzt. Implementiert wird dies auf einem sortierten Array einfach mittels Rückgabe des Elements mit dem Index $i$.
Rank
: Für einen Schlüssel(wert) $k$ gib den Index des Objekts in dem Array zurück, mit einem Schlüsselwert maximal $k$. Implementation mit der Annahme "keine Duplikate im Array": Suche das Objekt mit Schlüsselwert $k$ und gib dessen Index zurück falls gefunden. Wenn festgestellt wird, das $k$ zwischen dem $i$-ten und $i+1$-Element liegen würde, gib den Index $i$ zurück.
Operation | Laufzeit |
---|---|
Seach |
$O(\log n)$ |
Min (Max ) |
$O(1)$ |
Predecessor (Successor ) |
$O(\log n)$ |
OutputSorted |
$O(n)$ |
Select |
$O(1)$ |
Rank |
$O(\log n)$ |
Insert
: Füge ein neues Objekt $x$ der Datenstruktur hinzu.
Delete
: Für ein gegebenen Schlüssel, lösche das Objekt aus der Datenstruktur mit dem Schlüssel (falls es existiert).
Beachte: Von balacierten Suchbäumen werden diese Operationen dagegen gut unterstützt.
Operation | sortiertes Array | balancierte Suchbäume |
---|---|---|
Seach |
$O(\log n)$ | $O(\log n)$ |
Min (Max ) |
$O(1)$ | $O(\log n)$ |
Predecessor (Successor ) |
$O(\log n)$ | $O(\log n)$ |
OutputSorted |
$O(n)$ | $O(n)$ |
Select |
$O(1)$ | $O(\log n)$ |
Rank |
$O(\log n)$ | $O(\log n)$ |
Insert |
$O(n)$ | $O(\log n)$ |
Delete |
$O(n)$ | $O(\log n)$ |
Wenn die Anwendung eine geordnete Repräsentation einer sich dynamisch ändernden Menge benötigt, ist ein balancierter Suchbaum die geeignete Datenstruktur.
Implementation als binärer Suchbaum.
Ziel: Schnelle Suche nach dem Objekt-Schlüssel
Für jedes Objekt $x$ haben alle linken Nachfahren, d.h. die Objekte im linken Unterbaum, Schlüsselwerte kleiner als $x$.
Für jedes Objekt $x$ haben alle rechten Nachfahren, d.h. die Objekte im rechten Unterbaum, Schlüsselwerte größer als $x$.
# Hier Schlüsselwerte
tree = BinaryTree(nodes=[3, 1, 5, None, 2, 4, None])
tree.plot()
Image(filename='./tree.png')
Da binäre Bäume beliebige Struktur haben können, werden sie (typischerweise) mit Zeigern zwischen den Knoten implementiert.
Für jeden Knoten wird ein Zeiger zum Parent (Eltern) und zum linken und rechten Child (Kind) gespeichert.
Knoten | Parent | Left | Right | |
---|---|---|---|---|
1 | 3 | null | 2 | |
2 | 1 | null | null | |
3 | null | 1 | 5 | |
4 | 5 | null | null | |
5 | 3 | 4 | null |
Die Höhe (Tiefe) eines Baums ist die Länge des maximalen Wegs von der Wurzel zu einem Blatt des Baums.
Wir groß ist die minimale/maximale Höhe (height) eines Baumes mit $n$-Knoten?
Annahme: Alle Schlüssel sind eindeutig. Falls nicht müssen die Operationen/Bäume ggf. leicht modifiziert werden.
Search
in $O(\text{height})$¶
Search
: für einen Schlüssel $k$, gib einen Zeiger zu einem Objekt in der Datenstruktur zurück, das den Schlüssel $k$ hat (oder melde, dass kein solches Objekt existiert).
Min
(Max
) in $O(\text{height})$¶
Min
(Max
): gib einen Zeiger zu einem Objekt in der Datenstruktur zurück, das den kleinesten (größten) Schlüssel hat.
Predecessor
in $O(\text{height})$¶
Predecessor
(Successor
) d.h. Vorgänger(Nachfolger): Für einen Zeiger zu einem Objekt in der Datenstruktur gib einen Zeiger zu einem Objekt zurück, das den nächst-kleineren (nächst-größeren) Schlüssel besitzt. Wenn das gegebene Objekt den kleinsten (größten) Schlüssen hat, gib "none" (z.B. den Nullzeiger) zurück.
Max
auf dem linken Unterbaum zurück.Analog für Successor
.
OutputSorted
in $O(n)$¶
OutputSorted
: Gib die Objekte in der Datenstruktur nacheinander aus in der Ordnung Ihrer Schlüssel.
Prozedur
OutputSorted
:
- Rufe rekursiv
OutputSorted
auf dem linken Unterbaum auf (beginnend mit dem Wurzelknoten).- Gib das Wurzelobjekt zurück.
- Rufe rekursiv
OutputSorted
auf dem rechten Unterbaum auf.
Insert
in $O(\text{height})$¶
Insert
: Füge ein neues Objekt $x$ der Datenstruktur hinzu.
Gehe im Baum passend zum Schlüssel $k$ des einzufügenden Objektes $x$ nach unten (wie bei Search
) bis ein Null-Zeiger erreicht ist, d.h.
Ersetze den Null-Zeiger mit einem Zeiger auf den neuen Knoten des einzufügenden Objektes $x$. Setze die Kind-Zeiger des neuen Knotens auf Null-Zeiger.
Delete
in $O(\text{height})$¶Delete
: Für ein gegebenen Schlüssel $k$, lösche das Objekt aus der Datenstruktur mit dem Schlüssel (falls es existiert).
Search
um einen Knoten (ein Objekt) $x$ mit Schlüssel $k$ zu finden. Falls kein Knoten gefunden wurde, beende die Prozedur.Rank
und Select
mit Augmentierung des Baums¶Für die Operationen Rank
und Select
kann man die Knoten des Suchbaums mit dem Metadaten "Anzahl der Knoten des Unterbaumes" (size
) erweitern. So können diese auch in $O(height)$ durchgeführt werden.
Hierfür müssen die Operationen, die den Baum ändern (wie Insert
und Delect
) entsprechend erweitert werden.
Rank
und Select
zu implementieren.¶Wie kann Rank
und Select
durchgeführt werden, wenn die Information size
("Anzahl der Knoten des Unterbaumes") an jedem Knoten des Baums vorhanden ist?
Die Höhe eines Baums bestimmt die Laufzeit der Operationen. Im Idealfall ist die Höhe $\log (n)$. Durch Ausbalancieren kann dieser Ideallfall angenähert/erreicht werden.
Beispiele für solche ausbalancierten Bäume sind:
Die gängigste Technik zum Ausbalancieren ist Rotation, siehe [Rough2] Für weitere Implementierungsdetails siehe z.B. [Corman]
[Corman] Introduction to Algorithms von T. Corman, C. Leiserson, R. Rivest und C. Stein, second edition, MIT Press.
[Rough2] T. Roughgarden, Algorithms Illuminated, Part 1: The Basics