6.2 Zusammenhang, Kreise finden, Tiefensuche, Breitensuche
In diesem Teilkapitel sind alle Graphen ungerichtet.
Der erste algorithmische Problem, das wir untersuchen werden, ist Graphenzusammenhang.
Algorithmisches Problem 6.2.2
(
Wir haben dies als ein Entscheidungsproblem formuliert, also als Ja/Nein-Frage. In der Komplexitätstheorie untersuchen wir vorwiegend solche Entscheidungsprobleme. In der Praxis wollen wir natürlich nicht nur eine Ja/Nein-Antwort, sondern in diesem konkreten Fall auch den Pfad, sollte er denn existieren.
Wir lösen
def depthFirstSearch(u):
if marked[u]: return
else:
marked[u] = true
for v in neighbors(u):
depthFirstSearch(v)
marked
als [false, ..., false]
initialisieren und depthFirstSearch(u)
aufrufen,
dann terminiert der Aufruf; nach Terminierung ist
marked[v] = true
genau dann, wenn es einen Pfad von
u
nach v
gibt.
In Graph.java finden Sie eine einfache Graphen-Klasse. Meine Implementierung von Tiefensuche finden Sie in UndirectedConnectivity.java.
Datenformat. Das Programm UndirectedConnectivity.java liest die Daten in folgendem Format ein:
n m # n ist die Anzahl der Knoten, m ist die Anzahl der Kanten u_1 v_1 # die erste Kante; die Reihenfolge spielt keine Rolle; v_1 u_1 funktioniert also auch u_2 v_2 # die zweite Kante ... u_m v_m # die letzte Kante s # Startknoten, an welchem die Tiefensuche starten soll
input-01.txt
/ output-01.txt
und vergleichen Sie meinen
Output mit dem von Ihnen per Hand geschriebenen "korrekten" Output.
(Beachten Sie, dass die Meldungen please enter n:
etc. von
meinem Programm auf System.err
gedruckt werden; wenn Sie
java UndirectedConnectivity < input-xx.txt > output-dominik-xx.txt
machen,
dann wird output-dominik-xx.txt
nur den "echten" Output
enthalten, nicht aber die Meldungen).
Im folgenden werde ich Sie bitten, aufbauend auf meinem Code (oder
auch from scratch, wenn Sie wollen), Algorithmen für mehrere Probleme
zu implementieren.
Schreiben Sie zu jedem Problem auch ein paar Test-Cases.
Alle Algorithmen sollten Laufzeit
diff
mit meiner oder anderen vergleichen kann.
Tip. Nutzen Sie mein im Moment sehr rudimentäres Zeichenprogramm, um Graphen zu zeichnen und in das oben beschriebene Format zu exportieren:

Übungsaufgabe 6.2.3
Erweitern Sie meine Datei UndirectedConnectivity,
so dass es nicht eine True/False-Tabelle ausgibt, sondern neben dem Startknoten
java UndirectedConnectivity
5 6 4 0 0 1 0 2 1 2 1 3 2 3 4 3yes
4
0
1
3
Cyclicity
, das testet, ob der gegebene Graph
einen Kreis enthält.
Input. Ein Graph in dem oben definierten Format.
Output. Eine einzelne Zeile, die entweder
das Wort cyclic
oder acyclic
enthält.
Die Zusammenhangskomponente von
Input. Ein Graph im obigen Format.
Output.
Beispiel-Input (für den Graphen
):
6 4 4 0 1 3 1 2 2 3
Beispiel-Output:
0 0 1 1 2 1 3 1 4 0 5 5
Input. Ein Graph s t
, die zwei
int
enthält.
Output.
Falls es keinen Pfad in false
in einer
einzelnen Zeile. Falls es einen Pfad gibt:
ein solcher Pfad, beginnend mit
Tiefensuche ohne Rekursion
Viele Laufzeitumgebungen stürzen ab, wenn der Rekursionsstack eine gewisse Höhe überschreitet.
Demo. Kompilieren
Sie das Programm
LongPath.java und führen es aus.
Es erzeugt einen Pfad mit
Alternativ können wir den Rekursionsstack "selbst" unterhalten, also als Stack-Datenstruktur, die dann aber auf dem Laufzeit-Heap liegt. Der Pseudocode sieht dann so aus:
def depthFirstSearchNonRecursive(s):
stack = new Stack();
visited = [false, ..., false]
stack.push(s)
visited[s] = true
while (stack.size() > 0):
u = stack.pop()
for v in neighbors(u):
if (!visited[v]):
visited[v] = true
stack.push(v)
return visited
Breitensuche: kürzeste Wege finden
Tiefensuche erlaubt uns, zu erkennen, ob es einen Pfad
von queue
und ein Array visited
, das uns anzeigt, ob
ein Knoten bereits einmal in der Warteschlange war.
def breadthFirstSearch(s):
queue = new Queue();
visited = [false, ..., false]
queue.push(s)
visited[s] = true
while (queue.size() > 0):
u = queue.pop()
for v in neighbors(u):
if (!visited[v]):
visited[v] = true
queue.push(v)
return visited
Lemma 6.2.5
Nachdem breadthFirstSearch
terminiert hat,
ist visited[u] = true
genau dann, wenn es einen Pfad
von s
nach u
gibt.
Darüberhinaus gilt: wenn Zeilen 10 und 11 durchlaufen werden,
dann gilt
Meine Java-Implementierung finden Sie in BreadthFirstSearch.java.
Input. Ein Graph im obigen Format, gefolgt von
einer Zeile, die ein einzelnes int
enthält: den Knoten
5 6 0 1 0 2 0 4 1 2 1 3 2 3 4
Output. int
enthalten. Die
0 1 1 2 2 2 3 3 4 0Solle es keinen Pfad von
Mit etwas zusätzlicher "Buchhaltung" können wir uns merken, über welche Pfade ein Knoten erreicht worden ist:
Übungsaufgabe 6.2.8
Passen Sie meine Implementierung der Breitensuche so an, dass nach Eingabe
des Graphen und zweier Knoten