Graphen - fbi.h

Werbung
FB Informatik
Prof. Dr. R.Nitsch
Programmieren 2
Graphen
• Darstellung
•Traversierung
• 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 direkt
verbundenen Nachbarknoten
(= Kantenmenge E5 )
Adjazenzlisten erlauben Antwort auf
Fragen zu Graphen G wie z.B.
• 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
Speicherbedarf?  proportional zur
Anzahl Knoten plus Anzahl Kanten 
O(|V|+|E|)
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
1
2
3
i 4
5
6
1
0
1
0
1
0
0
2
1
0
0
1
1
0
j

3 4
0 1
0 1
0 0
0 0
1 1
1 0
5
0
1
1
1
0
0
6
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
Adjazenzlisten erlauben Antwort
auf Fragen zu Graphen G wie
z.B.
• Wieviele Kanten enden an vi?
• Welche Nachbarn vj hat vi?
• Existiert Kante E=(vi,vj) mit
Zeitkomplexität 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
Anwendungsbeispiel - Futurecar-Projekt
FB Informatik
Prof. Dr. R.Nitsch
• Die Futurecaranwendung ist ein Testunit für AutopilotAlgorithmen zur autonomen Steuerung von Fahrzeugen.
• Das TestUnit besteht aus einem virtuellen
Strassenlabyrinth (FCWelt, s. Abb.) in dem sich viele
virtuelle Fahrzeuge (Futurecars) gesteuert durch ihren
individuellen Autopiloten unfallfrei bewegen müssen.
Allgemeine Vorgaben/Einschränkungen:
• Die FCWelt soll auf einem zeichenorientierten Display
darstellbar sein.
– Dazu muss sie rechteckig und in in virtuelle Parzellen
(s. Abbildung) unterteilt sein.
– Jede Parzelle symbolisiert ein FutureCars, ein Haus
oder einen Teil der Strasse.
– Alle Strassen sind zweispurig und verlaufen senkrecht
zueinander. Sie werden begrenzt durch Häuser.
– Jedes Futurecar verfügt über einen Scanner, zum
Abtasten seiner unmittelbaren Umgebung
– Für Navigationsaufgaben ist das Strassennetz in Form
von Adjazenzlisten darzustellen.
09.10.2008
Projekt FutureCar
4
Adjazenzliste des Graphen der FutureCar World
# # # #
Rasterkarte (Grid) von FC-City
#
1
4
#
#
FB Informatik
Prof. Dr. R.Nitsch
#
x
y

