Die k kürzesten Wege in gerichteten Graphen

Größe: px
Ab Seite anzeigen:

Download "Die k kürzesten Wege in gerichteten Graphen"

Transkript

1 Die k kürzesten Wege in gerichteten Graphen Marc Benkert Wintersemester 001/00 1 Einführung 1.1 Problemstellung In einem gerichteten, gewichteten Graphen G = (V, E) sollen die k kürzesten Wege zu zwei vorgegebenen Knoten s, t gefunden werden. 1. Technische Voraussetzungen Gegeben sei Graph G = (V, E), V = n, E = m Kantenfunktion l : E R + 0 Zykel, Selbst- und Mehrfachkanten sind erlaubt Startknoten s V Zielknoten t V jeder Knoten v V sei von s aus erreichbar ein s t Weg darf an einem Knoten mehrmals vorbeikommen, Zykel dürfen also durchlaufen werden 1. Motivation Was bringt es uns zusätzlich zu der kürzesten Verbindung zweier Punkte auch noch die nächstkürzten zu kennen? Eine Auswahl bezüglich anderer Kriterien als nur der Länge ist möglich. Ein Radfahrer wird z.b. auf die Höhenmeter achten, die er auf einer Strecke zurückzulegen hat (kürzeste Wege im Strassennetz). Die Sensitvität der Lösungen (Ähnlichkeit) kann beitragen zum Verständnis der Aussage, die der Graph illustrieren soll. Dies spielt vor allem in der Molekularbiologie eine Rolle. Allgemein kann man sagen, daß man sich von der Kenntnis der k kürzesten Wege ein besseres Verständnis der Aussage des Graphen verspricht, als von der Kenntnis lediglich eines kürzesten Weges. 1

2 1. Der vorgestellte Algorithmus Der Algorithmus den ich im folgenden vorstellen werde ist sehr konstruktiv. Er benötigt die Vorberechnung eines single destination shortest path tree. Die Destination wird in unserem Fall der Knoten t sein. Ich erhalte den single destination shortest path tree, indem ich in G R einen single source shortest path tree bzgl Knoten s bestimme, wobei G R den Graphen bezeichne, den ich aus G durch umdrehen aller Kanten erhalte. Siehe hierzu Abb. 1. Die erforderliche Laufzeit ist in O(m + n log n) [1] und ist somit verträglich mit der Laufzeit O(m+n log n+k log k), die der Algorithmus zur Bestimmung der k kürzesten s t Wege benötigt. Zur Bestimmung der k kürzesten Wege von s zu allen anderen Knoten wird O(m + n log n + nk log k) benötigt. Die Idee des Algorithmus ist nun, einen s t Weg durch einen beliebigen s u und einen kürzesten u t Weg darzustellen. Dieser wird durch den single destination shortest path tree induziert. Wir müssen nun wissen, wieviel man beim Laufen von s nach u bzgl eines kürzesten s t Weges an Länge verliere. Hierzu wird die Kantenpotentialfunktion δ(e) eingeführt. Abb. Vorbereitungen des Algorithmus.1 Verwendete Bezeichnungen Graph G = (V, E), s, t V T E shortest destination path tree zu t Für u, v V sei d(u, v) Distanz des kürzesten u v Weges induziert durch T Für e E sei δ(e) = l(e) + d(target(e), t) d(source(e), t) Kantenpotentialfunktion e G T sidetrack-kante next T (v) Nachfolger von v in T. Darstellung der Lösungswege Geben wir den kürzesten Weg explizit aus, so kann es passieren, daß der i-te kürzeste Weg die Länge i n hat. Dies ist z.b. der Fall, besteht G aus einem Kreis. Summieren wir die Länge der k Wege auf erhalten wir: k i=1 i n O(k n), was doch eher schädlich wäre gegenüber unser angestrebten Laufzeit des Algorithmus. Da aber nun ein s t Weg durch die verwendeten sidetrack-kanten eindeutig bestimmt ist, (zwischen den sidetrack-kanten laufen wir auf T) genügt es diese auszugeben. Unsere Ausgabe besteht dann aus einer Liste, die einen Pointer auf das Listenelement enthält, aus der der gespeicherte Weg durch Ausführen derselben sidetracks des in diesem Element gespeicherten Weges plus Ausführen des letzten sidetracks, der ebenfalls im Listeneintrag gespeichert ist, hervorgeht. Darüberhinaus kann z.b. die Länge des Weges gespeichert werden. Die Ausgabe des i-ten Weges benötigt in unserer impliziten Darstellung also nur noch konstante

