1 Wiederholung Sei G ein einfacher (ungerichteter), zusammenhängender Graph mit den Knoten V (Vertices) und den Kanten E (Edges). 1.1 Gewichte Das Gewicht zwischen zwei benachbarten Knoten j und k wird durch die Gewichtsfunktion g(j, k) : V × V → R bestimmt. Diese wird oft auch als Distanz- oder Kostenfunktion interpretiert. 1.2 Weg Ein Weg w von x1 nach x2 ist eine Folge von Knoten (x1 , x2 , ..., xn ), wobei je zwei aufeinanderfolgende Knoten Nachbarn sind. Der Weg heisst geschlossen falls x1 = xn und einfach falls jeder Eckpunkt höchstens einmal vorkommt. Ein Graph heisst zusammenhängend wenn zwischen zwei beliebigen Knoten ein Weg existiert. P Die Länge eines Weges ist definiert als L(w) := 1n−1 g(xi , xi + 1). Die Länge des kürzesten Weges von j zu k ist eine Graphenmetrik d(j, k) := inf {L(w) : w = (j, v1 , v2 , ..., k)} 2 Der Algorithmus von Dijkstra 2.1 Einleitung Der Algorithmus von Dijkstra wurde 1959 von Edsger Dijkstra entdeckt und findet den kürzesten Weg zwischen einem Startknoten und allen anderen Knoten in einem Graphen. 1 . Damit der Algorithmus angewendet werden darf müssen alle Gewichte positiv und der Graph zusammenhängend sein. 2.2 Der Algorithmus Der Algorithmus von Dijkstra unterscheidet zwischen permanenten und temporären Knoten. Anfangs sind alle Knoten bis auf den Startknoten temporär mit einer Distanz von +∞. In den einzelnen Schritten werden die Knoten zu denen der kürzeste Weg gefunden wurde als permanente Knoten 1 Mit einer kleinen Änderung der Abbruchbedingung könnte der Algorithmus auch den kürzesten Weg zwischen einem Startknoten und nur einem einzigen Endknoten finden. 1 mit der entsprechenden Distanz markiert. Sobald alle Knoten als permanent markiert sind endet der Algorithmus. Für jeden Knoten muss die aktuelle Distanz, der Vorgänger und der Zustand (temporär oder permanent) gespeichert werden. Ausserdem ist immer genau ein Knoten als aktiv markiert. 1. Markiere den Startknoten als permanent mit Distanz 0, alle anderen Knoten als temporär mit Distanz +∞. 2. Wähle den Startknoten als aktiven Knoten a. 3. Sei j ein Nachbar des aktiven Knotens und temporär. Falls Distanz(a)+ g(a, j) < Distanz(j) setze Distanz(j) = Distanz(a) + g(a, j) und V orgaenger(j) = a. Dieser Schritt heisst Update. 4. Wähle einen temporären Knoten mit minimaler Distanz und markiere ihn als permanent und aktiv. 5. Gehe zu Schritt 3 falls noch temporäre Knoten vorhanden sind. Ansonsten endet der Algorithmus. Das Ergebnis ist eine Baumstruktur die den kürzesten Weg zu jedem Knoten im Graphen enthält. 2 2.3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Pseudocode f u n c t i o n D i j k s t r a ( Graph , s o u r c e ) : // S c h r i t t 1 : I n i t i a l i s i e r e n for each v e r t e x v i n Graph : d i s t [ v ] := i n f i n i t y v o r g a e n g e r [ v ] := u n d e f i n e d d i s t [ s o u r c e ] := 0 Q := th e s e t o f a l l nodes i n Graph // S c h r i t t 5 : Solang noch Knoten vorhanden s i n d : while Q i s not empty : // S c h r i t t 4 : Waehle Knoten mit min . D i s t a n z // Beim e r s t e n D u r c h l a u f i s t das S c h r i t t 2 ! a := v e r t e x i n Q with s m a l l e s t d i s t [ ] remove a from Q // S c h r i t t 3 : Update for each n e i g h b o r j o f a : a l t := d i s t [ a ] + g ( a , j ) if alt < dist [ j ] d i s t [ j ] := a l t v o r g a e n g e r [ j ] := a return v o r g a e n g e r [ ] 2.4 Laufzeitanalyse Die Initialisierung (Zeilen 1-8) wirkt sich auf jeden Knoten aus, also O(|V |). Wird eine Liste als Datenstruktur für vorgaenger verwendet dauert das Finden und Entfernen des Knotens mit der kürzesten Entfernung (Zeilen 14,15) je O(|V |). Weil dieser Schritt auf jeden Knoten angewendet wird ergibt das O(|V |2 ) Das Update (Zeilen 18-22) wird zwar |V |-mal durchgeführt, insgesamt muss die Schleife aber höchstens |E| mal durchlaufen werden, da jede Kante nur genau einmal verwendet wird. Die Anweisungen in der Schleife sind je mit konstanter Zeit O(1) beschränkt. Das ergibt eine Laufzeit von O(|E|) Insgesamt ergibt das O(|V |2 +|E|). Bei Verwendung einer besseren Datenstruktur2 für die temporären Knoten können minimale Knoten in O(log(|E|) 2 Fibonacci Heap, http://de.wikipedia.org/wiki/Fibonacci-Heap 3 statt O(|E|) gefunden und entfernt werden (Zeilen 14,15). Das verbessert die Laufzeit auf O(|E| log |E| + |V |). 4 5 6 3 A* 3.1 Einleitung Der A* Algorithmus (1968 von Peter Hart, Nils Nilson und Bertram Raphael) findet den kürzesten Weg zwischen zwei Knoten in einem Graphen. Er ist unter bestimmten Umständen eine Verallgemeinerung des Algorithmus’ von Dijkstra 3 . Während Dijkstra immer blind den Knoten mit der kürzesten Distanz vom Startknoten aus behandelt, verwendet A* eine heuristische Funktion um den verbleibenden Weg zum Ziel abzuschätzen und so den nächsten Knoten des Pfades auf "intelligente" Art zu finden. Suchalgorithmen die eine solche heuristische Funktion verwenden nennt man informierte Algorithmen. Für die Anwendung des Algorithmus gelten die selben Voraussetzungen wie für Dijkstra: zusammenhängender Graph mit positiven Gewichten. 3.2 Informeller Algorithmus A* versucht immer den Pfad weiterzugehen, für den die bekannte Länge gemeinsam mit der heuristischen Restlänge minimal ist. Für jeden Knoten wird die Distanz, Gesamtdistanz (geschätzt bis zum Ziel), der Vorgänger und der Zustand (temporär oder permanent) gespeichert. Ausserdem gibts es immer genau einen akiven Knoten a. 1. Markiere alle Knoten als temporär mit Distanz und Gesamtdistanz +∞ und setze alle Vorgänger als undefiniert. Nur der Startknoten wird als permanent mit Distanz 0 markiert. 2. Markiere den Startknoten als aktiven Knoten. 3. Sei j ein Nachbar des aktiven Knotens und temporär. Falls Distanz(a)+ g(a, j) < Distanz(j) setze V orgaenger(j) = a. Distanz(j) = Distanz(a) + g(a, j) GesamtDistanz(j) = Distanz(j) + h(j) 4. Wähle einen neuen temporären Knoten mit minimaler Gesamtdistanz als aktiv und markiere ihn als permanent. 3 Und zwar nur dann wenn der Algorithmus von Dijkstra ebenfalls verwendet wird um den kürzesten Weg von einem Startpunkt zu einem Endpunkt zu berechnen und gestoppt wird sobald das Ziel als permanent markiert ist. 7 5. Falls der aktive Knoten gleich dem Zielknoten ist: beende den Algorithmus 6. Beende den Algorithmus falls der aktive Knoten a gleich dem Zielknoten ist. 3.3 Ein Beispiel Selber ausdenken, lösen und prüfen. Danke! 3.4 Die heuristische Funktion Von der heuritischen Funktion wird gefordert dass h(x) < d(x, ziel). Durch diese Forderung kann der Algorithmus sicherstellen dass gewisse Wege garantiert küzer oder länger sind als andere und diese dann zuallererst, bzw. gar nicht behandelt. Ein Beispiel für eine zulässige heuristische Abschätzung bei einem Routenplaner wäre die verbleibende Entfernung als Luftlinie. Gibt es zwei heuristische Funktionen so kann h(x) := min(h1 (x), h2 (x)) als kombinierte Variante verwendet werden. 8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 f u n c t i o n AStar (G, s t a r t , g o a l ) temporary := V permanent := empty d i s t [ V−s t a r t ] := +i n f d i s t [ s t a r t ] := 0 t o t a l _ d i s t [ V−s t a r t | := +i n f t o t a l _ d i s t [ s t a r t ] := h_score [ s t a r t ] p re [V] = u n d e f i n e d while temporary i s not empty a := v : v i n temporary where t o t a l _ d i s t [ v ] i s minimal i f a == g o a l return path remove a from temporary add a t o permanent f o r e a c h y i n neighbor_nodes ( a ) and y not i n permanent : new_dist := d i s t [ a ] + g ( a , y ) i f new_dist < d i s t [ y ] : p re [ y ] := a d i s t [ y ] := new_dist t o t a l _ d i s t [ y ] := d i s t [ y ] + h ( y ) return no_path_found 3.5 Laufzeitanalyse Die Laufzeitanalyse ergibt, genau wie bei Dijkstra, O(|V | log V ). Die tatsächliche Laufzeit hängt jedoch kaum mehr mit der berechenbaren O-Laufzeit zusammen, sondern vielmehr mit der Qualität der heuristischen Funktion. Eine perfekte Heruistik würde immer den richtigen Nachfolgeknoten auf einem gesuchten Pfad erkennen 4 . 4 In diesem Fall würde es sich aber auch nicht mehr um ein Suchproblem handeln, da der beste Pfad offensichtlich direkt berechenbar wäre. 9 3.6 Beweis: A* ist optimal Sei w1 der optimale Pfad mit Länge L1 und w2 mit Länge L2 die gefundene suboptimale Lösung. Für die suboptimale Lösung gilt sicher dass L2 > L1 (sonst wäre die Lösung nicht suboptimal). Weil h eine zulässige Heuristik ist gilt für jeden Knoten auf dem optimalen Pfad: d(x) + h(x) <= L1 Damit dist_total(x) = d(x) + h(x) <= L1 < L2 Wegen Schritt (4) des Algorithmus wird also jeder Punkt x der auf dem optimalen Pfad liegt als aktiver Knoten ausgewählt werden bevor der Zielknoten über einen suboptimalen Pfad als Zielknoten ausgewählt würde. Setz man x = zielknoten kann man sehen dass der Zielknoten nur über den optimalen Pfad ausgewählt werden kann. 4 Quellen E.W. Dijkstra, A note on two problems in connexion with Graphs (http://wwwm3.ma.tum.de/twiki/pub/MN0506/WebHome/dijkstra.pdf) M. Schmuckenschlaeger, Graphentheorie (http://www.quixquax.at/bleichling/anwendungen/h Wikipedia: Dijkstra Algorithm, A* Algorithm 10