FB Informatik
Prof. Dr. R.Nitsch
Programmieren 2
Future Car Projekt
Praktikum 6 - Graphen
- Speichern von Graphen
- Traversieren von Graphen
- Kürzeste Wege
Reiner Nitsch
reiner.nitsch@h-da.de
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
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
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
1
#### # ## # #
#
#
# ## ##
# ## ##
# ## ##
#
#
# ## ##
# ## ##
Rasterkarte (Grid) von FC-City
3,4 4,4 5,4 6,4 7,4 8,4
3,5 4,5 5,5 6,5 7,5 8,5
2
4
Rasterkarte von FC-City
mit XY-Koordinaten
3,6 4,6 5,6 6,6 7,6 8,6
FB Informatik
Prof. Dr. R.Nitsch
4,7
5,8
6,5
7,7
5,5
4,6
7,7
5,8
7,6
6,5
5,8
4,6
6,8
7,7
4,6
6,5
3,7
4,7
4,6
3,6
3,7 4,7 5,7 6,7 7,7 8,7
Adjazenzlisten
3,8 4,8 5,8 6,8 7,8 8,8
3,9 4,´9 5,9 6,9 7,9 8,9
5
Graph zur Modellierung der
Erreichbarkeitsbeziehungen
zwischen den Zellen
der Rasterkarte
Anmerkung: Wenden ist nicht
vorgesehen
3
09.10.2008
Projekt FutureCar
47
55
76
68
37
46
58
46
65
77
47
36
65
77
58
46
77
58
46
65
Adjazenzlisteninfo
als Textsequenz 4
Implementierungsempfehlungen
4,7
5,8
6,5
7,7
5,5
4,6
7,7
5,8
7,6
6,5
5,8
4,6
6,8
7,7
4,6
6,5
FB Informatik
Prof. Dr. R.Nitsch
Node
+ loc
5,5
+ adjList 4,6 7,7 5,8
+ Konstruktor:
1..N
1
3,7
4,7
4,6
3,6
Adjazenzlisten
Graph
- nodes:Node[ ]
+ Graph(filename:string)
+…
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
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 (genauer: einer Komponente desselben) besucht
Node
+ loc:Location
+ adjList
+ visited:bool
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.
Markiere v als besucht
Bestimme einen Nachbarknoten von v und nenne
diesen vnext
WHILE(vnext existiert
UND noch nicht besucht ist)
beginne weitere Tiefensuche bei vnext
Wieder zurück,
bestimme weiteren Nachbarknoten von v
und nenne diesen wieder vnext
END WHILE
procedure traverse-dfs(v)
visited(v) := true
vnext := adjList[v]
WHILE( exist(vnext)
AND NOT visited(vnext))
traverse-dfs(vnext)
vnext := succ(vnext)
END WHILE
Algorithmus in kompakter selbsterklärender, leicht merkbarer Syntax
(Programmiersprachenunabhängig)
Algorithmus in Umgangssprache
09.10.2008
+ Konstruktor:
Projekt FutureCar
7
Beispiel zur Tiefentraversierung
FB Informatik
Prof. Dr. R.Nitsch
procedure traverse-dfs(v)
visited(v) := true
vnext := adjList[v]
WHILE( exist(vnext)
AND NOT visited(vnext))
traverse-dfs(vnext)
vnext := succ(vnext)
END WHILE
Adjazenzlisten
von Seite 2
Startknoten (willkürl.)
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
3
6
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
4
5
6
Projekt FutureCar
Alle Knoten und
Kanten besucht!
8
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 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) THEN
// 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!
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 über 1 Kante erreichbaren Knoten besucht
– Danach alle über mindestens 2 Kanten erreichbaren Knoten, usw.
– Entsteht formal aus iterativer Tiefentraversierung, wenn der Stack zu einer Queue wird.
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) THEN
// Noch einen Unbesuchten gefunden?
visit(vnext)
// diesen besuchen (und bearbeiten),
visited(vnext) := true
// als "besucht" markieren und
enqueue(t,vnext)
// 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
10
Projekt FutureCar
END09.10.2008
WHILE
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 := adjList[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)
2
t:
ELSE
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!
11
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 der 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 kürzesten Weg.
09.10.2008
Projekt FutureCar
12
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
Node
+ loc:Location
+ adjList
Verweis auf Vorgängerknoten
+ pred
WHILE NOT empty(t) AND front(t)!=d DO // Abbruch der Suche wenn d besucht
+ visited:bool
v := front(t)
// vordersten Knoten in t lesen
+ Konstruktor:
vnext := adjList[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(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
IF empty(t) THEN
{ kein Pfad von s nach d }
09.10.2008
Projekt FutureCar
13
Implementierung der Wegesuche mittels Breitentraversierung
FB Informatik
Prof. Dr. R.Nitsch
std::list<Location>& Graph::bfSearchForShortestPath( const Location& src, const Location& d,
std::list<Location>& route )
{
if( world.getTrait(src)==NOTTRAVERSABLE ) // PRE: src is TRAVERSABLE
throw "RUNTIME_ERROR GraphUnweighted::bfSearchForShortestPath PRE s";
if ( world.getTrait(d)==NOTTRAVERSABLE ) // PRE: dest is TRAVERSABLE
throw "RUNTIME_ERROR GraphUnweighted::bfSearchForShortestPath PRE d";
std::list<Node*> t; // t := empty-queue (contains addresses of neighbors)
nodes[s].pred = &nodes[s]; // pred(s) := nil (std::map<Location, Node> nodes)
nodes[s].visited = true; // visited(s) := true
t.push_back( &nodes[s] ); // enqueue(t,s)
while( !t.empty() && !(t.front()->loc==d) ) // WHILE NOT empty(t) AND front(t)!=d DO
{
Node* v = t.front(); // v := front(t)
// Get adjacencylist for node v
std::list<Node*>& v_neighbors = nodes[v->loc].adjNodePointerList;
std::list<Node*>::iterator iter = v_neighbors.begin(); // vnext := adjList[v]
// Find neighbor not yet visited
while( iter!=v_neighbors.end() ) { // WHILE exist(vnext)
if( !(*iter)->visited ) {
// AND NOT visited(vnext) DO
Node* vnext = *iter;
vnext->visited = true;
// visited(vnext) := true
vnext->pred = &nodes[v->loc]; // pred(vnext) := v Vorgängerknoten merken
t.push_back(vnext);
// enqueue(t,vnext)
}
++iter;
// AND visited(vnext) DO
} // END while
t.pop_front(); // ELSE DO dequeue(t)
}
// Fortsetzung nächste Seite
09.10.2008
Projekt FutureCar
14
Path-Container mit Knotensequenz des kürzesten Weges füllen
// 1 .
// 2.
// 3.
// 4.
// 5.
// 6.
// 7.
//
FB Informatik
Prof. Dr. R.Nitsch
dest in Path-Container für Location-Objekte schreiben
v=dest
In nodes das mit v assoziierte Node-Objekt suchen und den Vorgängerknoten pred(v) ermitteln
pred(v) in Path-Container speichern
Solange 2. bis 4. wiederholen, bis pred(v)==src
Falls die Knotenreihenfolge im Container dest -> src ist, diese noch umdrehen: src -> dest
Die Knotensequenz in eine Folge von Navi-Kommandos (GOAHEAD3, GOAHEAD1, GORIGHT, GOLEFT, TURN ) übersetzen.
Die Route ist berechnet …
09.10.2008
Projekt FutureCar
15