23.06.2013 Aufrufe

finale Version des Vorlesungsskripts - ZIB

finale Version des Vorlesungsskripts - ZIB

finale Version des Vorlesungsskripts - ZIB

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!