Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

GIN2 – Vorlesung, SS04 Prof. Dr. Wolfram Conen

Ähnliche Präsentationen


Präsentation zum Thema: "GIN2 – Vorlesung, SS04 Prof. Dr. Wolfram Conen"—  Präsentation transkript:

1 GIN2 – 4.+5. Vorlesung, SS04 Prof. Dr. Wolfram Conen 15.4.2004
Rund um Dijkstra: - Kosten, Dials Variante - Radix Heaps (c) W. Conen, FH GE, GIN2

2 Kosten für Dijkstra mit Priority-Queues (kurz: PQueues)
Zur Erinnerung: gerichteter Graph G = (V,E) mit nicht-negativen Gewichten w(¢) (Bogenlänge in unserem Algorithmus), Startknoten s 2 V; zur einfacheren Darstellung: keine Schleifen, jeder Knoten ist von s aus erreichbar n = |V| = Anzahl Knoten, m = |E| = Anzahl Bögen Dijkstra liefert alle kürzesten Wege von s zu den anderen Knoten; in S werden die Knoten gesammelt, zu denen bereits kürzeste Wege bekannt sind In D(v) = Distanz(v) wird die bisher bekannt gewordene kürzeste Länge von s über Knoten in S zu v registriert; V(v) = Vorgänger(v) vermerkt den Vorgänger auf dem kürzesten bisher gefundenen Weg von s zu v (bei mehreren Wegen gleicher Länge wird hier der Vorgänger im ersten gefunden Weg vermerkt). Dijkstra-Ablauf: Initialisieren der Distanzen aller Knoten (INIT-Phase), S Ã {s} (INSERT) Iteratives Hinzufügen aller Knoten aus V \ S zu S: jeweils Auswahl des Knotens v* mit der minimalen Distanz zu s (DELETE MIN) Anschauen aller von Bögen, die von v* ausgehen, ggfs. Updates der Distanzen (und Vorgängerbeziehung) (DECREASE KEY) (c) W. Conen, FH GE, GIN2

3 Kosten für Dijkstra mit PQueues
Dijkstra-Ablauf: Initialisieren der Distanzen aller Knoten (INIT-Phase), S Ã {s} Iteratives Hinzufügen aller Knoten aus V \ S zu S: jeweils Auswahl des Knotens v* mit der minimalen Distanz zu s Anschauen aller von Bögen, die von v* ausgehen, ggfs. Updates der Distanzen (und Vorgängerbeziehung) Dijkstra Kosten: n * INSERTs n * Hinzufügen; jeweils mit 1* DELETE MIN Gradout(v*) Vergleiche Insgesamt also n * INSERT n * DELETE MIN jeder Bogen wird angepackt, d.h. z.B. m Vergleiche max. DECREASE KEY-Aufrufe (maximale Anzahl Updates), s. nächste Folie Anzahl der aus einem Knoten herausführenden Bögen, die Summe dieser Grade über alle Knoten ist natürlich die Anzahl der Kanten, m (c) W. Conen, FH GE, GIN2

4 Kosten für Dijkstra mit PQueues
Wieviele Updates gibt es maximal? Beispiel: Graphen für 5 Knoten Insgesamt 10 Updates und 10 Kanten Davon kann man 4 per INSERT erledigen Also 6 DECREASE KEY Wenn man eine beliebige Kante entfernt, hat man weniger Updates! Wenn man eine Kante hinzufügt, kann man nicht mehr Updates bekommen! ) Maximale Anzahl Updates für 5 Knoten: 5 * 2 = 5 * (5-1) / 2 = 10. Maximale Anzahl allgemein für n Knoten: n*(n-1) / 2 Maximales Verhältnis Updates pro Kante: 1 Minimales Verhältnis: (n-1) Updates bei n*(n-1) Kanten = 1/n 5 2 1 1 1 3 3 3 5 1 5 4 s 7 2 9 6 6 6 9 8 7 7 5 6 5 5 1 7 Updates: 4 + 3 + 2 + 1 (c) W. Conen, FH GE, GIN2

