3.4 Treaps: Zufällige Einfüge-Reihenfolge simulieren
WennIn diesem Abschnitt lernen wir eine recht einfache Datenstruktur kennen, die unseren BST so aussehen lässt, als seien die Elemente in zufälliger Reihenfolge eingefügt worden, und erreicht damit eine effiziente Laufzeit beim Suchen, Einfügen und Löschen.
Gehen wir einen Schritt zurück. Ein Binärbaum ist entweder
ein leerer Baum
So ein Binärbaum ist ein binärer Suchbaum (BST), wenn
jeder innere Knoten (also der nicht ein Blatt ist) einen Schlüssel
Heaps
Zusätzlich verlangen wir in diesem Kapitel, dass alle Knoten unterschiedliche Gewichte haben.
Stellen Sie sich nun vor, wir hätten einen Binärbaum u
einen
Schlüssel key(u)
und auch ein
Gewicht weight(u)
hat. Wenn
{(a, 84),
(b, 29),
(c, 95),
(d, 47),
(e, 12),
(f, 86),
(g, 17),
(h, 94)}
Die blauen Buchstaben sind die Keys, die roten Zahlen sind die Gewichte.
Erklären Sie ihrem Sitznachbarn, wie Sie vorgegangen sind.
(k, w)
,
in der kein Key und kein Gewicht doppelt vorkommen. Zeigen
Sie, dass es genau einen Treap für diese Menge gibt.
Suchen in einem Treap
Da Treaps Suchbäume in Bezug auf Ihre Keys sind, geht das Suchen genauso wie in BSTs.
Die Laufzeit von find
ist proportional zur
Tiefe des Knotens, der den gesuchte Key
x
enthält.
x
nicht enthält,
wie können Sie dann die Laufzeit
von find
beschreiben?
Einfügen in einen Treap
Um ein neues Element in einen Treap einzufügen, gehen
wir in zwei Phasen vor. In der ersten betrachten
wir nur die Keys und fügen das neue Paar
(x,
w)
ein wie in einen BST.
Das Problem ist, dass nach dieser Operation
unser Baum zwar ein BST ist, aber möglicherweise
kein Heap mehr: eventuell ist
w
zu schwer und
wir müssen es nach oben treiben lassen.
Dass wir es weiter nach unten sinken lassen
müssen, kann nicht sein: nach dem BST-Einfügen
ist
(x,
w)
ein Blatt (bzw. hat zwei leere Teilbäume, je nachdem, ob Sie
die Blätter
In einer Phase lassen wir nun
(x,
w)
nach oben treiben, solange das Gewicht kleiner
ist als das des Elternknoten. Das ist
aber komplizierter als in Heapsort, weil wir
immer die BST-Bedingung mit im Kopf behalten müssen!
Wir können also nicht einfach
swap(u, parent(u))
aufrufen.
Wir demonstrieren es an einem Beispiel.
Meiner Meinung nach ist in diesem Falle der Pseudo-Code aber tatsächlich leichter zu verstehen als das Beispiel.
Löschen aus einem Treap
In einem BST löschen wir einen Key (x, w)
und setzen dann sein Gewicht auf unendlich. Er ist nun also zu schwer
für seine Position und muss nach unten sinken, bis er zu einem Blatt geworden ist.
Dann können wir ihn ohne Probleme löschen.
Der erste Teil, das Lokalisieren, geht wie für BSTs:
Die eigentliche Arbeit findet nun in
delRoot
statt. Wir gehen recht anders vor als
für BSTs. Wir können nicht einfach das kleinste Element
Ansonsten sieht die Lage so aus:
(y, v)
oder
(z, w)
,
je nachdem, welcher davon "leichter" ist, wessen Gewicht also kleiner ist.
Also:
Finden Sie einen Treap und eine insert
-Operation,
die die Höhe reduziert!
Treap als randomisierte Datenstruktur und Programmierübung
Um Treaps zu einer "lieferfertigen" Datenstruktur zu machen, müssen wir festlegen, wie die Gewichte bestimmt werden. Sehen Sie, unsere "Kunden", also die User, die unseren Treap verwenden, wollen jatreap.insert("dog")schreiben und nicht
treap.insert("dog", 74)Die Gewichte dienen ausschließlich der internen Organisation und sollen nicht von außen sichtbar sein. Also wählen wir für jedes neue Element ein zufälliges Gewicht, und zwar eine zufällige reelle Zahl zwischen 0 und 1. In Java-Syntax:
private void insert(A key, B value, double weight) {
// many lines of code hidden here
}
public void insert(A key, B value) {
this.insert(key, value, Math.random());
}
Implementieren Sie mir einen BST. Die Programmiersprache
dürfen Sie sich aussuchen. Da ich mich nicht durch
hunderte Lines of Code wühlen will, geben Sie Ihrem
Programm ein "User-Interface". Es soll einfache Befehle
wie insert, find, delete
von der Konsole lesen können und sich folgendermaßen bedienen lassen:
java BST # oder eben 'python BST.py'
insert dog Hund
insert cat Katze
insert bear Baer
insert whale Wal
insert wolf Wolf
find wolf
true Wolf
find squid
false
insert squid Tintenfisch
find dog
true Hund
find cat
true Katze
delete dog
find dog
false
Wenn der Input zu Ende ist, soll das Programm terminieren.
Achten Sie genau auf das Format, damit ich mit
grep
Ihren Output mit meinem (hoffentlich korrekten)
Output vergleichen kann.
Wiederholen Sie alles, nun aber mit Treap. Das User-Interface soll identisch sein.
Schreiben Sie Ihren Namen als Kommentar in den Quelltext. Wir wollen ja dann einen "Sieger" verkünden.
Laufzeitanalyse
Sei
Beispiel. Wenn
{S = (a, 84),
(b, 29),
(c, 95),
(d, 47),
(e, 12),
(f, 86),
(g, 17),
(h, 94)}
,
dann ist
[e:b] = {(b, 29),
(c, 95),
(d, 47),
(e, 12)}
.
Die Richtung
-
1. Fall:
ist ein Vorfahre von . Aus der Heap-Eigenschaft folgt nun, dass ist, und somit ist leichter als . -
2. Fall:
ist kein Vorfahre von . Nun ist nach Annahme weder Vorfahre von noch umgekehrt. Also haben sie einen gemeinsamen Vorfahren: . Da ein binärer Suchbaum ist, gilt und somit . Da ein Heap ist, gilt , also ist leichter als .
Die Richtung
Wir bauen nun den Treap, in dem wir die Elemente
von leicht nach schwer sortiert in einen BST einfügen.
Wir pausieren, nachdem
Wir haben nun beide Richtungen gezeigt.
Wir führen nun etwas Notation ein.
Sei
Wir demonstrieren anhand mehrerer Beispiele, wie
Die Anzahl der Einsen in der Zeile von
Wenn wir nun die Gewichte zufällig wählen, dann
werden Treap und die obige Matrix zu Zufallsvariablen, sie
nehmen also je nach konkretem Ausgang des Zufallsexperiments verschiedene Gestalt an.
Somit varriert auch
Die erwartete Tiefe des kleinsten Schlüssels (und auch des größten Schlüssels) ergibt sich daher nach der Formel
Die obige Summe hat einen Namen: die harmonische Zahl
Als nächstes zeigen wir eine präzise Abschätzung für
In der oberen Abbildung sehen Sie den Graph der Funktion
find,insert,delete
in einem Treap haben
erwartete Laufzeit find,insert,delete
ist klar,
dass die Laufzeit höchstens proportional zur Tiefe des Baumes ist;
die Tiefe des Baumes ist die maximale Tiefe aller Knoten, also
-
find x
. Falls , dann ist die Laufzeit vonfind
proportional zu , was im Erwartungswert höchstens ist. Schwieriger wird es, wenn . Dann wirdfind
in jedem Fall bis zu einem Blatt von laufen und dannnull
bzw.Nothing
zurückliefern. Wie können wir die Tiefe dieses Blattes beschränken? Seien der größte Schlüssel mit und der kleinste Schlüssel mit . Wir erkennen, dass der letzte innere Knoten, denfind x
besucht, entweder der Knoten von oder ist. Die erwartete Anzahl der besuchten inneren Knoten ist also Ein Maximum zweier Zufallsvariablen abzuschätzen ist nicht immer einfach. Wir behelfen uns mit einem Trick: , und daher gilt -
insert x
. Beim Einfügen plazieren wir erst einmal an einem Blatt von , so als wäre nur ein BST und kein Treap. Dann lassen wir mittels Rechts- oder Linksrotationen hochsteigen, bis es seinen richtigen Platz gefunden haben wird. Sei die Tiefe von im resultierenden Treap. Sei die Tiefe, die eingenommen hat, als wir es als Blatt eingefügt haben, bevor wir also die Rotationen durchgeführt haben. Dies beschreibt auch die Laufzeit dieser ersten Phase. In Phase zwei schieben wir pro Rotationen eine Ebene nach oben, also führen wir Rotationen aus; die Gesamtlaufzeit ist also proportional zu . Wir wissen bereits, dass ist, weil das ja die Tiefe von im wiederhergestellten Treap ist. Wie steht es um , die Anzahl der Rotationen? Hier verwende ich eine wohl recht suboptimale Abschätzung: bei jeder Rotation gewinnt der an hängende Unterbaum einen weiteren Knoten hinzu, gewinnt also einen weiteren Nachkommen. Daher ist höchstens die Anzahl der Knoten im Unterbaum von , also und ist daher im Erwartungswert genau die Tiefe von . Wir folgern: -
delete x
. Dies führt zu allererst die gleichen Schritte aus diefind x
, also viele; sobald es in lokalisiert hat, ruft esdeleteRoot
am entsprechenden Unterbaum auf. Dies führt eine Reihe von Rotationen aus, um nach unten zu bewegen, bis eines seiner Kinder ein Blatt ist. In jeder Rotation nimmt die Anzahl der Nachkommen von ab; hatte anfangs also mindestens Nachfahren (sich selbst eingeschlossen, daher die ), und somit ist Wir folgern, dass auchdelete x
höchstens Schritte durchführt.