Vorlesung Informatik 2 Algorithmen und Datenstrukturen (25 – Fibonacci-Heaps – Analyse)

Preview:

DESCRIPTION

Vorlesung Informatik 2 Algorithmen und Datenstrukturen (25 – Fibonacci-Heaps – Analyse). T. Lauer. Fibonacci-Heaps: Idee. - PowerPoint PPT Presentation

Citation preview

1

Vorlesung Informatik 2

Algorithmen und Datenstrukturen

(25 – Fibonacci-Heaps – Analyse)

T. Lauer

2

Fibonacci-Heaps: Idee

• Liste von Bäumen (beliebigen Verzweigungsgrades), die alle heapgeordnet sind.

Definition:

Ein Baum heißt (min-)heapgeordnet, wenn der Schlüssel jedes Knotens größer

oder gleich dem Schlüssel seines Vaterknotens ist (sofern er einen Vater hat).

• Die Wurzeln der Bäume sind in einer doppelt verketteten, zirkulären Liste

miteinander verbunden (Wurzelliste).

• Der Einstiegspunkt ist ein Zeiger auf den Knoten mit minimalem Schlüssel.

2 19

13 45 8

36 21

24

15 83 52

79

117

3

Fibonacci-Heaps: Knotenformat

class FibNode { Object content; // der eigentliche Inhalt int key; // Schlüssel (Priorität) FibNode parent, child; // Zeiger auf Vater und einen Sohn FibNode left, right; // Zeiger auf linken und rechten

Nachbarn

int rank; // Anzahl der Söhne dieses Knotens boolean mark; // Markierung}

Die Zahl rank gibt an, wie viele Söhne der Knoten hat (= der Rang des Knotens)

Die Bedeutung der Markierung mark wird später deutlich. Diese Markierung gibt an, ob der Knoten bereits einmal einen seiner Söhne verloren hat, seitdem er selbst zuletzt Sohn eines anderen Knotens geworden ist.

4

Fibonacci-Heap: Detaillierte Darstellung

2 19

13 45 8

36 21

24

15 83 52

79

117

5

Fibonacci-Heaps: Operationen

• Q.accessmin(): Gib den Knoten Q.min zurück (bzw. null, wenn Q leer ist).

• Q.insert(int k): Erzeuge einen neuen Knoten N mit Schlüssel k und füge ihn

in die Wurzelliste von Q ein. Falls k < Q.min.key, aktualisiere den Minimum-

Zeiger (setze Q.min = N). Gib den neu erzeugten Knoten zurück.

6 19

13 45 8

36 21

24

15 83 52

79

117

6

Fibonacci-Heaps: Operationen

• Q.accessmin(): Gib den Knoten Q.min zurück (bzw. null, wenn Q leer ist).

• Q.insert(int k): Erzeuge einen neuen Knoten N mit Schlüssel k und füge ihn

in die Wurzelliste von Q ein. Falls k < Q.min.key, aktualisiere den Minimum-

Zeiger (setze Q.min = N). Gib den neu erzeugten Knoten zurück.

6 19

13 45 8

36 21

24

15 83 52

79

117 4

7

Fibonacci-Heaps: Operationen

• Q.accessmin(): Gib den Knoten Q.min zurück (bzw. null, wenn Q leer ist).

• Q.insert(int k): Erzeuge einen neuen Knoten N mit Schlüssel k und füge ihn

in die Wurzelliste von Q ein. Falls k < Q.min.key, aktualisiere den Minimum-

Zeiger (setze Q.min = N). Gib den neu erzeugten Knoten zurück.

6 19

13 45 8

36 21

24

15 83 52

79

117 4

8

Manipulation von Bäumen in Fibonacci-Heaps

Drei Basis-Methoden zur Manipulation von Bäumen in Fibonacci-Heaps:

link: „Wachstum“ von Bäumen.

Zwei Bäume werden zu einem neuen verbunden.

cut: „Beschneiden“ von Bäumen im Inneren.

Ein Teilbaum wird aus einem Baum herausgetrennt und als neuer Baum in die

Wurzelliste eingefügt.

remove: „Spalten“ von Bäumen an der Wurzel.

Entfernt die Wurzel eines Baums und fügt die Söhne der Wurzel als neue Bäume

in die Wurzelliste ein.

9

Baummanipulation: link

link:

Input: 2 Knoten mit demselben Rang k in der Wurzelliste

Methode: vereinigt zwei Bäume mit gleichem Rang, indem die Wurzel mit größerem

Schlüssel zu einem neuen Sohn der Wurzel mit kleinerem Schlüssel gemacht wird.

Die Gesamtzahl der Bäume verringert sich um 1, die Knotenzahl ändert sich nicht.

Output: 1 Knoten mit Rang k+1

Kosten: O(1)

2

13 8

36 21

24

83 52

2

13 8

36 21

24

83 52

10

Baummanipulation: cut

cut:

Input: 1 Knoten, der nicht in der Wurzelliste ist

Methode: trennt den Knoten (samt dem Teilbaum, dessen Wurzel er ist) von seinem

Vater ab und fügt ihn als neuen Baum in die Wurzelliste ein.

Die Gesamtzahl der Bäume erhöht sich um 1, die Knotenzahl ändert sich nicht.

Kosten : O(1)

2

13 45 8

36 2115

26

2

13 45 8

36

21

15

26

11

Baummanipulation: remove

remove:

Input: 1 Knoten mit Rank k aus der Wurzelliste

Methode: entfernt die Wurzel eines Baums und fügt stattdessen seine k Söhne in die

Wurzelliste ein. Die Zahl der Bäume erhöht sich um k-1, die Gesamtzahl der

Knoten verringert sich um 1.

Kosten: O(1) [sofern die Vaterzeiger der Söhne nicht gelöscht werden!]

2

13 45 8

36 2115

26

13 45 8

36 2115

26

12

Entfernen des Minimums

Q.deletemin():

• Wenn Q leer ist, gib null zurück.

• Andernfalls:

– Entferne den Minimalknoten (mit remove).

– „Konsolidiere“ die Wurzelliste:Verbinde (mit link) je zwei Wurzelknoten mit demselben Rang, und zwar solange, bis nur noch Knoten mit unterschiedlichem Rang in der Wurzelliste vorkommen.Lösche dabei die Markierungen der Knoten, die zum Sohn eines anderen werden, und entferne außerdem evtl. vorhandene Vaterzeiger der Wurzelknoten.

– Finde unter den Wurzelknoten das neuen Minimum.

– Gib den entfernten Knoten zurück.

13

deletemin: Beispiel

19

24

83 52

117 2

13 45 8

36 2115

14

consolidate: Beispiel

1913 45 8

36 21 2415

83 52

117

0 1 2 3 4 5Rang-Array:

15

consolidate: Beispiel

1913 45 8

36 21 2415

83 52

117

0 1 2 3 4 5Rang-Array:

16

consolidate: Beispiel

19

13 45 8

36 21

24

15

83 52

117

0 1 2 3 4 5Rang-Array:

17

consolidate: Beispiel

19

13

45 8

36 21

24

15

83 52

117

0 1 2 3 4 5Rang-Array:

18

consolidate: Beispiel

19

13 45

8

36 21

24

15

83 52

117

0 1 2 3 4 5Rang-Array:

19

consolidate: Beispiel

19

13 45

8

36 21

24

15

83 52

117

20

Laufzeitanalyse

Laufzeit von deletemin():

remove: O(1)

consolidate: O(#links) + O(maxRank(n))

updatemin: O(#Wurzelknoten nach consolidate) = O(maxRank(n))

Nach dem Konsolidieren gibt es von jedem Rang nur noch höchstens einen

Wurzelknoten.

Definiere maxRank(n) als den höchstmöglichen Rang, den ein Wurzelknoten in einem

Fibonacci-Heap der Größe n haben kann. (Berechnung von maxRank(n) später.)

21

Analyse von consolidate

rankArray = new FibNode[maxRank(n)+1]; // Erstelle das Array

for „each FibNode N in rootlist“ {

while (rankArray[N.rank] != null) { // Position besetzt

N = link(N, rankArray[N.rank]); // Verbinde Bäume

rankArray[N.rank-1] = null; // Lösche alte Pos.

}

rankArray[N.rank] = N; // Eintragen in Array

}

Die Gesamtkosten von consolidate werden dominiert von der Anzahl der

link-Operationen und der Anzahl der Array-Modifikationen.

22

Gesamtkosten von deletemin

remove: O(1)

Erstellen des Rang-Arrays: O(maxRank(n))

for-Schleife: O(#links) + O(maxRank(n))

Update des Minimum-Zeigers: O(maxRank(n))

Gesamtkosten: O(#links) + O(maxRank(n))

23

Herabsetzen eines Schlüssels

Q.decreasekey(FibNode N, int k):

• Setze den Schlüsselwert von N auf k herab.

• Wenn die Heap-Bedingung nicht mehr erfüllt ist (k < N.parent.key):

– Trenne N von seinem Vater ab (mit cut)

– Falls der Vater markiert ist (N.parent.mark == true), trenne auch ihn

von seinem Vater ab; wenn auch dessen Vater markiert ist, trenne auch diesen

ab, usw. („cascading cuts“)

– Markiere den Knoten, dessen Sohn zuletzt abgetrennt wurde (sofern dieser

kein Wurzelknoten ist).

– Aktualisiere den Minimum-Zeiger (falls k < min.key).

24

Beispiel für decreasekey

6 5

13 45 8

36 21

24

15 83 52

117

64

64

Setze den Schlüssel 64 auf 14 herab.

25

Beispiel für decreasekey

6 5

13 45 8

36 21

24

15 83 52

117

64

14

26

Beispiel für decreasekey

6 5

13 45 8

36 21

24

15 83 52

117

64

14

27

Beispiel für decreasekey

6 5

13 45 8

36

21

24

15 83 52

117

64

14

28

Beispiel für decreasekey

6 5

13 45 8

36

21

24

15 83 52

117

64

14

29

Laufzeitanalyse

Laufzeit von decreasekey():

Schlüssel neu setzen: O(1)

cut: O(1)

Cascading cuts: #cuts · O(1)

Markieren: O(1)

Gesamtkosten: O(#cuts)

30

Entfernen eines Knotens

Q.delete(FibNode N):

• Falls N == Q.min, führe Q.deletemin() aus.

• Andernfalls:

– Falls N nicht in der Wurzelliste ist:

• Trenne N von seinem Vater ab (mit cut)

• Falls der Vater markiert ist (N.parent.mark == true), trenne auch ihn von seinem Vater ab; wenn auch dessen Vater markiert ist, trenne auch diesen ab, usw. („cascading cuts“)

• Markiere den Knoten, dessen Sohn zuletzt abgetrennt wurde (sofern dieser kein Wurzelknoten ist).

– Entferne N aus der Wurzelliste (mit remove).

31

Beispiel für delete

6 5

13 45 8

36 21

24

15 83 52

117

64

64

Lösche den Knoten mit Schlüssel 21.

32

Beispiel für delete

6 5

13 45 8

36 21

24

15 83 52

117

64

64

33

Beispiel für delete

6 5

13 45 8

36

21

24

15 83 52

117

64 64

34

Beispiel für delete

6 5

13 45 8

36

21

24

15 83 52

117

64 64

35

Beispiel für delete

6 5

13 45

8

36

21

24

15 83 52

117

64 64

36

Beispiel für delete

6 5

13 45

8

36

21

24

15 83 52

117

64

64

37

Laufzeitanalyse

Laufzeit von delete():

• Falls der entfernte Knoten der Minimalknoten ist, sind die Kosten gleich wie bei

deletemin: O(#links) + O(maxRank(n))

• Ansonsten sind sie ähnlich wie bei decreasekey:

cut: O(1)

Cascading cuts: #cuts · O(1)

Markieren: O(1)

remove: O(1)

Gesamtkosten: O(#cuts)

38

Analyse

Beobachtungen:

Bei deletemin beeinflusst die Zahl der link-Operationen die tatsächliche Laufzeit.

Bei decreasekey (und delete) ist es die Zahl der cascading cuts.

In beiden Fällen bekommen wir im schlimmsten Fall eine lineare Laufzeit:

Aber: Wie häufig kann das passieren?

5 8 217 2

28

11

5

39

Amortisierte Analyse

Beobachtungen:

Bei deletemin beeinflusst die Zahl der link-Operationen die tatsächliche Laufzeit.

Bei decreasekey (und delete) ist es die Zahl der cascading cuts.

Idee: Spare dafür Guthaben an (Bankkonto-Paradigma)!

Annahme: Die Kosten pro link und pro cut seien jeweils 1€.

(1) Sorge dafür, dass für jeden Wurzelknoten immer 1€ Guthaben vorhanden ist,

mit dem sich die link-Operation bezahlen lässt, wenn dieser Knoten an einen

anderen angehängt wird.

(2) Sorge dafür, dass für jeden markierten Knoten, der nicht in der Wurzelliste ist,

immer 2€ Guthaben vorhanden sind. Damit kann man bei „cascading cuts“

das Abtrennen (cut) dieses Knotens bezahlen und hat noch 1€ übrig für (1).

40

Beispiel

913 45 3

36 21 24

75

52

79

107

1147

36

3214

50 39

61

41

Guthaben ansparen

Bei welchen Operationen müssen wir etwas dazu bezahlen?

Neue Wurzelknoten können entstehen bei:

– insert: gib dem neu eingefügten Wurzelknoten noch 1€ dazu.

– decreasekey: bezahle 1€ für den abgetrennten Knoten dazu.

– deletemin und delete: bezahle bei remove für jeden Sohn des entfernten

Knotens 1€ dazu, insgesamt also bis zu maxRank(n) €.

Markierte Knoten können nur am Ende von decreasekey und delete entstehen:

– Bezahle beim Markieren zusätzlich 2€ für den markierten Knoten.

42

Amortisierte Kosten von insert

Erstellen des Knotens: O(1)

Einfügen in Wurzelliste: O(1) + O(1)

Amortisierte Gesamtkosten: O(1)

43

Amortisierte Kosten von deletemin

remove: O(1) + O(maxRank(n))

Erstellen des Rang-Arrays: O(maxRank(n))

link-Operationen: #links · O(1) wird vom Guthaben bezahlt!

Restl. Eintragungen: O(maxRank(n))

Update Minimum-Zeiger: O(maxRank(n))

Amortisierte Gesamtkosten: O(maxRank(n))

44

Beispiel

913 45 3

36 21 24

75

52

79

107

1147

36

3214

50 39

61

45

Beispiel

913 45 36 21

24

75

52

79

107

1147

36

3214

50 39

61

remove: Bezahle zusätzlich für jeden Sohn 1€:

46

Beispiel

913 45

36

21

24

75

52

79

107

1147

36

3214

50 39

61

link: Bezahle alle Kosten mit dem Guthaben.

47

Beispiel

913 45

36

2124

75

52

79

107

1147

36

3214

50 39

61

link: Bezahle alle Kosten mit dem Guthaben.

48

Beispiel

913

45

36

2124

75

52

79

107

1147

36

3214

50 39

61

link: Bezahle alle Kosten mit dem Guthaben.

49

Beispiel

9

1345

36

2124

75

52

79

107

1147

36 3214

50 39

61

link: Bezahle alle Kosten mit dem Guthaben.

50

Beispiel

9

13

45

36

21

24

75

52

79

10

7

1147

36

3214

50 39

61

link: Bezahle alle Kosten mit dem Guthaben.

51

Amortisierte Kosten von decreasekey

Schlüsselwert neu setzen: O(1)

cut: O(1) + O(1)

Cascading cuts: #cuts · O(1) wird vom Guthaben bezahlt!

Markieren: O(1) + 2 · O(1)

Amortisierte Gesamtkosten: O(1)

52

Beispiel

913 45 3

36 21 24

75

52

79

7

1147

36

3214

50 39

53

Beispiel

913 45 3

36 21 24

20

52

79

7

1147

36

3214

50 39

Bezahle für den ersten cut noch 1€ zusätzlich:

54

Beispiel

913 45 3

36 21 24

20

52

79

7

1147

36

3214

50 39

Bezahle für den ersten cut noch 1€ zusätzlich:

55

Beispiel

913 45 3

36 21 24

20

52

79

7

1147

36

3214

50 39

Alle weiteren cascading cuts werden vom Guthaben bezahlt:

56

Beispiel

913 45 3

36 21 24

20

52

79

7

1147

36

3214

50

39

Alle weiteren cascading cuts werden vom Guthaben bezahlt:

57

Beispiel

913 45 3

36 21 24

20

52

79

7

1147

36

3214

50

39

Beim Markieren bezahle noch 2€ extra:

58

Amortisierte Kosten von delete

• Falls der entfernte Knoten der Minimalknoten ist, sind die Kosten dieselben wie bei

deletemin: O(maxRank(n))

• Ansonsten:

cut: O(1)

Cascading cuts: #cuts · O(1) wird vom Guthaben bezahlt!

Markieren: O(1) + 2 · O(1)

remove: O(1) + O(maxRank(n))

Amortisierte Gesamtkosten: O(maxRank(n))

59

Amortisierte Analyse

Amortisierte Kosten

• Insert: O(1)

• Accessmin: O(1)

• Deletemin: O(maxRank(n))

• Decreasekey: O(1)

• Delete: O(maxRank(n))

• Meld: O(1)

Noch zu zeigen: maxRank(n) = O(log n). D.h. der maximale Rang eines Knotens in

einem Fibonacci-Heap ist logarithmisch in der Größe n des Fibonacci-Heaps.

60

Berechnung von maxRank(n)

Lemma 1:

Sei N ein Knoten in einem Fibonacci-Heap und k = N.rank. Betrachte die Söhne

C1, ..., Ck von N in der Reihenfolge, in der sie (mit link) zu N hinzugefügt wurden.

Dann gilt:

(1) C1.rank ≥ 0

(2) Ci.rank ≥ i - 2 für i = 2, ..., k

Beweis: (1) klar

(2) Als Ci zum Sohn von N wurde, waren C1, ..., Ci-1 schon Söhne von N,

d.h. es war N.rank ≥ i-1. Da durch link immer Knoten mit gleichem Rang

verbunden werden, war beim Einfügen also auch Ci.rank ≥ i-1. Seither kann

Ci höchstens einen Sohn verloren haben (wegen cascading cuts), daher

muss gelten: Ci.rank ≥ i - 2

61

Berechnung von maxRank(n)

Lemma 2:

Sei N ein Knoten in einem Fibonacci-Heap und k = N.rank.

Sei size(N) = die Zahl der Knoten im Teilbaum mit Wurzel N.

Dann gilt: size(N) ≥ Fk+2 ≥ 1.618k

D.h. ein Knoten mit k Söhnen hat mind. Fk+2 Nachkommen (inkl. sich selbst).

Beweis: Sei Sk = min {size(N) | N mit N.rank = k}, d.h. die kleinstmögliche Größe

eines Baums mit Wurzelrang k. (Klar: S0 = 1 und S1 = 2.)

Seien wieder C1, ..., Ck die Söhne von N in der Reihenfolge, in der sie zu N

hinzugefügt wurden.

62

Berechnung von maxRank(n)

Beweis: Sei S(k) = min {size(N) | N mit N.rank = k}, d.h. die kleinstmögliche Größe eines

Baums mit Wurzelrang k. (Klar: S0 = 1 und S1 = 2.)

Seien wieder C1, ..., Ck die Söhne von N in der Reihenfolge, in der sie zu N hinzugefügt

wurden.

Es ist size(N) ≥

13

36

21

10

3214

61

k

i

k

iS

kSS

rankCSrankCSrankCSkS

2

21

)2(2

)2(...)22(11

).(...).().(1)(

63

Berechnung von maxRank(n)

• Es ist size(N) S(k) =

k

i

iS2

)2(2

64

Berechnung von maxRank(n)

Satz:

Der maximale Rang maxRank(n) eines beliebigen Knotens in einem

Fibonacci-Heap mit n Knoten ist beschränkt durch O(log n).

Beweis: Sei N ein Knoten eines Fibonacci-Heaps mit n Knoten und sei k = N.rank.

Es ist n ≥ size(N) ≥ 1.618k (nach Lemma 2)

Daher istk ≤ log1.618(n) = O(log n)

65

Zusammenfassung

Lineare Liste (Min-)Heap Fibonacci-Heap

insert: O(1) O(log n) O(1)

accessmin: O(1) O(1) O(1)

deletemin: O(n) O(log n) O(log n)*

decreasekey: O(1) O(log n) O(1)*

delete: O(n) O(log n) O(log n)*

meld: O(1) O(m log(n+m)) O(1)

*Amortisierte Kosten

Recommended