Der Kruskal-Algorithmus ist ein Beispiel für die “greedy”-Strategie, weil er immer nur die lokal minimal kostende Kante zwischen zwei Knoten der Menge hinzufügt. Die “greedy”-Strategie ist eine Entwurfsstrategie für Algorithmen, bei der immer das lokal beste bearbeitet(hinzugefügt) wird. -1.1 Wegeprobleme in Graphen Situation: Gegeben sei ein gerichteter Graph G = (V, E), dessen Kanten mit nichtnegativen reellen Zahlen R≥0 markiert sind, welche sich aus der Kostenfunktion c : E → R≥0 ergeben. Die Kanten können auf zwei Arten dargestellt werden: 1. Listendarstellung: Für jeden Knoten gibt es eine Liste der adjazenten Knoten mit jeweils auch Kosten der entsprechenden Kante. 2. Matrixdarstellung: Die Listen sind zu einer n × n-Matrix mit Einträgen ∈ R≥0 ∪ {∞} (“∞”, wenn keine Kante zwischen i und j existiert, also (i, j) ∈ / E) Problem -1.1.1. Wir betrachten nun folgende Probleme: a) geg: Knoten u, v finde: kürzesten Weg von u nach v, d.h. die Summe aller Kanten ist minimal (Beispiel: Routenplanung) b) SSSP - “single source shortest paths” geg: Knoten s finde: Länge aller kürzesten Wege von s nach allen anderen Knoten c) APSP - “all pairs shortest paths” geg: u, v ∈ V finde: für alle Paare u, v den kürzesten Weg von u nach v Anmerkung: Ein Algorithmus der eins dieser Probleme löst, löst automatisch die vorhergehenden Probleme. Allerdings ist kein schnellerer Algorithmus für a) bekannt, als b) zu lösen. -1.1.1 SSSP & Dijkstras Algorithmus Idee: Es wird eine Menge S aufrecht erhalten, welche jene Knoten enthält, zu denen bereits der kürzeste Weg (ausgehend von s) gefunden wurde. Initialisiert wird die Menge mit S = {s}. Wir definieren dazu ein Feld D, welches mit D [v] die Länge des kürzesten Weges von s nach v angibt, der nur Zwischenknoten ∈ S verwendet. 1 Algorithmus von Dijkstra: 1 2 3 4 5 6 7 S := {s} D [s] := 0; für alle Knoten v 6= s: D [v] := C (s, v); while V \ S 6= ∅ do wähle den Knoten w ∈ V \ S mit minimalem D [w]; S := S ∪ {w}; for each u ∈ V \ S, u adjazent zu w do D [u] := min (D [u] , D [w] + C (w, u)) Algorithm 1: Dijkstra i 0 1 2 3 D [v1 ] 0 0 0 0 D [v2 ] 2 2 2 2 D [v3 ] ∞ 5 5 5 D [v4 ] ∞ ∞ 9 9 D [v5 ] 10 9 9 9 S {1, 2} {1, 2, 3} {1, 2, 3, 4} {1, 2, 3, 4, 5} Korrektheit von Dijkstra Behauptung -1.1.2. Zu jeder Zeit gilt, dass zu jedem Knoten v in S D[v] gleich der Länge des kürzesten Weges von s nach v ist. Beweis. Induktion über die Anzahl k der Iterationen der Schleife (3)-(6) Induktionsanfang: Sei d(v) = Länge des kürzesten Weges von s nach v,k = 0. Es ist S = {s} und D[s] = 0 Induktionsschritt: k → k + 1 Sei w der Knoten, der in der (k + 1)-ten Iteration zu S kommt. Angenommen, die Behauptung sei falsch für w, also D[w] > d[w] (denn D[w] ist die Länge eines Weges von s nach w). Betrachte den kürzesten Weg π von s nach w. (u, v) sei die Kante auf π, die als erste aus Sk herausführt, wobei Sk = S nach k Iterationen. Dann ist nach Induktionsvoraussetzung d[u] = D[u] d(v) = d(u) + c(u, v), wobei c(v,u) die Kosten der Kanten von u nach v sind 2 d(v) = d(u) + c(u, v) = D[u] + c(u, v) ≤ D[v], denn beim Einfügen von u in S wurde D[v] mit D[u] + c(u, v) verglichen und auf das Minimum von beiden gesetzt(Zeile 6). Also ist d(v) ≤ D[v] ⇒ d(v) = D[v]. Andererseits ist D[w] ≥ d(w) ≥ d(v) = D[v]. Dies ist ein Widerspruch zu Zeile 4 des DijkstraAlgorithmus, denn dort wäre v ausgewählt worden. Datenstrukturen und Laufzeit Für Knoten V \S brauchen wir eine Prioritätswarteschlange. Hierfür bietet sich ein “Heap” an. Die Initialisierung in Zeile 2 ist in O(n) Zeit möglich. Das “Minimum streichen” in Zeile 4 braucht jeweils O(log n) Zeit, den “Wert vermindern” in Zeile 6 braucht jeweils O(log n) Zeit, 6 wird m-mal ausgeführt , wobei m = Anzahl der Kanten. Also kommt man insgesamt auf O( n log n + m log n) = O((m + n) log n). | {z } | {z } Zeilen2,4 Zeile6 Satz -1.1.3. Der Algorithmus von Dijkstra berechnet die Längen aller kürzesten Wege eines gerichteten, markierten Graphen mit n Knoten und m Kanten in O((m + n) log n) Zeit. Also höchstens O(n2 log n) -1.1.2 Kürzeste Wege zwischen allen Knotenpaaren Man könnte n-mal Dijkstra ausführen. Dadurch würde sich eine Laufzeit von O((nm + n2 ) log n) = O(n3 log n) ergeben. Algorithmus von Floyd-Warshall (k) Die Knotenmenge sei o.B.d.A V = {1, ..., n}. Wir definieren Pij = Menge aller (k) Wege von i nach j mit Zwischenknoten ∈ {1, ..., k}, dij = Länge des kürzesten (k) Weges in Pij , wobei 1 ≤ i, j ≤ n 0 ≤ k ≤ n Dann gilt für i 6= j: c(i, j) if (i, j) ∈ E; (k) dij = ∞ sonst. 3