Vorlesung 7 - Universität Düsseldorf: Informatik - Heinrich

Werbung
Algorithmen und Datenstrukturen
Prof. Martin Lercher
Institut für Informatik
Heinrich-Heine-Universität Düsseldorf
Teil 7
Vorrangwarteschlangen (Priority Queues)
Version vom 2. Dezember 2016
1 / 53
Vorlesung 11
Fortsetzung 29. November 2016
2 / 53
Vorrangwarteschlangen (Priority-Queues)
Eine Priority-Queue D soll Elemente mit Schlüsseln speichern, auf denen
über ihre Schlüssel eine Ordnung definiert ist. Folgende Operationen
sollen zur Verfügung stehen:
• Initialisierung einer Datenstruktur D:
D := init()
bzw. Initialisierung einer Datenstruktur D mit einem Element k:
D := init(k)
• Einfügen eines Elementes k in Datenstruktur D:
insert(D, k)
• Entfernen eines Elementes k:
delete(D, k)
• Element mit kleinstem Schlüssel in D bestimmen:
access-min(D)
3 / 53
Vorrangwarteschlangen (Priority-Queues)
• Element mit kleinstem Schlüssel entfernen:
delete-min(D)
• Verändern des Schlüssels x von Element k zu Schlüssel y :
relocate(D, k, y )
• Verkleinern des Schlüssels x von Element k auf Schlüssel y :
decrease(D, k, y )
• Zusammenfügen von zwei Datenstrukturen D1 , D2 zu einer neuen
Datenstruktur D:
D = merge(D1 , D2 )
4 / 53
Vorrangwarteschlangen (Priority-Queues)
Anwendungen:
• Diskrete Simulationen (Ereignisse, die zu einer bestimmten Zeit in
der Zukunft passieren)
• Verwaltung von Bandbreite (Zeitkritische Information, z.B. für VoIP,
wird prioritisiert)
• Hilfsfunktion für Algorithmen, die wiederholt Minima benötigen
(z.B. in vielen Graphalgorithmen)
5 / 53
Vorrangwarteschlangen (Priority-Queues)
Anmerkungen:
Die Operation access-min(D) soll in O(1) Schritten, alle übrigen
Operationen sollen in O(log(n)) Schritten ausführbar sein.
Es ist nicht notwendig, die Elemente in der Datenstruktur zu suchen!
Priority-Queues können auf verschiedene Arten implementiert werden,
zum Beispiel durch Linksbäume, Binomial-Queues oder Fibonacci-Heaps.
Ein Baum mit n + 1 Blättern und n inneren Knoten ist balanciert, wenn
jedes Blatt eine Tiefe aus O(log(n)) hat.
Zur Implementierung von Priority-Queues reicht eine wesentlich
schwächere Forderung aus, um zu sichern, dass die obigen Operationen in
der vorgegebenen Zeit ausführbar sind.
6 / 53
Linksbäume
Linksbäume sind binäre heapgeordnete, links-rechts geordnete Bäume, die
in ihren inneren Knoten Elemente mit Schlüsseln speichern.
1
Die Schlüsselwerte der Söhne sind stets größer als der Schlüsselwert
des Vaters (Heapeigenschaft).
2
Jeder Knoten besitzt einen Distanzwert.
Der Distanzwert der Blätter ist 0.
Der Distanzwert an einem inneren Knoten ist der Distanzwert
des rechten Sohnes plus 1.
3 Der Distanzwert des rechten Sohnes ist kleiner oder gleich dem
Distanzwert des linken Sohnes.
1
2
7 / 53
Linksbäume
Beispiel
In den Zeichnungen lassen wir ab jetzt die Blätter weg.
1 2
Schlüssel
5 3
12 2
14 1 13 1
17 1 16 1
Distanzwert
3 1
7 2 15 1
23 1
20 1
27 1
19 1
8 / 53
Linksbäume
Bemerkung:
Die Anzahl der Blätter (und somit auch die Anzahl der inneren Knoten)
in einem Linksbaum B mit einer Wurzel mit Distanzwert r ist mindestens
2r .
=⇒ In einem Linksbaum hat das rechteste Blatt eine Tiefe von
O(log(n)).
Alle Operationen lassen sich auf das Verschmelzen von zwei Linksbäumen
zurückführen.
Die Operation insert(D, k)
Verschmelze D mit einem Linksbaum, der einen einzigen inneren Knoten
k mit Distanz 1 hat.
Die Operation delete-min(D)
Entferne die Wurzel und verschmelze die beiden Teilbäume der Wurzel.
9 / 53
Linksbäume
Die Operation delete(D, k)
Ersetze den Knoten k durch ein Blatt, das gegebenenfalls mit seinem
Bruder vertauscht wird, um links den Teilbaum mit größerer Distanz
anzuordnen. Adjustiere die Distanzwerte vom eingefügten Blatt bis zur
Wurzel bzw. bis zum Ende in einem linken Sohn. Verschmelze den
entstandenen Linksbaum und die beiden Teilbäume von k miteinander.
Das Adjustieren der Distanzwerte benötigt höchstens log(n) viele
Schritte. Die Distanzwerte beginnen bei 0 mit dem erzeugten Blatt und
werden bei der Adjustierung auf dem Weg zur Wurzel immer schrittweise
um genau 1 größer.
Die Operation relocate(D, k, y )
delete(D, k), ändere den Schlüssel von k auf y und führe anschließend
insert(D, k) aus.
Die Operation decrease(D, k, y )
relocate(D, k, y )
10 / 53
Linksbäume
Die Operation merge(A, B)
Wenn A (bzw. B) keine Elemente speichert, also aus genau einem Blatt
besteht, dann ist das Ergebnis der Linksbaum B (bzw. A).
Ohne Beschränkung der Allgemeinheit sei der Schlüssel an der Wurzel
von A kleiner als der Schlüssel an der Wurzel von B (ansonsten werden
die beiden Linksbäume vertauscht).
Zuerst wird rekursiv mit der gleichen Methode der rechte Teilbaum von A
mit dem Linksbaum B vereinigt. Das Ergebnis ist der neue rechte
Teilbaum von A. Ist die Distanz vom rechten Teilbaum von A größer als
die Distanz vom linken Teilbaum von A, so werden die beiden Teilbäume
von A vertauscht.
11 / 53
Linksbäume
Beispiel
Verschmelzen zweier Linksbäume A und B mit merge(A, B):
A
12 2
14 1 13 1
17 1 16 1
B
7 2
23 1
20 1
27 1
19 1
12 / 53
Linksbäume
Beispiel
Bäume vertauschen:
A
7 2
23 1
27 1
B
12 2
20 1
14 1 13 1
17 1 16 1
19 1
13 / 53
Linksbäume
Beispiel
Verschmelzen zweier Linksbäume A und B mit merge(A.rechts, B):
A
20 1
B
12 2
14 1 13 1
17 1 16 1
19 1
14 / 53
Linksbäume
Beispiel
Bäume vertauschen:
A
12 2
B
20 1
14 1 13 1
17 1 16 1
19 1
15 / 53
Linksbäume
Beispiel
Verschmelzen zweier Linksbäume A und B mit merge(A.rechts, B):
A
13 1
B
20 1
16 1
16 / 53
Linksbäume
Beispiel
Ergebnis der letzten Rekursion:
13 2
16 1
20 1
17 / 53
Linksbäume
Beispiel
Ergebnis der vorletzten Rekursion:
12 2
13 2
16 1
20 1
14 1
17 1
19 1
18 / 53
Linksbäume
Beispiel
Ergebnis:
7 2
12 2
23 1
13 2
16 1
20 1
14 1
27 1
17 1
19 1
19 / 53
Linksbäume
Die Laufzeit der Vereinigungsoperation ist beschränkt durch die Summe
der Längen der beiden Pfade von der Wurzel zum jeweils rechtesten Blatt,
also logarithmisch in der Anzahl der gespeicherten Elemente beschränkt.
Laufzeiten:
init()
insert(D, k)
access-min(D)
delete-min(D)
delete(D, k)
relocate(D, k, y )
decrease(D, k, y )
merge(D1 , D2 )
O(1)
O(log(n))
O(1)
O(log(n))
O(log(n))
O(log(n))
O(log(n))
O(log(n))
20 / 53
Vorlesung 12
2. Dezember 2016
21 / 53
Binomial-Queues
Binomialbäume sind heapgeordnete Bäume, die in allen Knoten Elemente
mit Schlüsseln speichern.
Ein Baum vom Typ B0 besteht aus genau einem Knoten.
Ein Baum vom Typ Bi+1 für i ≥ 0 besteht aus zwei Kopien von Bäumen
vom Typ Bi , indem man die Wurzel der einen Kopie zum Sohn der
Wurzel der anderen macht.
22 / 53
Binomial-Queues
B0
B1
B2
B3
B4
B i+1
Bi
Bi
23 / 53
Binomial-Queues
Eigenschaften:
Ein Baum vom Typ Bi hat 2i Knoten, die Höhe i und hi Knoten auf
Höhe h. (Binomialkoeffizient hi =Anzahl h-elementiger Teilmengen
einer i-elementigen Menge)
Die i Teilbäume der Wurzel sind vom Typ Bi−1 , Bi−2 , . . . , B0 .
Zur Speicherung einer Menge von n Elementen verwenden wir einen
Wald, der von jedem Typ Bi maximal einen Baum besitzt.
Es werden genau so viele Binomialbäume benötigt wie Einsen in der
Binärdarstellung von n = (dm−1 . . . d0 ) enthalten sind. Es wird genau
dann ein Baum vom Typ Bj benötigt, wenn dj = 1.
=⇒ Die Anzahl der Bäume in einer Binomial-Queue mit n > 1
Elementen ist höchstes log(n).
Beispiel:
11
(dezimal)
=
1 0 |{z}
1 |{z}
1
|{z}
B3
B1
(binär)
B0
Eine derartige Repräsentation einer Menge mit n Elementen ist eine
Binomial- Queue vom Typ Dn .
24 / 53
Binomial-Queues
Beispiel
n = 11, {7, 10, 12, 13, 14, 16, 17, 19, 20, 23, 27}
B0
B1
27
20
B3
7
23
17
14
12
16
13
10
19
25 / 53
Binomial-Queues
Die Operation init(k)
Erzeuge einen Baum mit genau einem Knoten, der k speichert.
Zeitaufwand O(1)
Die Operation access-min(Dn )
Durchsuchen der Wurzeln der Binomial-Bäume ⇒ Zeitaufwand O(log(n))
Die Operation merge(Dn , Dm )
(Das Vereinigen zweier Bäume vom gleichen Typ ist bereits definiert,
∈ O(1).)
Vereinige die Bäume aus beiden Binomial-Queues vom Typ Dn und Dm .
Gibt es in der Vereinigung zwei Bäume vom Typ B0 , so vereinige sie zu
einem Baum vom Typ B1 . Gibt es anschließend zwei Bäume vom Typ B1 ,
so vereinige sie zu einem Baum vom Typ B2 , usw.
(Addition zweier (Binär-)Zahlen nach der Schulmethode!)
⇒ Laufzeit: O(log(n + m))
26 / 53
Binomial-Queues
Die Operation insert(Dn , k)
merge(Dn , init(k))
Die Operation delete-min(Dn )
Sei Bj der Baum mit access-min(Dn ) in der Wurzel.
Sei Dn−2j der Wald Dn ohne Bj .
Sei D2j −1 der Wald, der aus Bj entsteht, wenn in Bj die Wurzel entfernt
wird.
Verschmelze Dn−2j mit D2j −1 um delete-min(Dn ) zu erhalten.
⇒ Laufzeit: O(n − 2j + 2j − 1) = O(log(n))
27 / 53
Binomial-Queues
Die Operation delete(Dn , k)
Sei Bj der Baum, der k enthält. Entferne Bj aus Dn . Sei Dn−2j das
Ergebnis. Zerlege nun Bj in einen Wald F .
r
l
der rechte Teilbaum, aus dem Bj
der linke Teilbaum und Bj−1
Sei Bj−1
r
zusammengesetzt wurde (Bj−1 beinhaltet die Wurzel von Bj ).
r
l
l
, dann wird Bj−1
in F aufgenommen und mit Bj−1
Ist k in Bj−1
fortgefahren.
r
l
r
Ist k in Bj−1
, dann wird Bj−1
in F aufgenommen und mit Bj−1
fortgefahren.
Ist der Teilbaum mit Wurzel k erreicht, dann entferne k und füge die
entstehenden Teilbäume zu F hinzu.
Vereinige Dn−2j und F zu einer Binomial-Queue.
⇒ Laufzeit: O(log(n))
28 / 53
Binomial-Queues
Die Operation relocate(H, k, y )
delete(D, k), ändere den Schlüssel von k auf y und führe anschließend
insert(D, k) aus.
Bemerkung:
Die Implementation von Binomial-Queues verlangt die
programmtechnische Realisation von Bäumen mit unbeschränktem Grad
(d.h. mit unbeschränkter Zahl von Söhnen).
29 / 53
Binomial-Queues
Beispiel
Nacheinander werden folgende Elemente eingefügt: 13, 17, 7, 20, 12, 19
13
13
13
7
17
20
17
13
13
17
17
13
7
17
20
7
7
13
20
17
7
13
17
20
12
...
13
7
12
20
19
17
Aufgabe: Welche Boxen sind Binomial-Queues, welche Zwischenschritte?
30 / 53
Binomial-Queues
Beispiel
Herausnahme von 13
7
15
13
10
17
20
19
14
16
15
7
16
10
17
20
19
14
7
15
10
17
20
19
14
16
31 / 53
Binomial-Queues
Laufzeiten:
init(k)
insert(D, k)
access-min(D)
delete-min(D)
delete(D, k)
relocate(D, k, y )
decrease(D, k, y )
merge(D1 , D2 )
O(1)
O(log(n))
O(log(n))
O(log(n))
O(log(n))
O(log(n))
O(log(n))
O(log(n))
32 / 53
Fibonacci-Heaps
Ein Fibonacci-Heap ist eine Sammlung heapgeordneter Bäume.
Die Struktur der Fibonacci-Heaps ist implizit durch die erklärten
Operationen definiert, d.h., jede mit den bereitgestellten Operationen
aufbaubare Struktur ist ein Fibonacci-Heap.
Die Wurzeln der Bäume sind Elemente einer doppelt verketteten zyklisch
geschlossenen Wurzelliste.
Der Fibonacci-Heap besitzt einen Zeiger (Minimalzeiger) auf den Knoten
mit kleinstem Schlüssel (den Minimalknoten) in der Wurzelliste.
Die Söhne jedes Knotens sind ebenfalls doppelt zyklisch verkettet.
Jeder Knoten hat einen Rang (:= Anzahl der Söhne) und ein
Markierungsfeld (s.u.).
33 / 53
Fibonacci-Heaps
Beispiel
8
17
12
1
22
18
4
10
20
34 / 53
Fibonacci-Heaps
Die Operation init()
Erzeuge einen leeren Fibonacci-Heap mit einem “Null”-Zeiger.
Die Operation access-min(H)
Der Minimalzeiger von H zeigt auf den Minimalknoten. Somit kann der
kleinste Schlüssel direkt angegeben werden kann.
Die Operation merge(H1 , H2 )
Hänge die Wurzellisten von H1 und H2 aneinander. Der neue
Minimalzeiger wird auf den kleineren der Minimalknoten von H1 und H2
gesetzt.
Die Operation insert(H, k)
Erzeuge einen Fibonacci-Heap H 0 , der nur das Element k enthält. Der
Rang von k ist 0. Das Element k ist unmarkiert. Anschließend werden H
und H 0 mit der Operation merge(H, H 0 ) vereinigt.
Bemerkung: Alle bisherigen Operationen sind in Zeit O(1) ausführbar!
35 / 53
Fibonacci-Heaps
Die Operation delete-min(H)
Entferne den Minimalknoten u aus der Wurzelliste und bilde eine neue
Wurzelliste durch Einhängen der Liste der Söhne von u an Stelle von u.
Durchführbar in O(1) Schritten (Pointer!).
Verschmelze nun solange zwei heapgeordnete Bäume, deren Wurzeln
denselben Rang haben, zu einem neuen heapgeordneten Baum, bis die
Wurzelliste nur Bäume enthält, deren Wurzeln einen verschiedenen Rang
haben.
Beim Verschmelzen zweier Bäume B und B 0 vom Rang i entsteht ein
Baum vom Rang i + 1. Ist das Element in der Wurzel v von B größer als
das Element in der Wurzel v 0 von B 0 , dann wird v ein Sohn von v 0 .
Das Markierungsfeld von v 0 wird auf unmarkiert gesetzt.
Aktualisiere dabei auch den Minimalzeiger.
(Dies entspricht der Schulmethode zur Addition von Binärzahlen.)
⇒ Worst-Case Laufzeit: O(n)
36 / 53
Fibonacci-Heaps
Beispiel
Aufbau eines neuen Fibonacci-Heaps mit den Schlüsseln 17, 20, 7, 10, 8
17
17
20
17
20
17
17
7
7
17
7
20
10
17
10
20
20
7
17
8
17
7
7
20
10
20
8 10 20
37 / 53
Fibonacci-Heaps
Beispiel
delete-min(H)
17 7
8
17
10
20
8 10 20
8
17
17
8 10 20
10
20
38 / 53
Fibonacci-Heaps
Beispiel
Einfügen der Schlüssel 12, 1, 22, 18 ,4 in den bestehenden
Fibonacci-Heap
8
17
12
22
18
4
10
20
Aufgabe: Welcher Schlüssel fehlt in der Abbildung?
39 / 53
Fibonacci-Heaps
Beispiel
delete-min(H)
17
8
12
4
10
22
18
8
17
20
20
10
4
12
18
22
4
17
8
12
10
22
18
20
40 / 53
Fibonacci-Heaps
Beobachtungen:
Bis jetzt gilt:
1
Nach jeder insert(), delete-min() und merge()-Operation sind die
Bäume in der Wurzelliste Binomialbäume.
2
Nach jeder delete-min()-Operation bilden die Bäume in der
Wurzelliste eine Binomial-Queue.
41 / 53
Fibonacci-Heaps
Die Operation decrease(H, k, y )
Trenne k von seinem Vater ab, verkleinere den Schlüssel auf y , und
hänge den beim Abtrennen entstandenen neuen (heapgeordneten) Baum
in die Wurzelliste. Aktualisiere den Minimalzeiger.
Durchführbar in O(1) Schritten.
Es soll verhindert werden, dass mehr als ein Sohn von einem Vater
abgetrennt wird. Beim Abtrennen eines Knotens p von seinem Vater v
wird v markiert. War v bereits markiert, wird auch v von seinem Vater v 0
abgetrennt und v 0 markiert, usw.
Alle abgetrennten Teilbäume werden in die Wurzelliste aufgenommen.
⇒ Worst-Case Laufzeit: O(n) (leider, Übungsaufgaben!)
42 / 53
Fibonacci-Heaps
Beispiel
1
4
8
17
*
*
12
15
22
19
7
11
13
14
16
20
43 / 53
Fibonacci-Heaps
Beispiel
decrease(H, 17, 5)
1
15
7
11
13
14
*
16
4
12
*
8
*
5
20
22
19
44 / 53
Fibonacci-Heaps
Die Operation delete(H, k)
Setze k auf einen sehr kleinen Schlüssel (mit decrease(H, k, −∞)) und
führe dann delete-min(H) aus.
⇒ Worst-Case Laufzeit: O(n)
Die Operation relocate(H, k, y )
delete(H, k), ändere den Schlüssel von k auf y und führe anschließend
insert(D, k) aus.
⇒ Worst-Case Laufzeit: O(n)
45 / 53
Fibonacci-Heaps
Lemma (Hilfssatz für einen späteren Beweis)
Sei p ein Knoten eines Fibonacci-Heaps H.
Ordnet man die Söhne von p in der zeitlichen Reihenfolge, in der sie an p
angehängt wurden, so gilt:
Der i-te Sohn von p hat mindestens den Rang max{i − 2, 0}.
46 / 53
Fibonacci-Heaps
Beweis.
Als der i-te Sohn u von p an p angehängt wurde, hatten u und p den
gleichen Rang.
Dieser Rang war i − 1, falls Knoten p seither keinen Sohn verloren hat,
der vor dem i-ten Sohn (zeitlich betrachtet) angehängt wurde, bzw. i,
falls p seither einen Sohn verloren hatte, der zeitlich betrachtet vor dem
i-ten Sohn angehängt wurde, bzw. i − 1 + j mit j ≥ 2, falls p seither j
Söhne verloren hat, die zeitlich betrachtet vor dem i-ten Sohn angehängt
wurden (letzteres kann nur vorkommen, wenn p in der Wurzelliste ist).
Also hat u mindestens den Rang i − 1, falls u bisher noch keinen Sohn
verloren hat. Da Knoten u höchstens einen Sohn verloren haben kann, da
er sonst von p abgetrennt worden wäre, hat u mindestens den Rang
i − 2.
47 / 53
Fibonacci-Heaps
Lemma
Jeder Knoten p vom Rang k eines Fibonacci-Heaps H ist Wurzel eines
Teilbaums mit mindestens Fk+2 Knoten, wobei Fk+2 die (k + 2)-te
Fibonacci-Zahl ist.
Beweis. (induktiv)
Sei Sk die minimale Anzahl der Knoten in einem Teilbaum mit Wurzel p
vom Rang k in einem Fibonacci-Heap.
Induktionsanfang:
S0 ≥ 1 = F2
S1 ≥ 2 = F3
48 / 53
Fibonacci-Heaps
Beweis Fortsetzung.
Induktionsschritt:
Sei p ein Knoten vom Rang k ≥ 2. Ordne die k Söhne in der Reihenfolge,
in der sie an p angehängt wurden.
Nach obigem Lemma hat der i-te Sohn mindestens den Rang
max{i − 2, 0}.
Daraus folgt (inklusiv p):
Sk ≥
+
+
+
+
+
..
.
1
F1
F2
F3
F4
F5
..
.
+
Fk
Knoten p
(= 1) Knoten im
(= 1) Knoten im
Knoten im
Knoten im
Knoten im
..
..
.
.
und somit Sk ≥ 1 +
ersten Teilbaum mit Rang ≥ 0
2-ten Teilbaum mit Rang ≥ 0
3-ten Teilbaum mit Rang ≥ 1
4-ten Teilbaum mit Rang ≥ 2
5-ten Teilbaum mit Rang ≥ 3
Knoten im k-ten Teilbaum mit Rang ≥ i − 2
Pk
i=1
Fi .
49 / 53
Fibonacci-Heaps
Beweis Fortsetzung.
Für k ≥ 2 gilt:
1+
k
X
Fi = Fk+2
i=1
denn:
Fk+2
=
=
=
=
=
=
Fk+1 + Fk
Fk + Fk−1 + Fk
Fk−1 + Fk−2 + Fk−1 + Fk
Fk−2 + Fk−3 + Fk−2 + Fk−1 + Fk
F2 + F1 + F2 + · · · Fk−1 + Fk
Pk
1 + i=1 Fi
Damit gilt also:
Sk ≥ 1 +
k
X
Fi = Fk+2
i=1
50 / 53
Fibonacci-Heaps
Bemerkung:
Da die Fibonacci-Zahlen exponentiell mit einer Basis von etwa 1.618
wachsen, hat jede Wurzel in einem Fibonacci-Heap mit n Knoten einen
Rang k ∈ O(log(n)).
Nach einer delete-min()-Operation ist der Fibonacci-Heap eine
Binomial-Queue. Daraus folgt, dass die Anzahl der Bäume in der
Wurzelliste nach einer delete-min()-Operation aus O(log(n)) ist.
51 / 53
Fibonacci-Heaps
Worst-Case Laufzeiten:
init(k)
insert(H, k)
access-min(H)
delete-min(H)
delete(H, k)
relocate(H, k, y )
decrease(H, k, y )
merge(H1 , H2 )
O(1)
O(1)
O(1)
O(n)
O(n)
O(n)
O(n)
O(1)
Die Operationen delete-min(), delete(), relocate(), decrease() in
Fibonacci-Heaps haben zwar eine schlechte Worst-Case-Laufzeit, aber
dafür eine sehr gute amortisierte Laufzeit (siehe nächstes Kapitel).
52 / 53
Fibonacci-Heaps
Theorem (Satz)
Für das Ausführen von n Operationen beginnend mit einem leeren
Fibonacci-Heap H ist die insgesamt benötigte tatsächliche Zeit
beschränkt durch die gesamte amortisierte Zeit, wobei
1
die amortisierten Zeiten der
delete-min(H)-, delete(H, k)- und relocate(H, k, y )-Operationen
aus O(log(n)) sind und
2
die amortisierten Zeiten aller Operationen
– init(k), insert(H, k), access-min(H, k) und decrease(H, k, y ) –
aus O(1) sind.
Das bitte merken!
53 / 53
Herunterladen