5 Kosten für Dijkstra mit PQueues
Wie läßt sich das abhängig von m ausdrücken? Maximal kann es m = n*(n-1) / 2 Kanten geben, die zu einem Update führen können. Gibt es mehr Kanten, dann sind mindestens m – n*(n-1)/2 hiervon irrelevant. Mindestens muss es m = n-1 Kanten geben (der Graph ist zusammenhängend), und die müssen alle zu Updates führen (Annahme: jeder Knoten ist erreichbar von s, maximales Kantengewicht nicht vorher bekannt) Ausgehend von m gibt es also maximal m Updates, aber nie mehr als n*(n-1)/2 insgesamt Hier sind die Updates in der Initialisierungsrunde mitgezählt, diese führen aber nicht zu einem DECREASE KEY, da man in dieser Runde das initiale INSERT mit dem Kantengewicht von s zu diesem Knoten ausführen würde, es ist also die Anzahl der von s ausgehenden Kanten jeweils abzuziehen: Also maximal (m-1)-DECREASE KEY, aber nie mehr als (n-1)*(n-2)/2. (c) W. Conen, FH GE, GIN2

6 Kosten für Dijkstra mit PQueues
Von Interesse: Kosten für INSERT, DELETE MIN, DECREASE KEY Alle anderen Operationen betrachten wir als elementar zu konstanten Kosten, also Kosten in der Größenordnung O(1) Zwei Fragen: Wieviel Aufwand verursachen die Operationen für eine konkrete PQueue-Realisierung bzw. Implementierung Wie groß ist der Aufwand mindestens, unabhängig von einer Implementierung (hierzu braucht man Annahmen über die Art der Realisierung, z.B. Sortieren per Vergleich zwischen je zwei Schlüsseln)? Welche Aufwände sind interessant: Worst case (schlimmster Fall), Best case (bester Fall), Average Case („durchschnittlicher Fall“) Wenn man Operationen wiederholt ausführt, dann kann z.B. die erste Ausführung teuer sein (Einfügen des ersten Elements in eine neu zu errichtende Datenbank), weitere Folgeoperationen werden aber deutlich günstiger. Dann analysiert man die amortisierten Kosten. (c) W. Conen, FH GE, GIN2

7 Kosten für Dijkstra mit PQueues
Simple Realisierung von PQueues: Annahme: alle Knoten sind eindeutig von 1 bis n nummeriert Array [1..n] speichert die Distanzen D(v) Kosten: INSERT: O(1) DELETE MIN: Suche über das ganze Array, also O(n) DECREASE KEY: O(1) Insgesamt also O(n2): n*O(INSERT) + n*O(DELETE MIN) + O(m)*O(DECREASE KEY) = n*O(1) n*O(n) O(m)*O(1) = O(n) O(n2) O(m) = O(n2), weil m < n2. Die ersten beiden Faktoren sind unabhängig von der Kantenanzahl, ihre Kosten fallen IMMER an. Der zweite Faktor dominiert alle anderen Kosten, d.h.: die Kosten fallen im worst, im average und im best case an! (c) W. Conen, FH GE, GIN2

8 Kosten für Dijkstra mit PQueues
Realisierung von PQueues imt „normalem“ Heap: Annahme: alle Knoten sind eindeutig von 1 bis n nummeriert Ein Heap speichert die Knoten mit ihren Distanzen D(v) als Key Kosten: INSERT, naiver Aufbau: ein Insert pro Knoten): log1+log2+...+log n-1=(n log n)=O(n log n) DELETE MIN: Minimum entnehmen: O(1) + Heap reorganisieren maximal bis zur Tiefe log n: O(log n) = O(log n) DECREASE KEY: Key finden (direkt über Verweis auf Heapelement vom Knoten aus einem Knotenarray [1..n]), also O(1) + Reorg: O(log n) = O(log n) Insgesamt also O(n): (n log n) + n*O(DELETE MIN) + O(m)*O(DECREASE KEY) = O(n log n) + n*O(log n) O(m)*O(log n) = O(n*log n) + O(m*log n) = O((n +m) * log n) = O(m * log n) [weil alle Knoten von s erreichbar sind nach Annahme und daher m zwischen O(n) und O(n2) liegt, also nicht von n dominiert wird, dieses aber ggfs. dominiert – im ersten Fall ergibt sich O(n + n) = O(n), im zweiten O(n2 + n) = O(n2), also jeweils O(m), ebenso für alle „Zwischenfälle“!] (c) W. Conen, FH GE, GIN2

