Document

Werbung
ADS – Vorlesung zu Dijkstra/Dial
Prof. Dr. Wolfram Conen
Rund um Dijkstra:
- Kosten, Dials Variante
06.05.2008
(c) W. Conen, FH GE, ADS
1
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 ∈ 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)
06.05.2008
(c) W. Conen, FH GE, ADS
2
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)
‰
wir vernachlässigen hier, dass
nur noch Kanten nach V/S
geprüft werden
„
Anzahl der aus einem Knoten herausführenden Bögen,
die Summe dieser Grade über alle Knoten ist natürlich
die Anzahl der Kanten, m
06.05.2008
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
(c) W. Conen, FH GE, ADS
3
Kosten für Dijkstra mit PQueues
Wieviele Updates gibt es maximal?
Beispiel: Graphen für 5 Knoten
„
„
„
„
„
„
5
2
1
5
3
„
„
1
s 0 7
5
4
2
9
„
„
9
8 1 5
7
6
7
6
7
06.05.2008
„
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
Updates: 4 + 3 + 2 + 1
(c) W. Conen, FH GE, ADS
4
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*(n1)/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)*(n2)/2.
06.05.2008
(c) W. Conen, FH GE, ADS
5
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 PQueueRealisierung 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.
06.05.2008
(c) W. Conen, FH GE, ADS
6
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) =
2
2
„
O(n ), weil m < n .
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!
06.05.2008
(c) W. Conen, FH GE, ADS
7
Kosten für Dijkstra mit PQueues
„
Realisierung von PQueues mit „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)
O(n log n)
O(n*log n)
O((n +m)
+ n*O(DELETE MIN) + O(m)*O(DECREASE KEY) =
+ n*O(log n)
+ O(m)*O(log n) =
+
O(m*log n) =
* 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“!]
06.05.2008
(c) W. Conen, FH GE, ADS
8
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, 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!
06.05.2008
(c) W. Conen, FH GE, ADS
9
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 OKalkül ausgeblendet werden) „gut“?
„
06.05.2008
Ist das überhaupt interessant? Ja! Denn n*d1 = n*d2 = O(n), auch
wenn d1=5 und d2 = 500.000!
(c) W. Conen, FH GE, ADS
10
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, 2nd 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)
06.05.2008
(c) W. Conen, FH GE, ADS
11
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 durch die Töpfe
06.05.2008
(c) W. Conen, FH GE, ADS
12
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:
1
s
„
„
„
„
2
n-1
z
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!
06.05.2008
(c) W. Conen, FH GE, ADS
13
Dials simple Vorgehensweise
5
C = 9, maximales Bogengewicht
C*(n-1) = 35, maximale kürzeste
Weglänge
1
1
1
s 0 7
5
2
2
5
4
2
9
7
0
Init:
1
2
3
4
5
6
7
8
9
10
9 1 7
4
3
C*(n-1)
s
1
2
3
4
v*=s
06.05.2008
1
2
3
(c) W. Conen, FH GE, ADS
4
14
Kosten für Dijkstra mit PQueues
5
C = 9, maximales Bogengewicht
C*(n-1) = 35, maximale kürzeste
Weglänge
1
1
1
s 0 7
5
2
2
5
3
4
2
9
7
0
1
2
3
4
11
v*=4
v*=2
v*=1
v*=3
06.05.2008
5
6
8
9
3
2
2
7
3
43
4
9
8 1 5
7
6
7
6
3
4
C*(n-1)
10
4
4
(c) W. Conen, FH GE, ADS
D(1) = 1
D(2) = 3
D(3) = 5
D(4) = 6
15
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)
06.05.2008
(c) W. Conen, FH GE, ADS
16
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!
Das versuchen Radix-Heaps, s. Literatur (im vorletzten Jahr haben wir die
noch angeschaut, ist aber doch eine reichlich komplizierte Datenstruktur,
deshalb sei sie hier nur erwähnt).
Typische Implementierungen verwenden heute z.B. Fibonacci-Heaps (auch
nicht ganz banal ;-)
06.05.2008
(c) W. Conen, FH GE, ADS
17
Literatur
„
„
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 zu Datenstrukturen für Dijkstra (only for the *very* brave ones):
‰
Ahuja, Mehlhorn, Orlin, Tarjan: Faster Algorithms for the Shortest Path Problem.
Verwendet Radix-Heaps, Präsentation hierzu z.B. auf James Orlins Webseite am
MIT.
‰
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.
„
Leider nicht so leicht praktisch umsetzbar...
„
Es nutzt die Idee, dass man nicht immer das Minimum auswählen muß,
sondern nur einen Knoten, der nicht mehr verbessert werden kann (darunter
ist auch immer das Minimum)
‰
...und eine große Anzahl weiterer Arbeiten, u.a. zu Fibonacci-Heaps etc. im
Zusammenhang mit Dikstra
06.05.2008
(c) W. Conen, FH GE, ADS
18
Herunterladen