Algorithmen & Datenstrukturen Lösungen P. Fierz / HS 2012/2013 Kapitel 5 Graphen Die Programme sind im Zipfile algorithmen.u5.zip in den Packages u5.graph und u5.util gespeichert. In den folgenden Lösungen steht E (edges) für die Menge der Kanten und V (vertices) für die Menge der Knoten eines Graphen. |E| (resp. |V |) bezeichnen die Anzahl Kanten (resp. Knoten) des Graphen. Aufgabe 5.1 [Gerichtete Graphen] a) Es existieren die azyklischen Pfade (0,3), (0,1,2,3) und (0,1,5,2,3) also 3. b) Es sind die Knoten 0, 1 und 4. c) Es sind die Knoten 1, 2 und 5. d) (1,1), (0,1,5,0), (0,1,2,3,4,5,0), (0,3,4,5,0), (0,3,4,1,5,0), (1,2,3,4,1), (1,5,2,3,4,1) und (2,3,4,5,2). e) Da hat es natürlich einige: (1,1,1), . . . (1,1,1,1,1,1,1,1) (0,1,1,5,0), . . . (0,1,1,1,1,1,5,0) (0,1,5,0,1,5,0), (0,1,1,2,3,4,5,0) (1,5,0,1,2,3,4,1) usw. f) Graphisch in der Abbildung 1-1 dargestellt. Die Tatsache dass hier für alle Knoten nur ein Pfad entsteht ist Zufall! 5-1 Algorithmen & Datenstrukturen Lösungen 7 P. Fierz / HS 2012/2013 7 20 1 35 10 8 7 20 1 2 35 10 8 7 35 0 3 15 22 3 15 22 7 3 15 22 35 10 8 35 10 35 3 15 22 9 5 0 3 3 15 4 10 7 9 5 0 2 8 7 9 20 1 2 8 7 5 7 20 1 2 4 5 7 20 1 3 4 5 5 0 3 4 5 9 5 0 3 10 7 9 5 2 8 7 9 20 1 2 3 22 5 4 5 0 3 15 3 22 5 4 Abbildung 5-1: Kürzeste Pfade nach Dijkstra g) Die Matrix von Floyd sieht folgendermassen aus: | 0 1 2 3 4 5 -+----------------------------------------0| 0,-1 17,4 32,5 5,-1 8,3 24,4 1| 22,5 0,-1 15,5 25,5 28,5 7,-1 2| 44,5 22,4 0,-1 10,-1 13,3 29,4 3| 34,5 12,4 27,5 0,-1 3,-1 19,4 4| 31,5 9,-1 24,5 34,5 0,-1 16,1 5| 15,-1 30,4 8,-1 18,2 21,3 0,-1 Dabei steht die erste Zahl für die Länge des Weges (-1 = kein Pfad) und die zweite Zahl steht für den Pivot, der zum kürzesten Weg geführt hat (-1 = kürzester Pfad ist eine Kante). Aufgabe 5.2 [Ungerichtete Graphen] a) Es sind dies die folgenden Wege: (0,3), (0,1,2,3), (0,1,5,4,3), (0,1,4,3), (0,5,4,3), (0,5,1,4,3) und (0,5,1,2,3). b) Es existiert im Prinzip nur einen solchen Zyklus, der aber in beiden Richtungen durchlaufen werden kann. Also (0,1,2,3,4,5,0) und (0,5,4,3,2,1,0). c) Es sind die Knoten 1, 3 und 5. d) Die Lösung ist in der Abbildung 1-2 dargestellt. 5-2 Algorithmen & Datenstrukturen Lösungen 20 1 1 2 P. Fierz / HS 2012/2013 2 10 35 10 7 7 9 9 5 0 0 3 15 5 3 3 3 22 5 4 5 4 Abbildung 5-2: Minimal spanning tree Aufgabe 5.3 [Graphen und Relationen] 1. Wir müssen zeigen, dass die Relation reflexiv, symetrisch und transitiv ist. reflexiv: Da uSv gilt falls u = v ist diese Aussage trivial. symetrisch: Falls u = v ist die Aussage trivial. Falls u 6= v und uSv so heisst das, dass ein Zyklus existiert, der durch u und v geht. Derselbe Zyklus geht aber natürlich auch durch v und u also glit vSu. transitiv: Falls uSv und vSw gilt, so gibt es einen Zyklus der Form (u, (p1), v, (p2), u) und einen Zyklus der Form (v, (p3), w, (p4), v) (dabei sind (p1) . . . (p4) Pfade im Graph. Wir können nun den Zyklus (u, (p1), v, (p3), w, (p4), v, (p2), u) konstruieren. Dieser Zyklus geht durch u und w also gilt auch uSw. 2. Wir müssen als erstes entscheiden, ob die Induktion über die Knoten oder über die Kanten gemacht werden soll. Im Prinzip ist beides möglich, aber es ist über die Knoten einfacher. Basis: Für n = 1 stimmt die Aussage (2 · 0 = 0). Annahme: Wir nehmen nun an, dass die Aussage für n Knoten gilt. Induktionsschluss: Wir müssen noch zeigen, dass unter der Annahme die Aussage auch für n + 1 Knoten und |E| Kanten gilt. Wir entfernen als erstes den Knoten n + 1 aus dem Graph mit allen Kanten, die zu diesem Knoten führt. Der restliche Graph hat also n Knoten und |E| − Grad(n + 1) Kanten. Für diesen Graphen erhalten wir nach Induktionsannahme n X Grad(k) = 2 · (|E| − Grad(n + 1)) k=1 Falls wir (n+1) wieder dazu nehmen, so existieren im Graphen nun Grad(n + 1) Knoten (≤ n), deren Grad um 1 grösser wird (die Kanten, die zum Knoten n + 1 führen). Wenn wir die Summe nun bis zum Knoten n + 1 führen erhalten wir: n+1 X Grad(k) = 2 · (|E| − Grad(n + 1)) + Grad(n + 1) + Grad(n + 1) = 2 · |E| k=1 5-3 Algorithmen & Datenstrukturen Lösungen P. Fierz / HS 2012/2013 Aufgabe 5.4 [Adjazenzmatrix versus Adjazenzliste] Adjazenzmatrix: a) Es kann mit einem Zugriff auf das Element u, v der Matrix entschieden werden ob die Kante existiert. b) Wir müssen die ganze Zeile des Knotens in der Matrix lesen. Das heisst, wir brauchen |V | Zugriffe. c) Wir müssen die ganze Kolonne des Knotens in der Matrix lesen. Das heisst, wir brauchen |V | Zugriffe. d) Die Prozedur für die Tiefensuche ist nachstehend aufgeführt. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 boolean arc[][] = new boolean[KMAX][KMAX]; boolean visited[] = new boolean[KMAX]; public void search() { for (int i = 0; i < KMAX; i++) visited[i] = false; for (int i = 0; i < KMAX; i++) { if (!visited[i]) { visit(i); } } } private void visit(int k) { visited[k] = true; // Knoten behandeln for (int i = 0; i < KMAX; i++) { if (arc[k][i]) { if (!visited[i]) { visit(i); } } } } Wir brauchen nicht rekursiv zu denken, da die Prozeduren so gestaltet sind, dass die Prozedur visit genau |V | mal aufgerufen wird (für jeden Knoten einmal). Wir müssen also nur die Komplexität von visit mal Anzahl Knoten rechnen. In visit wird immer eine ganze Zeile der Matrix durchlaufen. Das heisst, wir brauchen O(|V | · |V |) Operationen. Adjazenzliste a) Wir müssen im Durchschnitt die Hälfte der Adjazenzliste des Knotens u durchlaufen falls der Knoten v in der Liste ist, sonst die ganze Liste. Wir brauchen im |E| Durchschnitt also |V | Zugriffe. b) Wir müssen die Adjazenzliste des Knotens v durchlaufen. Im Durchschnitt brauchen |E| wir also |V | Zugriffe. 5-4 Algorithmen & Datenstrukturen Lösungen P. Fierz / HS 2012/2013 c) Wir müssen die adjazenzliste aller Knoten lesen um festzustellen, ob der Knoten in der Liste vorkommt. Wir brauchen also ungefähr |V | + |E| Zugriffe. d) Die Prozedur für die Tiefensuche ist nachstehend aufgeführt. 1 int adjList[][] = new int[KMAX][]; 2 boolean visited[] = new boolean[KMAX]; 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public void search() { for (int i = 0; i < KMAX; i++) visited[i] = false; for (int i = 0; i < KMAX; i++) { if (!visited[i]) { visit(i); } } } private void visit(int k) { visited[k] = true; // Knoten behandeln for (int i = 0; i < adjList[k].length; i++) { if (!visited[adjList[k][i]]) { visit(adjList[k][i]); } } } Auch wenn wir einen Array von adjazenzlisten haben, können wir feststellen, dass die Prozedur visit genau |V | mal aufgerufen wird. In der Prozedur visit werden aber nur die effektiven Kanten die vom Knoten u ausgehen getestet. Wir nennen diese Zahl mu . Wir können also sagen, dass dieser Loop X mu u∈V mal durchlaufen wird. Dies ist aber gerade |E|. O(|E| + |V |) Aufgabe 5.5 [Implementation von Graphen] Die Aufgabe ist in den folgenden java-Files geloest: Interfaces: Link.java und Linkable.java Implementationen: GraphNode.java bzw. GraphLink.java Abstracte Klassen: GeneralNet.java und Graph.java Konkrete Graphen: DirectedGraph.java und UndirectedGraph.java Kleine Anwendung: GraphTest.java Aufgabe 5.6 [Tiefensuche] In Graph.java implementiert. Aufgabe 5.7 [Finden von Zyklen] In DirectedGraph.java implementiert. 5-5 Algorithmen & Datenstrukturen Lösungen P. Fierz / HS 2012/2013 Aufgabe 5.8 [Topologisches Sortieren] In DirectedGraph.java implementiert. Aufgabe 5.9 [Floyd] In Graph.java implementiert. Aufgabe 5.10 [Flussgraphen] a) In der Figur 1-3 ist der Schnitt eingezeichnet. Wir müssen also die Summe der Kapazitäten der Vorwärtskanten bestimmen. Die Vorwärtskanten des Schnitts sind die Kanten 1 7→ 2, q 7→ s und 3 7→ 2. c(X ) = 27 0/14 1 0/12 2 0/10 0/8 0/5 0/9 0/5 q s 0/3 0/7 0/10 3 4 X Abbildung 5-3: Der Schnitt X = ({q, 1, 3}, {2, 4, s}) b) In der Abbildung 1-4 ist die Lösung dargestellt. Der maximale Fluss ist |f | = 15. c) In der Figur 1-4 ist auch ein minimaler Schnitt X = ({q, 1, 2, 3}, {4, s}) angegeben. Aufgabe 5.11 [Flussgraph und Kapazität] a) Offensichtlich muss die Kapazität einer Kante erhöht werden, die auf einem minimalen Schnitt liegt. Falls eine andere Kante verändert wird, so bleibt der Flaschenhals bestehen. Algorithmus: (a) Finden eines minimalen Schnitts X für den Flussgraphen. (b) Für jede Kante et auf den minimalen Schnitt: Erhöhen der Kapazität der Kante et auf ∞ und berechnen des maximalen Flusses fet . (c) Bestimmen des Maximums aller gefundenen Flüsse fe′t . Die Kapazität der Kante e′t muss vergrössert werden. 5-6 Algorithmen & Datenstrukturen Lösungen 0/14 1 0/12 0/10 0/8 0/5 0/12 0/9 0/5 2 0/10 0/8 0/5 q 0/14 1 2 P. Fierz / HS 2012/2013 0/9 5/5 q s s 0/3 0/3 0/7 0/7 0/10 3 0/14 1 0/12 0/10 4 3 2 1 7/10 7/8 0/5 3/12 5/5 10/10 0/9 5/5 q s 2 7/8 0/5 0/9 q 3/14 4 s 0/3 0/3 7/7 7/7 0/10 3 0/10 4 3 4 Abbildung 5-4: Maximaler Fluss im Flussgraph N b) Nein. Wenn im Graph zwei verschiedene minimale Schnitte existieren, die keine gemeinsame Kanten besitzen, so kann der maximale Fluss nicht vergrössert werden, indem die Kapazität von nur einer Kante erhöht wird. In der Figur 1-5 sind die beiden Schnitte X1 und X2 minimal (15) und haben keine gemeinsamen Kanten. Offensichtlich müssen die Kapazität von mindestens 2 Kanten erhöht werden um den maximalen Fluss zu vergrössern. 1 5 5 q s 1o 10 2 X 1 X 2 Abbildung 5-5: Flussgraph mit zwei minimalen Schnitten ohne gemeinsame Kante 5-7