9 Kosten für Dijkstra mit PQueues
Mit einer „normalen“ Heap-Implementierung von Priorityqueues erreichen wir also Kosten für Dijkstra von O(m* log n). Das ist jetzt zunächst eine Worst-Case-Abschätzung (wenn es gar keine Updates gibt, haben wir im Best case, z.B. nur O(n log n + m) an Kosten) Es ist aber auch eine average case Abschätzung (schauen wir noch an), Amortisierung spielt hier keine Rolle (es wird nichts durch mehrfaches Verwenden billiger) Insgesamt ist das besser, als die O(n2)-Implementierung bei der einfachen Array-Implementierung, falls gilt m = o(n2/log n) (sonst lohnt der Aufwand nicht!) Typischerweise gibt es wesentlich mehr DECREASE KEY-Operationen, als EXTRACT MIN. Heapvarianten, die günstigere (amortisierte) Kosten für DECREASE KEY-Operationen haben (z.B. Fibonacci-Heap, Radix-Heap, beide gemeinsam) verbessern das Laufzeitverhalten weiter. Mit Fibonacci-Heaps erreichen wir O(n log n + m), weil die amortisierten Kosten für DECREASE KEY nur bei O(1) liegen! (c) W. Conen, FH GE, GIN2

10 Kosten für Dijkstra mit PQueues
Problem unserer Analysen: Wir bleiben ein wenig „unscharf“: ist ihre konkrete Implementierung effizient (in Sinne des Größenordnungmäßig erreichbaren)? Entspricht ihre Implementierung tatsächlich den Algorithmen für Heap-basierte Pqueues? Sind die Annahmen über die Kosten „elementarer“ Operation für ihre konkret verwendete Rechnerarchitektur und für ihre spezielle Maschine gerechtfertigt? Daraus ergibt sich auch, ob die „idealisierten“ Kostenanalysen für die PQueue-Operationen gerechtfertigt sind und sie diese übernehmen können. Wenn ja, ist sie auch im Hinblick auf die „Konstanten“ (die im O-Kalkül ausgeblendet werden) „gut“? Ist das überhaupt interessant? Ja! Denn n*d1 = n*d2 = O(n), auch wenn d1=5 und d2 = ! (c) W. Conen, FH GE, GIN2

11 Kostenanalysen generell
„Exaktere“ Analysen: Unterstellen sehr genau eine bestimmte (abstrakte) Rechnerarchitektur (mit Details zur Darstellung von Zahlen, exakten Kosten zu einzelnen Klassen von Operationen, etc.), z.B. verwendet Knuth in The Art of Computer Programming eine Architektur mit einer eigenen (Assembler-)Sprache, deren Kosten (und Effekte!) sehr exakt analysierbar sind und in der er die Algorithmen darstellt Überlegungen zum Speicherbedarf haben wir noch gar nicht angestellt, ab einer bestimmten Größe für die Laufzeit wichtig, wo gespeichert wird: Cache, primär (Hauptspeicher), sekundär (Platte), tertiär Speicher (Netz, CD-RW, Band) Weitere Probleme: Was genau sind „average cases“? Wie bestimmt man amortisierte Kosten? etc. Details hierzu (generell und zu vielen Algorithmen und Datenstrukturen) z.B. in Cormen, Leierson, Rivest, Stein: Introduction to Algortithms, 2.nd Edition, MIT Press, 2001. Für diese und ähnliche Analysen (und für vieles mehr ;-) braucht man häufig Zählargumente (Kombinatorik) und Wahrscheinlichkeitsanalysen (Probabilistik) – Prof. Engels kann das sicher gut erklären, ansonsten kann es auch nicht schaden, mal ein passendes Buch hierzu aufzuschlagen, z.B. Steger: Diskrete Strukturen (Band 1 und 2) (c) W. Conen, FH GE, GIN2

12 Kosten für Dijkstra mit PQueues
Jetzt schauen wir noch ein paar Spezialfälle an! Angenommen, alle Bogengewichte sind ganzzahlig und aus dem Intervall [1..C]. Für diesen Fall haben wir bereits eine Idee gesehen, die auf Dial (1969) zurückgeht: verwende ein Array [0..C*(n-1)] von Töpfen (Buckets), um die Knoten in Töpfe einzusortieren, die ihrem Gewicht entsprechen. Beginne vorn und schreite auf der Suche nach Knoten mit minimalem Abstand (c) W. Conen, FH GE, GIN2

