Kapitel 9: Graphen und Graph-Algorithmen _________________________________ Graphen: Begriffe und Definitionen Bewertete Graphen Graphen-Implementierungen Tiefen- und Breitensuche Transitive Hülle Kürzeste Wege Traveling Salesman-Problem PInf II 9.20 Graphen 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. 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 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: a d V = { a,b,c,d,e,f,g,h} f E = { (a,d), (d,a), (a,b), (b,c), (c,a), (b,e), (a,e), (f,g), (f,f)}. b h g c e 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 } Köln 5 Marburg 4 Kassel 7 Gießen 0 Bonn Mannheim 6 2 Fulda 3 1 Frankfurt 8 Würzburg 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 Beispiele G = <V,E> sei wie oben definiert. • (b,c,a,d,a) ist ein Pfad von b nach a. a d • Er enthält einen Zyklus: (a,d,a). f • (c,a,b,e) ist einfacher Pfad von c nach e. b h • (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. g c e PInf II 9.70 Bewertete Graphen Ein 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. Für einen Weg w = (x=a0 , a1, ..., ap=y) heißt len(w) = Σi=0 p-1 gw(ai, ai+1) die bewertete Länge von w. Beispiel: len (Marburg, Gießen, Frankfurt, Mannheim) = 184 Marburg 104 Köln 5 174 30 7 Gießen 106 34 3 0 181 66 104 Bonn Frankfurt 1 224 88 136 6 Mannheim 4 Kassel 96 2 Fulda 93 8 Würzburg PInf II 9.80 Ungerichtete Graphen und Zusammenhang Ungerichtete 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 Zusammenhangskomponenten genannt) . • Ein zusammenhängender zyklenfreier Graph ist ein Baum. f • Eine Gruppe paarweise nicht zusammenhängender Bäume heißt Wald. Jeder zyklenfreie ungerichtete Graph ist also ein Wald. Zusammenhangskomponenten von G d a b h g c e 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 starken Zusammenhangskomponenten 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. a d f b h g Beispiel:Starke Zusammenhangskomponenten von G c e PInf II 9.100 Aufspannender Baum • Ist G ungerichtet und zusammenhängend und R ein zusammenhängender, zyklenfreier Teilgraph von G, der alle Knoten von G enthält, so heißt R ein (auf)spannender Baum (engl.: spanning tree) von G. 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). Graph mit aufspannendem Baum PInf II 9.110 Repräsentation von Graphen Für die Repräsentation eines Graphen kommen in erster Linie zwei Datenstrukturen 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 public class class AGraph AGraph {{ String[] knoBez; String[] knoBez; int int knAnz; knAnz; boolean[][] boolean[][] aMx; aMx; // // alle alle Knoten-Bezeichner Knoten-Bezeichner // Anzahl // Anzahl der der Knoten Knoten // Adjazemnzmatrix // Adjazemnzmatrix AGraph AGraph (String[] (String[] knBez, knBez, boolean[][] boolean[][] kanten) kanten) {{ ... ... /* /* Konstruktor, Konstruktor, besetzt besetzt Knoten-Bezeichner Knoten-Bezeichner und und Kanten-Matrix Kanten-Matrix mit mit den den gegebenen gegebenen Parameter-Werten 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 // // Konkretisierung Konkretisierung zum zum vorigen vorigen Programm Programm String[] knoBez = {"a","b","c","d","e","f","g","h"}; String[] knoBez = {"a","b","c","d","e","f","g","h"}; boolean[][] boolean[][] kanten kanten == {{false, {{false, true, true, false, false, true, true, true, true, false, false, false, false, false}, false}, {false, false, true, false, true, false, false, {false, false, true, false, true, false, false, false}, false}, ... // }; ... // usw. usw. }; AGraph gBsp = new AGraph (knoBez, kanten) ; AGraph gBsp = new AGraph (knoBez, kanten) ; ... ... a d f b h g c e a b c d e f g h a x x x b x x c x d x e f x x g h ( x = True, " " = False ) PInf II 9.140 Bewertete Adjazenzmatrizen Die Idee der Adjazenzmatrix läßt sich leicht auf bewertete Graphen ausdehnen. 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.: w, falls e =(u,v) ∈ E und gw(e) = w, M[u,v] = 0, falls u=v und e =(u,u) ∈ E , ∞ (bzw. MAX_VALUE) sonst { public public class class BGraph BGraph {{ String[] knoBez; // String[] knoBez; // Alle Alle Knoten-Bezeichner Knoten-Bezeichner int knAnz; // Anzahl int knAnz; // Anzahl der der Knoten Knoten int[][] bMx; // Matrix mit Gewichten int[][] bMx; // Matrix mit Gewichten BGraph BGraph (String[] (String[] knBez, knBez, int[][] int[][] kanten) kanten) {{ ... /* Konstruktor, besetzt ... /* Konstruktor, besetzt knoBez, knoBez, knAnz knAnz und und bMx mit den gegebenen Werten */; bMx mit den gegebenen Werten */; }} ... ... }.. }.. PInf II 9.150 Bewertete Adjazenzmatrix: Beispiel BN BN FF FD FD GI GI KS KS KK BN --- 34 BN 00 181 181 -34 FF 181 -181 00 104 104 66 66 -FD FD -- 104 104 00 106 106 96 96 -GI -66 -- 174 GI 66 106 106 00 174 KS 96 0 -KS 96 0 - steht für ∞ KK 34 -- 174 34 -174 -MA --MA 224 224 88 88 -MR --- 30 MR -30 104 104 WÜ 136 93 -WÜ 136 93 - 00 -- MA MA 224 224 88 88 ----- -- 00 -- -- -- MR MR WÜ WÜ ---136 136 -- 93 93 -- 30 30 104 104 ----- -- 00 -- -00 Ist G ein ungerichteter Graph, so ist die Matrix symmetrisch - d.h. man kommt im Prinzip mit einer Dreiecksmatrix aus. PInf II 9.160 Bewertete Adjazenzmatrix: Initialisierung String[] String[] knBez knBez == {"Bonn", {"Bonn", "Frankfurt", "Frankfurt", "Fulda", "Fulda", "Gießen", "Gießen", "Kassel", "Köln", "Mannhem", "Marburg", "Würzburg"}; "Kassel", "Köln", "Mannhem", "Marburg", "Würzburg"}; int[][] int[][] kanten kanten == {{ {0, {0, 181, 181, M, M, M, M, M, M, 34, 34, 224, 224, M, M, M}, M}, {181, 0, 104, 66, M, M, 88, M, 136}, {181, 0, 104, 66, M, M, 88, M, 136}, ... ... {M, {M, 136, 136, 93, 93, M, M, M, M, M, M, M, M, M, M, 0} 0} }; }; BGraph bahnNetz = new BGraph (knBez, BGraph bahnNetz = new BGraph (knBez, kanten); kanten); M steht für ∞ (genauer für: Integer.MAX_VALUE) 4 Kassel Marburg 104 Köln 96 7 5 174 Gießen 30 106 2 Fulda 34 3 0 181 66 104 Bonn 93 Frankfurt 1 224 88 136 8 Würzburg Mannheim 6 PInf II 9.170 Knoten- und Kantenzugriffe Der Zugriff auf einzelne Knoten und Kanten ist mit Hilfe einfacher Zugriffsfunktionen möglich:. public public class class BGraph BGraph {{ ... ... String gibKnoBez String gibKnoBez (int (int k) k) {{ return return knoBez[k]; knoBez[k]; }} int int kantenW kantenW (int (int u, u, int int v) v) {{ return bMx[u][v]; return bMx[u][v]; }} // // Forts. Forts. von von oben oben // Knoten-Zugriff // Knoten-Zugriff // // Kanten-Zugriff Kanten-Zugriff Beispiel: bahnNetz.gibKnoBez(3); bahnNetz.gibKnoBez(3); bahnNetz.kantenW bahnNetz.kantenW (3,7); (3,7); // // Ergebnis: Ergebnis: "Gießen" "Gießen" // Ergebnis: 30 // Ergebnis: 30 PInf II 9.180 Adjazenzlisten (1) Eine zweite (weniger speicheraufwendige) Möglichkeit zur Repräsentation eines Graphen besteht darin, jedem Knoten eine Liste seiner Nachbarknoten - ggf. mit den zugehörigen Kantengewichten - zuzuordnen. Eine solche Liste wird auch Adjazenzliste genannt. a d a b c d b c a a d e e f f f b h g c e e g g h PInf II 9.190 Adjazenzlisten: Klassendefinitionen (1) ziel class class VerbListe VerbListe {{ int // int ziel; ziel; // Endpunkt-Nr Endpunkt-Nr int // int gew; gew; // Kanten-Gewicht Kanten-Gewicht VerbListe VerbListe nx; nx; // // Nachfolger-Referenz Nachfolger-Referenz VerbListe VerbListe (int (int z, z, int int w, w, VerbListe VerbListe v) v) {{ ziel ziel == z; z; gew gew == w; w; nx nx == v; v; }} }} class class LKnoten LKnoten {{ String String knBez; knBez; VerbListe VerbListe nachbarn; nachbarn; LKnoten LKnoten (String (String s, s, VerbListe VerbListe v) v) {{ knBez = s; nachbarn = v; knBez = s; nachbarn = v; }} }} 2 4 b 5 5 d 7 e 2 gew a 5 2 4 5 b d e 7 2 knBez nachbarn PInf II 9.200 Adjazenzlisten: Klassendefinitionen (2) public public class class LGraph LGraph {{ static final static final int int maxInt maxInt == Integer.MAX_VALUE; Integer.MAX_VALUE; LKnoten[] knoten; // LKnoten[] knoten; // Array Array von von Knoten(-Referenzen) Knoten(-Referenzen) int knAnz; int knAnz; LGraph LGraph (LKnoten[] (LKnoten[] knL) knL) {{ // // besetzt besetzt Nachbarlisten Nachbarlisten knAnz = knL.length; knAnz = knL.length; knoten knoten == new new LKnoten[knAnz]; LKnoten[knAnz]; for (int i for (int i == 0; 0; ii << knAnz; knAnz; i++) i++) knoten[i] knoten[i] == knL[i]; knL[i]; }} int int kantenW kantenW (int (int u, u, int int v) v) {{ /* /* liefert liefert gew, gew, falls falls direkte Verbindung, sonst maxInt */ direkte Verbindung, sonst maxInt */ if if (u (u == == v) v) return return 0; 0; VerbListe vLi VerbListe vLi == knoten[u].nachbarn; knoten[u].nachbarn; while while (vLi (vLi != != null) null) {{ if (vLi.ziel if (vLi.ziel == == v) v) return return vLi.gew; vLi.gew; vLi = vLi.nx; } vLi = vLi.nx; } String String gibKnoBez gibKnoBez (int (int k) k) {{ return return maxInt; maxInt; return knoten[k].knBez; return knoten[k].knBez; }} ... ... }} ... ... }} PInf II 9.210 Adjazenzliste: Anwendung Beispiel: LKnoten[] LKnoten[] knoten knoten == {{ new new LKnoten LKnoten ("Bonn", ("Bonn", new VerbListe(1, 181, new VerbListe(1, 181, new new VerbListe(5, VerbListe(5, 34, 34, 4 Kassel new Marburg 104 new VerbListe(6, VerbListe(6, 224, 224, Köln 96 null null )))), )))), 5 174 30 7 .... 106 2 Fulda Gießen .... 34 3 // usw. // usw. 0 181 66 new new LKnoten LKnoten ("Würzburg", ("Würzburg", 104 Bonn new 93 Frankfurt 1 new VerbListe(1, VerbListe(1, 136, 136, new VerbListe(2, 93, 224 new VerbListe(2, 93, 88 136 null 8 null ))) ))) 6 Würzburg Mannheim }; }; LGraph LGraph bahnNetz bahnNetz == new new LGraph(knoten); LGraph(knoten); PInf II 9.220 Verkehrsnetz als Array von Adjazenzlisten 0 Bonn 1 181 5 34 6 224 nil 1 Frankfurt 0 181 2 104 3 66 6 88 8 136 nil 2 Fulda 1 104 3 106 4 96 8 93 nil 3 Gießen 1 66 2 106 5 174 7 30 nil 4 Kassel 2 96 7 104 nil 5 Köln 0 34 3 174 nil 6 Mannheim 0 224 1 88 nil 7 Marburg 3 30 4 104 nil 8 Würzburg 1 136 2 93 nil PInf II 9.230 Implementierung durch Listen von Listen 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. a a b d b c e c a d a d f b h e e g c e f f g g h PInf II 9.240 Graph als Liste von Listen (1) public public class class LLGraph LLGraph {{ String grBez; // String grBez; // Graph-Bezeichner Graph-Bezeichner LLKnoten kn; // LLKnoten kn; // Liste Liste der der Knoten Knoten des des Graphen Graphen int knAnz; int knAnz; LLGraph LLGraph (String (String bez) bez) {{ // // erzeugt erzeugt Graphen Graphen namens namens bez bez grBez = bez; grBez = bez; kn kn == null; null; knAnz knAnz == 0; 0; }} public public LLKnoten LLKnoten fuegeKnEin fuegeKnEin (String (String knBez) knBez) {{ // // fügt fügt Knoten Knoten knBez knBez in in die die Liste Liste ein ein kn kn == new new LLKnoten LLKnoten (knBez, (knBez, kn); kn); knAnz knAnz ++; ++; return return kn; kn; }} // // end end fuegeKnEin fuegeKnEin .... // .... // weiter weiter s. s. nächste nächste Folie Folie PInf II 9.250 Graph als Liste von Listen (2) // // Forts. Forts. von von voriger voriger Folie Folie public class LLKnoten { public class LLKnoten { String String knBez; knBez; NbListe // NbListe nachbarn; nachbarn; // Adjazenzliste Adjazenzliste für für ausg. ausg. Kanten Kanten LLKnoten // LLKnoten nxKn; nxKn; // Ref. Ref. auf auf nächsten nächsten Knoten Knoten LLKnoten LLKnoten (String (String s, s, LLKnoten LLKnoten nx) nx) {{ // // Konstruktor Konstruktor knBez = s; knBez = s; nachbarn nachbarn == null; null; nxKn nxKn == nx; nx; }} ... // ... // end end Konstruktor Konstruktor public public void void fuegeNbEin fuegeNbEin (LLKnoten (LLKnoten ziel, ziel, int int gw) gw) {{ // // Kante Kante vom vom akt. akt. Knoten Knoten zu zu Kn. Kn. ziel ziel mit mit Gewicht Gewicht gw gw nachbarn = new NbListe (ziel, gw, nachbarn); nachbarn = new NbListe (ziel, gw, nachbarn); }} .... // .... // end end fuegeNbEin fuegeNbEin }} // end // end LLKnoten LLKnoten PInf II 9.260 Graph als Liste von Listen (3) // // Forts. Forts. von von voriger voriger Folie Folie public public class class NbListe NbListe {{ LLKnoten // LLKnoten kn; kn; // Nachbarknoten, Nachbarknoten, Ziel Ziel einer einer Kante Kante int gw; // Kantengewicht für diese int gw; // Kantengewicht für diese Kante Kante NbListe // NbListe nxKn; nxKn; // Referenz Referenz auf auf nächsten nächsten Nachbarn Nachbarn NbListe NbListe (LLKnoten (LLKnoten zi, zi, int int g, g, NbListe NbListe nx) nx) {{ kn kn == zi; zi; gw gw == g; g; nxKn nxKn == nx; nx; }... // }... // end end Konstruktor Konstruktor }} // end NbListe // end NbListe Anwendungsbeispiel: LLGraph LLGraph bNetz bNetz == new new LLGraph LLGraph ("Bahnnetz"); ("Bahnnetz"); LLKnoten LLKnoten bn bn == bNetz.fuegeKnEin bNetz.fuegeKnEin ("Bonn"); ("Bonn"); LLKnoten f = bNetz.fuegeKnEin ("Frankfurt"); LLKnoten f = bNetz.fuegeKnEin ("Frankfurt"); ... ... bn.fuegeNbEin bn.fuegeNbEin (f, (f, 181); 181); f.fuegeNbEin f.fuegeNbEin (bn, (bn, 181); 181); ... ... PInf II 9.270 Vergleich der Implementierungen Alle hier betrachteten Möglichkeiten zur Implementierung von Graphen haben ihre spezifischen Vor- und Nachteile. Seien n = Knotenzahl und m = Kantenzahl eines Graphen G. Vorteile Nachteile Adjazenzmatrix Berechnung der Inzidenz mit O(1) hoher Platzbedarf und teure Initialisierung: beide O(n2) Adjazenzliste Platzbedarf beträgt nur O(n+m) Effizienz der Kantensuche abhängig von Knotenordnung Liste von Listen Knoten lassen sich flexibel hinzufügen/ löschen Effizienz von Knoten- und Kantensuche abhängig von Listenposition PInf II 9.280 Traversieren von Graphen • 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. 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. Tiefensuche (1) PInf II 9.290 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. Programmgerüst: void void depFVisit depFVisit (int (int k) k) {{ if ( ...) // k if ( ...) // k ist ist noch noch nicht nicht markiert markiert markiere(k); markiere(k); bearbeite bearbeite (k); (k); for for (( .. .. /* /* alle alle i, i, die die Nachbarn Nachbarn von von kk sind sind */ */ )) depFVisit (i); depFVisit (i); }} Konkreter Code hängt von der Graph-Implementierung ab. Tiefensuche (2) PInf II 9.300 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 void depFVisit depFVisit (int (int k) k) {{ int[] marken = new int[] marken = new int int [knAnz]; [knAnz]; for (int i = 0; i < for (int i = 0; i < knAnz; knAnz; i++) i++) marken marken [i] [i] == 0; 0; // initialisiere // initialisiere Marken Marken rekDFVisit rekDFVisit (marken, (marken, k); k); }} void void rekDFVisit rekDFVisit (int[] (int[] marken, marken, int int k) k) {{ if if (marken[k] (marken[k] << 1) 1) {{ // // kk ist ist noch noch nicht nicht markiert markiert marken [k] ++; marken [k] ++; System.out.println System.out.println (knoten[k].knBez); (knoten[k].knBez); // // bezieht bezieht sich sich auf auf Klasse Klasse LGraph LGraph for for (int (int ii == 0; 0; ii << knAnz; knAnz; i++) i++) if if (kantenW (kantenW (k, (k, i) i) << maxInt) maxInt) // // ii ist ist Nachbar Nachbar von von kk rekDFVisit rekDFVisit (marken, (marken, i); i); }} }} PInf II 9.310 Tiefensuche (Beispiel) Marburg Köln 5 4 Kassel 7 Gießen 2 3 Fulda Frankfurt Mannheim depFVisit (0) zur Ausgabe aller Städte in der folgenden Reihenfolge : 0 Bonn Falls wir die Nachbarn der Knoten in alphabetischer Reihenfolge erzeugen, führt der Aufruf 1 8 6 Würzburg Bonn, Frankfurt, Fulda, Gießen, Köln, Marburg, Kassel, Würzburg, Mannheim. Der Aufruf depFVisit (2) führt zur Ausgabe : Fulda, Frankfurt, Bonn, Köln, Gießen, Marburg, Kassel, Mannheim, Würzburg. Tiefensuche mit Stack void void dFSVisit dFSVisit (int (int k) k) {{ Stack st = new Stack(knAnz); Stack st = new Stack(knAnz); markiere(k); markiere(k); bearbeite bearbeite (k); (k); st.push(k); st.push(k); while Marburg while (! (! st.istLeer()) st.istLeer()) {{ Köln int akt int akt == st.top(); st.top(); 7 5 Gießen if if (( ... ... )) {{ /* /* es es existiert existiert noch noch nicht nicht 3 mark. mark. Nachbar Nachbar jj von von akt akt */ */ 0 akt akt == j; j; Bonn Frankfurt 1 markiere(akt); markiere(akt); bearbeite(akt); bearbeite(akt); st.push(akt); st.push(akt); }} Mannheim 6 else st.pop(); else st.pop(); }} }} PInf II 9.320 4 Kassel 2 Fulda 8 Würzburg 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. Breitensuche mit Queue PInf II 9.330 Ähnlich wie bei der Baum-Traversierung in Ebenen-Ordnung wird eine Warteschlange als Hilfsspeicher verwendet. void void bFVisit bFVisit (int (int k) k) {{ Queue q = new Queue(knAnz); Marburg Queue q = new Queue(knAnz); Köln markiere(k); markiere(k); 7 5 Gießen q.enQueue(k); q.enQueue(k); while while (! (! q.istLeer()) q.istLeer()) {{ 3 int akt 0 int akt == q.top(); q.top(); q.deQueue(); q.deQueue(); Bonn Frankfurt 1 bearbeite(akt); bearbeite(akt); for ( /* alle noch nicht for ( /* alle noch nicht mark. mark. Nachbarn Nachbarn jj von von akt akt */ */ )) Mannheim 6 {{ markiere(j); markiere(j); q.enQueue(j); q.enQueue(j); }} }} 4 Kassel 2 Fulda 8 Würzburg }} Wenn Nachbarn in alphabetischer Reihenfolge erzeugt werden, führt BFVisit(Marburg) zur Reihenfolge: Marburg, Gießen, Kassel, Frankfurt, Fulda, Köln, Bonn, Mannheim, Würzburg. Breitensuche mit Queue: Code PInf II 9.340 Für die Markierung wird wieder ein int-Array Marken der Länge knAnz verwendet: void void bFVisit bFVisit (int (int k) k) {{ int[] marken = new int[] marken = new int int [knAnz]; [knAnz]; for (int i = 0; i < for (int i = 0; i < knAnz; knAnz; i++) i++) marken[i] marken[i] == 0; 0; // initialisiere Marken // initialisiere Marken Queue Queue qq == new new Queue(knAnz); Queue(knAnz); marken[k] ++; marken[k] ++; q.enQueue(k); q.enQueue(k); while while (! (! q.istLeer()) q.istLeer()) {{ int akt int akt == q.top(); q.top(); q.deQueue(); q.deQueue(); System.out.println System.out.println (knoten[akt].knBez); (knoten[akt].knBez); for for (int (int jj == 0; 0; jj << knAnz; knAnz; j++) j++) if if (kantenW(akt, (kantenW(akt, j) j) << maxInt maxInt && && marken[j] marken[j] << 1) 1) {{ marken[j] marken[j] ++; ++; q.enQueue(j); q.enQueue(j); }} }} }} PInf II 9.350 Transitive Hülle Eine 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 KnotenPaaren von G Pfade existieren. Wie kann man t(G) berechnen? A D A D F F B B H C H G E Ausgangsgraph G C E G transitive Hülle t(G) PInf II 9.360 Warshall's Algorithmus Warshall's Algorithmus berechnet die transitive Hülle einer Relation (eines Graphen), die durch eine boolesche Adjazenzmatrix aMx dargestellt ist. Die Matrix aMx wird dabei schrittweise zur transitiven Hülle aufgefüllt: void void warshall warshall () () {{ // // berechnet berechnet trans. trans. Hülle Hülle für für Matrix Matrix AMx AMx [u][v]:= true <=> (u,v) boolean[][] aMx = {..// aMx E}; boolean[][] aMx = {..// aMx[u][v]:= true <=> (u,v) ∈∈ E}; for for (int (int yy == 0; 0; yy << knAnz; knAnz; y++) y++) for (int x = 0; x < for (int x = 0; x < knAnz; knAnz; x++) x++) if if (aMx[x][y]) (aMx[x][y]) for for (int (int zz == 0; 0; zz << knAnz; knAnz; z++) z++) if (aMx[y][z]) aMx[x][z] if (aMx[y][z]) aMx[x][z] == true; true; }} Frage: Funktioniert dieser Algorithmus auch, wenn man so schachtelt : for for (int (int xx == 0; 0; xx << knAnz; knAnz; x++) x++) for for (int (int yy == 0; 0; yy << knAnz; knAnz; y++) 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] = true gelten), 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) I(y)==“∀ “∀u, u,vv∈∈VV::Wenn Wennein einWeg Wegvon vonuu nach nachvvexistiert, existiert,dessen dessen sämtliche Zwischenknoten in Z = {0, .. y-1} sind, dann sämtliche Zwischenknoten in Z = {0, .. y-1} sind, danngilt: gilt: aMx[u,v] = true.” 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 : u . . ... .. . . . y . . ... .. .v Aus I(y) folgt, daß bereits gilt : aMx[u,y] und aMx[y,v] alle Zwischenknoten mit Nr. < y Beim nächsten Schleifendurchlauf (mit x=u, z=v) ... ... wird aMx[u,v] for auf true gesetzt und for (int (int xx == 0; 0; xx << knAnz; knAnz; x++); x++); if (aMx [x][y]) if (aMx [x][y]) damit gilt : I(y+1) for for (int (int zz == 0; 0; zz << knAnz; knAnz; z++) z++) if if (aMx (aMx [y][z]) [y][z]) aMx aMx [x][z] [x][z] == true; 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: 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: In unserem Beispiel gilt etwa: len(Marburg, Gießen, Frankfurt, Mannheim) = 184 und: len(Marburg, Gießen, Köln, Bonn, Mannheim) = 462 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 EinWeg Weg(u (u==aa00,,aa11,,... ...,,aapp==v) v)zwischen zwischenzwei zweiKnoten Knotenuuund undvvheißt heißt gw(k , k kürzester Weg , wenn seine bewertete Länge Σ i i minimalist. ist. kürzester Weg , wenn seine bewertete Länge Σ gw(k , ki+1))minimal i i i+1 Dieser DieserWert Wertist istdie die(kürzeste) (kürzeste)Entfernung Entfernungvon vonuunach nachv. v. • Um die kürzeste Entfernung zwischen je zwei Knoten eines Graphen zu finden (engl.: all pairs shortest path problem), kann man Warshall's Algorithmus 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[][] int[][] bMx bMx == {... {... // // bMx bMx[u][v]:= [u][v]:= gw(u,v) gw(u,v) für für (u,v) (u,v) ∈∈ E, E, maxInt sonst }; maxInt sonst }; PInf II 9.420 Floyd's Algorithmus: Code void void kWegFloyd kWegFloyd () () {{ // // berechnet berechnet kürzeste kürzeste Wege Wege für für bewertete bewertete Adj.-Matrix Adj.-Matrix bMx bMx int[][] // int[][] bMx bMx == {... {... }; }; // vgl. vgl. oben oben for for (int (int yy == 0; 0; yy << knAnz; knAnz; y++); y++); for (int x = 0; x < for (int x = 0; x < knAnz; knAnz; x++); x++); if if (bMx[x][y] (bMx[x][y] << M) M) for for (int (int zz == 0; 0; zz << knAnz knAnz ;; z++) z++) if ((bMx[y][z] < M) if ((bMx[y][z] < M) && && bMx[x][y] bMx[x][y] ++ bMx[y][z] bMx[y][z] << bMx[x][z]) bMx[x][z]) bMx[x][z] bMx[x][z] == bMx[x][y] bMx[x][y] ++ bMx[y][z]; bMx[y][z]; }} Komplexität von Floyd's Algorithmus: O (N3) Invariante : I(y) I(y)=="∀ "∀ u, u,vv∈V ∈V::bMx[u,v] bMx[u,v]==Länge Länge des deskürzesten kürzestenWeges, Weges,der dernur nur Zwischenknoten Zwischenknotenaus aus{0 {0....y-1} y-1} benutzt.” benutzt.” PInf II 9.430 Dijkstra’s Algorithmus Ein 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 v interessiert, so kann man i.a. vorzeitig abbrechen. Grundidee des Algorithmus: Eine Menge S ⊆ V (im Bild rechts grün eingefärbt) beschreibt den jeweils bereits bearbeiteten Teilgraphen von G. Anfangs ist S = {u} nk G S k u k' nk' • 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. PInf II 9.440 Dijkstra’s Algorithmus: Programmgerüst intSet intSetSS=={u}; {u}; int ∞∞, ,∞∞, ,..., int[knAnz] [knAnz]minEntf minEntf=={∞ {∞ ...,∞∞};}; while ¬ while(¬ (¬ ¬vv∈∈S) S){{ Finde Finde kk∈∈SSund und nk nk ∈∈(V (V--S) S)mit mit minEntf[k] minEntf[k]++bMx[k,nk] bMx[k,nk]ist istminimal minimal;; minEntf[nk] minEntf[nk]==minEntf[k] minEntf[k]++bMx[k,nk]; bMx[k,nk]; SS==SS∪∪{nk {nk};}; Invariante Invariante:: ∀∀ss∈∈SS:: minEntf[s] minEntf[s]==kürzeste kürzeste Entfernung Entfernung von vonuu nach nachs.s. }} ////minEntf[v] minEntf[v]==Minimale MinimaleDistanz Distanzvon vonuunach nachvv 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 Gesamtaufwand 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 knAnz gespeichert. 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. Dijkstra’s Algorithmus: Code PInf II 9.460 void void kWegDijkstra kWegDijkstra (int (int u, u, int int v) v) {{ // // kürz.Weg kürz.Weg von von uu nach nach vv boolean[] s = new boolean[knAnz]; boolean[] s = new boolean[knAnz]; for for (int (int ii == 0; 0; ii << knAnz; knAnz; i++) i++) s[i] s[i] == false; false; s[u] s[u] == true; true; System.out.println("Kürzeste System.out.println("Kürzeste Entfernung Entfernung von von "+ "+ knoBez[u]); knoBez[u]); int[] int[] minEntf minEntf == new new int[knAnz]; int[knAnz]; minEntf[u] minEntf[u] == 0; 0; while // while (! (! s[v]) s[v]) {{ // terminiert, terminiert, wenn wenn Ziel Ziel vv erreicht erreicht int int min min == M; M; int int kNeu kNeu == 0; 0; for for (int (int kk == 0; 0; kk << knAnz; knAnz; k++) k++) if (s[k]) if (s[k]) for for (int (int nk nk == 0; 0; nk nk << knAnz; knAnz; nk++) nk++) if if (! (! s[nk] s[nk] && && bMx[k][nk] bMx[k][nk] << maxInt) maxInt) {{ int int kNeuEntf kNeuEntf == minEntf[k] minEntf[k] ++ bMx[k][nk] bMx[k][nk] ;; if (kNeuEntf < min) {min = kNeuEntf; if (kNeuEntf < min) {min = kNeuEntf; kNeu kNeu == nk;} nk;} }} s[kNeu] s[kNeu] == true; true; // // kNeu kNeu wird wird in in SS aufgenommen aufgenommen minEntf[kNeu] = min; minEntf[kNeu] = min; System.out.println System.out.println (" (" nach nach "" ++ knoBez[kNeu] knoBez[kNeu] ++ "" ist ist "" ++ min min ++ "" km."); 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. G Angenommen, es gäbe eine kürzere Verbindung 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 ! nk k nk' S u k' 104 PInf II 9.480 4 Kassel Marburg Köln 96 30 7 174 5 106 Gießen 2 Fulda 34 3 0 181 66 104 Bonn 93 Frankfurt 1 224 88 136 8 Mannheim 6 Würzburg S Zu zeigen : Es kann keine kürzere Verbindung von u zu nk geben als die über k . Beispiel: Kürzester Weg von Bonn nach Kassel minEntf {0} {0, 0, 0, 0, 0, 0, 0, 0, 0} (1) {0} {0, 0, 0, 0, 0, 0, 0, 0, 0} (2) {0, 5} {0, 0, 0, 0, 0, 34, 0, 0, 0} (3) {0, 1, 5} {0,181, 0, 0, 0, 34, 0, 0, 0} (4) {0, 1, 3, 5} {0,181, 0, 208, 0, 34, 0, 0, 0} (5) {0, 1, 3, 5, 6} {0,181, 0, 208, 0, 34, 224, 0, 0} (6) {0, 1, 3, 5, 6, 7} {0,181, 0, 208, 0, 34, 224, 238, 0} (7) {0, 1, 2, 3, 5, 6, 7} {0,181, 285, 208, 0, 34, 224, 238, 0} (8) {0, 1, 2, 3, 5, 6, 7, 8} {0,181, 285, 208, 0, 34, 224, 238, 317} (9) {0, 1, 2, 3, 4, 5, 6, 7, 8} k kNeu min 0 0 5 0 3 1 1 7 0 5 1 3 6 7 2 8 4 M 34 181 208 224 238 285 317 342 PInf II 9.490 Kürzester Weg und aufspannender Baum 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. 34 96 7 30 174 Gießen 5 4 Kassel 104 Marburg Köln 106 2 Fulda 3 0 181 66 104 Bonn 93 Frankfurt 1 224 88 136 8 Mannheim 6 Würzburg PInf II 9.500 Bewertete Graphen: ein weiteres Beispiel San Rafael 1 15 Richmond 2 18 San Francisco 15 12 0 Oakland 20 15 20 Pacifica 14 5 Hayward San Mateo 15 Half Moon Bay 3 25 13 20 4 14 18 20 15 Palo Alto 6 15 10 50 10 10 12 7 Fremont 9 8 San Jose Santa Clara 35 Scotts Valley Santa Cruz 70 60 Watsonville 11 PInf II 9.510 Adjazenzmatrix für SFBay-Beispiel 0 5 6 7 8 9 10 11 12 13 14 0 0 18 - 12 20 - 1 18 0 15 - 2 - 1 2 3 4 - - - - - - - - 15 - - - - - - - - - - - 15 0 15 - - - - - - - - - - - 20 - - - - - - - - - 3 12 - 4 20 - 15 0 - - - - - - - - 5 - - - 20 20 0 0 20 18 - 14 - - - - - - - 6 - - - - 18 - 0 15 - 10 - - - - - 7 - - - - - - - - - 8 - - - - - 60 - - - 14 15 0 20 - - - 20 0 15 - 9 - - - - - - 10 - 10 - - - - - - - - 11 - - - - - - - - 12 - - - - - - - - - - 13 - - - - 25 - - - - - - - 50 0 15 15 - - - - - - - - - 14 - - 15 0 35 - 35 0 60 - - - 25 - - - - 10 - - 0 70 - - 10 70 0 50 - 15 0 Speicherung von Graphen als Array:SFBay-Beispiel PInf II 9.520 private private static static final final int int MM == Integer.MAX_VALUE; Integer.MAX_VALUE; String[] SFKn = {"San String[] SFKn = {"San Francisco", Francisco", "San "San Rafael", Rafael", "Richmond", "Richmond", "Oakland", "Oakland", "San "San Mateo","Hayward", Mateo","Hayward", "Palo "Palo Alto", Alto", "Fremont", "Fremont", "San "San Jose", Jose", "Santa "Santa Clara", Clara", "Scotts "Scotts Valley", Valley", "Watsonville", "Watsonville", "Santa "Santa Cruz", Cruz", "Half "Half Moon Moon Bay", Bay", "Pacifica"}; "Pacifica"}; int[][] int[][] SFKanW SFKanW == {{ {{ 0, 0, 18, 18, M, M, 12, 12, 20, 20, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, 15}, 15}, {18, 0, 15, M, M, M, M, M, M, M, M, M, M, M, {18, 0, 15, M, M, M, M, M, M, M, M, M, M, M, M}, M}, {{ M, M, 15, 15, 0, 0, 15, 15, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M}, M}, {12, {12, M, M, 15, 15, 0, 0, M, M, 20, 20, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M}, M}, {20, {20, M, M, M, M, M, M, 0, 0, 20, 20, 18, 18, M, M, M, M, M, M, M, M, M, M, M, M, 25, 25, M}, M}, {{ M, M, M, M, M, M, 20, 20, 20, 20, 0, 0, M, M, 14, 14, M, M, M, M, M, M, M, 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, 18, M, 0, 15, M, 10, M, M, M, M, M}, M}, {{ M, M, M, M, M, M, M, M, M, M, 14, 14, 15, 15, 0, 0, 20, 20, M, M, M, M, M, M, M, M, M, M, M}, M}, {{ M, M, M, M, M, M, M, M, M, M, M, M, M, M, 20, 20, 0, 0, 15, 15, M, M, 60, 60, M, M, M, M, M}, M}, {{ M, M, M, M, M, M, 10, M, 15, 0, 35, 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, M, M, M, M, M, M, M, M, M, M, 35, 35, 0, 0, M, M, 10, 10, M, M, M}, M}, {{ M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, 60, 60, M, M, M, M, 0, 0, 70, 70, M, M, M}, M}, {{ M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, 10, 10, 70, 70, 0, 0, 50, 50, M}, M}, {{ M, M, M, M, M, M, M, M, 25, 25, M, M, M, M, M, M, M, M, M, M, M, M, M, M, 50, 50, 0, 0, 15}, 15}, {15, {15, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, M, 15, 15, 0}}; 0}}; BGraph SFBayNetz = new BGraph (SFKn, SFKanW); BGraph SFBayNetz = new BGraph (SFKn, SFKanW); PInf II 9.530 SFBay-Verkehrsnetz als Listenstruktur 0 San Francisco 18 1 12 3 20 4 1 San Rafael 18 0 15 2 nll 2 Richmond 15 1 15 3 nl 3 Oakland 12 0 15 2 20 5 nl 4 San Mateo 20 0 20 5 18 6 25 13 5 Hayward 20 3 20 4 14 7 nl 6 Palo Alto 18 4 15 7 10 9 nl 7 Fremont 14 5 15 6 20 8 nl 8 San Jose 20 7 15 9 60 11 nl 9 Santa Clara 10 6 15 8 35 10 nl 10 Scotts Valley 35 9 10 12 nl 11 Watsonville 60 8 70 12 nl 12 Santa Cruz 10 10 70 11 50 13 nl 13 Half Moon Bay 25 4 50 12 15 14 nl 14 Pacifica 15 0 15 13 nl nl 15 14 nl nl kurz für : null PInf II 9.540 Kürzester Weg: SFBay-Beispiel San Rafael 1 15 Richmond 2 18 San Francisco 15 12 0 Oakland 20 15 20 Pacifica 14 5 Hayward 20 San Mateo 4 15 Half Moon Bay 3 25 13 14 18 20 15 Palo Alto 6 15 10 50 10 10 12 7 Fremont 9 8 San Jose Santa Clara 35 Scotts Valley Santa Cruz 70 60 Watsonville 11 PInf II 9.550 Der Weg von San Rafael nach Scotts Valley Min-Suche Min-SucheAusgangsknoten: Ausgangsknoten:San SanRafael Rafael Zielknoten: Scotts Valley Zielknoten: Scotts Valley Die Diekürzeste kürzesteEntfernung Entfernungvon vonSan SanRafael Rafael nach Richmond ist 15 km. nach Richmond ist 15 km. nach nachSan SanFrancisco Franciscoist ist18 18km. km. nach nachOakland Oaklandist ist30 30km. km. nach nachPacifica Pacificaist ist33 33km. km. nach nachSan SanMateo Mateoist ist38 38km. km. nach nachHalf HalfMoon MoonBay Bayist ist48 48km. km. nach nachHayward Haywardist ist50 50km. km. nach nachPalo PaloAlto Altoist ist56 56km. km. nach nachFremont Fremontist ist64 64km. km. nach nachSanta SantaClara Claraist ist66 66km. km. nach nachSan SanJose Joseist ist81 81km. km. nach nachSanta SantaCruz Cruzist ist98 98km. km. nach Scotts Valley ist nach Scotts Valley ist101 101km. km. ------- PInf II 9.560 Traveling Salesman Problem Ein EinHandlungsreisender Handlungsreisendermuß mußeine eineReihe Reihevon vonOrten Ortenbesuchen. besuchen. Er Ermöchte möchte seine seineTour Tourso soplanen, planen,daß daßder derzurückzulegende zurückzulegende Weg Weg(= (=Zyklus, Zyklus,der deralle alle Orte Orteberührt) berührt)minimal minimalist. ist. Dies ist ein Problem über bewerteten Graphen. Die Knoten sind die zu besuchenden Orte, die (bewerteten) Kanten stellen die Entfernung (oder Fahrtkosten) zwischen den Orten dar. Das Graphenproblem lautet also : Finde Findeinineinem einemvorgegebenen vorgegebenen Graphen Grapheneinen einenZyklus Zyklusminimaler minimaler Länge, Länge,der deralle alleKnoten Knotenenthält. enthält. Finden Sie eine TSP-Tour in dem nebenstehenden Graphen! KS Kassel Marburg 104 Köln 96 MR K 174 Gießen 30 106 FD Fulda 34 GI BN 181 66 104 Bonn 93 Frankfurt F 224 88 136 WÜ Mannheim MA Würzburg PInf II 9.570 Eigenschaften des TSP Unser 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. Kassel Beispiel: Wenn zusätzlich eine Verbindung Mainz-Mannheim aufgenommen wird, gibt es mehrere Zyklen. KS Marburg 104 Köln 96 K 174 30 MR Gießen 106 FD Fulda 34 GI BN 140 66 104 MZ 41 Bonn 93 F Frankfurt 71 224 88 136 WÜ MA Würzburg Mannheim PInf II 9.580 Lösungen des TSP Bis heute ist keine effiziente Lösung des TSP bekannt. Alle bekannten Lösungen sind von der Art : Allgemeiner AllgemeinerTSP-Algorithmus: TSP-Algorithmus: •• Erzeuge Erzeugealle allemöglichen möglichenTouren; Touren; •• Berechne Berechnedie dieKosten Kostenfür fürjede jedeTour; Tour; •• Wähle Wähledie diekostengünstigsteTour kostengünstigsteTouraus. 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 TSP Eine 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ängenSchranke MinWegLen bleibt, wird der Weg bei k' mit Tiefe t+1 fortgesetzt (rekursiver Aufruf). PInf II 9.610 TSP-Algorithmus: Erläuterung (2) • 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 von MinWeg (MinWegLen) verglichen. Falls er kürzer ist, wird er zum neuen MinWeg 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. PInf II 9.620 TSP-Algorithmus: Programmgerüst void voidbesuche besuche(int (intk,k,int int[ [] ]weg, weg,int inttiefe, tiefe,int int[ [] ]marken, marken,int intziel) ziel){{ tiefe += 1; weg [tiefe] = k; tiefe += 1; weg [tiefe] = k; marken[k] marken[k]+= +=1; 1;int intbishWegLen bishWegLen==0; 0; ifif(tiefe (tiefe== ==0) 0)aktWegLen aktWegLen=0 =0 ////Vorbesetzung Vorbesetzung else else{bishWegLen {bishWegLen==aktWegLen; aktWegLen; aktWegLen aktWegLen+= +=kantenW kantenW(weg[tiefe-1], (weg[tiefe-1],k);} k);} ifif(aktWegLen < minWegLen) // abbrechen, (aktWegLen < minWegLen) // abbrechen,falls fallskeine keineVerbesserung Verbesserung ifif(testeWeg() { // Weg ist eine TSP-Tour) (testeWeg() { // Weg ist eine TSP-Tour) int int[ [] ]minWeg minWeg == ...... ////Kopie Kopievon vonweg wegbis biszur zurPosition Positiontiefe; tiefe; minWegLen minWegLen==aktWegLen; aktWegLen;}} else ////Weg else Wegist istkeine keineTSP-Tour TSP-Tour for ////alle for.... alleNachbarknoten Nachbarknotenj jvon vonkkmit mitMarken Marken[j][j]<<maxB maxB besuche besuche(j, (j,weg, weg,tiefe, tiefe,marken, marken,ziel); ziel); ////ininallen allenanderen anderenFällen FällenBesuch Besuchzurücknehmen: zurücknehmen: aktWegLen aktWegLen==bishWegLen; bishWegLen;marken marken[k] [k]-=1; -=1; }} Aufruf: besuche besuche(u, (u,weg, weg,-1, -1,marken, marken,v)v) PInf II 9.630 Klassenerweiterung für TSP-Implementierung 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. class class TSPGraph TSPGraph extends extends BGraph BGraph {{ private int schranke ; // private int schranke ; // Abschätzung Abschätzung für für Weglänge Weglänge private int minWegLen ; // Länge d.bish.minimalen private int minWegLen ; // Länge d.bish.minimalen Weges Weges private private final final int int maxB maxB == ...; ...; /* /* Maximalzahl Maximalzahl von von Besuchen Besuchen pro pro Knoten, Knoten, z.B. z.B. maxB maxB == 22 */ */ private int aktWegLen; // Länge des aktuellen private int aktWegLen; // Länge des aktuellen Weges Weges private /* private int int maxKnBes; maxKnBes; /* Schranke Schranke für für die die Anzahl Anzahl besuchter Knoten */ besuchter Knoten */ private // private int[] int[] minWeg; minWeg; // Bisher Bisher minimaler minimaler Weg Weg void void TSPSuche TSPSuche (int (int st, st, int int zi) zi) {{ // // siehe siehe nächste nächste Folie Folie }} ... ... }} PInf II 9.640 TSPSuche: Java-Programm void void TSPSuche TSPSuche (int (int st, st, int int zi) zi) {{ /* /* sucht sucht minimale minimale TSP-Tour TSP-Tour von st nach zi */ von st nach zi */ minWegLen minWegLen == schranke; schranke; // // Abschätzung Abschätzung nach nach oben oben maxKnBes = ... ; /* geeigneter Wert maxKnBes = ... ; /* geeigneter Wert >> knAnz, knAnz, z.B. z.B. (knAnz*maxB) +1 */ (knAnz*maxB) +1 */ weg weg == new new int[maxKnBes]; int[maxKnBes]; marken marken == new new int[knAnz]; int[knAnz]; for for (int (int kk == 0; 0; kk << knAnz; knAnz; k++) k++) marken[k] marken[k] == 0; 0; besuche (st, weg, -1, marken, zi); besuche (st, weg, -1, marken, zi); if if (minWegLen (minWegLen == == schranke) schranke) System.out.println System.out.println ("Kein ("Kein Weg Weg gefunden!"); gefunden!"); else { else { System.out.println System.out.println ("Länge ("Länge des des kürzesten kürzesten Weges: Weges: "" ++ minWegLen); minWegLen); System.out.println System.out.println ("Besuchte ("Besuchte Knoten: Knoten: "); "); for (int k = 0; k < minWeg.length; for (int k = 0; k < minWeg.length; k++) k++) System.out.println System.out.println (knoBez[minWeg[k]]); (knoBez[minWeg[k]]); }} }} Operation besuche PInf II 9.650 private private void void besuche besuche (int (int k, k, int[] int[] weg, weg, int int tiefe, tiefe, int[] int[] marken, int zi) { /* besucht Knoten k mit geg. marken, int zi) { /* besucht Knoten k mit geg. Weg, Weg, Tiefe, Tiefe, Markierung Markierung und und Ziel Ziel */ */ tiefe ++; tiefe ++; if if (tiefe (tiefe >= >= maxKnBes) maxKnBes) {{ System.out.println System.out.println ("Zulässige ("Zulässige Knotenzahl Knotenzahl im im Weg Weg überschritten."); }; überschritten."); return; return; }; weg[tiefe] weg[tiefe] == k; k; marken[k] marken[k] ++; ++; int int bishWegLen bishWegLen == 0; 0; if (tiefe // if (tiefe == == 0) 0) aktWegLen aktWegLen == 0; 0; // Vorbesetzung Vorbesetzung else { else { bishWegLen bishWegLen == aktWegLen; aktWegLen; aktWegLen aktWegLen += += kantenW kantenW (weg[tiefe-1], (weg[tiefe-1], k); k); }} if if (aktWegLen (aktWegLen << minWegLen) minWegLen) if if (testeWeg(weg, (testeWeg(weg, tiefe, tiefe, marken, marken, zi)) zi)) {{ // // ist ist TSP-Tour TSP-Tour minWeg = new int[tiefe + 1]; minWeg = new int[tiefe + 1]; System.arraycopy System.arraycopy (weg, (weg, 0, 0, minWeg, minWeg, 0, 0, tiefe tiefe ++ 1) 1) ;; minWegLen minWegLen == aktWegLen; aktWegLen; }} else // siehe else // siehe nächste nächste Folie Folie PInf II 9.660 Hilfsoperationen für TSP (Forts.) else // else // Weg Weg ist ist keine keine TSP-Tour TSP-Tour for for (int (int jj == 0; 0; jj << knAnz; knAnz; j++) j++) //für //für alle alle Nachbarn Nachbarn jj von von kk mit mit Marken[i] Marken[i] << maxB maxB if if (k (k != != jj && && kantenW kantenW (k, (k, j) j) << M) M) if if (marken[j] (marken[j] << maxB) maxB) besuche besuche (j, (j, weg, weg, tiefe, tiefe, marken, marken, zi); zi); // // in in allen allen anderen anderen Fällen Fällen Besuch Besuch zurücknehmen: zurücknehmen: aktWegLen aktWegLen == bishWegLen; bishWegLen; weg[tiefe] weg[tiefe] == -1; -1; marken[k] marken[k] --; --; }} private private boolean boolean testeWeg testeWeg (int[] (int[] weg, weg, int int tiefe, tiefe, int[] int[] marken, marken, int int zi) zi) {{ // // prüft prüft gegebenen gegebenen Weg Weg auf auf TSP-Tour TSP-Tour if if (tiefe (tiefe << knAnz-1 knAnz-1 |||| weg[tiefe] weg[tiefe] != != zi) zi) return return false; false; for (int k = 0; k < knAnz; k++) for (int k = 0; k < knAnz; k++) if if (marken[k] (marken[k] == == 0) 0) return return false; false; return return true; true; }} TSP von San Francisco nach San Rafael PInf II 9.670 Länge des kürzesten Weges: 342 Besuchte Knoten: San Francisco Pacifica Half Moon Bay Eine EineLösung Lösungdes desTSP TSPvon vonSan SanFrancisco Francisconach nach San Mateo San Rafael ist mit einem einfachen Weg möglich. San Rafael ist mit einem einfachen Weg möglich. Palo Alto Santa Clara Laufzeit Laufzeit(auf (auf33MHZ 33MHZ486 486PC) PC)<<11sec sec Scotts Valley Santa Cruz Watsonville San Jose Fremont Hayward Oakland Richmond San Rafael TSP von San Francisco nach San Mateo PInf II 9.680 Länge des kürzesten Weges: 364. Besuchte Knoten: San Francisco San Rafael Richmond Eine EineLösung Lösungdes desTSP TSPvon vonSan SanFrancisco Francisconach nach Oakland San Mateo gelingt nur mit Mehrfachbesuchen. San Mateo gelingt nur mit Mehrfachbesuchen. San Francisco Pacifica Laufzeit Laufzeit(auf (auf33MHZ 33MHZ486 486PC) PC)ca. ca.200 200sec sec Half Moon Bay Santa Cruz Scotts Valley Santa Cruz Watsonville San Jose Santa Clara Palo Alto Fremont Hayward San Mateo