Binäre Suchbäume Mengen Mengen: Anwendungen (I) Mengen

Werbung
12/3/12
Mengen
n 
Binäre Suchbäume
n 
Mengen, Funktionalität, Binäre
Suchbäume, Heaps, Treaps
n 
n 
n 
Mengen: Anwendungen (I)
n 
n 
n 
n 
Einfügen: Neue Telefonanschlüsse
Löschen: Aufgegebene Telefonanschlüsse
Suchen: Telefonnummer einer Person
Anforderung: effizient auch bei
grossen Mengen!
Nutzerverwaltung (Menge von Nutzern
mit Passwörtern und/oder weiteren
Informationen)
n 
n 
n 
n 
Mengen: Lösung mit Listen
n 
n 
Einfügen: zum Beispiel vorne anfügen
(effizient)
2
1
6
5
8
7
Effizienz: Keine Wartezeit beim Einloggen, auch bei Millionen von Nutzern.
Menge wird als Liste gespeichert.
n 
0
9
Einfügen: Neue Nutzer
Löschen: Ex-Nutzer
Suchen: Einloggen
Mengen: Lösung mit Listen
Menge wird als Liste gespeichert.
n 
Einfügen eines Elements
Löschen eines Elements
Suchen eines Elements
Mengen: Anwendungen (II)
Telefonbuch (Menge von Namen mit
zugehörigen Telefonnummern)
n 
Ziel: Aufrechterhalten einer Menge
(hier: ganzer Zahlen) unter folgenden
Operationen:
3
Löschen: Durchlaufen der Liste bis zum
Element (oder bis zum Ende, falls nicht
vorhanden),
Zeiger umhängen.
2
1
6
5
8
0
9
7
3
1
12/3/12
Mengen: Lösung mit Listen
n 
Mengen: Lösung mit Listen
Menge wird als Liste gespeichert.
n 
n 
Suchen: Durchlaufen der Liste bis zum
Element (oder bis zum Ende, falls nicht
vorhanden).
2
1
6
5
8
0
9
7
3
Mengen: Lösung mit Listen
n 
Beispiel: Suche nach den letzten 100
Elementen in einer Liste von 10,000,000
Elementen
Facebook hat
1,000,000,000
List l;
Nutzer, eine
for (int i=0; i<10000000; ++i)
Suche würde
l.push_front(i);
6.5 Sekunden
dauern!
// ...and search for the 100 first ones
for (int i=0; i<100; ++i)
6.5 Sekunden
l.find(i);
Mengen: Lösung mit
Binären Suchbäumen.
n 
n 
n 
Falls die Menge n Elemente enthält, so
ist der Aufwand fürs Suchen eines
Elements im schlimmsten Fall proportional zu log2 n.
Facebook: Zeit zum Einloggen ist
proportional zum Logarithmus der
Anzahl der Nutzer.
Falls die Menge n Elemente enthält, so
ist der Aufwand fürs Suchen eines
Elements im schlimmsten Fall proportional zu n.
Facebook: Zeit zum Einloggen wäre
proportional zur Anzahl der Nutzer.
Mengen: Lösung mit Listen
n 
n 
Beispiel: Suche nach den letzten 100
Elementen in einer Liste von 10,000,000
Elementen
Finden eines Elements:
const Node* List::find (int key)
{
for (const Node* p = head_; p != 0; p = p>get_next())
if (p->get_key() == key) return p;
return 0;
}
Mengen: Lösung mit
Binären Suchbäumen.
n 
n 
Falls die Menge n Elemente enthält, so
ist der Aufwand fürs Suchen eines
Elements im schlimmsten Fall proportional zu log2 n.
Auch Einfügen und Löschen geht in Zeit
proportional to log2 n.
log2(1,000,000,000) ≈ 30.
2
12/3/12
Binärbäume, Definition und
Terminologie
n 
n 
Binäre Suchbäume
Ein binärer Baum ist entweder leer,...
oder er besteht aus einem Knoten
(Kreis)
n 
n 
n 
Sowohl der linke als auch der rechte
Teilbaum sind binäre Suchbäume.
n  Der Schlüssel der Wurzel ist
mit einem Schlüssel (hier: ganze Zahl),...
und einem linken und rechten
Teilbaum, die jeweils Binärbäume sind.
3
Ein Binärbaum heisst binärer
Suchbaum, wenn er leer ist, oder
wenn folgende Bedingungen gelten:
n 
n 
Wurzel des Baums
n 
Linker Teilbaum
> alle Schlüssel im linken Teilbaum, und
≦ alle Schlüssel im rechten Teilbaum.
Rechter Teilbaum
Binäre Suchbäume:
Beispiel
Binäre Suchbäume:
Suchen nach einem Schlüssel
4
3
8
1
6
5
2
3
9
4 =5?
3
2
6
5
9
7
Binäre Suchbäume:
Suchen nach einem Schlüssel
nein
4 >5?
3
9
7
5
2
8
6
8
1
Blätter des
Baums
7
Binäre Suchbäume:
Suchen nach einem Schlüssel
1
5?
4
ja
1
8
6
2
5
Nein, also muss
die 5 rechts sein.
9
7
3
12/3/12
Binäre Suchbäume:
Suchen nach einem Schlüssel
Binäre Suchbäume:
Suchen nach einem Schlüssel
4
4
3
8
=5?
nein
3
8
Ja, also muss die
5 links sein.
>5?
nein
1
6
5
2
9
1
7
6
5
2
Binäre Suchbäume:
Suchen nach einem Schlüssel
4
3
8
6
5
2
=5?
nein
3
9
1
7
8
9
7
Binäre Suchbäume:
Suchen nach einem Schlüssel
0?
4
3
8
6
5
>5?
nein
5
4
2
6
Ja, also muss die
5 links sein.
2
Binäre Suchbäume:
Suchen nach einem Schlüssel
1
7
Binäre Suchbäume:
Suchen nach einem Schlüssel
4
1
9
=5?
3
9
7
Ja, Schlüssel gefunden!
8
1
0 müsste
links von
der 1 sein,
Teilbaum ist
aber leer ⇒
0 nicht da.
6
2
5
9
7
4
12/3/12
Mengen: Lösung mit binären
Suchbäumen
n 
n 
Mengen: Lösung mit binären
Suchbäumen
Speichere die Elemente der Menge als
Schlüssel in einem binären Suchbaum!
Suchzeit ist proportional zur Höhe des
Baums (Länge des längsten Pfades von
der Wurzel bis zu einem Blatt).
4
3
1
6
5
2
Mengen: Lösung mit binären
Suchbäumen
n 
n 
n 
n 
Binäre Suchbäume:
Einfügen
3
0 müsste
links von der
1 sein,
Teilbaum ist
aber leer ⇒
neues Blatt
2
5
0 einfügen
4
3
9
7
Höhe 3
Binäre Suchbäume:
Einfügen
8
6
7
Neues Element suchen und als Blatt an
der passenden Stelle anhängen!
0 einfügen
4
9
Binäre Suchbäume:
Einfügen
Speichere die Elemente der Menge als
Schlüssel in einem binären Suchbaum!
Suchzeit ist proportional zur Höhe des
Baums (Länge des längsten Pfades von
der Wurzel bis zu einem Blatt).
Einfügen und Löschen von Elementen?
1
8
8
1
0
6
2
5
9
7
5
12/3/12
Binäre Suchbäume:
Einfügen
n 
n 
Binäre Suchbäume:
Löschen
Neues Element suchen und als Blatt an
der passenden Stelle anhängen!
Auch die Einfügezeit ist proportional zur
Höhe des Baums.
n 
n 
Element suchen, seinen Knoten durch
Rotationen zu einem Blatt machen,
Blatt löschen!
Rotation: lokale Operation, welche die
Positionen von Knoten verändert, nicht
jedoch die Suchbaumeigenschaften.
Binäre Suchbäume:
Löschen
Rotation eines Teilbaums
Rechtsrotation
s
8 löschen
4
t
Linksrotation
3
t
8
s
C
A
A
1
B
B
A<t≦B<s≦C
=
C
6
7
A<t≦B<s≦C
Binäre Suchbäume:
Löschen
Binäre Suchbäume:
Löschen
8 löschen
4
3
6
8 löschen
4
8
1
5
2
9
3
C
9
1
6
5
B und C sind
leere Bäume
8
A
2
5
A
7
B
Rechtsrotation (Linksrotation wäre auch möglich)
2
Linksrotation (Rechtsrotation wäre auch möglich)
7
9
B
C
6
12/3/12
Binäre Suchbäume:
Löschen
Binäre Suchbäume:
Löschen
8 löschen
4
3
6
1
3
A, B, C sind
leere Bäume
5
9
8 löschen
4
1
6
5
9
8
7
C
2
Rechtsrotation (Linksrotation wäre nicht möglich)
A
n 
n 
delete
B
Binäre Suchbäume:
Löschen
n 
2
7
8
Die Höhe eines binären
Suchbaums...
Element suchen, seinen Knoten durch
Rotationen zu einem Blatt machen,
Blatt löschen!
Rotation: lokale Operation, welche die
Positionen von Knoten verändert, nicht
jedoch die Suchbaumeigenschaften.
Auch die Löschzeit ist proportional zur
Höhe des Baums (nicht mehr ganz so
offensichtlich)
Die Höhe eines binären
Einfügen in
Suchbaums... Beispiel:
sortierter Reihenfolge
1
n 
n 
n 
n 
n 
n 
2
3
Höhe = n-1, Baum sieht aus und verhält sich wie eine Liste.
Suchens,
Einfügens,
Löschens.
kann aber bereits bei einer ungünstigen
Einfügereihenfolge sehr gross werden!
Laufzeit im schlimmsten Fall nicht
besser als bei einer Liste!
Abhilfe: Balancierung
n 
n
bestimmt die Laufzeit des
n 
Beim Einfügen und Löschen stets darauf
achten, dass der Baum „balanciert“ ist,
(d.h. keine grossen Höhenunterschiede
zwischen linken und rechten Teilbäumen)
Verschiedene Möglichkeiten (mehr oder
weniger kompliziert):
n 
n 
AVL-Bäume, Rot-Schwarz-Bäume,...
Hier: Treaps (randomisierte Suchbäume)
7
12/3/12
Heap: Schlüssel heissen
Prioritäten
Treap = Tree + Heap
n 
Ein Binärbaum heisst Heap, wenn er
leer ist, oder wenn folgende
Bedingungen gelten:
n 
Sowohl der linke als auch der rechte
Teilbaum sind Heaps.
n  Der Schlüssel der Wurzel ist
Sowohl der linke als auch der rechte
Teilbaum sind Heaps.
n  Die Priorität der Wurzel ist
n 
n 
n 
das Maximum aller Schlüssel.
n 
Heap:
Beispiel
n 
7
8
4
6
3
das Maximum aller Prioritäten.
Treaps
9
2
n 
1
Schlüssel
Prioritäten
4, 9
Treaps: Eindeutigkeit
n 
3, 7
8, 8
n 
1, 4
6, 6
5, 2
Ein Binärbaum heisst Treap, wenn jeder
Knoten einen Schlüssel und eine
Priorität hat, so dass folgende Eigenschaften gelten:
Der Baum ist ein binärer Suchbaum bzgl.
der Schlüssel.
n  Der Baum ist ein Heap bzgl. der Prioritäten.
5
Treap:
Beispiel
2, 3
Ein Binärbaum heisst Heap, wenn er
leer ist, oder wenn folgende
Bedingungen gelten:
9, 5
7, 1
Satz: Für n Knoten mit verschiedenen
Schlüsseln und Prioritäten gibt es genau
einen Treap mit diesen Knoten.
Beweis: Die Wurzel ist der Knoten mit
der höchsten Priorität; im linken
Teilbaum sind alle Knoten mit kleineren
und im rechten Teilbaum alle Knoten
mit grösseren Schlüsseln. Mit Induktion
sind die Teilbäume auch eindeutig.
8
12/3/12
Treaps mit zufälligen
Prioritäten
Treaps: Einfügen
Wir benutzen einen Treap als binären
Suchbaum.
Beim Einfügen eines neuen Schlüssels
wird seine Priorität zufällig gewählt.
Wie bei normalen binären Suchbäumen:
neuer Knoten wird neues Blatt.
Zusätzlich muss die Treap-Eigenschaft
wieder hergestellt werden!
n 
n 
n 
n 
4, 9
0 einfügen, mit zufälliger
Priorität 7.5
3, 7
8, 8
1, 4
0, 7.5
6, 6
2, 3
5, 2
9, 5
7, 1
Neues Blatt
Treaps: Einfügen
4, 9
Treaps: Einfügen
0 einfügen, mit zufälliger
Priorität 7.5
3, 7
4, 9
8, 8
1, 4
6, 6
0 einfügen, mit zufälliger
Priorität 7.5
3, 7
9, 5
8, 8
1, 4
6, 6
9, 5
Heap-Eigenschaft
verletzt: 4 < 7.5
0, 7.5
2, 3
5, 2
7, 1
0, 7.5
2, 3
5, 2
7, 1
Rechtsrotation!
Treaps: Einfügen
4, 9
0 einfügen, mit zufälliger
Priorität 7.5
3, 7
4, 9
8, 8
0, 7.5
6, 6
0 einfügen, mit zufälliger
Priorität 7.5
3, 7
9, 5
1, 4
2, 3
Treaps: Einfügen
0, 7.5
8, 8
Heap-Eigenschaft
verletzt: 7 < 7.5
6, 6
9, 5
1, 4
5, 2
7, 1
2, 3
5, 2
7, 1
9
12/3/12
Treaps: Einfügen
4, 9
Treaps: Einfügen
0 einfügen, mit zufälliger
Priorität 7.5
3, 7
4, 9
8, 8
0, 7.5
6, 6
0 einfügen, mit zufälliger
Priorität 7.5
0, 7.5
9, 5
1, 4
8, 8
3, 7
6, 6
9, 5
1, 4
5, 2
2, 3
7, 1
2, 3
5, 2
7, 1
Rechtsrotation!
Treaps: Einfügen
4, 9
0, 7.5
Treaps: Löschen
0 einfügen, mit zufälliger
Priorität 7.5
Heap-Eigenschaft
ok, wir haben
wieder einen Treap!
n 
8, 8
n 
3, 7
6, 6
9, 5
1, 4
2, 3
5, 2
7, 1
Treaps: Laufzeit
n 
n 
Satz (Seidel, Aragon 1996): In einem
Treap über n Knoten mit zufälligen
Prioritäten ist der zeitliche Aufwand für
eine Einfüge-, Lösch- oder Suchoperation im Erwartungswert proportional zu
log2n.
Mehr dazu in der nächsten Stunde.
n 
Element suchen, seinen Knoten durch
Rotationen zu einem Blatt machen,
Blatt löschen!
Stets so rotieren, dass der Knoten mit
seinem Nachfolger höherer Priorität
vertauscht wird. Das erhält die HeapEigenschaft.
Suchbaum-Eigenschaft gilt wieder nach
Löschen des Blatts.
Mengen: Lösung mit Treaps
n 
Zur Erinnerung: Suche nach den letzten
100 Elementen in einer Liste von
10,000,000 Elementen
List l;
for (int i=0; i<10000000; ++i)
l.push_front(i);
// ...and search for the 100 first ones
for (int i=0; i<100; ++i)
6.5 Sekunden
l.find(i);
10
12/3/12
Mengen: Lösung mit Treaps
n 
Neu: Suche nach den letzten 100
Elementen in einem Treap von
10,000,000 Elementen
Treap t;
for (int i=0; i<10000000; ++i)
t.insert(i);
Sortieren mit Treaps
n 
n 
Folgerung: Durch Einfügen einer Folge
von n Zahlen in einen Treap mit zufälligen Prioritäten kann die Folge in Zeit
proportional zu n log2n sortiert werden.
Gleicher optimaler Proportionalitätsfaktor wie Mergesort
Das Treap-basierte Verfahren ist im
wesentlichen äquivalent zu Quicksort.
// ...and search for the 100 first ones
for (int i=0; i<100; ++i)
0.017 Sekunden
t.find(i);
n 
Treaps in C++
Die Klasse TreapNode
n 
Aehnlich wie bei Listen:
Klasse TreapNode für die Knoten
Jeder TreapNode speichert ausser
Schlüssel und Priorität zwei Zeiger auf
TreapNodes (linkes und rechtes Kind =
Wurzeln des linken und rechten Teilbaums)
n  Klasse Treap; jeder Treap ist durch einen
Zeiger auf den Wurzelknoten repräsentiert.
n 
n 
class TreapNode !
{!
public:!
// constructor!
TreapNode( int key, int priority, TreapNode* left = 0, TreapNode* right = 0);!
!
// Getters!
int get_key() const;!
int get_priority() const;!
const TreapNode* get_left() const;!
const TreapNode* get_right() const;!
!!
private:!
int key_;!
double priority_;!
Wie bei Facebook: Freunde dürfen
TreapNode* left_;!
auf private Daten und Methoden
TreapNode* right_;
!!
(Mitgliedsfunktionen) zugreifen.
friend class Treap;!
}; !
Die Klasse Treap
Die „Viererbande“ + Wurzel
Die Klasse Treap
Einfügen
class Treap {
public:
// default constructor:
// POST: *this is an empty tree
Treap();
void Treap::insert(const int key)!
{!
insert (root_, key, std::rand());!
}!
Konstruktor
// copy constructor
// POST *this is a copy of other
Treap(const Treap& other);
CopyKonstruktor
// assignment operator
// Post: other was assigned to *this
Treap& operator= (const Treap& other);
Zuweisungsoperator
// Destructor
~Treap();
private:
TreapNode* root_;
…
private Hilfsfunktion
Zufällige Priorität
Destruktor
Zeiger auf die Wurzel (0: leerer Treap)
11
12/3/12
Die Klasse Treap
Einfügen
Die Klasse Treap
Einfügen
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
eine leere Blattposition.
else
if (key < current->key_) {
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
eine leere Blattposition.
else
if (key < current->key_) {
9,
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
10
8, 8
9,
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
current
}
else {
insert (current->right_, key, priority);
if (priority > current->get_priority())
rotate_left (current);
}
8, 8
current
}
else {
insert (current->right_, key, priority);
if (priority > current->get_priority())
rotate_left (current);
Damit das klappt,
}
}
10
9, 10
muss current ein Alias des
entsprechenden Zeigers im Baum sein, keine Kopie!
}
Die Klasse Treap
Einfügen
Die Klasse Treap
Einfügen
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
eine leere Blattposition.
else
if (key < current->key_) {
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
einen Knoten.
else
if (key < current->key_) {
9,
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
10
8, 8
9,
9
}
else {
insert (current->right_, key, priority);
}
current
8, 8
}
else {
insert (current->right_, key, priority);
9, 10
if (priority > current->get_priority())
rotate_left (current);
}
8
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
current
Heap-Eigenschaft ist noch verletzt; das wird „weiter
oben“ in der Rekursion repariert.
10
if (priority > current->get_priority())
rotate_left (current);
}
}
Die Klasse Treap
Einfügen
Die Klasse Treap
Einfügen
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
einen Knoten.
else
if (key < current->key_) {
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
einen Knoten.
else
if (key < current->key_) {
9,
current
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
9,
A
8
if (priority > current->get_priority())
rotate_left (current);
9, 10
}
}
8, 8
}
else {
insert (current->right_, key, priority);
10
10
current
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
8, 8
}
else {
insert (current->right_, key, priority);
if (priority > current->get_priority())
rotate_left (current);
10
A
9, 10
}
}
12
12/3/12
Die Klasse Treap
Einfügen
Die Klasse Treap
Einfügen
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
einen Knoten.
else
if (key < current->key_) {
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
einen Knoten.
else
if (key < current->key_) {
9,
10
current
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
9,
}
else {
insert (current->right_, key, priority);
if (priority > current->get_priority())
rotate_left (current);
}
9, 10
}
current
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
8, 8
}
else {
insert (current->right_, key, priority);
if (priority > current->get_priority())
rotate_left (current);
}
}
10
9, 10
8, 8
A
Die Klasse Treap
Einfügen
// PRE: current is a pointer in *this
// POST: a new TreapNode with key and priority is inserted into the
//
subtree hanging off current
9,
10
void Treap::insert (TreapNode*& current, const int key, const int priority)
{
current verweist auf
if (current == 0)
current = new TreapNode (key, priority);
einen Knoten.
else
if (key < current->key_) {
current
insert (current->left_, key, priority);
if (priority > current->get_priority())
rotate_right (current);
9, 10
}
else {
insert (current->right_, key, priority);
if (priority > current->get_priority())
rotate_left (current);
}
}
A
8, 8
Fertig!
13
Herunterladen