3 s b e h a c f i d g t Abbildung 1: Beispielgraph und single destination shortest path tree Abbildung : Kantenpotentialfunktion δ(e)

4 Zeit, womit wir unser Problem gelöst hätten. Der explizite Weg läßt sich proportional zur Anzahl der Kanten des Weges berechnen. Sicherzustellen ist allerdings noch, daß die angegebenen sidetrack-kanten überhaupt einen s t Weg induzieren, sowie daß der Vorgänger-Weg eines Weges, induziert durch Weglassen des letzten sidetrack, vor dem Weg selbst ausgegeben wird. Dies wird.. sichern.. Erste Folgerungen..1 Folgerung (i) Für e E gilt δ(e) 0 (ii) Für e T gilt δ(e) = 0 Beweis: (i) Wäre δ(e) < 0 stände dies im Widerspruch dazu, daß T ein single destination shortest path tree ist. (ii) Für solche e gilt: l(e) = d(source(e), t) d(target(e), t) somit erhalte ich die Behauptung direkt aus der Definition von δ... Lemma Für jeden s t Weg p gilt l(p) = d(s, t) + e p δ(e) Beweis: Dies wird offensichtlich, da δ(e) den Verlust angibt, den ich bzgl des Weges, der in source(e) startet, e benutzt und dann auf T nach t läuft gegenüber dem Weg, der sofort von source(e) auf T nach t läuft, habe. Ein mathematischer Beweis wäre: l(p) = e p l(e) = e p δ(e)+d(source(e), t) d(target(e), t) = d(s, t) + d(t, t) + e p δ(e) = d(s, t) + e p δ(e)... Folgerung Für jeden s t Weg p = (e 1, e,..., e n ) gilt für den s t Weg p = (e 1, e,..., e n 1 ): l(p) l(p ), wobei e i den i.ten sidetrack bezeichne. Beweis: Folgt direkt aus..1 (i) und.... Der Kürzeste-Wege-Baum (gradunbeschränkt) Wir bilden nun einen Baum, dessen Knoten aus s t Wegen bestehen. Dabei stehen in den Knoten jeweils die ausgeführten sidetracks. Die Wurzel besteht aus der leeren Menge, also dem kürzesten s t Weg laut T. Die sidetracks eines Kindes bestehen aus den sidetracks des Vorgängers sowie einem weiteren sidetrack. Hierbei muss man natürlich aufpassen, welche sidetracks ausführbar sind... liefert uns eine Heap-Ordnung auf dem Baum: Man weiß, daß die Kinder eines Knotens einen höchstens längeren s t Weg induzieren als der Knoten selbst.

5 { } ,7 1,6 1, 1, 1,, ,7, 0 Abbildung : Kürzester Wegebaum, sidetrackkanten per δ identifiziert Abbildung zeigt einen Ausschnitt dieses Baumes für unseren Beispielgraphen. Man erkennt, daß der Baum durchaus unendlich sein kann. In der Abbildung ist dies anhand des Zykels, der durch die sidetrackkanten (,7) induziert wird, verdeutlicht. Wir brauchen uns allerdings gar nicht erst zu fragen, wieviel Laufzeit benötigt würde, diesen Baum zu erstellen, da das Suchen nach den k kürzesten Wegen in diesem Baum sowieso schon mehr Laufzeit verschlingen würde, als wir für unseren Algorithmus veranschlagt haben. Zur Erinnerung: Dies war O(m + n log n + k log k). Das Auffinden der k kürzesten Wege in einem Wegebaum Allgemeiner gesagt suchen wir die k kleinsten Knotenwerte in einem Heap H mit der Restriktion, daß die Nachfolgeknoten einen geringeren Wert haben. Bei uns ist der Heap H der kürzeste Wegebaum, und die Knotenwerte sind die Summen der δ W erte der sidetrackkanten.

