vnext

Werbung
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
 [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
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
Herunterladen