13 Kosten für Dijkstra mit PQueues
Der längste kürzeste Weg von s zu anderen Knoten kann höchstens (n-1) Bögen lang sein: Maximales Gewicht eines kürzesten Wegs: (n-1)*C Wir haben Erreichbarkeit unterstellt, also können wir alle Knoten ungleich s mit D(v) = (n-1)*C initialisieren. Die Knoten-IDs sind weiter aus [1..n]. Wir merken uns für jeden Knoten den Topf, in dem er gerade steckt. Machen wir das an unserem Beispielgraphen durch! s z 1 2 n-1 (c) W. Conen, FH GE, GIN2

14 Dials simple Vorgehensweise
5 1 2 2 C = 9, maximales Bogengewicht C*(n-1) = 40, maximale kürzeste Weglänge 1 5 1 5 4 s 7 2 9 9 7 1 4 3 7 1 2 3 4 5 6 7 8 9 10 C*(n-1) Init: s 1 2 3 4 v*=s 1 2 3 4 (c) W. Conen, FH GE, GIN2

15 Kosten für Dijkstra mit PQueues
5 1 2 2 C = 9, maximales Bogengewicht C*(n-1) = 40, maximale kürzeste Weglänge 1 1 1 3 3 3 5 1 5 4 s 7 2 9 7 6 6 6 9 8 5 7 5 6 5 1 4 4 3 7 1 2 3 4 5 6 7 8 9 10 C*(n-1) 1 1 2 3 4 D(1) = 1 v*=3 v*=1 v*=2 v*=4 3 4 4 D(2) = 3 2 3 4 D(1) = 5 D(1) = 6 (c) W. Conen, FH GE, GIN2

16 Kosten von Dials Variante
Im schlimmsten Fall erstreckt sich die Suche über alle Buckets, also von 1 bis C*(n-1) = O(nC). Das ist linear: C ist eine Konstante! Aber stören kann es schon...z.B. wenn für ihre Anwendungen n im Vergleich zu C eher klein ist ... Zudem führt eine direkte, naive Implementierung natürlich zu zusätzlichem Speicherbedarf von O(nC). Gesamte Kosten: INSERT (gesamt): O(n) DELETE MIN (gesamt): maximal O(nC) DECREASE KEY: Finden (mittels Hilfsarray): O(1), Löschen/Einfügen in Bucket: O(1) Insgesamt also: O(n) + O(nC) + O(m) = O(m + nC) (c) W. Conen, FH GE, GIN2

17 Verbesserungen zu Dial?
Nur ¼ des Bucket-Arrays wird im Beispiel genutzt! Wenn alle Knoten von s erreichbar sind: dann kann der nächste Knoten minimaler Distanz höchstens C Felder entfernt sein Wenn eine Distanz kleiner als C ist, z.B. C-x, und das momentane Array-Ende bei Ende liegt, dann werden die Felder [Ende-x+1..Ende] niemals besucht Ideal wäre die Suche, wenn man nur gefüllte Buckets besucht, diese nur Knoten mit gleicher Distanz enthalten und Verwaltung/Anlage/Update der Buckets billig ist. Das wird nicht gehen...aber wenig suchen und wenig und billig sortieren/finden (bei Knoten ungleichen Werts in den Buckets) ist schon ein gutes Ziel! (c) W. Conen, FH GE, GIN2

18 Review der einfachen Bucket Implementierung
2 1 3 4 5 6 Potentieller Engpaß: Nicht-Leeren Bucket finden 1 2 3 4 5 6 7 (c) W. Conen, FH GE, GIN2

19 Helfen Buckets mit größerer “Breite”?
22 Falls die Kosten größer sind, kann man größere Buckets verwenden. Aber wie geht dann ein “Finde Minimum” 45 2 4 22 29 14 26 3 1 6 41 27 31 3 5 Breite = 10 41 10 -19 20 -29 30 -39 40 -49 50 -59 60 -69 70 -79 0-9 2 3 (c) W. Conen, FH GE, GIN2