6 Geschickterweise geht man folgendermaßen vor: Man fügt die Wurzel von H in einen neuen Heap F ein. Solange noch keine k Knoten ausgegeben worden sind oder der Heap F leer ist (dies wäre der Fall hätte Heap H gar keine k Knoten, in unserem Fall würden also keine k s t Wege existieren) fügt man alle Kinder der aktuellen Wurzel von F in F ein, entnimmt die aktuelle Wurzel aus F und gibt sie aus. Alles in allem eine normale Breitensuche in einem Heap. d bezeichne nun die Anzahl der maximal möglichen Kinder eines Knotens. Die Breitensuche wird also Laufzeit O(d k + k log k) erfordern. Dabei brauchen wir O(d k) um den Heap F aufzubauen und O(k log k) für die solange-schleife. Zu beachten ist hierbei, daß ein Heap in Linearzeit aufgebaut und in logarithmischer Zeit aktualisiert werden kann. In unserem Fall sind die Kinder aber nur durch die Anzahl aller Kanten m beschränkt. Hierzu stelle man sich einen ausreichend dichten Graphen (Anzahl der Kanten von T fällt nicht ins Gewicht) vor, dessen single destination shortest path tree T aus einem einzigen Weg bestehe. Dies führt zu einer Laufzeit von O(m k + k log k) für die Breitensuche führen. Diese könnte wiederrum unsere veranschlagte Laufzeit dominieren! Die folgenden Konstruktionen haben deshalb nur ein Ziel: Einen Kürzesten-Wege-Baum zu erstellen, dessen Knoten konstant viele Nachfolger haben. In diesem Fall veranschlagt die Laufzeit der Breitensuche O(k log k) und verschlechtert die asymptotische Laufzeit des Algorithmus somit nicht mehr. Konstruktion des Kürzesten-Wege-Baums (gradbeschränkt) - Der Algorithmus Man konstruiert einen Graphen P (G) der einen Initialknoten r(s) enthalten wird und in dem alle Knoten maximal vier auslaufende Kanten haben. Der Kürzeste-Wege-Baum wird dann induziert durch alle von r(s) aus erreichbaren Knoten. Jeder Knoten hat also höchstens vier Nachfolger. Man geht dabei wie folgt vor: (i) Bilde für jeden Knoten v V einen -Heap H G (v) bestehend aus allen sidetrackkanten die ich von v aus auf dem durch T induzierten v t Weg erreichen kann. Die Heap-Ordnung wird dabei gegeben durch die δ-werte, der geringste Wert landet wieder in der Wurzel. (ii) Bilde mit all diesen Heaps einen azyklischen Graphen D(G) in dem jeder Knoten einer Kante in G T entspricht und höchstens drei auslaufende Kanten hat. (iii) Bilde aus D(G) den gewünschten Graphen P (G) mit höchstens vier auslaufenden Kanten Die Konstruktionen im einzelnen:.1 Der Heap H G (v) Wiederrum sind drei Schritte erforderlich (i) Zunächst werden für alle Knoten -Heaps H out (v) gebildet bestehend aus den von v direkt auslaufenden sidetrackkanten unter den üblichen Bedingungen. Hinzu kommt die Restriktion, daß die Wurzel nur einen Nachfolger haben darf. 6

