Datenstrukturen und Algorithmen Musterlösung zu Heimübungsblatt 9 Sebastian Kniesburges Aufgabe 31 (a) Eine topologische Sortierung ist 5, 2, 1, 6, 8, 7, 4, 3 oder auch 5, 6, 8, 7, 2, 1, 4, 3 (b) Die topologische Sortierung wird nur für DAGs definiert, da für allgemeine gerichtete oder ungerichtete Graphen kein vernünftiger Begriff existiert. Die Knoten eines Graphen können bei Kreisen oder ungerichteten Kanten in keine konsistente Ordnung gebracht werden. Die topologische Sortierung ist als lineare Ordnung der Knoten definiert, sodass u vor v in der Sortierung steht, falls die Kante (u, v) existiert. Bei ungerichteten Graphen müsste also u vor v und v vor u stehen. Gleiches gilt für Kreise in gerichteten Graphen. (c) entspricht Lemma 36. ⇒ Es existiert eine Rückwärtskante (u, v). Dann ist v ein Ahne von u, das heißt es existiert ein Weg von v nach u und die Kante (u, v) schließt den Kreis. ⇐ Der Graph G enthält einen Kreis c. Sei v der erste von DF S entdeckte Knoten in c und (u, v) die vorherige Kanten im Kreis c. Da u bisher nicht entdeckt wurde, wird er zu einem Nachfolger von v, damit ist (u, v) eine Rückwärtskante. Also ist ein Graph genau dann zyklisch, wenn DF S eine Rückwärtskante liefert. Entsprechend ist er genau dann azyklisch, wenn DF S keine Rückwärtskante liefert. (d) Nach b) kann der Algorithmus nur auf DAGs angewendet werden. Nach c) liefert DF S für DAGs keine Rückwärtskanten. Also liefert DF S nur Baum-, Kreuzungs- und Vorwärtskanten. Für all die Kanten gilt: Für eine Kante (u, v) gilt f [u] > f [v]. Für eine Baumkante (u, v) gilt: v ist ein Nachfahre von u und somit f [u] > f [v]. Für Kreuzungs- und Vorwärtskanten gilt: v ist schwarz und u ist grau bei der Entdeckung von (u, v). Also f [u] > f [v], da v bereit abgeschlossen wurde. Da für alle Kanten (u, v) gilt f [u] > f [v], liefert der Algorithmus ein korrektes Ergebnis. Aufgabe 32 Gegenbeispiel: Datenstrukturen und Algorithmen Musterlösung zu Heimübungsblatt 9 Sebastian Kniesburges Der Dijkstra Algorithmus liefert: Hierbei entpricht der erste Eintrag eines Knotens der Entfernung und der zweite dem Vorgänger. Der kürzeste Weg von a nach c über b mit Kosten 1 wird also nicht gefunden. Aufgabe 33 Zunächst wird ein Array mit W |V | Einträgen angelegt. Hierbei ist {0, . . . , W |V |} der Wertebereich der Pfadlängen. Die maximale Entfernung eines Knoten zum Statknoten kann nicht größer sein als W |V |. Jedes Array-Element ist ein Zeiger auf eine doppelt verkettete Liste. Ein Knoten wird entsprechend seinem d-Wert, also seiner Distanz zum Startknoten, in die Liste mit dem Index d[v] eingefügt. Der Array wird mit leeren Listen initialisiert. Bis auf die erste Liste, in diese wird der Startknoten eingefügt. Bei der Suche nach dem Minimum wird ausgehend von dem letzten Minimum das Array durchsucht, d.h. das Array wird von Index des letzten Minimums an nach rechts (aufsteigende Indizes) durchsucht. Dies funktioniert deshalb, da durch Relaxation nie ein d-Wert gebildet werden kann der kleiner als der zuletzt selektierte d-Wert ist. Denn alle Kantengewichte sind positiv, sonst wäre der Dijkstra-Algorithmus nicht anwendbar. Die Kosten für die Extraktion der Knoten aus dem Array betragen somit O(W |V |). Denn das Array wird genau einmal durchlaufen. Die Decrease-Key-Operation kann in O(1) durchgeführt werden. Dazu ist ein Pointer-Array nötig der auf Zeiger auf die Knoten enthält. Wird nun der Knoten v relaxiert, so verweist der ZEiger an der Stelle v auf den entsprechenden Knoten. Der Knoten wird aus der Liste mit Index dalt [v] entfernt, indem die Zeiger beim Vorgänger und Nachfolger umgehängt werden und in die Liste dneu [v] eingefügt. Der Datenstrukturen und Algorithmen Musterlösung zu Heimübungsblatt 9 Sebastian Kniesburges Zugriff auf die Liste dneu [v], ist in O(1) möglich, da nur auf das entsprechende Feld des Array zugegriffen werden muss. Auch das Umhängen der Zeiger ist in O(1) möglich. Der Gesamtaufwand beträgt somit O(W |V | + E). Aufgabe 34 Definition Eulerkreis: Sei G = (V, E) ein gerichteter Graph. Ein Kreis C in G heißt Eulerkreis, wenn C jede Kante aus E genau einmal enthält. Einzelne Knoten dürfen in C mehrfach vorkommen. Satz (Euler, Hierholzer) Ein endlicher gerichteter und schwach zusammenhängender Graph G = (V, E) besitzt genau dann einen Eulerkreis, wenn für alle v ∈ V gilt: Indegree(v) = Outdegree(v). Definition Ein endlicher gerichteter Graph ist schwach zusammenhängend, wenn der entsprechende ungerichtete Graph zusammenhängend ist. Satz: Sei C ein Kreis in G, der die Kanten E(C) und die Knoten V (C) enthält. Der 0 Restgraph G = (V, EE(C)) enthält genau dann noch Kanten, wenn es einen Knoten 0 u ∈ V (C) gibt, von dem noch Kanten in G ausgehen. Idee: Zunächst wird mit Hilfe der Eigenschaft aus dem Satz von Euler bzw. Hierholzer (Indegree(v) = Outdegree(v) für alle v ∈ V ) überprüft, ob der Graph einen Eulerkreis enthält. Dies in Zeit O(|V | + |E|) möglich bei gegebener Adjazenzlistendarstellung des Graphen. Wir bestimmen ausgehend von einem Knoten v0 einen Kreis C = (v0 → v1 . . . → vk ). Anschließend durchlaufen wir den Kreis C erneut und prüfen, ob es einen Knoten in V (C) gibt, von dem noch Kanten aus E E(C) starten. Sei vi der erste solche Knoten in V (C). Wir konstruieren nun ausgehend von vi solange kantendisjunkte Kreise, bis in vi keine unbenutzte Kanten mehr starten. Alle diese Kreise fügen wir in den Kreis C nach dem Knoten vi ein. Danach setzen wir unseren Durchlauf des aktualisierten Kreises C an der Stelle vi fort und suchen den nächsten Knoten von dem noch Kanten aus E E(C) starten. ERFORSCHE(G, v, current) konstruiert ausgehend von v einen Kreis K aus noch unbenutzten Kanten. current sei dabei ein Array von Zeigern auf die aktuellen Listeneinträge in den Adjazenzlisten. ERFORSCHE(G, v, current) 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: u←current[v] current[v]←next[current[v]] K←(v,u) while u 6= v do w←current[u] current[u]←next[current[u]] K←K∪(u→w) u←w end while return K EULER(G) konstruiert in Linearzeit einen Eulerkreis. EULER(G) 1: for all v ∈ V do Datenstrukturen und Algorithmen Musterlösung zu Heimübungsblatt 9 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: Sebastian Kniesburges current[v]←head[ADJ[v]] end for C←(v0 ) v←v0 repeat while current[v] 6= Nil do K←ERFORSCHE(G, v, current) Füge K in C an der Stelle v ein end while v← v0 mit v0 folgt v in C until v = v0 return C Laufzeit Jede Kante des Graphen wird genau einmal durch einen Aufruf von ERFORSCHE in einen Kreis aufgenommen und danach aus dem Graphen gelöscht“, d.h. mit Hilfe des ” current-Zeiger als benutzt markiert. Somit ist der gesamte Aufwand für alle Aufrufe von ERFORSCHE und damit auch für alle Durchläufe der While-Schleife in der Größenordnung O(|E|). Da der letztlich konstruierte Eulerkreis genau |E| + 1 Knoten enthält, finden insgesamt auch nur O(|E|) Durchläufe der Repeat-Until-Schleife statt. Die Initialisierung ist in O(|V |) durchführbar. Insgesamt ergibt sich daher eine Laufzeit von O(|V | + |E|).