20 Kommentar zu “einfachen” Buckets
Die “normale” Bucketimplementierung Finden des ersten Knoten in einem Bucket: O(1) Bucket-Breite 1: jeder Knoten hat minimale Distanz Bucket-Breite > 1: Wir müssen den Knoten mit minimaler Distanz im Bucket finden! Das kann O(n) Aufwand erfordern (falls ein beliebiger Anteil von n im Bucket sein kann) (c) W. Conen, FH GE, GIN2

21 Einfacher Radix Heap Ziel: Weniger Suche, weniger Speicherverbrauch
Idee: Knoten auf Buckets verteilen, deren Anzahl sich logarithmisch zu (n-1)*C verhält PS: Die Animationen/Beispiele auf den folgenden Animationsfolien stammen zum großen Teil von James Orlin, MIT, einem der Co-Authoren des „Fast algorithms...“-Papers (s. Ü-Blatt 1 bzw. Literaturhinweise weiter hinten) (c) W. Conen, FH GE, GIN2

22 Dijkstra-Beispiel mit Radix-Heap
1 2 4 5 3 6 13 8 15 20 9 Initialisiere die Distanzen. Initialisiere die Töpfe und ihre Wertebereiche. Plaziere die Knoten in den Töpfen. 2 3 4 7 8 15 16 31 32 63 2 1 3 4 5 1 6 (c) W. Conen, FH GE, GIN2

23 Select       Wähle den Knoten mit der minimalen Distanz aus. 2 4
5 Wähle den Knoten mit der minimalen Distanz aus. 2 4 15 2 13 1 1 6 20 8 9 3 5 2 3 4 7 8 15 16 31 32 63 2 1 3 4 5 1 6 (c) W. Conen, FH GE, GIN2

24 Update 13 15 5 Betrachte die Bögen, die aus Knoten 1 herausführen, passe die Distanzen an und stecke sie in den korrekten Topf! 2 4 15 2 13 1 1 6 20 8 9 3 5 20 2 3 4 7 8 15 16 31 32 63 2 1 3 4 2 5 5 3 4 6 (c) W. Conen, FH GE, GIN2

25 Select   13 15 Wähle den Knoten mit der minimalen Distanz aus. 2 4 1
1 1 6 Knoten 3 hat die minimale Distanz 0. 20 8 9 3 3 5 20 2 3 4 7 8 15 16 31 32 63 1 2 5 6 3 4 (c) W. Conen, FH GE, GIN2

26 Update 13 15 5 Betrachte die Bögen, die aus Knoten 3 herausführen und passe die Distanzen an. 2 4 15 2 13 1 1 6 20 8 9 3 3 5 20 9 2 3 4 7 8 15 16 31 32 63 1 2 5 6 3 4 5 (c) W. Conen, FH GE, GIN2

27 Select: Teil 1 13 15 5 Finde den ersten nicht-leeren Topf mittels eines Scans der Buckets von links nach rechts. 2 4 15 2 13 1 1 6 20 8 9 3 3 5 20 9 2 3 4 7 8 15 16 31 32 63 1 2 6 4 5 (c) W. Conen, FH GE, GIN2

28 Select: Teil 2 13 15 5 Bestimme die minimale Distanz im Topf durch ein Scan aller Knoten im Bucket 2 4 15 2 13 1 1 6 20 8 9 3 3 5 d(5) = 9 ist das Minimum. 20 9 2 3 4 7 8 15 16 31 32 63 1 2 6 4 5 (c) W. Conen, FH GE, GIN2

29 Select: Teil 3 13 15 5 Verteile den Range des Topfs 5 auf die ersten 4 Töpfe, beginnend mit Wert 9. 2 4 15 2 13 1 1 6 20 8 9 3 3 5 20 9 2 3 11 12 13 15 4 7 8 15 16 31 32 63 9 10 1 2 6 4 5 (c) W. Conen, FH GE, GIN2

30 Select: Teil 4 13 15 Füge die Knoten des Topfs neu in die korrekten Töpfe ein. Bestimme diese Töpfe durch einen Scan nach links. 5 2 4 15 2 13 1 1 6 20 8 9 Der Bucket ganz links ist danach (immer) nicht leer! 3 3 5 20 9 11 12 2 3 13 15 8 15 16 31 32 63 9 1 10 2 6 2 5 4 4 5 (c) W. Conen, FH GE, GIN2