7 Die erforderliche Laufzeit für einen Heap ist O( out(v) ). Die Laufzeit für alle diese Heaps beträgt O(m) da jede Kante maximal einmal in einen Heap eingefügt wird. (ii) Nun bildet man für jeden Knoten einen -Heap H T (v), der für alle Knoten w auf dem v t Weg in T die Wurzel von H out (w) enthält. Bilde hierfür zunächst H T (t) und laufe dann die Wege in T rückwärts entlang. Der Heap H T (v) wird gebildet, indem man die Wurzel von H out (v) in den bereits existierenden Heap H T (next T (v)) einfügt. Die Laufzeit beträgt somit im worst-case (T ein Weg) O(n) für diesen Schritt. (iii) Der Heap H G (v) entsteht nun, indem die entsprechenden Heaps H out (w) in den Heap H T (v) eingesetzt werden. Die Tatsache, daß der Heap H T (v) ein -Heap ist und die Restriktion in (i) sichern die -Heap-Eigenschaft von H G (v) Die Laufzeit zur Bildung von H G (v) ist also O(n + m) Als Beispiel für diese Konstruktion diene der Weg s, a, c, f, t in unserem Beispielgraphen. Die Heaps H out ergeben sich sofort zu: H out (t) = H out (f) = H out (c) = 9 10 H out (a) = H out (s) = 1 Die Heaps H T sowie den Heap H G (s) entnehmen Sie der Abbildung. Die Knoten werden wiederrum durch die δ-werte der Kanten identifiziert, denen sie entsprechen. Die *-Knoten werden von der Heapify-Operation, die die Wurzel von H out (w) in H T (next T (w)) einfügt, aktualisiert. Knoten, die kein * bekommen haben, werden später in D(G) dieselben Struktureigenschaften der Heaps H G ausnützen. Für sie wird nämlich in D(G) nur ein Knoten eingefügt werden. Dies ist besonders wichtig, um zu gewährleisten, daß die Anzahl der Knoten in D(G) nicht explodiert! Siehe hierzu auch Eigenschaften von D(G).. Der Graph D(G) Der Graph D(G) hat für jeden Knoten aus H out, der nicht Wurzel ist (Typ 1), sowie für jeden *- Knoten in H G (Typ ) einen Knoten. Ansonsten übernimmt er die H G -Struktur. Beachte dabei, das Kanten zu Nicht-*-Knoten auf den letzten entsprechenden *-Knoten der Vorgänger-Heaps gerichtet sind. Abbildung zeigt D(G) für unseren Beispielgraphen. Eigenschaften von D(G) D(G) ist azyklisch. 7

8 * * * * 9* H T (t) H T (f) H T (c) * 1* * * 9* 9 * H T (a) H T (s) 1* * 9 * H T (s) Abbildung : Heaps H T und Heap H G (s)

9 b e h i t d g f c a s Abbildung : Graph D(G) Jeder Knoten w D(G) kann mit einer Kante e w G identifiziert werden Jeder Knoten v G kann mit einem Knoten h(v) D(G) identifiziert werden, wobei h(v) gerade der Wurzelknoten von H G (v) ist, der immer ein *-Knoten ist. Die in D(G) von h(v) aus erreichbaren Kanten bilden den Heap H G (v), insbesondere hat jeder Knoten also maximal drei auslaufende Kanten. D(G) hat O(m + n log n) viele Knoten. Dies entspricht auch der benötigten Laufzeit, D(G) zu erstellen, da die Heaps H G vorbestimmt waren. Den letzten Punkt schauen wir uns noch etwas genauer an: Da Knoten des Typs 1 mit sidetrackkanten in G identifiziert werden können, existieren maximal m (n 1) Knoten des Typs 1 ( T = n 1). Knoten vom Typ sind etwas aufwendiger zu zählen. Beim Erstellen des Heaps H G (v) werden log i + 1 Knoten durch die Heapify-Operation beim Einfügen der Wurzel von H out (v) in H T (next T (v)) aktualisiert. Wobei i hier die Anzahl der Knoten auf dem v t Weg in T bezeichnet. Im worst-case ist T wieder ein Weg. Die Anzahl Typ-Knoten beläuft sich dann auf n 1 i=1 log i + 1 n + n i=1 log i n + n log n Typ1 und Typ-Knoten aufsummiert ergeben somit O(m + n log n) viele Knoten.. Der Graph P (G) Im Graphen D(G) bilden die von h(s) erreichbaren Knoten gerade den Heap H G (s). Ich kann somit den in h(s) startenden und in w D(G) endenden Weg identifizieren mit dem s t Weg in G, der auf T läuft und lediglich die Kante e w als sidetrackkante benutzt. 9

