finale Version des Vorlesungsskripts - ZIB
finale Version des Vorlesungsskripts - ZIB
finale Version des Vorlesungsskripts - ZIB
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
4.3 Datenstrukturen zur Speicherung von Graphen<br />
mit x inzidente Kante, die noch nicht benutzt wurde. Ist y nicht markiert, dann gehen<br />
wir zu y, markieren y und beginnen von neuem (y ist nun der letzte markierte Knoten).<br />
Wenn die Suche nach Kanten, die mit y inzidieren und die noch nicht benutzt wurden,<br />
beendet ist (d. h. alle Kanten, auf denen y liegt, wurden einmal berührt), kehren wir zu x<br />
zurück und fahren mit der Suche nach unbenutzten Kanten, die mit x inzidieren fort, bis<br />
alle Kanten, die x enthalten, abgearbeitet sind. Diese Methode nennt man Tiefensuche,<br />
da man versucht, einen Knoten so schnell wie möglich zu verlassen und „tiefer“ in den<br />
Graphen einzudringen.<br />
Eine derartige Tiefensuche teilt die Kanten <strong>des</strong> Graphen in zwei Teilmengen auf. Eine<br />
Kante xy heißt Vorwärtskante, falls wir bei der Ausführung <strong>des</strong> Algorithmus von einem<br />
markierten Knoten x entlang xy zum Knoten y gegangen sind und dabei y markiert<br />
haben. Andernfalls heißt xy Rückwärtskante. Man überlegt sich leicht, dass die Menge<br />
der Vorwärtskanten ein Wald von G ist, der in jeder Komponente von G einen aufspannenden<br />
Baum bildet. Ist der Graph zusammenhängend, so nennt man die Menge der<br />
Vorwärtskanten DFS-Baum von G. Mit Hilfe von Adjazenzlisten kann die Tiefensuche<br />
sehr effizient rekursiv implementiert werden.<br />
(4.6) Algorithmus Depth-First-Search.<br />
Eingabe: Graph G = (V, E) in Form einer Adjazenzliste, d. h. für jeden Knoten v ∈ V<br />
ist eine Nachbarliste N(v) gegeben.<br />
Ausgabe: Kantenmenge T (= DFS-Baum, falls G zusammenhängend ist).<br />
1: Alle Knoten v ∈ V seien unmarkiert.<br />
2: Setze T := ∅.<br />
3: for all v ∈ V do<br />
4: Ist v unmarkiert, dann rufe SEARCH(v) auf.<br />
5: end for<br />
6: Gib T aus.<br />
7: procedure SEARCH(v) ⊲ Rekursives Unterprogramm<br />
8: Markiere v.<br />
9: for all w ∈ N(v) do<br />
10: Ist w unmarkiert, setze T := T ∪ {vw} und rufe SEARCH(w) auf.<br />
11: end for<br />
12: end procedure<br />
In Algorithmus (4.6) wird im Hauptprogramm jeder Knoten einmal berührt, und im<br />
Unterprogramm jede Kante genau zweimal. Hinzu kommt die Ausgabe von T . Die Laufzeit<br />
<strong>des</strong> Verfahrens ist also O(|V | + |E|). Diese Laufzeit könnte man niemals bei der<br />
Speicherung von G in einer Adjazenzmatrix erreichen.<br />
Mit Hilfe <strong>des</strong> obigen Verfahrens können wir unser mehrmals zitiertes Problem „Enthält<br />
G einen Kreis?“ lösen. Offensichtlich gilt: G enthält genau dann einen Kreis, wenn<br />
E \ T nicht leer ist. Wir haben somit einen polynomialen Algorithmus zur Lösung <strong>des</strong><br />
Kreisproblems gefunden. Der DFS-Baum, von Algorithmus (4.6) produziert, hat einige<br />
interessante Eigenschaften, die man dazu benutzen kann, eine ganze Reihe von weiteren<br />
Graphenproblemen sehr effizient zu lösen. Der hieran interessierte Leser sei z. B. auf das<br />
73