31 Select: Teil 5   13 15 Wähle einen Knoten aus dem Topf ganz links. 2
4 15 2 13 1 1 6 20 8 9 3 3 5 5 20 9 11 12 2 3 13 15 8 15 16 31 32 63 9 10 1 6 2 5 4 (c) W. Conen, FH GE, GIN2

32 Update 13 15 Betrachte die Bögen, die aus Knoten 3 herausführen und passe die Distanzen an. 5 2 4 15 2 13 17 1 1 6 20 8 Stecke die Knoten in korrekte Töpf, finde diese durch Links-Scan. 9 3 3 5 5 20 9 2 3 11 12 13 15 8 15 16 31 32 63 9 1 10 6 2 6 4 (c) W. Conen, FH GE, GIN2

33 Select: Teil 1 und 2 13 15 5 Finde den nicht-leeren Topf mit dem kleinsten Index 2 4 15 2 13 17 1 1 6 Finde die minimale Distanz in diesem Topf 20 8 9 3 3 5 5 20 9 11 12 2 3 13 15 8 15 16 31 32 63 9 1 10 2 6 4 (c) W. Conen, FH GE, GIN2

34 Select: Teil 3 und 4  13 15 2 4 Verteile den Range des Topfes neu 17
17 1 1 6 Füge die Knoten in die korrekten Töpfe ein. 20 8 9 3 3 5 5 20 9 11 12 2 3 15 13 15 8 15 16 31 32 63 13 9 1 10 14 2 2 4 6 4 (c) W. Conen, FH GE, GIN2

35 Select: Teil 5  13 15 Wähle einen Knoten aus dem Topf ganz links. 2 2
4 15 2 13 17 1 1 6 20 8 9 3 3 5 5 20 9 2 3 11 12 15 13 15 8 15 16 31 32 63 13 9 1 14 10 2 4 6 (c) W. Conen, FH GE, GIN2

36 Update  13 15 Betrachte die Bögen von Knoten 2 zu anderen Knoten. 2 2
4 15 2 13 17 1 1 6 20 8 9 3 3 5 5 20 9 15 11 12 2 3 13 15 8 15 16 31 32 63 13 9 1 14 10 4 6 (c) W. Conen, FH GE, GIN2

37 Select: Modifizierte Regel 1
13 15 5 Finde den nicht-leeren Topf mit dem kleinsten Index 2 2 4 4 15 2 13 17 1 1 6 Falls der Topf die Breite 1 hat, dann wähle einen bel. Knoten aus ihm aus. 20 8 9 3 3 5 5 20 9 2 3 15 11 12 13 15 8 15 16 31 32 63 9 13 1 10 14 4 6 (c) W. Conen, FH GE, GIN2

38 Update  13 15 Betrachte die Bögen von Knoten 4 zu anderen Knoten. 2 2
17 1 1 6 20 8 9 3 3 5 5 20 9 11 12 15 2 3 13 15 8 15 16 31 32 63 13 9 14 1 10 6 (c) W. Conen, FH GE, GIN2

39 Select: Modifizierte Regel 2
Finde den nicht-leeren Topf mit dem minimalen Index 13 15 5 2 2 4 4 15 2 13 17 Gibt es dort einen einzelnen Knoten, dann wähle diesen. 1 1 6 6 20 8 Modifizierte Regeln und Heuristiken helfen oft, aber vorsichtig einsetzen! 9 3 3 5 5 20 9 15 11 12 2 3 13 15 8 15 16 31 32 63 9 13 1 14 10 6 (c) W. Conen, FH GE, GIN2

40 Der Algorithmus endet...  13 15
Es gibt keine Bögen mehr, die zu Updates führen könnten. 2 2 4 4 15 2 13 17 1 1 6 6 Es gibt keine Knoten mehr, die eine endgültige Distanz brauchen. 20 8 9 3 3 5 5 20 9 11 12 2 3 15 13 15 8 15 16 31 32 63 9 13 1 10 14 (c) W. Conen, FH GE, GIN2

41 Radix Heap im Original Wir nehmen an, dass das maximale Bogengewicht C ist und alle Distanzen (außer für s) mit 1 initialisiert sind. Knoten in S nennen wir „gescannt“, Knoten mit endlichem Gewicht in V \ S „gelabelt“, den Rest „ungelabelt“ Eigenschaften des Dijkstra-Algorithmus: (i) für jeden Knoten v mit endlichem Distanzwert d(v) gilt d(v) 2 [0..nC] (ii) für jeden gelabelten Knoten v ¹ s gilt d(v) 2 [d(v*)..d(v*)+C] ) Aufeinander folgende DELETE MIN Operation lieferen eine nicht-absteigende Folge von Distanzwerten (c) W. Conen, FH GE, GIN2

