6. Graphen
6.4 Topologisches Sortieren
Falls
Beweis. Wir müssen zwei Richtungen zeigen; erstens, falls
Die erste Richtung ist einfacher. Wenn
Die andere Richtung ist anstrengender. Nehmen wir an,
Beweis. Wir beschreiben einen Algorithmus, der entweder (1) einen Senkenknoten findet oder (2) einen Kreis.
- Nimm einen beliebigen Knoten
und initialisiere einen Pfad . Setze - while
mit : - füge
hinten an an und nenne es
In jedem Schritt wächst (\P\) um 1; es ist sicherlich ein Pfad, denn
Mit der gleichen Technik können Sie beweisen, dass jeder DAG einen Quellknoten hat: Sie
müssen einfach
den Pfad
, die leere Liste- while
noch Knoten besitzt: - finde einen Quellknoten
in - lösche
und alle angrenzenden Kanten aus - hänge
hinten an die Liste. - return
Stellen wir noch kurz sicher, dass die Liste
Ein Algorithmus für topologisches Sortieren
Wir haben oben nicht nur bewiesen, dass jeder DAG eine topologische Sortierung hat; wir haben auch einen Algorithmus angegeben, der eine solche findet. Allerdings ist dieser nicht besonders effizient, wenn wir ihn naiv implementieren; wie zum Beispiel finden wir einen Quellknoten schnell? Wenn wir hierfür
Wenn
- Legen eine Liste
currentIndeg = [indeg(u) for u in V]
an, die in Rein-Grade im derzeitigen Graphen repräsentiert (erinnern Sie sich: wir "verstümmeln" den Graphen, indem wir Knoten löschen; jetzt simulieren wir das durch eine Liste von Rein-Graden. Sammle alle Quellknoten:
- while currentSources not empty:
- Nimm einen Knoten
aus der Liste currentSources und entferne ihn aus ihr. for v in outNeighbors(u)
currentIndeg[v]--
if currentIndeg[v] == 0: add v to currentSources
- Nimm einen Knoten
currentSources = [u in V if indeg(u) == 0]
Dieser Algorithmus ist schneller, weil wir das Löschen nur simulieren, indem wir
die Rein-Grade immer aktuell halten. Schritt 1 braucht
Topologisches Sortieren mit Tiefensuche
Ich werde Ihnen nun einen weiteren Algorithmus vorstellen, der in linearer Zeit eine topologische Sortierung findet. Dieser hat zwei Vorteile. Erstens ist er vom Code her einfacher. Zweitens wird er später Baustein für einen nichttrivialen Algorithmus sein, der die starken Zusammenhangskomponenten (werden wir später erklären) eines Digraphs bestimmt.
def topSortDFS(G):
L = [] das wird unsere topologisch sortierte Liste
visited = [False, ..., False] Markierung für die Tiefensuche
for u in V:
dfsAndPrint(u)
def dfsAndPrint(u):
if visited[u]:
return
else :
visited[u] = True
for v in outNeighbors(u):
dfsAndPrint(v)
L.append(u)
return L
Schauen wir uns ein Beispiel an.
Die ganze Animation finden Sie auch in topsort.pdf. Hier noch einmal das Endergebnis.
Wir sehen in der Mitte die verschiedenen DFS-Bäume. Nennen wir diese mal
- Wenn
im gleichen Baum vorkommen und ein Vorfahre von ist, dann gibt es in einen Pfad von nach (den Pfad sehen Sie ja in selbst); -
aber nicht von
nach (sonst hätte einen Kreis); -
der Knoten
kommt später in als ; dies folgt direkt aus dem Code, weildfsAndPrint(u)
den Knoten erst am Schluß an anfügt, wenn alle rekursiven Aufrufe (zu denen ja auchdfsAndPrint(v)
gehört), abgearbeitet sind. -
Wenn
in liegt und in einem weiter rechts gelegenen , also , dann gibt es keinen Pfad von nach .
Dies ist nicht so offensichtlich wie Punkte 1, 2, 3. Nehmen wir an, es gäbe einen
solchen Pfad, also
dfsAndPrint
aufgerufen worden ist; zu dem Zeitpunkt, wo
dfsAndPrint(w)
aufgerufen wird, sind alle Knoten auf dem Teilpfad dfsAndPrint(w)
später als dfsAndPrint(u)
aufgerufen worden sein. Ein Widerspruch, weil wir ja angenommen haben,
Einfacher, aber nicht ganz formal korrekt ausgedrückt: gäbe es einen Pfad
von
steht links von . Dann steht sicherlich links von in der Output-Liste . . Dieser Fall ist schwieriger. Halten wir fest, dass kein Vorfahre von ist (ansonten würde der Pfad von nach zusammen mit der Kante einen Kreis schließen). Es gibt nun zwei Fälle:- wenn
dfsAndPrint(u)
vordfsAndPrint(v)
aufgerufen wird, dann wirddfsAndPrint(u)
sicherlich einen Aufruf vondfsAndPrint(v)
nach sich ziehen, und somit wird zuerst an angefügt werden; - wenn
dfsAndPrint(v)
vordfsAndPrint(u)
, dann erforscht die Tiefensuche von aus einen Teilbaum mit als Wurzelknoten; ist nicht in ; alle Knoten von werden also an angefügt, bevor die Tiefensuche verlässt und weitermacht (und schließlich bei ankommt); also steht links von in
- wenn