10 In den meisten Graphen kann ich aber mehrere sidetracks ausführen. Ich muss dies also durch Einfügen zusätzlicher Kanten in D(G) gewährleisten. Hierzu wird für jeden Knoten w D(G), der zu der sidetrackkante (u, v) G gehört, ein Kante (w, h(v)) eingefügt. Dies ist sinnvoll, da ich von h(v) den Heap H G (v) erreiche, also alle von h(v) ausführbaren sidetracks. Der Graph P (G) entsteht also aus D(G) durch Einfügen dieser Kanten, sowie Einfügen eines Initialknotens r(s) und einer Initialkante (r(s), h(s)). Da wir aus P (G) unsere kürzesten s t Wege ablesen wollen, benötigen wir eine Kantenfunktion, die angibt wieviel wir bzgl des kürzesten s t Weges an Länge verlieren. Da P (G)-Wege, die in r(s) starten, mit s t Wegen in G identifiziert werden sollen, geschieht dies wie folgt: Die Initialkante (r(s), h(s)) bekommt das Gewicht δ(h(s)) wobei mit δ(h(s)) der δ-wert der sidetrackkante gemeint ist, die zu h(s) gehört. Kanten (u, v), die schon in D(G) existierten, bekommen Gewicht δ(v) δ(u) Hinzugefügte Kanten (w, h(v)) bekommen Gewicht δ(h(v)) Die Wegindentifizierung läuft nun folgendermassen ab: Der leere Weg in P (G) induziert den kürzesten s t Weg laut T. Steige ich von r(s) zu h(s) ab, bedeutet dies, daß ich mindestens einen sidetrack ausführe. Da nun alle Knoten des Heaps H G (s) erreichbar sind, welche ja gerade alle von s erreichbaren sidetrackkanten identifizieren, wähle ich den auszuführenden sidetrack, indem ich zum entsprechenden Knoten in H G (s) laufe. Dabei wird ein sidetrack jeweils signifikant, falls der P (G)-Weg endet, oder ich wieder zu einem h(v)-knoten aufsteige. Im Fall des Aufsteigens befinde ich mich im Heap H G (v) und wähle hier wiederrum einen sidetrack, den ich ausführen möchte. Dieses Vorgehen kann ich beliebig iterieren. Die Gewichte in P (G) sind so gewählt, daß die Länge des in r(s) startenden Weges den Verlust bzgl des kürzesten s t Weges angibt. Man verdeutliche sich diesen Sachverhalt, indem man in D(G) selbst ein paar geeignete Kanten einfügt! Eigenschaften von P (G) P (G) hat O(m + n log n) viele Knoten, im Vergleich zu D(G) kommt lediglich der Initialknoten hinzu. P (G) kann in O(m + n log n) erstellt werden, da ich lediglich alle Knoten in D(G) betrachten muss, und für diese eine weitere Kante einzufügen habe. Jeder Knoten v P (G) hat maximal vier auslaufende Kanten, da D(G) maximal drei auslaufende Kante hat und eine Kante pro Knoten dazukommt. Jede Kante e P (G) hat ein positives Gewicht. Dies wird durch die Heap-Eigenschaft von H G und dadurch, daß alle Einträge im Heap nicht negativ sind gewährleistet. Jeder in r(s) startende Weg in P (G) entspricht einem s t Weg in G und umgekehrt 10