42 Radix Heap im Original Ein One-level Radix Heap besteht aus B = dlog(C+1)e + 2 Buckets, diese werden von 1 bis B indiziert. Für C = 20 ist B = = 7 Jeder Bucket hat eine Größe (size) Bucket i hat die folgende Größe: size(1) = 1 size(i) = 2i-2 für 2 · i · B-1 size(B) = nC + 1 Es gilt: åj-1 size(i) ¸ min{size(j), C+1} für 2 · j · B (1) Jeder Bucket hat zudem als Range ein ganzzahliges Intervall (c) W. Conen, FH GE, GIN2

43 Radix Heap Zu Beginn partitionieren (=zerlegen) die Ranges das Intervall [0..nC+1] Generell zerlegen sie [d(v*)..nC+1] Die obere Grenze des Ranges u(i) eines Buckets i wird gespeichert, daraus ergeben sich die Ranges: range(i) = [u(i-1)+1 .. u(i)] mit u(0) = d(v*) – 1 ...oder range(i) = ;, falls u(i-1) ¸ u(i) Die Größen verändern sich nicht, aber die Ranges! (u(i) fällt monoton mit der Zeit) (c) W. Conen, FH GE, GIN2

44 Radix Heap Zu Beginn ist u(i) = 2i-1-1 für 1 · i · B-1, und u(B) = nC+1 Konsequenz: |range(i)| · size(i) Diese Bedingung wird immer gelten! Die gelabelten Knoten werden in den Buckets gespeichert: v kommt in Bucket i, wenn d(v) 2 range(i) Frage: Ist das eindeutig? Zu Beginn wird s in Bucket 1 abgelegt. Der Range von Bucket 1 wird immer so gestaltet, dass für alle Knoten in 1 gilt: d(v) = u(1) die effektive Größe von 1 ist also 1 (c) W. Conen, FH GE, GIN2

45 Radix Heap Implementierung
Jeder Bucket wird als doppelt-verkette Liste mit Verweisen auf seine Knoten implementiert Einfügen / Löschen (nicht aber Finden!) von Knoten geht in konstanter Zeit Jeder Knoten merkt sich, in welchem Bucket er ist (es gibt also auch ein Knoten-Array!) (c) W. Conen, FH GE, GIN2

46 Radix Heap Operationen
INSERT(v): Einfügen eines Knotens v mit einer Distanz d(v) Starte mit Bucket i = B, suche solange nach links (also i--) bis der erste Bucket i mit u(i) < d(v) gefunden wurde, dann füge v in Bucket i+1 ein. DECREASE KEY(v,k): Ändern des Distanzwertes des Knotens v Lösche v aus seinem Bucket, z.B. Bucket j Reduziere d(v) auf k, füge ein wie bei INSERT, aber beginne mit i=j Die Kosten des INSERTs und aller DECREASE KEY-Operationen für einen Knoten v sind O(log C) (+ O(1) pro DECREASE), weil der Index des Buckets, der v enthält, nie ansteigen kann, insgesamt O(m + n log C) (c) W. Conen, FH GE, GIN2

47 Radix Heap Operationen
DELETE MIN: Entfernen des Knotens mit minimaler Distanz Wenn Bucket 1 nicht leer ist, liefere einen beliebigen Knoten aus Bucket 1 zurück (und tue sonst nichts) Ansonsten finde den ersten nicht-leeren Bucket von links (also mit minimalem Index für alle nicht-leeren Buckets) Sei j dieser Bucket. Suche in j einen Knoten v* mit einem Distanzwert, der minimal ist für alle Knoten in j. Merke dir v*. Entferne v* aus j. Bestimme die Ranges der Buckets links von j neu: Ersetze u(0) durch d(v*)-1, u(1) durch d(v*), und von i=2 bis j-1, ersetze u(i) durch min{u(i-1)+size(i), u(j)}. Verteile alle anderen Knoten aus j wie im Einfügen von DECREASE KEY. (c) W. Conen, FH GE, GIN2

