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 mittelsSearchund 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
OutputSortedauf dem linken Unterbaum auf (beginnend mit dem Wurzelknoten).- Gib das Wurzelobjekt zurück.
- Rufe rekursiv
OutputSortedauf 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