� Graphen: Begriffe und Definitionen
� Bewertete Graphen
� Graphen-Implementierungen
� Tiefen- und Breitensuche
� Transitive Hülle
� Kürzeste Wege
� Traveling Salesman-Problem
Kapitel 9: Graphen und Graph-Algorithmen_________________________________
PInf II 9.20 Graphen
Anwendungsbeispiele: (1) V1 = Menge aller Flughäfen in Deutschland.
E1 = { (x,y) ∈ V1 x V1 | Es gibt einen Direktflug zwischen x und y }(2) V2 = Menge der Einwohner von Marburg
E2 = { (x,y) ∈ V2 x V2 | x kennt y }(3) V3 = Menge der Einwohner von Marburg
E3 = { (x,y) ∈ V3 x V3 | x ist verheiratet mit y }. V3 ist Teilgraph von V2
Ein (gerichteter) Graph ist ein Paar G = <V, E>, wobei gilt:• V ist eine endliche Menge von Knoten (engl. Sg.:vertex) und• E ist eine zweistellige Relation auf V, d.h. E ⊆ V x V. Die Elemente
von E werden Kanten (engl. Sg.: edge) genannt. • Gilt G’ = <V’, E’> mit V’ ⊆ V und E’ ⊆ E, so heißt G’ Teilgraph von G.
PInf II 9.30 Bildliche Darstellung von Graphen
Sei G= <V, E>: • Einen Knoten v ∈ V stellt man durch einen Punkt oder durch einen
kleinen Kreis dar.• Eine Kante (x,y) ∈ E stellt man durch einen Pfeil vom Knoten x zum
Knoten y dar.
Beispiel:
V = { a,b,c,d,e,f,g,h}
E = { (a,d), (d,a), (a,b), (b,c), (c,a), (b,e), (a,e), (f,g), (f,f)}.
a
e
b
d
c
f
gh
PInf II 9.40 Ungerichtete Graphen
• Sei G = <V,E>. Falls für jedes e ∈ E mit e = (v1,v2) gilt: e’ = (v2,v1) ∈E (E ist symmetrisch), so heißt G ungerichteter Graph, ansonsten gerichteter Graph.
• Bei einem ungerichteten Graphen gehört zu jedem Pfeil von x nach y auch ein Pfeil von y nach x. Daher läßt man die Pfeil-spitzen ganz weg und zeichnet nur ungerichtete Kanten.
Beispiel:
V = einige Städte der Umgebung
E = { (x,y) | Es gibt eine direkte Bahnverbindung zwischen x und y }
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
PInf II 9.50
Pfade, Zyklen und Gewichte• Eine Kante k = (x,y) heißt inzident zu x und y.
• Ein Pfad (oder Weg) von x nach y ist eine Folge (x=a0 , a1, ... , ap=y) von Knoten mit (ai , ai+1 ) ∈ E. p wird die Länge des Weges von x nach y genannt.
• In einem einfachen Pfad kommt jeder Knoten höchstens einmal vor.
• Ein Pfad der Länge p ≥ 1 von x nach x, in dem außer x kein Knoten mehr als einmal vorkommt, heißt Zyklus.
• Ein gerichteter Graph <V, E> heißt zyklenfrei oder gerichteter azyklischer Graph (engl: directed acyclic graph, kurz: dag), wenn er keine Zyklen enthält.
• Im ungerichteten Fall schließt man i.a. triviale Zyklen der Form (x,x) oder (x,y,x) aus. Ein ungerichteter Graph ist zyklenfrei, wenn es zwischen jedem Paar von Knoten (x,y) höchstens einen Pfad (ohne triviale Zyklen) gibt.
PInf II 9.60
BeispieleG = <V,E> sei wie oben definiert.
• (b,c,a,d,a) ist ein Pfad von b nach a.
• Er enthält einen Zyklus: (a,d,a).
• (c,a,b,e) ist einfacher Pfadvon c nach e.
• (f,f,f,g) ist ein Pfad.
• (a,b,c,a) und (a,d,a) und (f,f) sind die einzigen Zyklen.
• (a,b,e,a) ist kein Pfad und kein Zyklus.
• <{a,b,c,e}, {(a,b), (b,c), (b,e), (a,e)}> ist ein azyklischer Teilgraph von G.
a
e
b
d
c
f
g
h
PInf II 9.70
Bewertete GraphenEin Graph G = <V, E> kann zu einem bewerteten Graphen G = <V, E, gw(E)> erweitert werden, wenn man eine Gewichtsfunktion
gw: E →→→→ int (oder gw: E →→→→ float/double) hinzunimmt, die jeder Kante e ∈ E ein (positives, ganzzahliges oder reelles) Gewicht gw(e) zuordnet.
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
34
224 88 136
104
30
66
96
93 181 104
174106
Beispiel:len (Marburg, Gießen, Frankfurt, Mannheim) = 184
Für einen Weg w = (x=a0 , a1, ..., ap=y) heißtlen(w) = Σi=0
p-1 gw(ai, ai+1)die bewertete Länge von w.
PInf II 9.80
Ungerichtete Graphen und ZusammenhangUngerichtete Graphen sind Spezialfälle von gerichteten Graphen.
Zusätzlich soll für ungerichtete Graphen gelten: • G heißt zusammenhängend, wenn es zwischen je zwei
(verschiedenen) Knoten einen Weg gibt. • Ist G nicht zusammenhängend, so zerfällt er in eine Vereinigung
zusammenhängender Komponenten (auch Zusammen-hangskomponenten genannt) .
• Ein zusammenhängender zyklenfreier Graph ist ein Baum.
• Eine Gruppe paarweise nicht zusammenhängender Bäume heißt Wald. Jeder zyklenfreie ungerichtete Graph ist also ein Wald.
a
e
b
d
c
f
g
h
Zusammenhangs-komponenten von G
PInf II 9.90
Zusammenhang in gerichteten Graphen� Die Definitionen für Zusammenhang und Zusammenhangskomponenten
lassen sich für gerichteten Graphen ausdehnen: � Ein gerichteter Graph G heißt stark zusammenhängend, wenn es
zwischen je zwei (verschiedenen) Knoten einen Weg gibt. Für einen beliebigen Graphen G kann man die Menge seiner starkenZusammenhangskomponenten betrachten. Zwei Knoten a und b liegen in der gleichen Komponente Z, wenn sowohl ein Weg von a nach b als auch einer von b nach a in Z existiert.
• Ein gerichteter Graph G heißt schwach zusammenhängend, wenn der entsprechende ungerichtete Graph, der aus G durch Hinzunahme aller Rückwärtskanten entsteht, zusammenhängend ist.
Beispiel:Starke Zusammen-hangskomponenten von G
a
e
b
d
cg
h
f
PInf II 9.100
Aufspannender Baum • Ist G ungerichtet und zusammenhängend und R ein zusammenhän-
gender, zyklenfreier Teilgraph von G, der alle Knoten von G enthält, so heißt R ein (auf)spannender Baum (engl.: spanning tree) von G.
Graph mit aufspannendem Baum
Rekursiver Algorithmus SpT zur Konstruktion des (auf)spannen-den Baums für G:
• Markiere einen beliebigen Knoten v ∈ V
• Wiederhole für alle von v ausgehenden Kanten e = (v,v') ∈ E:
Wenn v' unmarkiert, markiere v' und führe SpT(v') aus, sonst lösche e (und gehe zur nächsten Kante weiter).
PInf II 9.110
Repräsentation von Graphen
Für die Repräsentation eines Graphen kommen in erster Linie zweiDatenstrukturen in Frage:
• eine Boolesche Matrix (auch Adjazenzmatrix genannt)• eine Liste oder ein Array von Listen (für die Knoten des Graphen und
deren jeweilige Verbindungen) Repräsentation durch eine Adjazenzmatrix:Ein Graph G = ( V, E) ist i.W. durch die Angabe seiner Kanten E ⊆ V x V
bestimmt. • So wie Teilmengen von V durch Boolesche Arrays dargestellt werden
können, kann man Teilmengen von V × V durch Boolesche Matrizen (sog. Adjazenzmatrizen) darstellen.
• Voraussetzung dafür ist, daß die Menge der Knoten durch einen ordinalen Typ repräsentiert ist.
PInf II 9.120
Graphen: Java-Deklarationen
public class AGraph {
String[] knoBez; // alle Knoten-Bezeichner
int knAnz; // Anzahl der Knoten
boolean[][] aMx; // Adjazemnzmatrix
AGraph (String[] knBez, boolean[][] kanten) {... /* Konstruktor, besetzt Knoten-Bezeichner und
Kanten-Matrix mit den gegebenen Parameter-Werten */}
}
public class AGraph {
String[] knoBez; // alle Knoten-Bezeichner
int knAnz; // Anzahl der Knoten
boolean[][] aMx; // Adjazemnzmatrix
AGraph (String[] knBez, boolean[][] kanten) {... /* Konstruktor, besetzt Knoten-Bezeichner und
Kanten-Matrix mit den gegebenen Parameter-Werten */}
}
Die Klasse AGraph enthält als Basis-Definitionen für die Repräsentation von Graphen durch Adjazentmatrizen:
• ein Datenfeld knBez des Typs Array of String für die Auflistung der Knoten-Bezeichner,
• ein Datenfeld aMx des Typs Array of Array of boolean für die Darstellung der Adjazenzmatrix. Für diese gilt:
aMx[u][v]== true <=> (u,v) ∈∈∈∈ E
PInf II 9.130
Adjazenzmatrix: Beispiel
a
e
b
d
c
f
gh
a c d e f gb hx xx
x xxx
x x
abcdefgh
( x = True, " " = False )
// Konkretisierung zum vorigen Programm
String[] knoBez = {"a","b","c","d","e","f","g","h"};
boolean[][] kanten =
{{false, true, false, true, true, false, false, false},
{false, false, true, false, true, false, false, false},
... // usw. };
AGraph gBsp = new AGraph (knoBez, kanten) ;
...
// Konkretisierung zum vorigen Programm
String[] knoBez = {"a","b","c","d","e","f","g","h"};
boolean[][] kanten =
{{false, true, false, true, true, false, false, false},
{false, false, true, false, true, false, false, false},
... // usw. };
AGraph gBsp = new AGraph (knoBez, kanten) ;
...
PInf II 9.140
Bewertete AdjazenzmatrizenDie Idee der Adjazenzmatrix läßt sich leicht auf bewertete Graphenausdehnen. Statt eines booleschen Werts speichert man das Gewicht gw (u,v) jeder Kante an der betreffenden Position M[u,v] der Matrix M. Man setzt z.B.:
public class BGraph {
String[] knoBez; // Alle Knoten-Bezeichner
int knAnz; // Anzahl der Knoten
int[][] bMx; // Matrix mit Gewichten
BGraph (String[] knBez, int[][] kanten) {
... /* Konstruktor, besetzt knoBez, knAnz und
bMx mit den gegebenen Werten */;
} ...
}..
public class BGraph {
String[] knoBez; // Alle Knoten-Bezeichner
int knAnz; // Anzahl der Knoten
int[][] bMx; // Matrix mit Gewichten
BGraph (String[] knBez, int[][] kanten) {
... /* Konstruktor, besetzt knoBez, knAnz und
bMx mit den gegebenen Werten */;
} ...
}..
M[u,v] =w, falls e =(u,v) ∈ E und gw(e) = w,0, falls u=v und e =(u,u) ∈ E ,∞∞∞∞ (bzw. MAX_VALUE) sonst
{
PInf II 9.150
BN F FD GI KS K MA MR WÜBN 0 181 - - - 34 224 - -
F 181 0 104 66 - - 88 - 136
FD - 104 0 106 96 - - - 93
GI - 66 106 0 - 174 - 30 -
KS - - 96 - 0 - - 104 -
K 34 - - 174 - 0 - - -
MA 224 88 - - - - 0 - -
MR - - - 30 104 - - 0 -
WÜ - 136 93 - - - - - 0
BN F FD GI KS K MA MR WÜBN 0 181 - - - 34 224 - -
F 181 0 104 66 - - 88 - 136
FD - 104 0 106 96 - - - 93
GI - 66 106 0 - 174 - 30 -
KS - - 96 - 0 - - 104 -
K 34 - - 174 - 0 - - -
MA 224 88 - - - - 0 - -
MR - - - 30 104 - - 0 -
WÜ - 136 93 - - - - - 0
Bewertete Adjazenzmatrix: Beispiel
Ist G ein ungerichteter Graph, so ist die Matrix symmetrisch - d.h. man kommt im Prinzip mit einer Dreiecksmatrix aus.
- steht für ∞
PInf II 9.160
String[] knBez = {"Bonn", "Frankfurt", "Fulda", "Gießen",
"Kassel", "Köln", "Mannhem", "Marburg", "Würzburg"};
int[][] kanten = { {0, 181, M, M, M, 34, 224, M, M},
{181, 0, 104, 66, M, M, 88, M, 136},
...
{M, 136, 93, M, M, M, M, M, 0} };
BGraph bahnNetz = new BGraph (knBez, kanten);
String[] knBez = {"Bonn", "Frankfurt", "Fulda", "Gießen",
"Kassel", "Köln", "Mannhem", "Marburg", "Würzburg"};
int[][] kanten = { {0, 181, M, M, M, 34, 224, M, M},
{181, 0, 104, 66, M, M, 88, M, 136},
...
{M, 136, 93, M, M, M, M, M, 0} };
BGraph bahnNetz = new BGraph (knBez, kanten);
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
34
224 88 136
104
30
66
96
93 181
104
174106M steht für ∞ (genauer für:
Integer.MAX_VALUE)
Bewertete Adjazenzmatrix: Initialisierung
PInf II 9.170
Knoten- und Kantenzugriffe Der Zugriff auf einzelne Knoten und Kanten ist mit Hilfe einfacher Zugriffsfunktionen möglich:.
public class BGraph { ... // Forts. von oben
String gibKnoBez (int k) { // Knoten-Zugriff
return knoBez[k];
}
int kantenW (int u, int v) { // Kanten-Zugriff
return bMx[u][v];
}
public class BGraph { ... // Forts. von oben
String gibKnoBez (int k) { // Knoten-Zugriff
return knoBez[k];
}
int kantenW (int u, int v) { // Kanten-Zugriff
return bMx[u][v];
}
Beispiel:
bahnNetz.gibKnoBez(3); // Ergebnis: "Gießen"
bahnNetz.kantenW (3,7); // Ergebnis: 30
bahnNetz.gibKnoBez(3); // Ergebnis: "Gießen"
bahnNetz.kantenW (3,7); // Ergebnis: 30
PInf II 9.180
Adjazenzlisten (1)
Eine zweite (weniger speicheraufwendige) Möglichkeit zur Repräsen-tation eines Graphen besteht darin, jedem Knoten eine Liste seiner Nachbarknoten - ggf. mit den zugehörigen Kantengewichten - zuzu-ordnen. Eine solche Liste wird auch Adjazenzliste genannt.
a
b
d
e
a
e
b
d
c
f
gh
b
c
e
c
a
d
a
e f g h
f
g
PInf II 9.190
b d e
class VerbListe {
int ziel; // Endpunkt-Nrint gew; // Kanten-Gewicht
VerbListe nx; // Nachfolger-Referenz
VerbListe (int z, int w, VerbListe v) {ziel = z; gew = w; nx = v;
}
}
class LKnoten {
String knBez;
VerbListe nachbarn;
LKnoten (String s, VerbListe v) {
knBez = s; nachbarn = v;
}
}
class VerbListe {
int ziel; // Endpunkt-Nrint gew; // Kanten-Gewicht
VerbListe nx; // Nachfolger-Referenz
VerbListe (int z, int w, VerbListe v) {ziel = z; gew = w; nx = v;
}
}
class LKnoten {
String knBez;
VerbListe nachbarn;
LKnoten (String s, VerbListe v) {
knBez = s; nachbarn = v;
}
}
a b d e
5 7 2
5 7 2
2 4 5
2 4 5
knBez
nachbarn
gew
ziel
Adjazenzlisten: Klassendefinitionen (1)
PInf II 9.200
public class LGraph {
static final int maxInt = Integer.MAX_VALUE;
LKnoten[] knoten; // Array von Knoten(-Referenzen)
int knAnz;
LGraph (LKnoten[] knL) { // besetzt Nachbarlisten
knAnz = knL.length;
knoten = new LKnoten[knAnz];
for (int i = 0; i < knAnz; i++) knoten[i] = knL[i];
}
int kantenW (int u, int v) { /* liefert gew, falls
direkte Verbindung, sonst maxInt */
if (u == v) return 0;
VerbListe vLi = knoten[u].nachbarn;
while (vLi != null) {
if (vLi.ziel == v) return vLi.gew;
vLi = vLi.nx; }
return maxInt;
} ...
}
public class LGraph {
static final int maxInt = Integer.MAX_VALUE;
LKnoten[] knoten; // Array von Knoten(-Referenzen)
int knAnz;
LGraph (LKnoten[] knL) { // besetzt Nachbarlisten
knAnz = knL.length;
knoten = new LKnoten[knAnz];
for (int i = 0; i < knAnz; i++) knoten[i] = knL[i];
}
int kantenW (int u, int v) { /* liefert gew, falls
direkte Verbindung, sonst maxInt */
if (u == v) return 0;
VerbListe vLi = knoten[u].nachbarn;
while (vLi != null) {
if (vLi.ziel == v) return vLi.gew;
vLi = vLi.nx; }
return maxInt;
} ...
}
Adjazenzlisten: Klassendefinitionen (2)
String gibKnoBez (int k) {
return knoten[k].knBez;
} ...
String gibKnoBez (int k) {
return knoten[k].knBez;
} ...
PInf II 9.210
Beispiel:
LKnoten[] knoten = { new LKnoten ("Bonn",
new VerbListe(1, 181,
new VerbListe(5, 34,
new VerbListe(6, 224,
null )))),
....
// usw.
new LKnoten ("Würzburg",
new VerbListe(1, 136,
new VerbListe(2, 93,
null )))
};
LGraph bahnNetz = new LGraph(knoten);
LKnoten[] knoten = { new LKnoten ("Bonn",
new VerbListe(1, 181,
new VerbListe(5, 34,
new VerbListe(6, 224,
null )))),
....
// usw.
new LKnoten ("Würzburg",
new VerbListe(1, 136,
new VerbListe(2, 93,
null )))
};
LGraph bahnNetz = new LGraph(knoten);
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
34
224 88 136
104
30
66
96
93
181104
174106
Adjazenzliste: Anwendung
PInf II 9.220
Verkehrsnetz als Array von Adjazenzlisten
Bonn0 1 181 5 34 6 224 nil
Frankfurt1 0 181 2 104
nil
Fulda2 1 104 3 106 nil
Gießen3 1 66 2 106 5 174
Kassel4 2 96 7 104 nil
Köln5 0 34 3 174 nil
Mannheim6 0 224 1 88 nil
Marburg7 3 30 4 104 nil
Würzburg 8 1 136 2 93 nil
3 66 6 88
8 136
4 96 8 93
nil7 30
PInf II 9.230
Eine dritte Möglichkeit zur Implementierung von Graphen besteht darin, auch die Folge der Knoten auf eine Liste abzubilden, d.h. der gesamte Graph wird durch eine Liste von Listen dargestellt.
Implementierung durch Listen von Listen
a
e
b
d
c
f
gh
a b d e
b c e
c a
e
f f g
d a
g
h
PInf II 9.240
public class LLGraph {
String grBez; // Graph-Bezeichner
LLKnoten kn; // Liste der Knoten des Graphen
int knAnz;
LLGraph (String bez) { // erzeugt Graphen namens bez
grBez = bez;
kn = null;
knAnz = 0;
}
public LLKnoten fuegeKnEin (String knBez) {
// fügt Knoten knBez in die Liste ein
kn = new LLKnoten (knBez, kn);
knAnz ++;
return kn;
} // end fuegeKnEin
.... // weiter s. nächste Folie
public class LLGraph {
String grBez; // Graph-Bezeichner
LLKnoten kn; // Liste der Knoten des Graphen
int knAnz;
LLGraph (String bez) { // erzeugt Graphen namens bez
grBez = bez;
kn = null;
knAnz = 0;
}
public LLKnoten fuegeKnEin (String knBez) {
// fügt Knoten knBez in die Liste ein
kn = new LLKnoten (knBez, kn);
knAnz ++;
return kn;
} // end fuegeKnEin
.... // weiter s. nächste Folie
Graph als Liste von Listen (1)
PInf II 9.250
// Forts. von voriger Folie
public class LLKnoten {
String knBez;
NbListe nachbarn; // Adjazenzliste für ausg. Kanten
LLKnoten nxKn; // Ref. auf nächsten Knoten
LLKnoten (String s, LLKnoten nx) { // Konstruktor
knBez = s;
nachbarn = null;
nxKn = nx;
} ... // end Konstruktor
public void fuegeNbEin (LLKnoten ziel, int gw) {
// Kante vom akt. Knoten zu Kn. ziel mit Gewicht gw
nachbarn = new NbListe (ziel, gw, nachbarn);
} .... // end fuegeNbEin
} // end LLKnoten
// Forts. von voriger Folie
public class LLKnoten {
String knBez;
NbListe nachbarn; // Adjazenzliste für ausg. Kanten
LLKnoten nxKn; // Ref. auf nächsten Knoten
LLKnoten (String s, LLKnoten nx) { // Konstruktor
knBez = s;
nachbarn = null;
nxKn = nx;
} ... // end Konstruktor
public void fuegeNbEin (LLKnoten ziel, int gw) {
// Kante vom akt. Knoten zu Kn. ziel mit Gewicht gw
nachbarn = new NbListe (ziel, gw, nachbarn);
} .... // end fuegeNbEin
} // end LLKnoten
Graph als Liste von Listen (2)
PInf II 9.260
// Forts. von voriger Folie
public class NbListe {
LLKnoten kn; // Nachbarknoten, Ziel einer Kante
int gw; // Kantengewicht für diese Kante
NbListe nxKn; // Referenz auf nächsten Nachbarn
NbListe (LLKnoten zi, int g, NbListe nx) {
kn = zi;
gw = g;
nxKn = nx;
}... // end Konstruktor
} // end NbListe
// Forts. von voriger Folie
public class NbListe {
LLKnoten kn; // Nachbarknoten, Ziel einer Kante
int gw; // Kantengewicht für diese Kante
NbListe nxKn; // Referenz auf nächsten Nachbarn
NbListe (LLKnoten zi, int g, NbListe nx) {
kn = zi;
gw = g;
nxKn = nx;
}... // end Konstruktor
} // end NbListe
Graph als Liste von Listen (3)
Anwendungsbeispiel:
LLGraph bNetz = new LLGraph ("Bahnnetz");
LLKnoten bn = bNetz.fuegeKnEin ("Bonn");
LLKnoten f = bNetz.fuegeKnEin ("Frankfurt"); ...
bn.fuegeNbEin (f, 181);
f.fuegeNbEin (bn, 181); ...
LLGraph bNetz = new LLGraph ("Bahnnetz");
LLKnoten bn = bNetz.fuegeKnEin ("Bonn");
LLKnoten f = bNetz.fuegeKnEin ("Frankfurt"); ...
bn.fuegeNbEin (f, 181);
f.fuegeNbEin (bn, 181); ...
PInf II 9.270
Alle hier betrachteten Möglichkeiten zur Implementierung von Graphen haben ihre spezifischen Vor- und Nachteile. Seien n = Knotenzahl und m = Kantenzahl eines Graphen G.
Vergleich der Implementierungen
Vorteile Nachteile
Adjazenz-matrix
Adjazenz-liste
Liste von Listen
Berechnung der Inzidenz mit O(1)
Platzbedarf beträgt nur O(n+m)
Knoten lassen sich flexibel hinzufügen/ löschen
hoher Platzbedarf und teure Initialisierung: beide O(n2)
Effizienz der Kantensuche abhängig von Knotenordnung
Effizienz von Knoten- und Kantensuche abhängig von Listenposition
PInf II 9.280
Traversieren von Graphen
Für die Traversierung betrachten wir die folgenden Strategien: :Tiefensuche (depth first search) Breitensuche (breadth first search)
• Tiefensuche entspricht der Baum-Traversierung in Vorordnung, Breitensuche derjenigen in Ebenen-Ordnung.
• Alle folgenden Algorithmen können sowohl auf gerichtete als auch auf ungerichtete Graphen angewendet werden. Dabei setzen wir voraus,daß alle Graphen zusammenhängend sind.
• Viele Algorithmen auf Graphen beruhen darauf, daß man alle Knoten (oder alle Kanten) des Graphen durchläuft (den Graphen traversiert).
• Solche Traversierungen funktionieren ähnlich wie entsprechende Baum-Traversierungen, doch muß man bei Graphen darauf achten, daß man nicht in Endlos-Schleifen gerät, wenn der Graph Zyklen hat. Daher markiert man bereits besuchte Knoten.
PInf II 9.290 Tiefensuche (1)
Tiefensuche läßt sich am einfachsten implementieren: Wir betrachten sowohl eine rekursive Implementierung als auch eine mit einem Stack. Der folgende Algorithmus Depth-First-Visit besucht alle Knoten, die von einem Ausgangsknoten mit der Nummer k aus erreichbar sind. Wir setzen voraus, daß zu Beginn alle Knoten unmarkiert sind.
void depFVisit (int k) {
if ( ...) // k ist noch nicht markiert
markiere(k);
bearbeite (k);
for ( .. /* alle i, die Nachbarn von k sind */ )
depFVisit (i);
}
void depFVisit (int k) {
if ( ...) // k ist noch nicht markiert
markiere(k);
bearbeite (k);
for ( .. /* alle i, die Nachbarn von k sind */ )
depFVisit (i);
}
Programmgerüst:
Konkreter Code hängt von der Graph-Implementierung ab.
PInf II 9.300 Tiefensuche (2)
Zum Markieren benutzen wir ein Feld der Länge knAnz. Als Typ der Feldelemente wählen wir int, um den Algorithmus leicht auf Mehrfachbesuche erweitern zu können.
void depFVisit (int k) {
int[] marken = new int [knAnz];
for (int i = 0; i < knAnz; i++) marken [i] = 0;// initialisiere Marken
rekDFVisit (marken, k);}
void rekDFVisit (int[] marken, int k) {if (marken[k] < 1) { // k ist noch nicht markiert
marken [k] ++;System.out.println (knoten[k].knBez);
// bezieht sich auf Klasse LGraphfor (int i = 0; i < knAnz; i++)
if (kantenW (k, i) < maxInt) // i ist Nachbar von krekDFVisit (marken, i);
}}
void depFVisit (int k) {
int[] marken = new int [knAnz];
for (int i = 0; i < knAnz; i++) marken [i] = 0;// initialisiere Marken
rekDFVisit (marken, k);}
void rekDFVisit (int[] marken, int k) {if (marken[k] < 1) { // k ist noch nicht markiert
marken [k] ++;System.out.println (knoten[k].knBez);
// bezieht sich auf Klasse LGraphfor (int i = 0; i < knAnz; i++)
if (kantenW (k, i) < maxInt) // i ist Nachbar von krekDFVisit (marken, i);
}}
PInf II 9.310 Tiefensuche (Beispiel)
Falls wir die Nachbarn der Knoten in alphabetischer Reihen-folge erzeugen, führt der Aufruf
depFVisit (0)
zur Ausgabe aller Städte in der folgenden Reihenfolge :Bonn, Frankfurt, Fulda, Gießen, Köln, Marburg, Kassel, Würzburg, Mannheim.
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
Der Aufruf depFVisit (2)
führt zur Ausgabe :Fulda, Frankfurt, Bonn, Köln, Gießen, Marburg, Kassel, Mannheim,Würzburg.
PInf II 9.320 Tiefensuche mit Stack
Wenn Nachbarn in alphabetischer Reihenfolge erzeugt werden, führt dFSVisit (7) zu Bearbeitung und push-Operationen in folgender Reihenfolge: Marburg, Gießen, Frankfurt , Bonn, Köln, Mannheim, Fulda, Kassel, Würzburg.
void dFSVisit (int k) {Stack st = new Stack(knAnz);markiere(k); bearbeite (k);st.push(k);while (! st.istLeer()) {
int akt = st.top();if ( ... ) {
/* es existiert noch nichtmark. Nachbar j von akt */
akt = j;markiere(akt);bearbeite(akt);st.push(akt);}else st.pop();
}
}
void dFSVisit (int k) {Stack st = new Stack(knAnz);markiere(k); bearbeite (k);st.push(k);while (! st.istLeer()) {
int akt = st.top();if ( ... ) {
/* es existiert noch nichtmark. Nachbar j von akt */
akt = j;markiere(akt);bearbeite(akt);st.push(akt);}else st.pop();
}
}
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
PInf II 9.330 Breitensuche mit QueueÄhnlich wie bei der Baum-Traversierung in Ebenen-Ordnung wird eine Warteschlange als Hilfsspeicher verwendet.
void bFVisit (int k) {Queue q = new Queue(knAnz);markiere(k);q.enQueue(k);while (! q.istLeer()) {
int akt = q.top();q.deQueue();bearbeite(akt);for ( /* alle noch nichtmark. Nachbarn j von akt */ ){ markiere(j);q.enQueue(j); }
}
}
void bFVisit (int k) {Queue q = new Queue(knAnz);markiere(k);q.enQueue(k);while (! q.istLeer()) {
int akt = q.top();q.deQueue();bearbeite(akt);for ( /* alle noch nichtmark. Nachbarn j von akt */ ){ markiere(j);q.enQueue(j); }
}
}
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
Wenn Nachbarn in alphabetischer Reihenfolge erzeugt werden, führtBFVisit(Marburg) zur Reihenfolge:Marburg, Gießen, Kassel, Frankfurt, Fulda, Köln, Bonn, Mannheim,Würzburg.
PInf II 9.340 Breitensuche mit Queue: CodeFür die Markierung wird wieder ein int-Array Marken der Länge knAnzverwendet:
void bFVisit (int k) {
int[] marken = new int [knAnz];
for (int i = 0; i < knAnz; i++) marken[i] = 0;
// initialisiere Marken
Queue q = new Queue(knAnz);
marken[k] ++;
q.enQueue(k);
while (! q.istLeer()) {
int akt = q.top();
q.deQueue();
System.out.println (knoten[akt].knBez);
for (int j = 0; j < knAnz; j++)
if (kantenW(akt, j) < maxInt && marken[j] < 1)
{ marken[j] ++; q.enQueue(j); }
}
}
void bFVisit (int k) {
int[] marken = new int [knAnz];
for (int i = 0; i < knAnz; i++) marken[i] = 0;
// initialisiere Marken
Queue q = new Queue(knAnz);
marken[k] ++;
q.enQueue(k);
while (! q.istLeer()) {
int akt = q.top();
q.deQueue();
System.out.println (knoten[akt].knBez);
for (int j = 0; j < knAnz; j++)
if (kantenW(akt, j) < maxInt && marken[j] < 1)
{ marken[j] ++; q.enQueue(j); }
}
}
PInf II 9.350 Transitive HülleEine zweistellige Relation R auf einer Menge V heißt transitiv, falls gilt :
∀∀∀∀ x, y, z ∈∈∈∈ V : (x,y) ∈∈∈∈ R und (y,z) ∈∈∈∈ R →→→→ (x,z) ∈∈∈∈ R.Die transitive Hülle t(R) einer zweistelligen Relation R auf V ist die kleinste Relation Q, für die gilt: Q ist transitiv und R ⊆ Q.
Faßt man R als Kantenmenge eines Graphen G über V auf, so sei t(G) = (V, t(R)) und es gilt:
• Es gibt in eine Kante (x,y) ∈∈∈∈ t(R) ↔ in G existiert ein Pfad von x nach y.Das heißt, t(G) gibt direkt Auskunft darüber, zwischen welchen Knoten-Paaren von G Pfade existieren. Wie kann man t(G) berechnen?
A
E
B
D
C
F
GH
A
E
B
D
C
F
GH
Ausgangsgraph G transitive Hülle t(G)
PInf II 9.360
Warshall's AlgorithmusWarshall's Algorithmus berechnet die transitive Hülle einer Relation (eines Graphen), die durch eine boolesche Adjazenzmatrix aMxdargestellt ist. Die Matrix aMx wird dabei schrittweise zur transitiven Hülle aufgefüllt:
void warshall () { // berechnet trans. Hülle für Matrix AMx
boolean[][] aMx = {..// aMx[u][v]:= true <=> (u,v) ∈∈∈∈ E};
for (int y = 0; y < knAnz; y++)for (int x = 0; x < knAnz; x++)
if (aMx[x][y])for (int z = 0; z < knAnz; z++)
if (aMx[y][z]) aMx[x][z] = true;
}
void warshall () { // berechnet trans. Hülle für Matrix AMx
boolean[][] aMx = {..// aMx[u][v]:= true <=> (u,v) ∈∈∈∈ E};
for (int y = 0; y < knAnz; y++)for (int x = 0; x < knAnz; x++)
if (aMx[x][y])for (int z = 0; z < knAnz; z++)
if (aMx[y][z]) aMx[x][z] = true;
}
Frage: Funktioniert dieser Algorithmus auch, wenn man so schachtelt :
for (int x = 0; x < knAnz; x++)
for (int y = 0; y < knAnz; y++) ....
for (int x = 0; x < knAnz; x++)
for (int y = 0; y < knAnz; y++) ....?
PInf II 9.370
Korrektheit von Warshall's Algorithmus (1)Die Korrektheit läßt sich in 3 Schritten nachweisen:
• Schritt 1 - Terminierung: Die 3 Schleifen laufen jeweils bis zu einer festen, unveränderlichen Schranke → Terminierung i.O.
• Schritt 2 - Fundiertheit: Der Algorithmus stellt unter bestimmten Voraussetzungen neue Kanten her. Da er nur dann eine neue Kante (x,z) schafft (d.h. aMx[x,z] auf true setzt), wenn bereits (x,y) und (y,z) ∈∈∈∈ R sind (d.h. aMx[x,y] = true und aMx[y,z] = truegelten), tut er sicherlich nichts Falsches.
• Schritt 3 - Vollständigkeit: Es bleibt zu zeigen, daß er genug tut, d.h. daß am Ende tatsächlich die (vollständige) transitive Hülle erzeugt wurde.
Dieser Teil des Beweises nutzt vollständige Induktion über die Mächtigkeit der Menge von Zwischenknoten auf dem Wege zwischen zwei beliebigen Knoten u und v.
PInf II 9.380
Korrektheit von Warshall's Algorithmus (2)• Für den Beweis der Vollständigkeit der erzeugten Verbindungen spielt
es eine ausschlaggebende Rolle, daß die äußere for-Schleife über den Zwischenknoten y läuft.
• Wir formulieren die folgende Invariante der äußeren for-Schleife (in Abhängigkeit von y, Knoten werden o.B.d.A. mit ihren Nummern identifiziert).
I(y) = “∀ u, v ∈ V : Wenn ein Weg von u nach v existiert, dessen sämtliche Zwischenknoten in Z = {0, .. y-1} sind, dann gilt: aMx[u,v] = true.”
I(y) = “∀ u, v ∈ V : Wenn ein Weg von u nach v existiert, dessen sämtliche Zwischenknoten in Z = {0, .. y-1} sind, dann gilt: aMx[u,v] = true.”
Der Beweis erfolgt durch Induktion über die Kardinalität n der Menge Z.
(1) Z = {}. Es existiert ein Weg von u nach v ohne Zwischenknoten →→→→aMx[u,v] = true.
(2) Die Behauptung gelte für Z = {0, .. y-1}
PInf II 9.390 Korrektheit von Warshall's Algorithmus (3)
� Es sei ein beliebiger Weg von u nach v gegeben, dessen Zwischenknoten alle in Z = {0, .. y} liegen.
� Wenn y als Zwischenknoten nicht vorkommt, gilt nach I.V.: aMx[u,v] =true.
� Sei also y ein Zwischenknoten auf dem Weg von u nach v :
v u y . . ... .. . . . . . ... .. .
alle Zwischenknoten mit Nr. < y
Aus I(y) folgt, daß bereits gilt :
aMx[u,y] und aMx[y,v]
Beim nächsten Schleifendurchlauf (mit x=u, z=v) ...... wird aMx[u,v]auf true gesetzt und damit gilt : I(y+1)
for (int x = 0; x < knAnz; x++);if (aMx [x][y])
for (int z = 0; z < knAnz; z++)if (aMx [y][z]) aMx [x][z] = true;
for (int x = 0; x < knAnz; x++);if (aMx [x][y])
for (int z = 0; z < knAnz; z++)if (aMx [y][z]) aMx [x][z] = true;
PInf II 9.400
Wege in bewerteten Graphen� In einem bewerteten Graphen hatten wir bereits zu einem gegebenen
Weg zwischen zwei Knoten u und v die (bewertete) Länge des Weges definiert:
In unserem Beispiel gilt etwa:len(Marburg, Gießen, Frankfurt, Mannheim) = 184 und:len(Marburg, Gießen, Köln, Bonn, Mannheim) = 462
W = (u=k0 , k1, ..., kp=v)
len(W) = gw(k0,k1) + ... + gw(kp-1,kp)
� Für die Definition einer Entfernung ist diese Definition jedoch noch unbefriedigend, da wir verschiedene Wege mit gleichem Anfang und Ende haben können:
� Als Entfernung für zwei Knoten u und v in einem bewerteten zusammenhängenden Graphen definieren wir daher das Minimum der Längen aller möglichen Wege, d.h. die Länge des kürzesten Weges.
PInf II 9.410
Kürzeste Wege
Ein Weg (u = a0 , a1, ... , ap = v) zwischen zwei Knoten u und v heißt kürzester Weg , wenn seine bewertete Länge ΣΣΣΣi gw(ki, ki+1) minimal ist.
Dieser Wert ist die (kürzeste) Entfernung von u nach v.
Ein Weg (u = a0 , a1, ... , ap = v) zwischen zwei Knoten u und v heißt kürzester Weg , wenn seine bewertete Länge ΣΣΣΣi gw(ki, ki+1) minimal ist.
Dieser Wert ist die (kürzeste) Entfernung von u nach v.
• Um die kürzeste Entfernung zwischen je zwei Knoten eines Graphen zu finden (engl.: all pairs shortest path problem), kann man Warshall'sAlgorithmus leicht verändern. Dieser Algorithmus wird gewöhnlich R.W. Floyd zugeschrieben.
• Anstelle der booleschen Matrix verwenden wir jetzt wieder eine bewertete Adjazenzmatrix bMx und setzen dabei gw (u,v) = ∞ (bzw. = maxInt) , falls (u,v) ∉ E:
int[][] bMx = {... // bMx[u][v]:= gw(u,v) für (u,v) ∈∈∈∈ E,
maxInt sonst };
int[][] bMx = {... // bMx[u][v]:= gw(u,v) für (u,v) ∈∈∈∈ E,
maxInt sonst };
PInf II 9.420
Floyd's Algorithmus: Code
I(y) = "∀ u, v ∈ V : bMx[u,v] = Länge des kürzesten Weges, der nur Zwischenknoten aus {0 .. y-1} benutzt.”
I(y) = "∀ u, v ∈ V : bMx[u,v] = Länge des kürzesten Weges, der nur Zwischenknoten aus {0 .. y-1} benutzt.”
Invariante :Komplexität von Floyd'sAlgorithmus: O (N3)
void kWegFloyd () {
// berechnet kürzeste Wege für bewertete Adj.-Matrix bMxint[][] bMx = {... }; // vgl. oben
for (int y = 0; y < knAnz; y++);for (int x = 0; x < knAnz; x++);
if (bMx[x][y] < M)for (int z = 0; z < knAnz ; z++)
if ((bMx[y][z] < M) &&bMx[x][y] + bMx[y][z] < bMx[x][z])
bMx[x][z] = bMx[x][y] + bMx[y][z];
}
void kWegFloyd () {
// berechnet kürzeste Wege für bewertete Adj.-Matrix bMxint[][] bMx = {... }; // vgl. oben
for (int y = 0; y < knAnz; y++);for (int x = 0; x < knAnz; x++);
if (bMx[x][y] < M)for (int z = 0; z < knAnz ; z++)
if ((bMx[y][z] < M) &&bMx[x][y] + bMx[y][z] < bMx[x][z])
bMx[x][z] = bMx[x][y] + bMx[y][z];
}
PInf II 9.430
Dijkstra’s AlgorithmusEin weiterer Algorithmus berechnet für einen vorgegebenen Knoten u die kürzeste Entfernung zu allen anderen Knoten (engl.: single source shortest path problem). Dieser Algorithmus stammt von E.W.Dijkstra(1959). Ist man nur am kürzesten Weg zu einem bestimmten Knoten vinteressiert, so kann man i.a. vorzeitig abbrechen.Grundidee des Algorithmus:Eine Menge S ⊆⊆⊆⊆ V (im Bild rechts grüneingefärbt) beschreibt den jeweilsbereits bearbeiteten Teilgraphenvon G. Anfangs ist S = {u}• S wird schrittweise um je einen Knoten erweitert, so daß für alle k ∈ S gilt: Der kürzeste Weg von u nach k verläuft ausschließlich über Knoten von S. • NK = {nk | nk ist mit mindestens einem k ∈ S direkt verbunden} (im Bild gelb eingefärbt) ist die Menge der Nachbar- oder Kandidatenknoten zur Erweiterung von S. Aus K wird jeweils derjenige Knoten ausgewählt (und S zugeschlagen), der minimalen Abstand zu u hat.
S u nk'
nk k
k'
G
PInf II 9.440
intSet S = {u}; int [knAnz] minEntf = {∞∞∞∞, ∞∞∞∞, ..., ∞∞∞∞}; while (¬¬¬¬ v ∈∈∈∈ S) {
Finde k ∈∈∈∈ S und nk ∈∈∈∈ (V - S) mitminEntf[k] + bMx[k,nk] ist minimal ;minEntf[nk] = minEntf[k] + bMx[k,nk];S = S ∪∪∪∪ {nk };
}// minEntf[v] = Minimale Distanz von u nach v
intSet S = {u}; int [knAnz] minEntf = {∞∞∞∞, ∞∞∞∞, ..., ∞∞∞∞}; while (¬¬¬¬ v ∈∈∈∈ S) {
Finde k ∈∈∈∈ S und nk ∈∈∈∈ (V - S) mitminEntf[k] + bMx[k,nk] ist minimal ;minEntf[nk] = minEntf[k] + bMx[k,nk];S = S ∪∪∪∪ {nk };
}// minEntf[v] = Minimale Distanz von u nach v
Invariante :
∀∀∀∀ s ∈∈∈∈
S : minEntf[s] = kürzeste Entfernung von u nach s.
Invariante :
∀∀∀∀ s ∈∈∈∈
S : minEntf[s] = kürzeste Entfernung von u nach s.
Dijkstra’s Algorithmus: Programmgerüst
Komplexitätsbetrachtung:Im ungünstigsten Fall wird die while-Schleife n = knAnz-mal durchlaufen. Das Bestimmen des nächsten Knotens nk für S erfordert beim obigen (nicht optimierten) Algorithmus einen Aufwand von O(N2). Damit liegt der Gesamt-aufwand wieder bei O(N3). Dijkstra gibt allerdings eine Verbesserung seines Algorithmus an, die den Aufwand im durchschnittlichen Fall auf O(N2) drückt.
PInf II 9.450
Implementierung von Dijkstra’s Algorithmus
Die Menge S wird mit Hilfe eines booleschen Array S der Länge knAnzgespeichert. Es gilt S[k] = true ↔ k ∈ S.
Weiter wird ein int-Array minEntf der Länge anz zum Abspeichern der kürzesten Entfernung zu u für alle Knoten von S benötigt. minEntf kann mit 0 initialisiert werden, da später nur auf bereits in S liegende Elemente zurückgegriffen wird.
Zu Beginn ist S = {u} und NK = {nk | es exisitiert eine Kante (u, nk) }In jedem Schritt der äußeren (while-) Schleife wird ein Knotenpaar (k ∈
S, nk ∈ NK) so bestimmt, daß minEntf [k] + bMx [k][nk] minimal ist. kNeu = nk ist ein neuer Knoten, zu dem die kürzeste Entfernung kNeuEntf von u ermittelt wurde. kNeu wird zu S hinzugenommen und kNeuEntf wird in minEntf [kNeu] aufgenommen.
Der Algorithmus terminiert, sobald kNeu der Zielknoten v ist.
PInf II 9.460 Dijkstra’s Algorithmus: Code
void kWegDijkstra (int u, int v) { // kürz.Weg von u nach v
boolean[] s = new boolean[knAnz];for (int i = 0; i < knAnz; i++) s[i] = false;s[u] = true;System.out.println("Kürzeste Entfernung von "+ knoBez[u]);int[] minEntf = new int[knAnz];minEntf[u] = 0;while (! s[v]) { // terminiert, wenn Ziel v erreicht
int min = M; int kNeu = 0;for (int k = 0; k < knAnz; k++)
if (s[k])for (int nk = 0; nk < knAnz; nk++)
if (! s[nk] && bMx[k][nk] < maxInt) {int kNeuEntf = minEntf[k] + bMx[k][nk] ;if (kNeuEntf < min) {min = kNeuEntf; kNeu = nk;}
}s[kNeu] = true; // kNeu wird in S aufgenommenminEntf[kNeu] = min;System.out.println (" nach " + knoBez[kNeu] + " ist "+ min + " km.");
}
}
void kWegDijkstra (int u, int v) { // kürz.Weg von u nach v
boolean[] s = new boolean[knAnz];for (int i = 0; i < knAnz; i++) s[i] = false;s[u] = true;System.out.println("Kürzeste Entfernung von "+ knoBez[u]);int[] minEntf = new int[knAnz];minEntf[u] = 0;while (! s[v]) { // terminiert, wenn Ziel v erreicht
int min = M; int kNeu = 0;for (int k = 0; k < knAnz; k++)
if (s[k])for (int nk = 0; nk < knAnz; nk++)
if (! s[nk] && bMx[k][nk] < maxInt) {int kNeuEntf = minEntf[k] + bMx[k][nk] ;if (kNeuEntf < min) {min = kNeuEntf; kNeu = nk;}
}s[kNeu] = true; // kNeu wird in S aufgenommenminEntf[kNeu] = min;System.out.println (" nach " + knoBez[kNeu] + " ist "+ min + " km.");
}
}
PInf II 9.470
Beweis der Invarianten∀∀∀∀ x ∈∈∈∈ S : minEntf[x] = kürzeste
Entfernung von u nach x.
Finde k ∈ S und nk ∈ (V - S) mit
minEntf[k] + BMx[k,nk] ist minimal ;
minEntf[nk] = minEntf[k] + BMx[k,nk];
∀∀∀∀
x ∈∈∈∈ S ∪∪∪∪ {nk} : minEntf[x] = kürzeste Entfernung von u nach x.
Zu zeigen : Es kann keine kürzere Verbindung von u zu nk geben als die über k .
Angenommen, es gäbe eine kürzereVerbindung u, ..., nk, dann sei k' der letzte Zwischenknoten aus S auf diesem Weg und nk' der nächste, also u, ..., k', nk', ... , nk.
Dann wäre aber der Weg von u nach nk' kürzer und statt nk wäre nk'gefunden worden !
Su
nk'
nk k
k'
G
PInf II 9.480
S minEntf k kNeu min{0} {0, 0, 0, 0, 0, 0, 0, 0, 0} 0 M
(1) {0} {0, 0, 0, 0, 0, 0, 0, 0, 0} 0 5 34(2) {0, 5} {0, 0, 0, 0, 0, 34, 0, 0, 0} 0 1 181(3) {0, 1, 5} {0,181, 0, 0, 0, 34, 0, 0, 0} 5 3 208(4) {0, 1, 3, 5} {0,181, 0, 208, 0, 34, 0, 0, 0} 0 6 224(5) {0, 1, 3, 5, 6} {0,181, 0, 208, 0, 34, 224, 0, 0} 3 7 238(6) {0, 1, 3, 5, 6, 7} {0,181, 0, 208, 0, 34, 224, 238, 0} 1 2 285(7) {0, 1, 2, 3, 5, 6, 7} {0,181, 285, 208, 0, 34, 224, 238, 0} 1 8 317(8) {0, 1, 2, 3, 5, 6, 7, 8} {0,181, 285, 208, 0, 34, 224, 238, 317} 7 4 342(9) {0, 1, 2, 3, 4, 5, 6, 7, 8}
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
34
224 88 136
104
30
66
96
93 181 104
174106 Beispiel: Kürzester Weg
von Bonn nach Kassel
PInf II 9.490
Ist v der letzte (d.h. von u am weitesten entfernte) Knoten des Graphen G (wie im vorangegangenen Beispiel) so liefert Dijkstra's Algorithmus offenbar einen aufspannenden Baum für G - und zwar gerade denjenigen, der für jeden Knoten k den kürzesten Weg von u nach k darstellt.
7
3
4
6
1
2
8
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
5
0
Köln
Bonn
34
22488 136
104
30
66
96
93 181
104
174106
Kürzester Weg und aufspannender Baum
PInf II 9.500 Bewertete Graphen: ein weiteres Beispiel
Richmond
Oakland
Hayward
Fremont
San Jose
San Rafael
San Francisco
San Mateo
Palo Alto
15
1518
12
18
2020
20
10
15 20
14
12
0 3
5
74
68
10 Scotts Valley
12Santa Cruz
9Santa Clara
1513Half Moon Bay
11Watsonville
14Pacifica
15
15
25
50 35
10 70
60
PInf II 9.510 Adjazenzmatrix für SFBay-Beispiel
0 1 2 3 4 5 6 7 8 9 10 11 12 13 140 0 18 - 12 20 - - - - - - - - - 151 18 0 15 - - - - - - - - - - - -2 - 15 0 15 - - - - - - - - - - -3 12 - 15 0 - 20 - - - - - - - - -4 20 - - - 0 20 18 - - - - - - 25 -5 - - - 20 20 0 - 14 - - - - - - -6 - - - - 18 - 0 15 - 10 - - - - -7 - - - - - 14 15 0 20 - - - - - -8 - - - - - - - 20 0 15 - 60 - - -9 - - - - - - 10 - 15 0 35 - - - -10 - - - - - - - - - 35 0 - 10 - -11 - - - - - - - - 60 - - 0 70 - -12 - - - - - - - - - - 10 70 0 50 -13 - - - - 25 - - - - - - - 50 0 1514 15 - - - - - - - - - - - - 15 0
PInf II 9.520 Speicherung von Graphen als Array:SFBay-Beispielprivate static final int M = Integer.MAX_VALUE;
String[] SFKn = {"San Francisco", "San Rafael", "Richmond", "Oakland","San Mateo","Hayward", "Palo Alto", "Fremont", "San Jose", "SantaClara", "Scotts Valley", "Watsonville", "Santa Cruz", "Half MoonBay", "Pacifica"};
int[][] SFKanW = {{ 0, 18, M, 12, 20, M, M, M, M, M, M, M, M, M, 15},{18, 0, 15, M, M, M, M, M, M, M, M, M, M, M, M},{ M, 15, 0, 15, M, M, M, M, M, M, M, M, M, M, M},{12, M, 15, 0, M, 20, M, M, M, M, M, M, M, M, M},{20, M, M, M, 0, 20, 18, M, M, M, M, M, M, 25, M},{ M, M, M, 20, 20, 0, M, 14, M, M, M, M, M, M, M},{ M, M, M, M, 18, M, 0, 15, M, 10, M, M, M, M, M},{ M, M, M, M, M, 14, 15, 0, 20, M, M, M, M, M, M},{ M, M, M, M, M, M, M, 20, 0, 15, M, 60, M, M, M},{ M, M, M, M, M, M, 10, M, 15, 0, 35, M, M, M, M},{ M, M, M, M, M, M, M, M, M, 35, 0, M, 10, M, M},{ M, M, M, M, M, M, M, M, 60, M, M, 0, 70, M, M},{ M, M, M, M, M, M, M, M, M, M, 10, 70, 0, 50, M},
{ M, M, M, M, 25, M, M, M, M, M, M, M, 50, 0, 15},{15, M, M, M, M, M, M, M, M, M, M, M, M, 15, 0}};
BGraph SFBayNetz = new BGraph (SFKn, SFKanW);
private static final int M = Integer.MAX_VALUE;
String[] SFKn = {"San Francisco", "San Rafael", "Richmond", "Oakland","San Mateo","Hayward", "Palo Alto", "Fremont", "San Jose", "SantaClara", "Scotts Valley", "Watsonville", "Santa Cruz", "Half MoonBay", "Pacifica"};
int[][] SFKanW = {{ 0, 18, M, 12, 20, M, M, M, M, M, M, M, M, M, 15},{18, 0, 15, M, M, M, M, M, M, M, M, M, M, M, M},{ M, 15, 0, 15, M, M, M, M, M, M, M, M, M, M, M},{12, M, 15, 0, M, 20, M, M, M, M, M, M, M, M, M},{20, M, M, M, 0, 20, 18, M, M, M, M, M, M, 25, M},{ M, M, M, 20, 20, 0, M, 14, M, M, M, M, M, M, M},{ M, M, M, M, 18, M, 0, 15, M, 10, M, M, M, M, M},{ M, M, M, M, M, 14, 15, 0, 20, M, M, M, M, M, M},{ M, M, M, M, M, M, M, 20, 0, 15, M, 60, M, M, M},{ M, M, M, M, M, M, 10, M, 15, 0, 35, M, M, M, M},{ M, M, M, M, M, M, M, M, M, 35, 0, M, 10, M, M},{ M, M, M, M, M, M, M, M, 60, M, M, 0, 70, M, M},{ M, M, M, M, M, M, M, M, M, M, 10, 70, 0, 50, M},
{ M, M, M, M, 25, M, M, M, M, M, M, M, 50, 0, 15},{15, M, M, M, M, M, M, M, M, M, M, M, M, 15, 0}};
BGraph SFBayNetz = new BGraph (SFKn, SFKanW);
PInf II 9.530 SFBay-Verkehrsnetz als ListenstrukturSan Francisco
118 1 12 3 20 4 15 14 nl
San Rafael2
18 0 15 2 nll
Richmond3
15 1 15 3 nl
Oakland4
12 0 15 2 20 5 nl
San Mateo5
20 0 20 5 18 6 25 13 nl
Hayward6
20 3 20 4 14 7 nl
Palo Alto7
18 4 15 7 10 9 nl
Fremont8
14 5 15 6 20 8 nl
San Jose9
20 7 15 9 60 11 nl
Santa Clara10
10 6 15 8 35 10 nl
Scotts Valley11
35 9 10 12 nl
Watsonville12
60 8 70 12 nl
Santa Cruz13
10 10 70 11 50 13 nl
Half Moon Bay14
25 4 50 12 15 14 nl
Pacifica 15 0 15 13 nl
0
nl kurz für : null
PInf II 9.540 Kürzester Weg: SFBay-Beispiel
Richmond
Oakland
Hayward
Fremont
San Jose
San Rafael
San Francisco
San Mateo
Palo Alto
15
1518
12
18
2020
20
10
15 20
14
12
0 3
5
74
68
10 Scotts Valley
12Santa Cruz
9Santa Clara
1513Half Moon Bay
11Watsonville
14Pacifica
15
15
25
50 35
10 70
60
PInf II 9.550 Der Weg von San Rafael nach Scotts Valley
Min-Suche Ausgangsknoten: San RafaelZielknoten: Scotts Valley
Die kürzeste Entfernung von San Rafaelnach Richmond ist 15 km.nach San Francisco ist 18 km.nach Oakland ist 30 km.nach Pacifica ist 33 km.nach San Mateo ist 38 km.nach Half Moon Bay ist 48 km.nach Hayward ist 50 km.nach Palo Alto ist 56 km.nach Fremont ist 64 km.nach Santa Clara ist 66 km.nach San Jose ist 81 km.nach Santa Cruz ist 98 km.nach Scotts Valley ist 101 km.----
Min-Suche Ausgangsknoten: San RafaelZielknoten: Scotts Valley
Die kürzeste Entfernung von San Rafaelnach Richmond ist 15 km.nach San Francisco ist 18 km.nach Oakland ist 30 km.nach Pacifica ist 33 km.nach San Mateo ist 38 km.nach Half Moon Bay ist 48 km.nach Hayward ist 50 km.nach Palo Alto ist 56 km.nach Fremont ist 64 km.nach Santa Clara ist 66 km.nach San Jose ist 81 km.nach Santa Cruz ist 98 km.nach Scotts Valley ist 101 km.----
PInf II 9.560 Traveling Salesman Problem
Ein Handlungsreisender muß eine Reihe von Orten besuchen. Er möchte seine Tour so planen, daß der zurückzulegende Weg (= Zyklus, der alle Orte berührt) minimal ist.
Ein Handlungsreisender muß eine Reihe von Orten besuchen. Er möchte seine Tour so planen, daß der zurückzulegende Weg (= Zyklus, der alle Orte berührt) minimal ist.
Dies ist ein Problem über bewerteten Graphen. Die Knoten sind die zu besuchen-den Orte, die (bewerteten) Kanten stellen die Entfernung (oder Fahrtkosten) zwischen den Orten dar. Das Graphenproblem lautet also :
Finde in einem vorgegebenen Graphen einen Zyklus minimaler Länge, der alle Knoten enthält.
Finde in einem vorgegebenen Graphen einen Zyklus minimaler Länge, der alle Knoten enthält.
Finden Sie eine TSP-Tour in dem nebenstehenden Graphen!
MR
GI
KS
MA
F
FD
WÜ
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
K
BN
Köln
Bonn
34
224 88 136
104
30
66
96
93181
104
174106
PInf II 9.570
Eigenschaften des TSPUnser Beispielproblem läßt sich offenbar intuitiv auf einfache Weise (und
eindeutig!) lösen. Grundsätzlich ist das jedoch keineswegs der Fall. (a) Oft gibt es überhaupt keine Lösung.
Beispiel: Wenn ein weiterer Knoten "Mainz" zwischen Bonn und Frankfurt aufgenommen wird, ist das TSP nicht lösbar.
(b) Oft gibt es mehrere Zyklen, die alle zu besuchenden Knoten enthalten. Dann kann der kürzeste Zyklus nur durch Ausprobieren gefunden werden. Beispiel: Wenn zusätzlich eineVerbindung Mainz-Mannheimaufgenommen wird, gibt es mehrere Zyklen.
MR
GI
KS
MA
F
FD
WÜ
KasselMarburg
Gießen
Frankfurt
Fulda
WürzburgMannheim
K
BN
Köln
Bonn
34
224 88 136
104
30
66
96
93 140
104
174106
MZ41
71
PInf II 9.580
Lösungen des TSPBis heute ist keine effiziente Lösung des TSP bekannt. Alle bekannten Lösungen sind von der Art :
Allgemeiner TSP-Algorithmus:
• Erzeuge alle möglichen Touren;
• Berechne die Kosten für jede Tour;
• Wähle die kostengünstigsteTour aus.
Allgemeiner TSP-Algorithmus:
• Erzeuge alle möglichen Touren;
• Berechne die Kosten für jede Tour;
• Wähle die kostengünstigsteTour aus.
Folgerung : Die Komplexität aller bekannten TSP-Algorithmen ist O(cN), wobei N die Anzahl der Knoten und c die Maximalzahl der von einem Knoten ausgehenden Kanten ist. Das heißt: Wenn wir einen einzigen Knoten hinzunehmen, erhöht sich der Aufwand zur Lösung des TSP um den Faktor c!
Beispiel: Hinzunahme des neuen Knotens Mainz im obenstehenden Graphen.
PInf II 9.590
Varianten des TSPEine Verallgemeinerung des TSP besteht darin, auf die Rückkehr zum Ausgangspunkt zu verzichten:• TSP (u,v): Finde einen einfachen Weg von Knoten u nach Knoten v, der alle Knoten von G enthält. Offenbar stellt TSP(u,u) das ursprüngliche TSP-Problem dar. Beispiel: TSP(Marburg, Gießen) ist (im ursprünglichen Graphen) eindeutig
lösbar, TSP (Marburg, Köln) ist nicht lösbar, für TSP (Marburg, Würzburg) müssen im erweiterten Graphen mehrere mögliche Wege verglichen werden.
In einer weiteren Verallgemeinerung werden Mehrfachbesuche einzelner Knoten bis zu einer Schranke maxB (= Maximalzahl zulässiger Besuche) zugelassen.• TSP (u,v,maxB): Finde einen Weg von Knoten u nach Knoten v, der alle Knoten von G mindestens einmal und höchstens maxB-mal enthält.Mit dieser Verallgemeinerung (und geeignetem maxB) ist das TSP immer lösbar - allerdings mit noch erheblich gesteigerten Aufwand!
PInf II 9.600
TSP-Algorithmus: Erläuterung (1)Der folgende Algorithmus für das Problem TSP (u,v,maxB) verfolgt eine
Tiefensuche (depth first)- Strategie mit Zurücksetzen (backtracking). Er verwendet
• ein int-Array Marken der Länge knAnz zum Markieren der bereits besuchten Knoten,
• ein int-Array MinWeg der Länge maxKnBes zum Speichern des (bisher) minimalen Weges (mit maxKnBes = vorgegebene Schranke für die Anzahl zu besuchender Knoten),
• eine rekursive Prozedur Besuche, die einen begonnenen Weg der Tiefe t an der Stelle k mit dem Ziel z fortsetzt.Ist der Weg noch keine vollständige Tour, und existiert zu k ein Nachbarknoten k', der noch weniger als maxB-mal besucht wurde und bei dessen Besuch die gesamte Weglänge unterhalb der Weglängen-Schranke MinWegLen bleibt, wird der Weg bei k' mit Tiefe t+1fortgesetzt (rekursiver Aufruf).
PInf II 9.610
• Weiter wird durch eine boolesche Funktion TesteWeg geprüft, - ob k bereits der Zielknoten v ist,- ob alle Knoten besucht wurden.
• Ist ein Weg gefunden, so wird seine Länge (AktWegLen) mit der vonMinWeg (MinWegLen) verglichen. Falls er kürzer ist, wird er zum neuenMinWeg und seine Länge wird als neue MinWegLen festgehalten. Der Besuch wird abgebrochen.
• Beim Abbruch eines Besuchs wird zum vorherigen Knoten zurückgekehrt (backtrack) und der nächste mit diesem verbundene Knoten besucht. Wurden alle Wege durchprobiert und mindestens eine Tour gefunden, so wird MinWeg, ansonsten eine Fehlermeldung ausgegeben.
TSP-Algorithmus: Erläuterung (2)
PInf II 9.620
void besuche (int k, int [ ] weg, int tiefe, int [ ] marken, int ziel) {tiefe += 1; weg [tiefe] = k; marken[k] += 1; int bishWegLen = 0;if (tiefe == 0) aktWegLen =0 // Vorbesetzungelse {bishWegLen = aktWegLen;
aktWegLen += kantenW (weg[tiefe-1], k);}if (aktWegLen < minWegLen) // abbrechen, falls keine Verbesserung
if (testeWeg() { // Weg ist eine TSP-Tour) int [ ] minWeg = ... // Kopie von weg bis zur Position tiefe; minWegLen = aktWegLen; }
else // Weg ist keine TSP-Tourfor .. // alle Nachbarknoten j von k mit Marken [j] < maxB
besuche (j, weg, tiefe, marken, ziel); // in allen anderen Fällen Besuch zurücknehmen:
aktWegLen = bishWegLen; marken [k] -=1; }
void besuche (int k, int [ ] weg, int tiefe, int [ ] marken, int ziel) {tiefe += 1; weg [tiefe] = k; marken[k] += 1; int bishWegLen = 0;if (tiefe == 0) aktWegLen =0 // Vorbesetzungelse {bishWegLen = aktWegLen;
aktWegLen += kantenW (weg[tiefe-1], k);}if (aktWegLen < minWegLen) // abbrechen, falls keine Verbesserung
if (testeWeg() { // Weg ist eine TSP-Tour) int [ ] minWeg = ... // Kopie von weg bis zur Position tiefe; minWegLen = aktWegLen; }
else // Weg ist keine TSP-Tourfor .. // alle Nachbarknoten j von k mit Marken [j] < maxB
besuche (j, weg, tiefe, marken, ziel); // in allen anderen Fällen Besuch zurücknehmen:
aktWegLen = bishWegLen; marken [k] -=1; }
TSP-Algorithmus: Programmgerüst
Aufruf: besuche (u, weg, -1, marken, v)besuche (u, weg, -1, marken, v)
PInf II 9.630
class TSPGraph extends BGraph {
private int schranke ; // Abschätzung für Weglänge
private int minWegLen ; // Länge d.bish.minimalen Wegesprivate final int maxB = ...; /* Maximalzahl von Besuchen
pro Knoten, z.B. maxB = 2 */
private int aktWegLen; // Länge des aktuellen Weges
private int maxKnBes; /* Schranke für die Anzahlbesuchter Knoten */
private int[] minWeg; // Bisher minimaler Weg
void TSPSuche (int st, int zi) { // siehe nächste Folie} ...
}
class TSPGraph extends BGraph {
private int schranke ; // Abschätzung für Weglänge
private int minWegLen ; // Länge d.bish.minimalen Wegesprivate final int maxB = ...; /* Maximalzahl von Besuchen
pro Knoten, z.B. maxB = 2 */
private int aktWegLen; // Länge des aktuellen Weges
private int maxKnBes; /* Schranke für die Anzahlbesuchter Knoten */
private int[] minWeg; // Bisher minimaler Weg
void TSPSuche (int st, int zi) { // siehe nächste Folie} ...
}
Für die Implementierung des TSP-Algorithmus wird die Klasse Graph um zur Klasse TSPGraph erweitert. Dazu werden einige zusätzlich benötigte Datenfelder definiert. Die Operation TSPSuche ermittelt eine TSP-Tour. Als Hilfsroutinen benutzt sie die Operationen besuche und testeWeg.
Klassenerweiterung für TSP-Implementierung
PInf II 9.640
void TSPSuche (int st, int zi) { /* sucht minimale TSP-Tourvon st nach zi */
minWegLen = schranke; // Abschätzung nach oben
maxKnBes = ... ; /* geeigneter Wert > knAnz, z.B.(knAnz*maxB) +1 */
weg = new int[maxKnBes];marken = new int[knAnz];
for (int k = 0; k < knAnz; k++) marken[k] = 0;besuche (st, weg, -1, marken, zi);
if (minWegLen == schranke)System.out.println ("Kein Weg gefunden!");
else {System.out.println ("Länge des kürzesten Weges: " +minWegLen);System.out.println ("Besuchte Knoten: ");for (int k = 0; k < minWeg.length; k++)
System.out.println (knoBez[minWeg[k]]);}
}
void TSPSuche (int st, int zi) { /* sucht minimale TSP-Tourvon st nach zi */
minWegLen = schranke; // Abschätzung nach oben
maxKnBes = ... ; /* geeigneter Wert > knAnz, z.B.(knAnz*maxB) +1 */
weg = new int[maxKnBes];marken = new int[knAnz];
for (int k = 0; k < knAnz; k++) marken[k] = 0;besuche (st, weg, -1, marken, zi);
if (minWegLen == schranke)System.out.println ("Kein Weg gefunden!");
else {System.out.println ("Länge des kürzesten Weges: " +minWegLen);System.out.println ("Besuchte Knoten: ");for (int k = 0; k < minWeg.length; k++)
System.out.println (knoBez[minWeg[k]]);}
}
TSPSuche: Java-Programm
PInf II 9.650
private void besuche (int k, int[] weg, int tiefe, int[]marken, int zi) { /* besucht Knoten k mit geg. Weg,
Tiefe, Markierung und Ziel */tiefe ++;if (tiefe >= maxKnBes) {System.out.println ("Zulässige Knotenzahl im Wegüberschritten."); return; };
weg[tiefe] = k;marken[k] ++;int bishWegLen = 0;if (tiefe == 0) aktWegLen = 0; // Vorbesetzungelse {bishWegLen = aktWegLen;aktWegLen += kantenW (weg[tiefe-1], k);
}if (aktWegLen < minWegLen)if (testeWeg(weg, tiefe, marken, zi)) { // ist TSP-Tour
minWeg = new int[tiefe + 1];System.arraycopy (weg, 0, minWeg, 0, tiefe + 1) ;minWegLen = aktWegLen;
}else // siehe nächste Folie
private void besuche (int k, int[] weg, int tiefe, int[]marken, int zi) { /* besucht Knoten k mit geg. Weg,
Tiefe, Markierung und Ziel */tiefe ++;if (tiefe >= maxKnBes) {System.out.println ("Zulässige Knotenzahl im Wegüberschritten."); return; };
weg[tiefe] = k;marken[k] ++;int bishWegLen = 0;if (tiefe == 0) aktWegLen = 0; // Vorbesetzungelse {bishWegLen = aktWegLen;aktWegLen += kantenW (weg[tiefe-1], k);
}if (aktWegLen < minWegLen)if (testeWeg(weg, tiefe, marken, zi)) { // ist TSP-Tour
minWeg = new int[tiefe + 1];System.arraycopy (weg, 0, minWeg, 0, tiefe + 1) ;minWegLen = aktWegLen;
}else // siehe nächste Folie
Operation besuche
PInf II 9.660
else // Weg ist keine TSP-Tourfor (int j = 0; j < knAnz; j++)
//für alle Nachbarn j von k mit Marken[i] < maxBif (k != j && kantenW (k, j) < M)
if (marken[j] < maxB)besuche (j, weg, tiefe, marken, zi);
// in allen anderen Fällen Besuch zurücknehmen:aktWegLen = bishWegLen;weg[tiefe] = -1;marken[k] --;
}
private boolean testeWeg (int[] weg, int tiefe, int[]marken, int zi) { // prüft gegebenen Weg auf TSP-Tourif (tiefe < knAnz-1 |||||||| weg[tiefe] != zi) return false;for (int k = 0; k < knAnz; k++)if (marken[k] == 0) return false;
return true;}
else // Weg ist keine TSP-Tourfor (int j = 0; j < knAnz; j++)
//für alle Nachbarn j von k mit Marken[i] < maxBif (k != j && kantenW (k, j) < M)
if (marken[j] < maxB)besuche (j, weg, tiefe, marken, zi);
// in allen anderen Fällen Besuch zurücknehmen:aktWegLen = bishWegLen;weg[tiefe] = -1;marken[k] --;
}
private boolean testeWeg (int[] weg, int tiefe, int[]marken, int zi) { // prüft gegebenen Weg auf TSP-Tourif (tiefe < knAnz-1 |||||||| weg[tiefe] != zi) return false;for (int k = 0; k < knAnz; k++)if (marken[k] == 0) return false;
return true;}
Hilfsoperationen für TSP (Forts.)
PInf II 9.670 TSP von San Francisco nach San Rafael
Länge des kürzesten Weges: 342Besuchte Knoten:
San FranciscoPacificaHalf Moon BaySan MateoPalo AltoSanta ClaraScotts ValleySanta CruzWatsonvilleSan JoseFremontHaywardOaklandRichmondSan Rafael
Eine Lösung des TSP von San Francisco nachSan Rafael ist mit einem einfachen Weg möglich.
Laufzeit (auf 33MHZ 486 PC) < 1 sec
Eine Lösung des TSP von San Francisco nachSan Rafael ist mit einem einfachen Weg möglich.
Laufzeit (auf 33MHZ 486 PC) < 1 sec
PInf II 9.680 TSP von San Francisco nach San MateoLänge des kürzesten Weges: 364.Besuchte Knoten:
San FranciscoSan RafaelRichmondOaklandSan FranciscoPacificaHalf Moon BaySanta CruzScotts ValleySanta CruzWatsonvilleSan JoseSanta ClaraPalo AltoFremontHaywardSan Mateo
Eine Lösung des TSP von San Francisco nachSan Mateo gelingt nur mit Mehrfachbesuchen.
Laufzeit (auf 33MHZ 486 PC) ca. 200 sec
Eine Lösung des TSP von San Francisco nachSan Mateo gelingt nur mit Mehrfachbesuchen.
Laufzeit (auf 33MHZ 486 PC) ca. 200 sec