48 Radix Heap Operationen
Kosten des DELETE MIN: Eigenschaft (ii) und die Größeneigenschaft (1) garantieren, dass für j ¸ 2 jeder Knoten im Bucket j in ein Bucket mit echt-kleinerem(!) Index verschoben wird. ) Zeitaufwand pro DELETE MIN O(log C) (Neuverteilen der Ranges) plus O(log C) pro Vertex Insgesamt also O(n log C) Diese Implementierung des Dijkstra-Algorithmus verursacht also einen Zeitaufwand von O(m + n log C) mit Speicheraufwand O(m + log C). Verbesserung: Anzahl der Wiedereinfügungen reduzieren Two-Level Radix Heap: Segmentierung der Buckets O(m + n log C/log log (nC)) Fibonacci-Heap für die Suche nach nicht-leeren Segmenten O(m + np log C) (c) W. Conen, FH GE, GIN2

49 Radix Heap, letzte Klappe
Radix Heap und Verbesserungen sind auch praktisch sinnvoll einsetzbar (sagt James Orlin, einer der Väter der Struktur...), wenn eine Grenze C für diskrete Gewichte bekannt ist Bei Bedarf implementieren sie den Algorithmus im Handumdrehen! (die Beschreibung der drei Heap-Operationen ist recht präzise) Sie sollten den Ablauf nach der abstrakten Beschreibung noch einmal nachvollziehen – schauen sie sich die Animation noch mal an. (Der letzte Topf dort mit den Unendlich-Werten kommt in der Beschreibung im Paper nicht vor, dafür fehlt der Topf Nr. 7. In der abstrakten Beschreibung wird außerdem v* nicht mehr einsortiert in den ersten Topf, das stört aber weiter nicht, die Abläufe entsprechen, bis auf die heuristischen Modifikationen am Ende, dem abstrakten Ablauf, der sich aus der Anwendung der Operation INSERT, DELETE MIN und DECREASE KEY im Dijkstra-Algorithmus ergibt) Aktuell wird eine Mischung aus Heaps und Radix Trees bei der Weiterentwicklung des Managements der virtuellen Memory im Linux-Kernel untersucht, ein wenig besser können sie solchen Entwicklungen mit unseren Exkursen zu Priority Queues, Radix Sort, Heaps und Radix Heaps nun sicher folgen. Für die Klausur aber gilt: wenn sie den „Rest“ perfekt gelernt haben, dann dürfen sie sich auch Radix Heaps noch einmal anschauen ;-) (c) W. Conen, FH GE, GIN2

50 Literatur Eine vertiefende PowerPoint-Präsentation zu RadixHeap von James Orlin, MIT: Allgemein zur Algorithmik, nochmals erinnert sei an: Cormen, Leierson, Rivest, Stein: Introduction to Algorithms, MIT Press, 2nd Edition, 2001 (ein inhaltlich sehr gutes und optisch sehr schönes Buch, zum Nachschlagen für den Schrank, zum Lernen nicht so doll, weil ohne Lösungshinweise; zum Verstehen aber gut!) Speziell hier zu Datenstrukturen für Dijkstra (only for the *very* brave ones): Ahuja, Mehlhorn, Orlin, Tarjan: Faster Algorithms for the Shortest Path Problem (s. Link auf Übungsblatt 1) Mikkel Thorup: Integer Priority Queues with Decrease Key in Constant Time and the Single Source Shortest Paths Problem, Proc. STOC’03, ACM, 2003 (s. diesen Link) eine abstrakt beschriebene deterministische Fibonacci-Heap-Variante als Priority-Queue für Dijkstra mit insgesamt O(m + n log log C) für ganzzahlige Gewichte aus [0..C] – das löst ein offenes Problem aus dem obigen Paper – und verbessert die bekannten Grenzen! Ohne Gewichtsgrenze C erhält er O(m + n log log n), das größenordnungsmässig nur verbessert werden kann, wenn die bisher beste bekannte Lösung für deterministisches Sortieren von Han (eben mit O(n log log n) verbesserbar wäre. (c) W. Conen, FH GE, GIN2


Herunterladen ppt "GIN2 – Vorlesung, SS04 Prof. Dr. Wolfram Conen"

Ähnliche Präsentationen


Google-Anzeigen