FB Informatik Prof. Dr. R.Nitsch Programmieren 2 Future Car Projekt Praktikum 6 - Speichern von Graphen - Suchen in Graphen - Kürzeste Wege Reiner Nitsch [email protected] Darstellung von Graphen als Array von Listen • Grundlagen zu Graphen (siehe Vorlesung Mathematik 1) Array von Adjazenzlisten 1 4 2 5 3 6 Ungerichteter Graph G(V,E) V: Knotenmenge E: Kantenmenge 1 2 4 2 5 4 3 5 6 4 1 2 5 5 4 3 2 6 3 6 1 2 2 5 3 5 6 4 2 Gerichteter Graph 5 4 6 6 1 4 2 5 09.10.2008 3 FB Informatik Prof. Dr. R.Nitsch 4 6 Projekt FutureCar Adjazenzlisten 1 Die mit Knoten 5 verbunden Nachbarknoten (= Kantenmenge E5 ) Bietet Antwort auf Fragen zu Graphen G wie z.B. • Speicherbedarf? proportional zur Anzahl Knoten plus Anzahl Kanten O(|V|+|E|) • Wieviele Kanten enden an vi? • Welche Nachbarn vj hat vi? • Existiert Kante E=(vi,vj)? O(|Ei|) Gewichteter Graph: Gewicht als zusätzliche Info der Listenelemente 2 Darstellung von Graphen mit Adjazenzmatrix Adjazenzmatrix a mit Elementen aij aij = 1 wenn Kante E=(vi,vj) in G enthalten, sonst aij=0 1 2 3 4 5 6 Ungerichteter Graph G(V,E) 1 2 3 4 5 6 Gerichteter Graph G(V,E) 09.10.2008 0 1 2 i 3 4 5 0 0 1 0 1 0 0 1 1 0 0 1 1 0 j 2 3 0 1 0 1 0 0 0 0 1 1 1 0 4 0 1 1 1 0 0 5 0 0 1 0 0 1 aij = aji (symmetrisch) 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1 1 0 0 0 0 0 1 0 0 1 FB Informatik Prof. Dr. R.Nitsch Bietet Antwort auf Fragen zu Graphen G wie z.B. • Welche Kanten enden an vi? • Welche Nachbarn vj hat vi? • Existiert Kante E=(vi,vj)? O(1) Speicherbedarf? O(|V|2) ungünstig wenn G wenige Kanten hat Gewichteter Graph: Gewicht an Stelle von '1' in Matrix eintragen aij ≠ aji (unsymmetrisch) Projekt FutureCar 3 Adjazenzliste des Graphen der FutureCar World FB Informatik Prof. Dr. R.Nitsch Rasterkarte (Grid) von FC-City # # # # # 1 4 # # # x y 0,0 1,0 2,0 3,0 0,1 1,1 2,1 3,1 0,2 1,2 2,2 3,2 0,3 1,3 2,3 3,3 Rasterkarte von FC-City mit XY-Koordinaten 1,1 1,2 2,1 1,1 3,1 2,1 3,2 1,2 2,2 1,3 2,2 3,2 2,1 3,2 3,1 4,2 2 Adjazenzlisten 5 Graph zur Modellierung der Erreichbarkeitsbeziehungen zwischen den Zellen der Rasterkarte 3 09.10.2008 Projekt FutureCar 11 21 31 12 22 32 12 11 21 22 32 31 32 13 21 42 Adjazenzlisteninfo als Textsequenz 4 Implementierungsempfehlungen 1,1 FB Informatik Prof. Dr. R.Nitsch Node 1,2 + loc + adjList 2,1 1,1 3,1 2,1 3,2 1,2 2,2 1,3 2,2 3,2 2,1 3,2 3,1 4,2 3,1 2,1 3,2 + Konstruktor: 1..N 1 Graph - nodes:Node[ ] + Graph(filename:string) +… Adjazenzlisten Empfehlungen: map<Location, Node> nodes • Container zum Verwalten der Knoten? • Knoten in Container einfügen? nodes[ loc ] = Node(…) • Was sollte in den Adjazenzlisten gespeichert werden? Location oder Adressen der Nachbarknoten. • Welcher Datentyp eignet sich für Route? Verwendbarkeit im Navi sicherstellen (Siehe Praktikum 5) 09.10.2008 Projekt FutureCar 5 Traversieren von Graphen FB Informatik Prof. Dr. R.Nitsch • Als Traversieren bezeichnet man das systematische Besuchen aller Knoten und das Durchlaufen jeder Kante eines Graphen. • Algorithmen zum Traversieren eines Graphen dienen als Basis für viele andere grundlegende Algorithmen zur Verarbeitung von Graphen • Man unterscheidet zwischen – Breitentraversierung (breadth-first search, BFS): Die Knoten werden geordnet nach der "Entfernung" von einem Startknoten durchlaufen • zuerst alle Knoten mit 1 Kantenlänge Abstand vom Startknoten • danach alle diejenigen Knoten mit Abstand 2, • danach die mit Abstand 3, usw. – Tiefentraversierung (depth-first search, DFS): Dieser Algorithmus erhöht immer zuerst die Distanz vom Startknoten, bevor er in die Breite geht und Nachbarknoten mit gleicher Distanz besucht (meist rekursiv implementiert) • Bereits besuchte Knoten müssen markiert werden, Node weil sich die Algorithmen sonst in den Kreisen des + loc:Location Graphen verlieren. Markierung + adjList + visited:bool + Konstruktor: 09.10.2008 Projekt FutureCar 6 Beispiel zur Tiefentraversierung FB Informatik Prof. Dr. R.Nitsch procedure traverse-dfs(v) visited(v) := true vnext := adjList[v] WHILE( exist(vnext) UND NOT visited(vnext) traverse-dfs(vnext) vnext := succ(v_next) END WHILE Adjazenzlisten von Seite 2 Startknoten 2 3 1 2 3 1 2 3 4 5 6 4 5 6 4 5 6 1 2 3 4 09.10.2008 2 5 2 4 2 5 4 3 5 6 4 1 2 5 5 4 3 2 6 3 6 1 Komplexität: O(|V|+|E|) 1 1 1 3 6 4 5 6 Projekt FutureCar Alle Knoten und Kanten besucht! 9 Tiefentraversierung (Iterativ) FB Informatik Prof. Dr. R.Nitsch • Iterativer Algorithmus mit einem Stack, der ausgehend von einer unmarkierten Ecke vi, alle anderen Knoten vj, j!=i eines Graphen G (genauer: einer Komponente desselben) besucht PRE: --- POST: Alle Ecken, die von v erreichbar sind, sind markiert. procedure traverse-dfs(v) t := empty-stack visited(v) := true push(t,v) WHILE NOT empty(t) DO { v := top(t) vnext := adjList[v] // t ist ein lokaler Stack // markiere v als besucht // lege v auf den Stack // hole oberstes Element aus Stack // hole ersten Nachbarknoten WHILE exist(vnext) AND visited(vnext) DO // schon besucht? vnext := succ(vnext) // Ja! Dann eben den Nächsten END WHILE IF exist(vnext) DO // Noch einen Unbesuchten gefunden? visit(vnext) // diesen besuchen (und bearbeiten), visited(vnext) := true // als "besucht" markieren und push(t,vnext) // Erst mal auf den Stack damit ... ELSE DO pop(t) END IF END WHILE // Erledigt! Alle Nachbarn von v besucht } 09.10.2008 Projekt FutureCar … und hier schon wieder runter! 10 Breitentraversierung FB Informatik Prof. Dr. R.Nitsch • Iterativer Algorithmus, der alle Knoten eines zusammenhängenden Graphen geordnet nach der Entfernung vom Startknoten v durchläuft. – Zuerst werden alle vom Startknoten über 1 Kante erreichbaren Knoten besucht – Danach alle über mindestens 2 Kanten erreichbaren Knoten, usw. – Entsteht formal aus Tiefentraversierung, wenn man den Stack durch eine Queue ersetzt. PRE: --Post: Alle Knoten, die von v erreichbar sind, sind markiert, also besucht worden procedure bfs_node(v) t := empty-queue // Definition einer leeren lokalen Queue visited(v) := true // Starknoten v als "besucht" markieren enqueue(t,v) Die Änderungen gegenüber WHILE NOT empty(t) DO der Tiefensuche sind ROT v := front(t) // vordersten Knoten in t lesen markiert! vnext := adjList[v] // hole ersten Nachbarknoten WHILE exist(vnext) AND visited(vnext) DO // schon besucht? vnext := succ(vnext) // Ja! Dann eben den Nächsten END WHILE IF exist(vnext) DO // Noch einen Unbesuchten gefunden? visit(vnext) // diesen besuchen (und bearbeiten), visited(vnext) := true // als "besucht" markieren und enqueue(t,v_next) // erst mal in queue einreihen, wo sie bis zur // Bearbeitung ihrer Nachbarknoten warten ELSE DO dequeue(t) // Erledigt! Alle Nachbarn von v wurden besucht END IF 11 Projekt FutureCar END09.10.2008 WHLE Beispiel zur Breitentraversierung FB Informatik Prof. Dr. R.Nitsch procedure bfs_node(v) t := empty-queue visited(v) := true enqueue(t,v) WHILE NOT empty(t) DO v := front(t) vnext := adj[v] WHILE exist(vnext) AND visited(vnext) DO vnext := succ(vnext) IF exist(vnext) DO visit(v_next) visited(vnext) := true enqueue(t,vnext) ELSE 2 t: dequeue(t) END IF END WHILE Startknoten Adjazenzlisten von Seite 2 5 4 1 3 3 1 2 3 1 2 3 4 5 6 4 5 6 4 5 6 1 2 3 1 2 3 09.10.2008 6 4 5 4 2 5 4 3 5 6 4 1 2 5 5 4 3 2 6 3 6 1 6 2 5 2 Queue 1 4 1 6 Projekt FutureCar Jetzt sind alle Knoten mit Distanz "1Kante" zum Startknoten besucht Alle Knoten und Kanten besucht! 12 Kürzeste Wege mittels Breitensuche FB Informatik Prof. Dr. R.Nitsch • Gesucht ist eine Verbindung (Pfad) zwischen 2 Knoten: – Tiefensuche liefert eine entsprechende Kantenfolge, wenn es eine gibt (aber nicht unbedingt die Kürzeste). – Breitensuche liefert garantiert die Kürzeste. • Aufgabe Mit Hilfe eines Breitensuchverfahrens soll der kürzeste Weg in einem ungewichteten Graphen G vom Startpunkt src zum Zielknoten dest gefunden werden, der über die geringste Anzahl von Kanten verläuft. Dabei wird der Weg so codiert, dass man ihn hinterher rekonstruieren kann. • Lösung – Die Breitentraversierung durchläuft alle Knoten geordnet nach der Kantendistanz zu src. – Der Vorgängerknoten, von dem ausgehend der Knoten v betreten wird, verbindet somit v auf dem kürzesten Wege mit src (keine Kantengewichte!). – Im Bearbeitungsschritt merkt sich Knoten v daher seinen Vorgängerknoten – Nachdem Knoten dest betreten wurde und dieser sich seinen Vorgängerknoten gemerkt hat, ist die Suche beendet. – Der kürzeste Weg, der dest mit src verbindet, ergibt sich nun, indem man, beginnend bei dest, die Folge der Vorgängerknoten rekonstruiert. – Rückwärts gelesen (std::reverse) ergibt diese Folge den gesuchten kürzesten Weg. 13 09.10.2008 Projekt FutureCar Breitensuche des Knotens d ausgehend vom Startknoten s PRE: POST: FB Informatik Prof. Dr. R.Nitsch exist(s), exist(d) Alle Knoten, die von v erreichbar sind, sind markiert, also besucht worden procedure bf_search(s,d) t := empty-queue visited(s) := true pred(s) := nil enqueue(t,s) Ergänzungen zum vorherigen Algorithmus sind ROT markiert // Definition einer leeren lokalen Queue // Starknoten v als "besucht" markieren // s kennt seinen Vorgänger noch nicht Verweis auf Vorgängerknoten Node + loc:Location + adjList + pred + visited:bool WHILE NOT empty(t) AND front(t)!=d DO // Abbruch der Suche wenn d besucht + Konstruktor: v := front(t) // vordersten Knoten in t lesen vnext := adj[v] // hole ersten Nachbarknoten WHILE exist(v_next) AND visited(v_next) DO vnext := succ(vnext) // Bereits besuchte Knoten überspringen END WHILE IF vnext != nil DO // Solange unbesuchte Nachbarknoten zu v existieren visit(vnext) // diese besuchen (und bearbeiten), pred(vnext) := v // Vorgänger merken (besuchen & bearbeiten) visited(vnext) := true // als solche markieren und enqueue(t,vnext) // in queue einfügen, wo sie bis zur Bearbeitung // ihrer Nachbarknoten warten ELSE DO dequeue(t) // entferne vorderstes Element aus t END IF // Alle Nachbarn dieses Knotens sind besucht END WHILE IF empty(t) DO { kein Pfad von s nach d} ELSE DO reverse(t) END IF 09.10.2008 Projekt FutureCar 14