Datenstrukturen

Werbung
Datenstrukturen
Mariano Zelke
Sommersemester 2012
Prioritätswarteschlangen
Der abstrakte Datentyp Prioritätswarteschlange“: Füge Elemente (mit
”
Prioritäten) ein und entferne jeweils das Element höchster Priorität.
I
I
Eine Schlange ist eine sehr spezielle Prioritätswarteschlange: Die
Priorität eines Elements ist der negative Zeitpunkt der Einfügung.
Der abstrakte Datentyp Prioritätswarteschlange“ umfasst dann die
”
Operationen
I
I
I
I
void insert(x,Priorität),
int delete max(),
void change priority(wo,Priorität? ), wähle Priorität? als neue Priorität
und void remove(wo), entferne das durch wo beschriebene Element.
Wir müssen eine geeignete Datenstruktur entwerfen.
Mariano Zelke
Datenstrukturen
2/28
Der Heap
Ein Heap ist ein Binärbaum mit Heap-Struktur, der Prioritäten gemäß
einer Heap-Ordnung abspeichert.
Ein geordneter Binärbaum T der Tiefe t hat Heap-Struktur, wenn:
(a) jeder Knoten der Tiefe höchstens t − 2 genau 2 Kinder hat,
(b) wenn ein Knoten v der Tiefe t − 1 weniger als 2 Kinder hat, dann
I
I
haben alle Knoten der Tiefe t − 1, die rechts von v liegen, kein Kind
wenn v genau ein Kind hat, dann ist es ein linkes Kind.
(Daraus folgt, dass nur höchstens ein Knoten genau ein Kind haben
kann.)
Ein Binärbaum mit Heap-Struktur ist ein fast vollständiger binärer
Baum: Alle Knoten links von v haben zwei Kinder, alle Knoten rechts
von v haben keine Kinder.
Mariano Zelke
Datenstrukturen
3/28
Beispiele Heap-Struktur
Dieser Baum hat
Heap-Struktur:
genauso wie
dieser:
und dieser:
Dieser Baum hat
keine HeapStruktur:
dieser auch
nicht:
Mariano Zelke
Datenstrukturen
4/28
Heap-Ordnung
Ein geordneter binärer Baum T mit Heap-Struktur speichere für jeden
Knoten v die Priorität p(v ) von v .
T hat Heap-Ordnung, falls für jeden Knoten v und für jedes Kind w von
v gilt
p(v ) ≥ p(w )
I
Die höchste Priorität wird stets an der Wurzel gespeichert.
Für die Operation delete max() muss nur die Priorität der Wurzel
überschrieben werden.
I
Wie sollte man einen Baum mit Heap-Struktur implementieren?
Wir arbeiten mit einem Array.
Mariano Zelke
Datenstrukturen
5/28
Die Datenstruktur Heap
(Link)
Das Array H ist ein Heap für T , wenn T Heap-Struktur und
Heap-Ordnung hat. Zusätzlich muss gelten
I
H[1] = p(r ) für die Wurzel r von T und
I
wenn H[i] die Priorität des Knotens v speichert, dann gilt
H[2 · i] = p(vL ) für das linke Kind vL von v und
H[2 · i + 1] = p(vR ) für das rechte Kind vR .
Beispiel
9
4
7
besitzt den Heap:
(9,4,7,3,1)
Der folgende Baum verletzt
die Heap-Struktur:
9
3
1
4
dargestellt als Array
0 9 4 7 3 1
Mariano Zelke
3
Datenstrukturen
7
1
Sein Heap“
”
(9,4,7,3, ,1)
enthält ein
Loch.
6/28
Die Funktion Insert
I
Wie navigiert man in einem Heap H?
Wenn Knoten v in Position i gespeichert ist,
dann ist das linke Kind vL in Position 2 · i, das rechte Kind in
Position 2 · i + 1 und der Vater von v in Position bi/2c gespeichert.
I
Wenn wir die Priorität p einfügen wollen, liegt es nahe, p auf der
ersten freien Position abzulegen. Wir erhöhen also den Zähler n für
die Anzahl der Elemente im Heap um eins: n = n + 1, und speichern
danach die neue Priorität ab: H[n] = p
I
I
Der neue Baum hat Heap-Struktur,
aber die Heap-Ordnung ist möglicherweise verletzt.
Wie kann die Heap-Ordnung kostengünstig repariert werden?
Mariano Zelke
Datenstrukturen
7/28
Wir fügen die Priorität 11 ein
9
7
2
Nach dem Anhängen von 11
ist die Heap-Ordnung verletzt.
3
5
1
11
Darum rutscht die
11 nach oben:
9
7
2
11
5
1
3
Und ein weiterer Vertauschungsschritt
repariert die Heap-Ordnung:
11
7
2
Mariano Zelke
Datenstrukturen
9
5
1
3
8/28
Die Repair up Prozedur
Die Klasse heap enthalte die Funktion repair up.
void heap::repair up (int wo){
int p = H[wo];
while ((wo > 1) && (H[wo/2] < p)){
H[wo] = H[wo/2];
wo = wo/2;
}
H[wo] = p;
}
I
Wir verschieben die Priorität solange nach oben, bis
I
I
I
entweder die Priorität des Vaters mindestens so groß ist
oder bis wir die Wurzel erreicht haben.
Wie groß ist der Aufwand? Höchstens proportional zur Tiefe des
Baums!
Mariano Zelke
Datenstrukturen
9/28
Die Funktion Delete max()
H repräsentiere einen Heap mit n Prioritäten. Für delete max:
I
gib die Priorität H[1] zurück
I
Überschreibe die Wurzel mit H[n]
I
und verringere n um 1.
I
Durch das Überschreiben mit H(n) ist das entstandene Loch an der
Wurzel verschwunden:
Die Heap-Struktur ist wiederhergestellt.
Allerdings ist die Heap-Ordnung möglicherweise verletzt und muss
repariert werden.
I
Die Prozedur repair up versagt: sie ist nur anwendbar, wenn die
falsch stehende Priorität größer als die Vater-Priorität ist.
Mariano Zelke
Datenstrukturen
10/28
Ein Beispiel
5
3
2
1
Wir setzen das letzte
Heap-Element an
die Wurzel:
3
5
1
3
Nach Entfernen des Maximums fehlt die
Wurzel.
4
4
2
1
5
4
2
Damit ist die Heap-Ordnung
verletzt. Darum vertauschen
wir die Wurzel mit dem
größten Kind.
Heap-Ordnung immer noch verletzt. Vertausche weiter mit
3
dem größten Kind, bis
wieder Heap-Ordnung
1
erreicht.
5
4
2
Repariere die Heap-Ordnung nach unten.
Mariano Zelke
Datenstrukturen
11/28
Die Prozedur Repair down
Die Klasse heap enthalte die Funktion repair down.
void heap::repair down (int wo){
int kind; int p = H[wo];
while (wo <= n/2){
kind = 2 * wo;
if ((kind < n) && (H[kind] < H[kind + 1]))
kind ++;
if (p >= H [kind]) break;
H[wo] = H[kind];
wo = kind;
}
H[wo] = p;
}
Animation
I
Die Priorität p wird mit der Priorität des größten Kinds“ verglichen
”
und möglicherweise vertauscht. Die Prozedur endet, wenn wo die
richtige Position ist, bzw. wenn wo ein Blatt bezeichnet.
I
Wie groß ist der Aufwand? Höchstens proportional zur Tiefe.
Mariano Zelke
Datenstrukturen
12/28
Change priority und Remove
I
void change priority (int wo, int p):
I
I
Wir aktualisieren die Priorität, setzen also H [wo] = p.
Aber wir verletzen damit möglicherweise die Heap-Ordnung!
I
I
I
Wenn die Priorität angewachsen ist, dann rufe repair up auf.
Ansonsten hat sich die Priorität verringert und repair down ist
aufzurufen.
void remove(int wo):
I
I
Stelle die Heap-Struktur durch wieder her durch
H[wo] = H[n]; n = n − 1;
und repariere Heap-Ordnung wie bei change priority.
Alle vier Operationen insert, delete max, change priority und remove
benötigen Zeit höchstens proportional zur Tiefe des Heaps.
Mariano Zelke
Datenstrukturen
13/28
Die Tiefe eines Heaps mit n Knoten
Der Binärbaum T besitze Heap-Struktur.
I
Wenn T die Tiefe t besitzt, dann hat T mindestens
1 + 21 + 22 + . . . + 2t−1 + 1 = 2t Knoten
I
aber nicht mehr als 1 + 21 + 22 + . . . + 2t−1 + 2t = 2t+1 − 1 Knoten.
I
Also folgt 2Tiefe(T ) ≤ n < 2Tiefe(T )+1 .
Tiefe (T ) = blog2 nc und alle vier Operationen werden somit in
logarithmischer Zeit unterstützt!
Mariano Zelke
Datenstrukturen
14/28
Heapsort
Ein Array (A[1], . . . , A[n]) ist zu sortieren.
Dazu benutzen wir den Heap h, der anfangs leer ist.
for (i = 1; i <= n ; i++)
h.insert(A[i]);
for (i = n; i >= 1 ; i--)
A[i] = h.delete max();
//Das Array A ist jetzt aufsteigend sortiert.
I
Zuerst wird n Mal eingefügt und dann n Mal das Maximum entfernt.
I
Sowohl die anfängliche Einfügephase wie auch die letztliche
Entfernungsphase benötigen Zeit höchstens O(n · log2 n).
Heapsort ist eines der schnellsten Sortierverfahren. Die anfängliche
Einfügephase kann sogar noch weiter beschleunigt werden!
Mariano Zelke
Datenstrukturen
15/28
Wie kann der Heap schneller geladen werden?
Führe statt vielen kleinen Reparaturen eine große Reparatur durch.
I
Lade den Heap ohne Reparaturen
I
Beginne die Reparatur mit den Blättern. Jedes Blatt ist schon ein
Heap und eine Reparatur ist nicht notwendig.
Wenn t die Tiefe des Heaps ist, dann kümmern wir uns als nächstes
um die Knoten v der Tiefe t − 1:
I
I
I
I
I
Sei Tv der Teilbaum mit Wurzel v .
Tv ist nur dann kein Heap, wenn die Heap-Ordnung im Knoten v
verletzt ist: Repariere mit repair down, gestartet in v .
Höchstens ein Vertauschungsschritt wird benötigt.
Wenn v ein Knoten der Tiefe t − j ist, dann muss höchstens die
Heap-Ordnung im Knoten v repariert werden. Höchstens j
Vertauschungsschritte genügen.
Es gibt nur wenige teure Reparaturschritte!
Mariano Zelke
Datenstrukturen
16/28
Analyse
I
Es gibt 2t−j Knoten der Tiefe t − j (für j ≥ 1).
I
Für jeden dieser Knoten sind höchstens j Vertauschungsschritte
t
P
durchzuführen, für alle Knoten ist dies also durch
j · 2t−j
j=1
beschränkt.
I
Behauptung:
t
P
j · 2t−j = 2t+1 − t − 2.
j=1
Wir geben einen induktiven Beweis:
t+1
t
X
X
j · 2t+1−j = 2
j · 2t−j + t + 1
j=1
j=1
= 2 · (2t+1 − t − 2) + t + 1
= 2t+2 − (t + 1) − 2.
I
Da 2t ≤ n gilt, ist 2t+1 − t − 2 ≤ 2n − log n − 2
Der Heap kann in linearer Zeit geladen werden.
Mariano Zelke
Datenstrukturen
17/28
Die Klasse heap
class heap{
private:
int *H;
// H ist der Heap.
int n;
// n bezeichnet die Größe des Heaps.
void repair up (int wo);
void repair down (int wo);
public:
heap (int max) { H = new int[max]; n=0; } //Konstruktor
void insert (int priority);
int delete max( );
void change priority (int wo, int p);
void remove(int wo);
void heapsort();
void build heap();
void write (int i) { n++; H[n] = i; }
};
Mariano Zelke
Datenstrukturen
18/28
Prioritätswarteschlangen: Zusammenfassung
(a) Ein Heap mit n Prioritäten unterstützt jede der Operationen
insert, delete max, change priority und remove
in Zeit O(log2 n).
I
Für die Operationen change priority und remove muss die
Position der zu ändernden Priorität bekannt sein.
(b) build heap baut einen Heap mit n Prioritäten in Zeit O(n).
(c) heapsort sortiert n Zahlen in Zeit O(n log2 n).
Mariano Zelke
Datenstrukturen
19/28
Das Single-Source-Shortest-Path-Problem
Ein gerichteter Graph G = (V , E ) und eine Längen-Zuweisung
länge: E → R≥0 an die Kanten des Graphen ist gegeben.
Bestimme kürzeste Wege von einem ausgezeichneten Startknoten s ∈ V
zu allen Knoten von G .
I
Die Länge eines Weges ist die Summe seiner Kantengewichte.
I
Mit Hilfe der Breitensuche können wir kürzeste-Wege Probleme
lösen, falls länge(e) = 1 für jede Kante e ∈ E gilt.
Für allgemeine nicht-negative Längen brauchen wir eine
ausgeklügeltere Idee.
I
I
Mariano Zelke
Kantengewichte sind nicht-negativ: Die kürzeste, mit s verbundene
Kante (s, v ) ist ein kürzester Weg von s nach v .
Dijkstras Algorithmus setzt diese Beobachtung wiederholt ein.
Datenstrukturen
20/28
Dijkstras Algorithmus
(1) Setze S = {s} und für alle Knoten v ∈ V \ {s} setze
länge (s, v ) wenn (s, v ) ∈ E
distanz[v] =
∞
sonst.
/* distanz[v] ist die Länge des bisher festgestellten kürzesten Weges
von s nach v .
*/
(2) Solange S 6= V wiederhole
(a) wähle einen Knoten w ∈ V \ S mit kleinstem Distanz-Wert.
/* distanz[w] ist die tatsächliche Länge eines kürzesten Weges
von s nach w .
*/
(b) Füge w in S ein.
(c) Aktualisiere die Distanz-Werte der Nachfolger von w :
Setze für jeden Nachfolger u ∈ V \ S von w
distanz[u] = min(distanz[u], distanz[w]+länge(w,u));
Mariano Zelke
Datenstrukturen
21/28
Beispiel Dijkstras Algorithmus
4
3
10
s
20
5
4
Mariano Zelke
5
2
4
3
0
Datenstrukturen
23
28
29
1
22/28
Beispiel Dijkstras Algorithmus
S V \S
distanz[4]=3
4
3
10
s
2
4
20
5
distanz[5]=0
3
distanz[3]=20
4
Mariano Zelke
distanz[2]=∞
5
0
distanz[0]=4
Datenstrukturen
23
28
29
1
distanz[1]=∞
22/28
Beispiel Dijkstras Algorithmus
S V \S
distanz[4]=3
4
3
10
s
2
4
20
5
distanz[5]=0
3
distanz[3]=13
4
Mariano Zelke
distanz[2]=8
5
0
distanz[0]=4
Datenstrukturen
23
28
29
1
distanz[1]=32
22/28
Beispiel Dijkstras Algorithmus
distanz[4]=3
4
3
10
s
2
4
20
5
distanz[5]=0
3
distanz[3]=12
4
Mariano Zelke
distanz[2]=8
5
0
distanz[0]=4
Datenstrukturen
23
28
29
1
distanz[1]=32
22/28
Datenstrukturen für Dijkstras Algorithmus
In der Vorlesung Algorithmentheorie“ wird gezeigt, dass Dijkstras
”
Algorithmus korrekt ist und das Kürzeste-Wege-Problem effizient löst.
I
I
Darstellung des Graphen G : Wir implementieren G als
Adjazenzliste, da wir dann sofortigen Zugriff auf die Nachfolger u
von w im Aktualisierungschritt (2c) haben.
Implementierung der Menge V \ S:
I
I
Knoten sind gemäß ihrem anfänglichen Distanzwert einzufügen.
Ein Knoten w mit kleinstem Distanzwert ist zu bestimmen und zu
entfernen.
Wähle einen Min-Heap, um die entsprechende
Prioritätswarteschlange zu implementieren:
I
I
Mariano Zelke
Ersetze die Funktion delete max() durch die Funktion
delete min().
(Und passe repair up und repair down entsprechend an.)
Implementiere den Aktualisierungschritt (2c) durch
change priority(wo,neue Distanz).
Woher kennen wir die Position wo?
Datenstrukturen
23/28
Minimale Spannbäume
(Link)
Sei G = (V , E ) ein ungerichteter, zusammenhängender Graph.
Jede Kante e ∈ E erhält eine positiv, reellwertige Länge länge(e)“.
”
I Ein Baum T = (V 0 , E 0 ) heißt ein Spannbaum für G , falls V 0 = V
und E 0 ⊆ E .
I
Die Länge eines Spannbaums ist die Summe der Längen seiner
Kanten.
I
Ein minimaler Spannbaum ist ein Spannbaum minimaler Länge.
I
Je zwei Knoten von G bleiben auch in einem Spannbaum T
miteinander verbunden, denn ein Baum ist zusammenhängend.
Wenn wir aber irgendeine Kante aus T entfernen, dann zerstören
wir den Zusammenhang.
I
Wir suchen nach einem zusammenhängenden Teilgraph in G , der
minimale Länge hat.
Mariano Zelke
Datenstrukturen
24/28
Der Algorithmus von Prim: Die Idee
I
Angenommen wir wissen, dass ein Baum B in einem minimalen
Spannbaum enthalten ist.
I
Wir möchten eine kreuzende Kante zu B hinzufügen: e soll also
einen Knoten in B mit einem Knoten außerhalb von B verbinden.
I
Der Algorithmus von Prim wählt eine kürzeste kreuzende Kante.
I
In der Vorlesung Algorithmentheorie“ wird gezeigt, dass auch
”
B ∪ {e} in einem minimalen Spannbaum enthalten ist:
Der Algorithmus berechnet also einen minimalen Spannbaum.
Worauf müssen wir bei der Implementierung achten?
I
I
I
Mariano Zelke
Eine kürzeste kreuzende Kante muss schnell gefunden werden.
Wenn der Baum B um einen neuen Knoten u anwächst, dann
erhalten wir neue kreuzende Kanten, nämlich in u endende Kanten.
Datenstrukturen
25/28
Der Algorithmus von Prim
(1) Setze S = {0}.
/* B ist stets ein Baum mit Knotenmenge S. Zu Anfang besteht B
nur aus dem Knoten 0.
*/
(2) Solange S 6= V , wiederhole:
(a) Bestimme eine kürzeste kreuzende Kante e = {u, v }.
(b) Füge e zu B hinzu.
(c) Wenn u ∈ S, dann füge v zu S hinzu. Ansonsten füge u zu S hinzu.
/* Beachte, dass wir neue kreuzende Kanten erhalten, nämlich alle
Kanten die den neu hinzugefügten Knoten als einen Endpunkt und
einen Knoten aus V \ S als den anderen Endpunkt besitzen.
*/
Mariano Zelke
Datenstrukturen
26/28
Beispiel Prims Algorithmus
Hamburg
Rostock
Bremen
Oldenburg
2
9
39
16
12
11
21
18
Berlin
Hannover
6
7
Dortmund
16
19
Leipzig
8
17
10
Frankfurt/M.
14
12
Karlsruhe
Dresden
Nürnberg
15
3
München
Mariano Zelke
Datenstrukturen
27/28
Beispiel Prims Algorithmus
Hamburg
Rostock
Bremen
Oldenburg
2
9
39
16
12
11
21
18
Berlin
Hannover
6
7
Dortmund
16
19
Leipzig
8
17
10
Frankfurt/M.
V \S
14
12
S
Karlsruhe
Dresden
Nürnberg
15
3
München
Mariano Zelke
Datenstrukturen
27/28
Beispiel Prims Algorithmus
Hamburg
Rostock
Bremen
Oldenburg
2
9
39
16
12
11
21
18
Berlin
Hannover
6
7
Dortmund
16
19
Leipzig
8
17
10
Frankfurt/M.
V \S
14
12
S
Karlsruhe
Dresden
Nürnberg
15
3
München
Mariano Zelke
Datenstrukturen
27/28
Beispiel Prims Algorithmus
Hamburg
Rostock
Bremen
Oldenburg
2
9
39
16
12
11
21
18
Berlin
Hannover
6
7
Dortmund
16
19
Leipzig
8
17
10
Frankfurt/M.
V \S
14
12
S
Karlsruhe
Dresden
Nürnberg
15
3
München
Mariano Zelke
Datenstrukturen
27/28
Beispiel Prims Algorithmus
Hamburg
Rostock
Bremen
Oldenburg
2
9
39
16
11
Berlin
Hannover
6
7
Leipzig
8
Dortmund
10
Frankfurt/M.
Dresden
14
12
Nürnberg
Karlsruhe
3
München
Mariano Zelke
Datenstrukturen
27/28
Die Datenstrukturen für Prims Algorithmus
I
I
Für jeden Knoten u ∈ V \ S bestimmen wir die Länge l(u) einer
kürzesten Kante, die u mit einem Knoten in S verbindet.
Wir verwalten die Knoten in V \ S mit einer Prioritätswarteschlange
und definieren l(u) als die Priorität des Knotens u.
I
I
I
I
Initialisiere einen Min-Heap, indem jeder Nachbar u von Startknoten
0 mit Priorität länge({0, u}) einfügt wird, bzw. mit Priorität ∞,
wenn u kein Nachbar ist.
Wir bestimmen also eine kürzeste kreuzende Kante, wenn wir einen
Knoten in u ∈ V \ S mit niedrigster Priorität bestimmen.
Beachte, dass sich nur die Prioritäten der Nachbarn von u ändern.
Implementiere G durch eine Adjazenzliste, da wir stets nur auf die
Nachbarn eines Knoten zugreifen müssen.
Mariano Zelke
Datenstrukturen
28/28
Herunterladen