Robert Elsässer u.v.a. Paderborn, den 25. Juni 2009 Beispiellösung zu den Übungen Datenstrukturen und Algorithmen SS 2009 Blatt 10 AUFGABE 1: Zunächst einmal kann festgestellt werden, dass sich die Laufzeitschranke auch wie folgt ausdrücken lässt: O(|V | · |V | + |E|2 ) = O(|E| · (|V | + |E|)). Um zu überprüfen ob ein ungerichteter Graph G = (V, E) eine Brückenkante enthält, müssen wir für jede Kante aus e ∈ E überprüfen, ob der Graph G0 = (V, E \ {e}) noch zusammenhängend ist. Ist G0 nicht mehr zusammenhängend, war e eine Brückenkante. Der Algorithmus HasBride(G) bekommt als Eingabe einen ungerichteten Graphen G und liefert false zurück, falls G keine Brückenkante hat, sonst true. 1 for each e ∈ E do 2 G0 = (V, E \ {e}) 3 BF S(G0 , v ∈ V ) 4 if G0 nicht mehr zusammenhängend 5 then return true 6 else return false Zur Laufzeit: Die For-Schleife wird höchstens |E| mal durchlaufen. In jeder Iteration wird eine Breitensuche auf einem Teilgraphen G0 von G ausgeführt mit einer bekannten Laufzeit von O(|V | + |E|). Die Zeilen (4)-(6) können in konstanter Zeit erledigt werden. Insgesamt ergibt sich also folgende Laufzeit für HasBride(G): O(|E| · (|V | + |E|)) = O(|V | · |V | + |E|2 ). Zur Korrektheit: Fallunterscheidung • Sei G = (V, E) ein ungerichteter Graph mit einer Brückenkante a ∈ E. a wird in einer Iteration der for-Schleife aus G entfernt, d.h. der Graph G0a hat mindestens zwei Zusammenhangskomponenten. Die Breitensuche auf G0a liefert, dass G0a nicht zusammenhängend ist. HasBride(G) liefert true zurück, was korrekt ist. • Sei G = (V, E) ein ungerichteter Graph ohne Brückenkante. Graph G0a besteht für alle a ∈ E immer aus genau einer Zusammenhangskomponente, was durch die Breitensuche überprüft wird. HasBride(G) liefert false zurück, was korrekt ist. AUFGABE 2: a) Die zu G zugehörige Adjazenzmatrix sieht wie folgt aus: Der Graph sieht wie folgt aus: Tabelle 1: Adjazenzmatrix zum Graphen G s a b c d e f s 10 5 5 a 2 1 b 3 5 5 6 c 3 5 2 d 1 1 e 1 1 f 2 4 Abbildung 1: Gerichteter, gewichteter Graph G = (V, E) V \S d[v] a 10 V \S d[v] b c d 5 ∞ ∞ a c d e 8 10 ∞ ∞ V \S d[v] V \S d[v] V \S d[v] e f S = {s}, δ(s, s) = 0 ∞ 5 f S = {s, b}, δ(s, b) = 5 5 a c d e S = {s, b, f }, δ(s, f ) = 5 8 10 ∞ 9 c d 10 ∞ e S = {s, b, f, a}, δ(s, a) = 8 9 c d S = {s, b, f, a, e}, δ(s, e) = 9 10 ∞ V \S d[v] d S = {s, b, f, a, e, c}, δ(s, c) = 10 15 S = {s, b, f, a, e, c, d}, δ(s, d) = 15 b) c) Um das Problem Single Source Shortest Path in Zeit O(|V | + |E|) für einen gerichteten, gewichteten Graphen zu lösen, wobei alle Kanten e ∈ E das Gewicht 1 haben, kann die Breitensuche angewendet werden. Input: Ein gewichteter, ungerichteter Graph und ∀e ∈ E: gewicht(e) = 1 und einen Startknoten s ∈ V . Output: Der kürzeste Weg von s zu allen anderen Knoten v ∈ V . Ausgehend von einem Startknoten s werden in der Breitensuche zunächst alle, zu s adjazenten Knoten, entdeckt, wobei jeder dieser adjazenten Knoten die Distanz 1 hat. Zu diesen Knoten wurde also bereits ein kürzester Weg gefunden. Anschließend werden die Nachbarn, der Knoten mit Distanz 1, entdeckt. Zu diesen wurde ebenfalls ein kürzester Weg gefunden, denn diese Knoten sind nicht adjazent zu s und die Distanz zu s ist mit 2 minimal für einen nicht-adjazenten Knoten. So werden im Laufe der Ausführung der Breitensuche zu allen verbleibenden Knoten v ∈ V kürzeste Wege gefunden. Die Laufzeit der Breitensuche ist mit O(|V | + |E|) bekannt und somit können in einem gerichteten, gewichteten Graphen mit Kanten, deren Gewicht genau 1 ist, kürzeste Wege in Zeit O(|V | + |E|) berechnet werden. AUFGABE 3: Grundsätzlich gibt es zwei Möglichkeiten die Aufgabe zu lösen. Es kann entweder ein Algorithmus entworfen werden, der über alle Knoten v ∈ V iteriert und überprüft ob für jeden Knoten gilt: Eingangsgrad(v) =Ausgangsgrad(v). Bei diesem Algorithmus sollte allerdings zur Korrektheit bewiesen werden, dass in einem gerichteten, stark zusammenhängenden Graphen genau dann ein Eulerkreis existiert, wenn für alle Knoten Eingangsgrad(v) =Ausgangsgrad(v). Alternativ kann ein Algorithmus entworfen werden, der den Graphen traversiert und nach einfachen (kantendisjunkten) Kreisen sucht, also Kreisen im Graphen G, die keine gemeinsamen Kanten haben. 1) Input: G = (V, E) ein endlicher, gerichteter Graph. Output: GraphContainsEulerCircle“, wenn G einen Eulerkreis enthält, GraphContains” ” NoEulerCircle“ sonst. 1 Strongly − Connected − Components(G) 2 if G consists of more than one strongly connected component 3 then return GraphContainsN oEulerCircle“ ” 4 else 5 for for each v ∈ V do 6 if Indegree(v) ! = Outdegree(v) 7 then return GraphContainsN oEulerCircle“ ” 8 else 9 return GraphContainsEulerCircle“ ” Zur Korrektheit: In einem endlichen, gerichteten, stark zusammenhängenden Graphen G = (V, E) existiert genau dann ein Eulerkreis, wenn für alle v ∈ V gilt: Eingangsgrad(v) =Ausgangsgrad(v). Proof: ⇒“ Wir nennen einen Kreis in G einfach, wenn beim Durchlaufen des Kreies jeder ” Knoten nur einmal besucht wird. Wir nennen einen Kreis komplex, wenn ein Knoten beim Durchlauf des Kreises mehrfach besucht wird. In einem einfachen Kreis C gilt für jeden Knoten v ∈ C, dass Eingangsgrad(v)=Ausgangsgrad(v) = 1. Jeder komplexe Kreis kann als Vereinigung von einfachen Kreisen ausgedrückt werden. Das impliziert, dass jeder Knoten in einem komplexen Kreis (insbesondere in einem Eulerkreis) einen Eingangsgrad haben muss, der gleich dem Ausgangsgrad ist. Wenn ein Graph also einen Eulerkreis hat, so muss jeder Knoten gleichen Ein- und Ausgangsgrad haben. Proof: ⇐“ Angenommen wir haben einen zusammenhängenden Graphen G = (V, E) und ” für alle v ∈ V gilt Ausgangsgrad(v) =Eingangsgrad(v). Es sei weiterhin K der längste komplexe Kreis in G. Wenn K kein Eulerkreis ist, dann gibt es einen Knoten v ∈ G, der in K enthalten ist, aber noch Kanten hat, die nicht zu K gehören. Wir konstruieren nun einen Kreis K 0 in G − K, der mit v beginnt und sich wieder schließt, indem wir G − K traversieren. Da der Graph stark zusammenhängend ist und für alle Knoten, deren Kanten noch nicht zu K gehören gilt, dass Ausgangsgrad=Eingangsgrad, muss es so einen Kreis geben. Somit gibt es aber einen noch größeren komplexen Kreis K 00 der aus der Vereinigung von K und K 0 konstruiert werden kann. Dies widerspricht der Voraussetzung, dass K der längste komplexe Kreis in G ist und somit muss K ein Eulerkreis sein. In den Zeilen (1)-(3) prüft der Algorithmus, ob der Graph aus mehreren starken Zusammenhangskomponenten besteht. Sollte dies der Fall sein (gilt insbeondere für Knoten, die Ausgangsgrad=Eingangsgrad=0 haben) so gibt der Algorithmus korrekter Weise GraphCon” tainsNoEulerCircle“ zurück. Andernfalls wird ab Zeile (4) für jeden Knoten v ∈ V geprüft, ob gilt: Eingangsgrad(v) =Ausgangsgrad(v). Gilt dies für einen Knoten nicht, so kann G keinen Eulerkreis enthalten und der Algorithmus liefert GraphContainsNoEulerCircle“ zurück. ” Andernfalls gilt die Eigenschaft für alle Kanten und der Algorithmus liefert GraphContain” sEulerCircle“ zurück. Wenn man den Pfad des Eulerkreises überdies Ausgeben möchte, muss man sukzessive einfache Kreise in G entdecken und diese an Kreuzenden Knoten miteinander zu einem maximal großen, komplexen Kreis zusammenführen. Hat man alle einfachen Kreise entdeckt und zu einem komplexen Kreis ergänzt, kann man den Pfad dieses komplexen Kreises ablaufen und ausgeben. Dieser Kreis muss, wie schon gezeigt wurde, ein Eulerkreis sein. Zur Laufzeit: Starke Zusammenhangskomponenten können mittels einer modifizierten Tiefensuche in Zeit O(|V | + |E|) berechnet werden (siehe PÜ 10). Die Überprüfung aller Kanten auf die Gleichheit von Ein- und Ausgangsgrad kann mit O(|E|) abgeschätzt werden. Sollte man darüber hinaus auch noch den Pfad berechnen wollen, iteriert man bei der Suche nach einfachen Kreisen genau einmal über alle Knoten und ihre adjazenten Kanten. Somit kann die Pfadausgabe mit O(|V | + |E|) abgeschätzt werden. Insgesamt ergibt sich eine Laufzeit von O(2 · |V | + 3 · |E|) was asymptotisch nicht schlechter ist als O(|V | + |E|). AUFGABE 4: Zu dieser Programmieraufgabe gibt es keine Musterlösung.