11 Für die Länge L eines solchen Weges in P (G) gilt: d(s, t) + L = l(induzierter s t Weg) Somit liefert mir die von r(s) in P (G) erreichbaren Knoten den Heap, in dem ich die in.. vorgestellte, gradbeschränkte Breitensuche zum Auffinden der k kürzesten s t Wege laufen lassen kann. Die Gesamtlaufzeit beträgt somit O(m + n log n + k log k), O(m + n log n) für das Erstellen von P (G) und O(k log k) für die anschliessende Breitensuche. Variationen des Problems und abschliessendes Beispiel.1 Die k kürzesten Wege von s zu allen Knoten Die gemachte Konstruktion löst primär das Problem, kürzeste Wege von allen Knoten zu t zu finden. (Einfügen weiterer Initialknoten r(v) in P (G) für beliebigen Knoten v G) Man behilft sich wieder durch G R. Dieselbe Konstruktion wird in G R mit Zielknoten s ausgeführt. Die gefundenen kürzesten Wege von allen Knoten zu s in G R korrespondieren somit zu den gesuchten kürzesten Wegen von s in G. Da wir die Breitensuche nun für jeden Knoten laufen lassen, ergibt sich die Laufzeit zu O(m + n log n + n k log k).. Wege kürzer als vorgebene Grenze Wir können natürlich auch alle s t Wege bestimmen, die kürzer sind als eine vorgegebene Grenze. Das Abbruchkriterium der Breitensuche verändert sich einfach zu: Solange der gefundene Weg eine Länge Grenze hat.. Kürzeste Wege in Graphen mit negativen Kantengewichten Mit der Einschränkung, daß der Graph keine Zykel negativer Länge enthalten darf (denn dann ist der kürzeste Weg nicht bestimmt) funktioniert der Algorithmus auch in Graphen mit negativen Kantengewichten. Man muss allerdings noch die Laufzeit beachten, die man für das Finden eines single source shortest path tree in einem solchen Graphen benötigt. Diese beläuft sich auf O(m n) [].. Die k längsten Wege In azyklischen Graphen (das Problem eines negativen Zykels eleminiert sich also von selbst) können die k längsten Wege gefunden werden, indem man alle Kantengewichte negiert und in G alle kürzesten Wege sucht. Diese induzieren die k längsten Wege in G. Hierzu noch folgendes Beispiel:. Das 0-1 KnapsackProblem Gegeben seien n Objekte, von denen jedes allerdings nur einmal vorhanden ist. ( 0-1). Die Objekte haben Volumen c i N sowie Wertigkeiten w i R +. 11

12 Das übliche Knapsack Problem besteht nun darin, den Sack mit Volumen L N möglichst wertvoll aufzufüllen. Formell: x i w i unter der Bedingung x i c i L zu maximieren. Wobei x i = 1 falls wir das Objekt i nehmen, 0 sonst. Zur Lösung bildet man den Graphen G, der zwei ausgezeichnete Knoten s,t besitzt, sowie für alle i = 1... n + 1 und alle j = 0... L einen Knoten i, j. Von s zu jedem Knoten 1, j verläuft eine Kante mit Gewicht 0, von jedem Knoten i, n + 1 verläuft eine Kante zu t mit Gewicht 0. Für jeden Knoten i, j mit j < n + 1 verläuft eine Kante zu i + 1, j mit Gewicht 0, und eine Kante zu i + 1, j + c i mit Gewicht wi, falls j + c i L. Abbildung 6 zeigt den Graphen G für folgende Werte: L = c 1 =, w 1 = c =, w = c =, w = c = 1, w = c =, w = Stehe ich beim Knoten i, j bedeutet dies, daß ich schon j viel Raum verbraucht habe. Nehme ich eine Querkante, so ist x i =0. Nehme ich eine Kante nach unten, so ist x i = 1. Der gebildete Graph ist azyklisch. Ich kann also laut. die längsten s t Wege suchen, die mir die besten Lösungen für das Knapsack Problem liefern. Berücksichtigen muss ich allerdings, daß mir zwei verschiedene Wege eine gleiche Lösung induzieren können. Literatur [1] M.L.Fredman and R.E.Tarjan. Fibonacci heaps and their uses in improved network optimization algorithms. J.Assoc Comput.Mach. : Assoc.for Computing Machinery, 197. [] A.V.Goldberg. Scaling algorithms for the shortest paths problem. SIAM J.Computing ():9-0. Soc.Industrial and Applied Math., June 199 [] David Eppstein. Finding the k shortest paths, SIAM J.Computing, Vol.(), 6-67, 199 1

13 1,0,0,0,0,0 6,0 1,1,1,1,1,1 6,1 s 1,,,,, 6, t 1,,,,, 6, 1,,,,, 6, 1,,,,, 6, Abbildung 6: Graph zum 0-1 Knapsack Problem