2
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
Graph zur Modellierung der
Erreichbarkeitsbeziehungen
zwischen den Zellen
der Rasterkarte
09.10.2008
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 
Adjazenzlisten
5
3
1,1
Projekt FutureCar
11
21
31
12
22
32
12
11
21
22
32
31
32
13
21
42
Adjazenzlisteninfo
als Textsequenz 5
Implementierungsempfehlungen
1,1
FB Informatik
Prof. Dr. R.Nitsch
Node
1,2 
3,1
2,1 3,2
+ loc
+ adjList:List
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 
+ Konstruktor:
1..N
1
Graph
- nodes:Nodes
+ Konstruktor
+…
Adjazenzlisten
Empfehlungen:
• Datentyp für Ortskoordinaten? Location als Alias für std::pair<int,int>
• Container zum Verwalten der Knoten? Nodes als Alias für map<Location, Node>
• Wie Knoten in Container einfügen? nodes[ loc ] .= Node(…)
• Besser Koordinaten oder Adressen der Nachbarknoten
in den Adjazenzlisten speichern?
Das hängt vom Algorithmus ab, der sie verwendet!
• Welcher Datentyp eignet sich für Route? Das hängt vom Algorithmus ab, der sie verwendet!
09.10.2008
Projekt FutureCar
6
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.
+ adjList
Markierung
+ visited:bool
+ Konstruktor:
09.10.2008
Projekt FutureCar
7
Tiefentraversierung (Rekursiv)
FB Informatik
Prof. Dr. R.Nitsch
• Rekursiver Algorithmus (in Pseudocode), der ausgehend von einer unmarkierten Ecke
vi, alle anderen Knoten vj, j!=i eines Graphen G besucht
Funktion: traverse-dfs(v)
Zweck: Tiefensuche in einem Graphen
Parameter v: Ecke bei dem die Suche beginnt
PRE: --POST: Alle Ecken, die von v erreichbar sind,
sind gefunden.
Node
+ loc:Location
+ adjList
+ visited:bool
+ Konstruktor:
procedure traverse-dfs(v)
visited(v) := true
Markiere v als besucht
vnext := adjList[v]
Bestimme einen Nachbarknoten von v und nenne
diesen vnext
WHILE( exist(vnext)
WHILE(vnext existiert
AND NOT visited(vnext)
UND noch nicht besucht ist)
traverse-dfs(vnext)
beginne weitere Tiefensuche bei vnext
Wieder zurück,
vnext := succ(v_next)
bestimme weiteren Nachbarknoten von v
und nenne diesen wieder vnext
END WHILE
END WHILE
09.10.2008
Projekt FutureCar
8
Beispiel zur (rekursiven) Tiefentraversierung
procedure traverse-dfs(v)
visited(v) := true
vnext := adjList[v]
WHILE( exist(vnext)
AND NOT visited(vnext)
traverse-dfs(vnext)
vnext := succ(v_next)
END WHILE
FB Informatik
Prof. Dr. R.Nitsch
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
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 s ü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 s erreichbar sind, sind markiert, also besucht worden
procedure bfs_node(s)
t := empty-queue
// Definition einer leeren lokalen Queue
visited(s) := true
// Startknoten s als "besucht" markieren
enqueue(t,s)
WHILE NOT empty(t) DO
v := front(t)
// vordersten Knoten in t lesen
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) THEN
// 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 mit gleichem
END IF
// Abstand vom Startknoten wurden besucht.
11
09.10.2008
Projekt FutureCar
END WHLE
procedure bfs_node(s)
t := empty-queue
visited(s) := true
enqueue(t,s)
WHILE NOT empty(t) DO
v := front(t)
vnext := adj[v]
WHILE exist(vnext) AND visited(vnext) DO
vnext := succ(vnext) END WHILE
IF exist(vnext) THEN
visit(vnext)
visited(vnext) := true
enqueue(t,vnext)
ELSE
dequeue(t)
END IF
2
t:
END WHILE
Beispiel zur Breitentraversierung
FB Informatik
Prof. Dr. R.Nitsch
Adjazenzlisten
von Seite 2
Queue
5
4
1
3
1
2
4 
2
5
4
3
5
6 
4
1
2
5 
5
4
3
2 
6
3
6
1 
6
Startknoten
1
2
3
1
2
3
1
2
3
4
5
6
4
5
6
4
5
6
1
2
3
1
2
3
4
09.10.2008
5
6
4
5
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 s zum Zielknoten d 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 s.
– Der Vorgängerknoten, von dem ausgehend der Knoten v betreten wird, verbindet
somit v auf dem kürzesten Wege mit s (keine Kantengewichte!).
– Im Bearbeitungsschritt merkt sich Knoten v daher seinen Vorgängerknoten
– Nachdem Knoten d betreten wurde und dieser sich seinen Vorgängerknoten
gemerkt hat, ist die Suche beendet.
– Der kürzeste Weg, der d mit s verbindet, ergibt sich nun, indem man, beginnend
bei d, 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
FUNCTION 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(vnext) AND visited(vnext) DO
vnext := succ(vnext)
// Bereits besuchte Knoten überspringen
END WHILE
IF vnext != nil THEN
// Solange unbesuchte Nachbarknoten zu v existieren
visit(vnext)
// diese besuchen (und bearbeiten),
pred(v_next) := 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
IF empty(t) THEN
{ kein Pfad von s nach d }
09.10.2008
Projekt FutureCar
14
Wegesuche in gewichtetem Graphen
FB Informatik
Prof. Dr. R.Nitsch
Aufgaben
– Von einem Knoten s aus die kürzesten Pfade zu allen anderen Knoten des Graphen
finden: Lösung mit single-source shortest path (SSSP) Algorithmen
– Für alle Paare von Knoten die kürzesten Pfade finden: Lösung mit all-pair
shortest path (APSP) Algorithmen
• Kürzeste Wege
– im ungewichteten Graph: Pfad mit geringster Kantenzahl
– im gewichteten Graph: Pfad mit geringstem Gesamtgewicht
• Kürzeste Wege sind nicht eindeutig
– wenn 2 Wege das gleiche Gesamtgewicht haben
• Kürzeste Wege existieren dann nicht
– wenn gar kein Weg zwischen 2 Knoten existiert
– falls der Graph Zyklen mit negativem Gesamtgewicht hat (jeder Durchlauf
verringert das Gewicht des Pfades.
• Wichtige Algorithmen:
– Dijkstra-Algorithmus (SSSP): für Graphen mit Kantengewichten  0
– Belman-Ford-Algorithmus: Für Graphen mit negativen Gewichten aber ohne
negative Zyklen (= Zyklen mit Kantensumme< 0).
09.10.2008
Projekt FutureCar
15
Dijkstra Algorithmus
FB Informatik
Prof. Dr. R.Nitsch
• Liefert für alle Knoten eines Graphen G die kürzesten Pfade zu einem Startknoten s
• Wirkungsweise
algorithm dijkstra(G,s)
Node
– Zusätzlich zu seiner Adjazenzliste erhält
Eingabe: Graph G, Startknoten s
+ loc:Location
jeder Knoten v die Angabe
FOR EACH Knoten v aus V(G) DO
+ adjList
d2s[v]=
• zur minimalen Distanz d2s[v] zum
+ pred
pred[v]=nil
Startnoten s
+ d2s:float
OD
• des Vorgängerknotens pred[v] auf dem
d2s[s]=0 // Startknoten erhält höchste
Priorität
+ Konstruktor:
kürzesten Weg zum Startknoten
PQ:=V
– Einfügen aller Knoten in eine Priorityueue PQ
WHILE NOT empty(PQ) DO
– Mit jedem Durchlauf wird der Knoten vmin mit
vmin = dequeueMin(PQ)
der bis dahin geringsten Distanz zu s aus einer
FOR EACH vnext OF vmin DO
PriorityQueue PQ entnommen
d2sneu:=d2s(vmin)+w(vmin,vnext)
• die Distanz der über vmin zu all seinen
Nachbarknoten führenden Wege ermittelt
wobei w(vmin,vnext) das Gewicht der
Kante vmin->vnext ist.
IF( d2sneu<d2s(vnext) DO
• falls die bisher für vnext notierte Distanz
d2s(vnext):=d2sneu
größer als dist2s(vnext)neu ist, wird diese
pred:=vmin
aktualisiert und vmin als neuer
FI
Vorgängerknoten eingetragen
OD
OD
09.10.2008
Projekt FutureCar
16
Herunterladen