So, einen wunderschönen guten Morgen. Ton da. Perfekt. Gut,
wir befinden uns in der Graf-Algorithmik und haben uns in der
letzten Vorlesung zunächst ein paar einfache Anwendungen der Tiefensuche angeschaut.
Und dann sind wir eingestiegen in eine etwas komplexere Anwendung und es ging um,
oder es geht um die Erkennung oder Charakterisierung von sogenannten B-Zusammenhangskomponenten
in Grafen,
in ungerichteten Grafen
Spiels nochmal die Einführungsfolie zu dem Thema. Wir betrachten Graphen und
wir interessieren uns für Knoten, die die Eigenschaft haben,
dass die Entnahme dieses Knotens den Graphen in Teile zerlegt,
die dann nicht mehr zusammenhängend sind.
Solche Knoten nennen wir Gelenkknoten oder auch Articulation Notes.
Und hier in dem Beispiel sehen wir vier solcher Knoten, die diese Eigenschaft haben.
Und auf der Basis solcher Gelenknoten können wir den B-Zusammenhang definieren. Wir können dann sagen,
dass zwei Kanten, wie Zusammenhang ist über Kanten definiert,
zwei Kanten, in einer V-Zusammenhangskomponente sind genau dann,
wenn sie auf einem einfachen Zyklus liegen und demnach zerfällt so ein Graf an diesen Gelenknoten in solche wie Zusammenhangskomponenten.
Da diese Relation, die den einfachen Zyklus beschreibt,
auf Kanten eine Äquivalenzrelation ist, also reflexiv, symmetrisch transitiv,
können wir die Kanten entsprechenden Äquivalenzklassen aufteilen, diese Äquivalenzklassen,
sind dann die wie Zusammenhangskomponenten und hier in dem Beispiel kann man das nochmal ganz gut sehen,
da hat man hier diese roten Kanten,
die einen Teil des Grafen beschreiben und die liedernden Grüne und so weiter und das sind jeweils einzelne,
wie Zusammenhangskomponenten.
Warum interessiert uns das? Es gibt eine Reihe von Anwendungen,
wenn wir Graphen haben, wo solche Gelenknoten,
Schlüsselpositionen sind, das sind Engpässe im Netzwerk zum Beispiel,
sind eben kritische Stellen im Graphen,
die den Zusammenhang zerstören können und die möchte man häufig natürlich kennen.
Das ist der Grund, warum man vielleicht an diesen Gelenknoten interessiert ist,
warum man wie Zusammenhangskomponenten herausfinden möchte,
es hat häufig was mit Zerlegen von Graphen zu tun.
Man kann Probleme vielleicht leichter lösen, wenn wir den Graphen eben vorher zerlegen,
wir können Effizienzgewinne haben, wenn wir das entsprechend vorher machen
Und deswegen ist das interessant. Und eine ganz wichtige Anwendung,
wo diese Bi-Zusammenhangs-Komponenten eine Rolle spielen, ist bei den Planaritätstests von Graphen.
Das werden wir gleich am Ende auch noch sehen.
Okay, gut, das war das Thema. Und dann
haben wir uns das folgende Strukturlämmer angeschaut. Mit diesem Lämmer
können wir genau beschreiben, wann ein Knoten ein Gelenkknoten ist.
Und da gibt es dann drei Fälle, die wir unterscheiden mussten.
Wir machen jetzt also eine Tiefensuche. Und dabei entsteht ja
immer ein Tiefensuchbaum, den bezeichnen wir als GP.
Und der hat Wurzeln, der hat eine Wurzel,
der hat Blätter und der hat auch innere Knoten
Und für jeden dieser drei Fälle können wir uns überlegen,
wann so ein Knoten ein Gelenknoten sein kann.
Wenn es die Wurzel des DFS-Baums ist, dann ist es
genau dann ein Gelenknoten, wenn er mindestens zwei Baumkanten hat,
also zwei Äste, die da rausgehen.
Ein Blatt ist nie in Gelenknoten und ein innerer Knoten,
da ist die komplizierteste Bedingung. Da sagen wir,
dass ein innerer Knoten V an Gelenknoten ist, genau dann,
wenn kein, wenn, Entschuldigung,
wenn ein Kind S von V existiert,
welches keine Rückhande von S oder ein Nachfolger von S zu einem Vorgänger von V hat.
Das war die Bedingung,
die gelten muss
Und wir haben uns dann in dem Beweis klargemacht, warum das so ist.
Also hier war das nochmal für die Wurzel,
das ist aber der einfache Teil und für die Blätter und
das ist hier der schwierige Teil für die inneren Knoten.
Und zwar war der Punkt, dass wir so einen Knoten V haben,
der irgendwo in dem Graf liegt und der ist eingebettet halt in einem, in einem größeren tiefen Suchbaum. Ja,
das ist jetzt der Baum hier mit den Kanten EP,
der hat natürlich noch weitere Knoten und wir sagen,
dass so ein Knoten gerade dann ein Gelenknoten ist,
wenn es, es irgendein Kind gibt,
zum Beispiel dieses Kind hier, welches von allen Knoten,
die hier noch da drunter sind, keine Möglichkeit hat,
so einen Vorgänger von V zu kommen.
Also irgendwie hier eine Rückkante hat, um hier nach oben zu kommen.
Ja, wenn die nicht existiert für dieses Kind,
dann ist das offensichtlich ein Gelenknoten. Warum ist das so? Intuitiv gesprochen,
wenn wir V entnehmen, dann können wir über Baumkanten einen Knoten,
der hier unten ist, nicht mehr erreichen von einem Knoten,
der oberhalb von V ist.
Das war so das Argument, wenn hier irgendwo ein Knoten ist,
die können wir nicht mehr über Baumknoten erreichen,
weil der Knoten ja entfernt ist
Und der Baum hat ja keine Zyklen. Und
ein ungerichterter Graf, der hat nur Baumkanten oder Rückkanten.
Und wenn so eine Rückkante hier nicht existiert,
die uns nach oben in den Baum führt,
dann haben wir keine Möglichkeit, die Knoten voneinander zu erreichen.
Und der Baum wird quasi, und der Graf wird
quasi durch die Entnahme von V zerlegt.
Das ist quasi das Argument. Den Beweis haben wir uns
dann genau angeschaut. Und sind jetzt quasi an dem Punkt,
dass wir das Ganze jetzt umsetzen in einen Tiefensuchalgorithmus
Diese Eigenschaft, die wir in dem Lämmer gesehen haben,
die müssen wir jetzt quasi berechnen, während der Algorithmus läuft.
Und dazu definieren wir einen neuen Wert. Wir haben ja bei der Tiefensuche
immer schon Zeiten protokolliert, also die Discovery- und Finishing-Zeiten.
Jetzt berechnen wir auf dieser Basis einen neuen Wert.
Das ist der sogenannte Low-Point. Der Low-Point ist, wie folgt definiert,
er steht hier, also der Low-Point von einem Knoten-V,
ist das Minimum über eine Reihe von Discovery-Zeiten von anderen Knoten.
Und hier steht, welche das sein können.
Entweder ist es der Knoten-V selbst oder es ist ein Knoten,
den wir über eine Kante erreichen können von V aus
Oder es ist die Zeit für einen Knoten weh,
der von einem Nachfahre von U aus mit einer Kante erreichbar ist.
Das sind die drei Fälle, die wir verwenden, um den Low-Point zu definieren.
Und mit dem anschließenden Corona kann man genau sehen,
dass dieser Low-Point gerade beschreibt,
welchen Knoten wir von einem ausgehend von einem beliebigen Knoten im Baum
bezüglich der Historie von den Discovery-Zeiten noch erreichen können.
Was sozusagen der höchste Knoten ist in diesem Baum,
den wir noch erreichen können.
Das sagt uns
dieser Low-Point
Und dementsprechend gibt es eine ganz enge Beziehung zwischen den Low-Points und unserem Strukturlämmer,
was wir gerade hatten. Und wir können zeigen, dass,
wenn wir also einen Grafen haben und dann haben wir einen Gelenknoten,
genau dann, wenn einer von zwei Fällen eintritt,
entweder wir haben eine Wurzel eines DFS-Baums mit mindestens zwei Kindern,
den Fall kennen wir schon,
der ist eins zu eins aus dem Lämmer übernommen.
Oder V besitzt ein Kind C mit der Eigenschaft,
dass der Low-Point dieses Kindes-C größer gleich der Discovery-Zeit von dem Knoten-V ist.
Dann wissen wir, wir haben ja also ein Kind hier unten,
Und das hat die Eigenschaft,
dass den höchsten Punkt in dem Baum,
den wir noch erreichen können,
der liegt unterhalb von dem Knoten V. Oder ist Knoten V selber.
Es kann natürlich auch sein. Wir können natürlich durchaus
so eine Rückhandel zu diesem Knoten haben. Und dann würde
die Entnahme von V ja auch den Zusammenhang zerstören.
Okay, so, jetzt müssen wir uns überlegen,
wie wir dieses Minimum über die Discovery-Zeiten in dem DFS berechnen können.
Wir können schlecht sozusagen, wenn wir auf dem Knoten sind,
hier tatsächlich anfangen und versuchen, es auszurechnen,
dass wir vorne ist ja noch kein Problem.
Das wäre ja nur die Discovery-Zeit des Knoten selber.
Und die kannten, die betrachten wir ja eh
Wie können wir minimieren? Aber die Frage ist,
was machen wir mit dem hier? Ja, das ist ja
eine Aussage über alle Nachfahren dieses Knotens, V in dem Grafen.
Da müssen wir sozusagen minimieren über alles, was hier unten noch kommt.
Wie können wir dieses Minimum bilden? Da gibt es einen ganz einfachen Trick,
denn wir haben ja einen DFS-Algorithmus und der DFS-Algorithmus wird ja
von V aus in der Tiefensuche diesen ganzen Teilbaum hier durchlaufen.
Ja, und dann wieder zu V zurückzukehren. Das heißt,
was wir machen können, ist, wir können quasi
das Minimum immer in den Baum nach oben propagieren.
Wir können beim rekursiven Aufruf steigen wir hier ab in den Baum,
wir besuchen alle Knoten, Und immer dann,
wenn wir diesen Low-Point von einem Knoten berechnet haben, können wir den beim rekursiven,
bei der Rückkehr vom rekursiven Aufruf wieder an den Vorgänger übergeben.
Und dadurch minimieren wir quasi die Werte von allen Knoten,
die wir in so einem Teilbaum haben und kriegen dann für
den Knoten V den korrekten Low-Point.
Das ist die Art und Weise, wie wir das berechnen können.
Also wir können sie direkt in dem DFS-Lauf bestimmen.
Und das machen wir quasi, indem wir das Minimum nach
oben durchpropagieren. Jetzt stellt sich natürlich noch die Frage,
wie berechnen wir denn jetzt
diese B-Zusammenhangskomponenten
Also wenn wir jetzt die Kanten markieren wollen und an jeder Kante ranschreiben wollen,
zu welcher Beat-Zusammenhangs-Komponente die denn jetzt gehört,
dann müssen wir die alle angucken, müssen entscheiden,
okay, also das ist jetzt, was weiß ich,
die erste Komponente oder die zweite oder die dritte.
Und das ist jetzt natürlich schwierig,
weil wir quasi diese Komponente erst abschließend erkennen,
wenn wir wieder an diesem Knoten-V angekommen sind.
Wenn wir aber an dem Knoten-V angekommen sind,
dann haben wir diese ganzen Kanten hier unten,
die haben wir alle schon prozessiert und die sind schon wieder verschwunden aus unserer Berechnungshistorie.
Wir waren bei diesen Die haben wir alle besucht, wir haben die Kanten sondiert.
Und zu dem Zeitpunkt wussten wir noch nicht,
dass die alle in einer Komponente sind. Erst,
wenn wir wieder an V angekommen sind, können wir sagen,
ja, V ist ein Gelenkknoten und dementsprechend ist das hier alles eine wie Zusammenhangskomponente.
Das ist ein bisschen unschön und die einzige Möglichkeit,
dann die V-Zusammenhangskomponenten tatsächlich noch zu bestimmen,
auf der Ebene der Kanten ist, dass wir uns die Kanten merken.
Wir merken uns alle Kanten, die wir hier unten prozessiert haben.
Und zwar verwenden wir dafür einen Stack, einen Stapel,
auf dem wir alle Kanten drauflegen
Denn was man sieht in diesem Algorithmus ist, dass die Zusammenhangskomponenten immer am Stück erkannt werden.
Man sieht also durch die Art und Weise,
wie das prozessiert wird, dass wir,
wir laufen hier runter und irgendwann kommen wir wieder hier hoch und dann sehen wir,
okay, das ist ein Gelenknoten und in dem Augenblick haben wir eine,
wie Zusammenhangskomponente erkannt.
Die liegen also immer am Stück vor, bezüglich der Berechnungsreihenfolge.
Und deswegen können wir die Kanten auf einen Stapel legen und immer dann,
wenn wir einen Gelenknoten haben, bauen wir diesen Stapel ab,
bis zu der letzten Kante,
die hier zu diesem Knotenfoul geführt hat und wissen,
alle diese Kanten gehören zu einer wie Zusammenhangskomponente und die
können wir
entsprechend minimieren
Ja, okay, das ist die Strategie.
Ich habe jetzt den Pseudo-Code mal in zwei Teile aufgeteilt.
Wir machen erst mal den einfacheren Teil,
indem wir uns nur anschauen, wie wir die Gelenkknoten identifizieren.
Wir lassen die Geschichte mit dem Stack mal noch für ein paar Minuten weg.
Und das bauen wir nachher in unseren Algorithmus hier entsprechend ein. Gut,
wir haben die folgenden Variablen, wir haben die Discovery-Zeiten,
die kennen sich schon D und wir haben die Low-Point-Slow.
Und was wir zusätzlich noch machen, ist,
dass wir in dem DFS-Durchlauf in einem zweiten Parameter angeben,
von welchem Knoten wir gekommen sind.
Das ist eine wichtige Information. Also wenn wir hier von zu V und Liebling Knoten V bearbeiten,
dann wüssten wir gerne, was der Vorgänger von V,
wenn wir mal V-Strich nennen, welcher Knoten das war.
Die Information brauchen wir. Wenn wir den Algorithmus dann das
erste Mal aus der Hauptroutine aufrufen, den rekrusiven Teil,
dann übergeben wir hier einen Neil. Die Variante,
die Sie im Skript haben, die hat noch einen dritten Parameter,
der dritte Parameter heißt Is Root und ist eine bulsche Variable,
die verwendet wird, um zu unterscheiden, Knoten-U,
über den wir das jetzt gestartet haben, eine Wurzel ist oder nicht.
Das habe ich mal eingeführt, weil man diese Unterscheidung machen muss.
Die müssen wir nach wie vor auch machen. Allerdings ist es so,
dass wir auch an diesem Vorgänger hier natürlich sofort erkennen können,
ob das eine Wurzel ist oder nicht, weil wenn der Vorgänger Neil ist,
in der Knoten ihr also keinen Vorgänger hat,
wenn der hier also auf Neil zeigt,
dann wissen wir, dass es die Wurzel ist.
Deswegen ist diese Boote variabel, streng genommen nicht nötig.
Und die habe ich dann irgendwann wieder gestrichen,
damit es ein bisschen einfacher ist.
Aber so wie es im Skript ist, ist es auch korrekt.
Okay, gut. Also Initialisierung,
wir initialisieren die Discovery-Zeiten, wir initialisieren die Low-Points,
sind alles
mit Null initialisiert
Und streng genommen ist die gar nicht nötig, diese Initialisierung,
weil wir, bevor wir mit den Low-Points arbeiten,
sie sowieso immer mit der Discovery-Zeit initialisieren werden,
aber damit man hier, wenn es alles sauber ist,
schreiben wir da erstmal Nullen rein.
Gut, der Rest ist eben Standard-DFS. Unsere Uhr läuft hier,
wir werden die Discovery-Zeiten einfach verwenden, um zu entscheiden,
ob der Knoten weiß ist oder grau.
Dann können wir die Farbe einsparen, die brauchen wir dann hier gar nicht.
Und hier sehen wir die Schleife über alle Knoten,
wenn die Discovery-Zeit noch null ist,
haben wir diesen Knoten nicht bearbeitet und rufen dann
unsere rekursive Funktion auf
Gut, die DFS Visit-Funktion sieht dann wie folgt aus.
Schwarz ist wieder alles, was Standardtiefensuche ist. Und das Grüne ist das,
was jetzt hinzukommt, um die Gelenknoten zu bestimmen.
Wir haben also als Parameter hier den Knoten U,
auf den wir jetzt unsere Visit-Funktion laufen lassen und einen Parameter Parent.
Das ist quasi der Vorgänger des Knotens. Also von dem
wir gekommen sind, wenn wir U aufrufen.
Okay, als erstes wird unsere Zeit hochgezählt.
Der Zeitzähler geht eins hoch und wir registrieren die Discovery-Zeit
Und jetzt kommt das erste Mal die erste Stelle,
wo wir den an dem Low-Point arbeiten.
Und zwar initialisieren wir den Low-Point von U mit D von U.
Mit der Discovery-Zeit. Und wir haben noch eine zweite Variable.
Da zählen wir die Anzahl der B-Zusammenhangskomponenten an dem Knoten.
Das ist hier eine Initialvariable, die wir gleich brauchen.
Ich schalte nochmal einmal zurück zur Definition von dem Low-Point.
Ich habe eben schon darauf hingewiesen, es gibt drei Teile,
über die wir minimieren müssen. Einmal der Fall,
dass der Knoten, der KnotenV selbst ist, die Kante,
die von V nach W führt oder die von einem Nachfahren kommt.
Gerade haben wir die Stelle gesehen, für den ersten Teil
der Berechnung des Low-Points. Wir haben nämlich initialisiert,
Entschuldigung, falsche Richtung, wir haben initialisiert mit der Discovery-Zeit.
Okay, gut. So, jetzt kommt wieder Standard-DFS.
Wir sondieren unsere Addizenz-Liste. Da betrachten wir jetzt alle Knoten V.
Die Kinder sind von dem Knoten U, bei dem wir gerade sind,
wenn D von V gleich 0 ist, dann wissen wir,
aha, weißer Knoten, haben wir noch nicht gesehen.
Wir machen den Abstieg. Wir rufen dann den DFS Visit
auf für diesen Knoten
Und wir übergeben als zweiten Parameter das U,
weil das U ist ja jetzt der Vorgänger von dem Knotenfarm.
So, jetzt kommt wieder an der Stelle, wo wir
den Low-Point verändern. Und zwar berechnen wir den Low-Point von
unserem Knoten U an dieser Stelle als das Minimum des Low-Points,
den wir bisher schon berechnet haben.
Und dem Low-Point von dem Knoten V,
von dem wir quasi jetzt Recco sich wieder zurückgekommen sind.
Dies ist die Stelle, wo der Low-Point von den Nachfahren zurückpropagiert wird,
an die aufrufende Funktion, wenn sie so wollen.
Also wo wir den Low-Point von unten wieder nach oben weitergeben.
Und diese Stelle hier, die entspricht in der Definition des Low-Points dem hinteren Teil. Ja,
wir wissen schon für die Nachfahren von U haben wir
den Low-Point schon berichtigt berechnet und wir müssen quasi,
wir können hier über den Low-Point dieser Knoten minimieren,
wenn wir hier diese Minimierung durchführen.
Also das ist die Coach-Stelle, die diesen Teil,
diesen dritten Teil der Definition des Low-Points quasi bearbeitet.
Okay, so
Jetzt müssen wir schauen, ob wir die Situation haben,
dass unser Knoten, den wir jetzt gerade abgeschlossen haben,
tatsächlich ein Low-Point hat, der größer, gleich DVU ist.
Wenn das so ist, dann wissen wir,
dass dieser Knoten V so genauso ein Kind ist.
So ein Kind, was keine Rückkante nach oben hat,
weil der Low-Point zu groß geworden ist.
Und wenn das so ist, dann wissen wir,
wir haben eine B-Zusammenhangskomponente,
die hier an diesem Knoten dranhängt
Und dann zählen wir die erst mal. Wir zählen hier und sagen,
okay, die Anzahl der Wie-Zusammenhangskomponenten an dem Knoten-U ist jetzt um eins größer geworden,
weil wir ein Kind gefunden haben mit einem Low-Point,
der größer gleich der Discovery-Zeit von US.
So, dann gibt es natürlich auch noch einen Els-Teil,
der Els-Teil hier beschreibt quasi jetzt die Rückkanten.
Ja, das sind die Baumkanten gewesen. Jetzt kommen
die Rückkanten. Da kommt noch so ein If-Statement dahinter,
weil wir Rückkanten von echten Rückkanten Unterscheiden müssen.
Wir gehen mal wieder davon aus, dass der ungerichtete Graf
durch antiparallele Kanten repräsentiert ist. Also das heißt, jede Kante,
die wir haben, wird dargestellt durch so ein Anti-Paralleliskantenpaar.
Und wenn wir das machen, dann wäre in dem Augenblick ja,
ja, dann hätten wir ein Problem,
weil wir quasi die Kante, wenn wir hier zurücklaufen,
auch als Rückkante interpretieren würden.
Und das ist natürlich nicht gemeint.
Deswegen prüfen wir hier, ob das V
dem Parent entspricht
Ja, also ob wir tatsächlich gerade in der Addiazenzliste diese Kante
zurück in den Baum zum Vorgänger sehen, und die ignorieren wir einfach.
Alle anderen Kanten, die wir sehen, sind echte Rückhanden.
Und bei den echten Rückhanden wissen wir schon,
was wir zu tun haben,
da minimieren wir einfach den Low-Point für diesen Knoten-U,
auf den wir gerade sitzen,
in dem wir uns die Discovery-Zeit von diesem Knoten-V anschauen,
den wir erreichen können.
Dies ist die Stelle, diese Zeile 10,
wo wir diesen mittleren Teil der Definition des Low-Points abarbeiten.
Weil hier steht, dass wir über alle Kanten VW
die also den Knoten V verlassen, minimieren müssen.
Die kannten, die diesen Knoten V verlassen, das kriegt ja zwei Möglichkeiten.
Das sind Baumkanten oder Rückkanten. Wenn es eine Baumkante ist,
dann ist die Discovery-Zeit ja immer größer als die von dem Knoten selber.
Und deswegen muss ich gar nicht über die minimieren.
Die können hier gar keine Rolle spielen,
weil der Knoten selber ist ja auch in dieser Menge drin.
Die einzigen Kanten, die hier relevant sind, sind Rückhanden.
Und das Minimieren über die entsprechenden Discovery-Zeiten der Rückhanden mache ich genau an dieser Stelle.
Und das entspricht dir der Teil zehn. Beachten Sie,
hier nehmen wir das Minimum von Low-Point-U und der Discovery-Zeit,
während wir hier beim Propagieren nach oben das Minimum des
Low-Points von U und des Low-Points von V nehmen.
Also ein wichtiger Unterschied. Gut, wenn die Sondierungsschleife dann abgeschlossen ist hier,
dann können wir ganz leicht feststellen, ob unser Knoten ein Gelenkknoten ist.
Und zwar müssen wir nur gucken, ob wir mehr als
eine B-Zusammenhangskomponente festgestellt haben, dann ist es auf alle Fälle ein Gelenknoten.
Und dann haben wir noch einen Sonderfall,
nämlich wenn wir die Wurzel haben,
Dann müssen wir echt mehr als eine Komponente haben,
während wir bei einem inneren Knoten, es ausreicht,
wenn wir eine Komponente identifiziert haben.
Also für einen inneren Knoten reicht es aus,
wenn ein Kind existiert mit der Eigenschaft,
für die Wurzel wissen wir, wir brauchen zwei Baumkanten.
Deswegen ist hier dieser zusätzliche Fall, also das beschreibt dir
quasi alle inneren Knoten und das ist quasi vorne der Fall für die Wurzeln.
Okay, wenn das dann der Fall ist,
dann haben wir so einen Gelenknoten gefunden,
den können wir dann ausgeben oder markieren oder einen Array
schreiben oder irgendwas weiteres
Damit machen, ja. Das ist also der DFS-Algorithmus,
der die Idee des Low-Points umsetzt und auf entsprechend die Gelenknoten berechnen kann.
Ja, gut, jetzt müssen wir den noch erweitern.
Jetzt wollen wir gerne noch alle Kanten markieren.
Wir wollen also quasi Bezug auf rundes Beispiel,
was wir am Anfang gesehen haben, diese Färbung hinkriegen.
Wir wollen an jeder Kante dranschreiben, zu welcher Komponente
sie gehört. Wir werden die einfach nummerieren,
die Nummerierung ist dann willkürlich, aber wir wissen,
wenn die Kanten die gleiche Nummer haben,
dann gehören sie zugleichen
wie Zusammenhangskomponente
Dafür brauchen wir jetzt noch eine zusätzliche, zusätzliches Array,
das heißt BCC und da werden wir einfach Nummern reinschreiben,
von eins bis zur Anzahl der B-Zusammenhangskomponenten, die wir so finden.
Dann brauchen wir noch unseren Stapel, auf dem wir die Kanten drauflegen.
Ja, so, wie funktioniert das Ganze? Wir haben
hier quasi wieder die Hauptroutine. Wir initialisieren die Discovery-Zeiten,
die Low-Points, ja, die brauchen wir jetzt natürlich wieder,
um zu erkennen, wo die B-Zusammenhangskomponenten auch aufhören.
Wir initialisieren diese BCC-Werte erstmal alle mit null.
Dann initialisieren wir unseren Stack und als leeren Stack und wir haben
hier einen Zähler für die Anzahl der B-Zusammenhangskomponenten und den
setzen wir jetzt erstmal auf eins, weil wir wissen,
eine wird es auf alle Fälle geben.
Dann haben wir unsere Uhr, die wir mit null initialisieren
und hier ist jetzt die Schleife, die jetzt alle Knoten betrachtet.
Immer wenn die Discoveryzeit null ist, haben wir einen weißen Knoten,
wir starten unsere Visit-Routine Und zwar mit dem Knoten U selber.
Der Vorgänger, hier würde wieder der Vorgänger stehen,
das ist jetzt Neil, weil wir eine Wurzel haben.
Und dann übergeben wir hier die aktuelle Nummer für die Anzahl der B-Zusammenhangskomponenten.
Okay, diese Übergabe der Nummer ist ein Call by Reference.
Das heißt, wir haben eigentlich eine globale Variable, die wir benutzen.
Immer wenn wir eine Komponente gefunden haben und haben die markiert,
während wir diesen Zähler einfach eins erhöhen.
Okay, so, was macht unsere DFS-Visit-Routine?
Der Anfang ist wieder Standard. Die Zeit wird um eins
erhöht,
die Discovery-Zeit
Entsprechend gesetzt. Jetzt ist schon alles, was wir eben
schon hatten, für die Low-Point-Berechnung, ist jetzt schon schwarz.
Das haben wir schon. Sie sehen die Unterschiede wieder in Grün
. Wir initialisieren den Low-Point mit der Discovery-Zeit des Knotens.
Wir sondieren unsere Adjazenz-Liste. Wenn wir den Knoten V,
hier den wir als erstes betrachten, noch nicht gesehen haben,
weißer Knoten, haben wir eine Baumkante.
Das ist der aufwendigere Teil hier. Anderenfalls haben wir eine Rückkante,
das beginnt ja ab Teile 14. Wenn wir eine Baumkante haben,
dann legen wir die Kante von UNAV als erstes Mal
auf den Stack
Gleiches werden wir bei der Rückkante übrigens auch tun.
Das hätte man theoretisch auch quasi noch vor das Ifs ziehen können.
Dann gehen wir rekrusiv in den DFS-Algorithmus hinein, bearbeiten den Teilbaum.
Wenn wir zurückkommen, aktualisieren wir unseren Low-Point,
macht die Zeile 7, haben wir eben gerade schon gesehen.
Und jetzt schauen wir, ob wir eine neue B-Zusammenhangskomponente identifiziert haben an diesem Knoten V. Und zwar,
wenn der Low-Point für diesen Knoten V größer gleich der Discovery-Zeit von U ist,
dann wissen wir,
wir haben hier gerade so ein Kind zu Ende bearbeitet mit der Eigenschaft,
dass alles, was hier drunter,
also keinen Zugang über eine Rückkante nach weiter oben im Baum hat.
So, das bedeutet, den Teil können wir jetzt quasi markieren.
Und diese Kanten, die liegen alle jetzt quasi auf dem Stack.
Wir haben dann hier so eine Du-Wile-Schleife, die entnimmt Kanten aus
dem Stack und markiert die Kanten mit der aktuellen BCC-Nummer.
Und das machen wir so lange,
bis wir diese Kante von U
nach V sehen
Das ist die letzte, die noch zu der B-Zusammenhangskomponente gehört.
Und als letzte Aktion erhöhen wir dann entsprechend den Zähler um eins.
Ja, die Rückhanden. Bei den Rückhanden ist es so,
wir schauen wieder, ob wir eine echte Rückkante haben.
Das macht hier der Vergleich mit Parent.
Und... und...
wie mich jetzt gerade
Ich ignoriere das noch für einen Augenblick.
Ich bin mir sicher, es muss dahin,
aber ich weiß jetzt gerade nicht mehr, warum.
Okay, also wir haben eine Rückhande. Wenn wir die Rückhande haben,
dann legen wir sie auf den Stack und dann müssen wir minimieren über den Low-Point,
so wie wir das vorher schon gesehen haben.
Und das ist quasi alles und auf die Art und Weise
können wir die Kanten markieren.
Über die stelle ich mich jetzt gerade
Okay, muss ich mal drüber nachdenken, warum die da steht.
Also, ich meine, die Karte ging richtig.
Ah, genau, es gibt Rückhanden, es gibt Rückhanden.
Ja, genau. Es hat wieder was mit der Modellierung
hier zu tun. Ja, ja. Und zwar,
wenn wir in unserem Baum, wenn wir eine Rückhande sehen,
die geht jetzt hier hoch,
dann ist natürlich auch diese Rückhande modelliert durch zwei antiparallele
Kanten in den Adjazenzlisten
Ja, danke, dass ich mich darauf hingewiesen habe.
Muss man eigene Folien lesen. Okay, also,
dann ist auch die Rückkante hier modelliert durch zwei Antiparallelekanten,
ja, und wir sind natürlich interessiert an der echten Rückkante,
also die, die hier hochgeht,
und wenn wir irgendwann wieder an diesem Punkt sind,
dann sehen wir auch die Kante hier runter.
Das wäre quasi in dem, in dem gerichteten Grafen eine Vorwärtskante,
ja, aber wir haben natürlich hier keine Vorwärtskanten,
weil wir ja eigentlich einen ungerichteten Grafen haben.
Und diese Kante ist keine Rückkante, ja, deswegen interessiert uns die hier nicht.
Wahrscheinlich wäre es klug, die an dieser Stelle auch auszuschließen. Ja,
da müssten wir mal, vielleicht ist es egal an dieser Stelle,
weil wir nach unten gehen und der Low-Point,
wahrscheinlich ist es egal, aber es wäre wahrscheinlich besser,
um es klarzumachen, dass wir wirklich nur die echten Rückhanden haben,
das hier auch einzufügen.
Okay, gut. Soweit, so gut. So können wir
, wie Zusammenhang berechnen. Hier ist mal ein Beispiel,
also das Beispiel, was wir schon die ganze Zeit immer betrachtet haben.
Und zwar, hier sehen Sie wieder den Graf,
wie schwarzen Dicken sind die Baumkanten,
die roten Dünnen
sind die Rückkanten
Die wir finden. Und die Zahlen, die sich auf der linken Seite
in den eckigen Kästen sehen, das sind die Discovery-Zeiten.
Also wecken Sie mal dran, Discovery-Zeiten sind nicht eindeutig.
Das heißt, Sie hätten auch in irgendeiner anderen Weise durch den Grafen laufen können. Ich bin jetzt so da durchgelaufen. Ja,
ich bin hier oben bei A gestartet, dann zu F,
dann zu E, dann zu G,
dann zu L, dann zu J,
dann zu M und dann hier hoch und dann zu K,
hier wieder hoch, bis G, dann zu C,
dann zu Ha, dann zu I,
dann wieder hoch, dann zu D, hoch,
hoch und dann zu dem Knoten B.
Das ist jetzt mein DFS-Durchlauf gewesen und dann haben wir diese Discovery-Zeiten.
Das sind die Low-Points, die da rauskommen.
Und zwar sind das die Low-Points nach vollständigen Ablauf.
Ich habe jetzt also nicht für jeden Zustand so ein Bild gemalt.
Und die sind am Anfang natürlich noch größer.
Also wenn wir jetzt bei den Knoten G zum Beispiel betrachten,
da wäre der Low-Point am Anfang mal vier.
Also er wird ja initialisiert mit der Discovery-Zeit. Und dann
betrachten sie ja die Kanten und irgendwann würden sie bei G
zum diese Rückhande sehen nach hier oben, zu Knoten A.
Und das wäre der Zeitpunkt, wo sie den Low-Point
hier auf einsetzen würden.
Dadurch würde der hier eins
Und wenn sie hier den Knoten L sehen zum Beispiel,
der würde am Anfang inditialisiert mit der Discovery-Zeit 5, dann würden sie hier runtergehen.
Und hierhin, da würden sie aber keinen größeren Low-Point,
kleineren Low-Point sehen, die müssen ja minimieren.
Am M würden wir auch keinen kleineren Low-Point sehen,
aber wenn wir über J diese Rückkante prozessieren,
dann würden wir zu dem Knoten kommen mit der Discovery-Zeit 4. Beachten Sie,
wir minimieren bei Rückhanden immer über die Discovery-Zeit,
nicht über den Low-Point, den wir da berechnet haben.
So, dann würden wir hier den Low-Point 4 berechnen und
wenn wir dann rekursiv in dem Algorithmus wieder zum Vorgänger gehen.
Also wenn die Rekursion wieder aufgehoben wird durch den Rücksprung zum Vorgänger,
minimieren wir jetzt über den Low-Point, den der Knoten selber hatte.
Da wird hier die vier übertragen und dann hat der
hier zum Beispiel die vier gekriegt. Ja,
auf diese Art und Weise können sie hier die ganzen Low-Points nachvollziehen und das dritte Bild,
was ich dann noch habe, sind jetzt die Markierungen der Kanten.
Ja, also der Algorithmus würde,
der prozessiert ja hier den Grafen von oben nach unten mehr oder weniger
und der würde quasi hier unten die Kante von K nach J, das wäre die erste, die er detektiert.
Das wäre quasi die erste, Zusammenhangskomponente,
die er finden würde, diese einfache Kante von Karna J,
dann würde er in dem Augenblick, wo er dann hier M gesehen hat,
J und wieder zurückkommen würde nach L, hätte er,
nee, da noch nicht, zurückkommen würde nach G,
hätte er ein Kind, nämlich L, was die Eigenschaft hat,
dass der Low-Point von L, das ist vier,
größer gleich der Discovery-Zeit von dem Knoten G ist.
Und dann würde er sagen, okay, dieses Kind hier repräsentiert
eine B-Zusammenhangskomponente und dann würde er diese ganzen Kanten hier markieren.
Die hat das schon markiert, die liegt nicht mehr
auf dem Stack
So, und dann wird er den Teil hier machen und
hier weiter so hoch und dann hätten wir das Gesamtergebnis.
Okay, gut, das zum Thema B-Zusammenhang.
Vielleicht sollte man noch mal ganz kurz,
wir haben ja die Laufzeit, haben wir uns angeschaut,
also ist die Laufzeit O von V plus E,
ist es, glaube ich, ganz offensichtlich bei dem Algorithmus.
Man sollte mal noch mal eine Sekunde über den Speicherbedarf nachdenken.
Also eine normale Tiefensuche haben wir eigentlich noch nie so über
Speicherbedarf gesprochen. Erst hat man so das Gefühl,
ja klar, das hat irgendwie konstant einen Speicherbedarf,
weil wir ja in jedem rekursiven Aufruf nur so
ein paar Variablen haben
Dann sagt man, okay, vielleicht hat man so ein paar Arrays,
da muss man sich auch noch Informationen speichern, zum Beispiel diesen Low-Point.
Dann kommt man also auf einen zusätzlichen Bedarf,
der erstmal linear wäre, Ovon V, um diese Informationen zu speichern.
Streng genommen stimmt das auch mit dem Speicherbedarf,
mit dem linearen Speicherbedarf nicht, weil wir haben ja den Stack,
auf dem wir die Informationen speichern und jeder rekrusive Aufruf benötigt ja Speicher.
Das heißt, eigentlich müssten wir sowas wie eine Tiefe,
eine Rekursionstiefe abschätzen, um zu überlegen,
wie viel Speicher wir brauchen.
Das ist das,
was die Tiefensuche sowieso an Speicherbedarf hat
Dann kommt hier aber noch der Stack dazu, auf dem
wir die Kanten legen. Und im Worst Case haben wir
nur eine B-Zusammenhangskomponente und wir müssen alle Kanten auf den Stack legen,
bevor wir dann diesen Stack aufräumen und entsprechend alle Kanten markieren.
Und das ist tatsächlich ein höherer Zeitbedarf als eine normale Tiefensuche.
Eine normale Tiefensuche hätte einen Zeitbedarf irgendwie proportional zur Rekursionstiefe.
Aber die Regressionstiefe ist ja höchstens so groß wie die Anzahl der Knoten.
Auf diesem Stack brauchen wir aber Platz für die Anzahl
der Kanten und das
sind wesentlich mehr
Diese Tiefensuche hat noch so einen kleinen Haken. Sie ist
noch nicht absolut perfekt, ja, weil schöner wäre es,
wenn wir eben diesen zusätzlichen Speicherbedarf nicht bräuchten.
Aber wenn Sie das noch lösen, können Sie berühmt werden.
Also ich kenne kein Verfahren, was also jetzt ohne diesen Stack auskommt.
Ja, es ist einfach erstmal ein Nachteil, den man da akzeptieren muss.
So, also was wir gesehen haben, ist,
in einem ungerichteten Grafen können wir wie Zusammenhangskomponenten in asymptotischer Worst-Case-Laufzeit,
O von V plus E bestimmen.
Ja, Speicher, wie gesagt, ist O von E
, ist ein bisschen höher
Ja, die Korrektheit des DFS-Algorithmus folgt aus diesem Corola,
wo wir gesehen haben, dass der Low-Point uns sagt,
wo die Gelenknoten sind, nur dass wir daran die WCCs erkennen können.
Der Beweis ist hier noch ein bisschen verkürzt.
Also das Corola könnte man natürlich noch mal ausführlicher begründen.
Und auch die Tatsache, dass die Kanten immer gemeinsam,
also hintereinander auf dem Stack liegen,
muss man natürlich streng genommen auch noch beweisen.
Okay, Wenn es einen Beat-Zusammenhang gibt,
dann gibt es wahrscheinlich auch einen Tri- oder Dreifach-Zusammenhang.
Das ist auch so. Dafür gibt es eine Definition,
und zwar ein gerichteter Graf G, bezeichnen wir als drei zusammenhängend.
Genau dann, wenn das Entfernen zweierbeliebiger Knoten den Zusammenhang nicht zerstört.
Die maximal drei zusammenhängenden Teilgrafen von G heißen dann drei Zusammenhangskomponenten.
Die gibt es auch, also TCCs. Und keine Angst,
die gucken wir uns jetzt nicht noch an. Aber es ist tatsächlich so,
dass man auch den Dreifach-Zusammenhang mit einer tiefen Suche bestimmen kann.
Hier sehen Sie nochmal so ein Bild,
wie man sich jetzt die Zerlegung eines Graphen vorstellen kann.
Wir haben auf der einen Seite Zusammenhangskomponenten,
das sind hier so getrennte Bereiche, wie das hier.
Wir haben B-Zusammenhangskomponenten und wir wissen, die B-Zusammenhangskomponenten hängen zusammen über
einen einzelnen Knoten und die Entfernung dieses Knotens zerstört den Zusammenhang.
Die kann man sich so vorstellen wie so Kugelgelenke, ja, diese Knoten.
Und dann haben wir den Tri-Zusammenhang und diese Tri-Zusammenhangskomponenten,
die sind so miteinander verbunden, dass wir zwei Knoten haben,
die wir entfernen müssen, um den Zusammenhang zu zerstören.
Also um sie zu trennen voneinander. Und die,
wenn sie sich so geometrisch vorstellen, dann sind so zwei Knoten,
die sind so wie so ein Scharnier,
an dem sie die Zeile bewegen können.
Dieser dreifach zusammenhängende Teil, der ist tatsächlich relativ fest irgendwie durch Kanten miteinander verwoben.
Sonst würde das weiter zerlegt werden. Also das heißt,
auf diese Art und Weise können wir also den Graphen in diese Komponenten zerlegen.
Diese Zerlegung in Zusammenhang Bi-Zusammenmenhang und dreifacher Zusammenhang. Der ist
ganz wichtig für Planaritätstests. Sie hatten eine Frage. Ja,
müsste die Formulierung sein, ein zweier beliebiger Kanten. Im Zusammenhang war das,
so wie wir es haben, fehlt ja noch,
also weil wir genau zwei Knoten rausnehmen würden,
müsste doch gerade dann, da ist es dann nochmal,
entweder zwei Kranken oder ein Knoten.
Oder wo bin ich jetzt? Also wahrscheinlich,
also die Definition des B-Zusammenmenhangs ist ja so,
dass wir einen Knoten rausnehmen können und der zerfällt
Und der Triezusammenhang ist, dass wir zwei Knoten rausnehmen können und er zerfällt.
Ja, und da steht ein Ungerechtmeter, ist er viel zusammenhängt,
genau dann, wenn zwei in Bernd nicht zerstört.
Ja, das ist der Zusammenhang. Genau, die sind zusammenhängend,
wenn ich zwei rausnehmen kann und der Zusammenhang wird nicht zerstört.
Zum Beispiel, da zerfällt rechts, wenn du es gut zusammenhängst.
Genau, diese beiden Teile sind zwar bi zusammenhängend,
aber nicht Trie zusammenhängt. Innerhalb dieses grauen Bereichs hätten sie,
können sie den Graf nichts auseinanderlegen,
zerlegen,
wenn sie
zwei Knoten entfernen
Gut. So, also es hat alles, hat was mit Planarität
zu tun und hier nochmal erstmal, was ist Planarität?
Also wir haben einen Graf und den bezeichnen wir als Planar,
genau dann, wenn es eine Abbildung gibt, der Knoten auf R2,
also die Ebene, ja, sodass alle Kanten aus E überschneidungsfrei
durch Linienzüge in R2 platziert werden können oder repräsentiert werden können.
Ich habe hier das, können Sie sich noch,
wenn Sie das ausgedruckt haben,
dass durch Linienzüge habe ich hier noch eingefügt,
um auch nochmal deutlich zu machen, es geht nicht darum,
dass Sie eine Gerade Linie zwischen zwei Knoten ziehen müssen.
Sie können das auch, das kann also beliebiger Linienzug sein,
mit dem sie zwei Knoten verbinden.
Es geht nicht darum, dass es ein Gradesliniensegment ist,
sondern nur, dass sie an zusammenhängenden Linienzug haben.
So, dann bezeichnen wir den Grafen als Planar.
Planarität ist in Anwendung, in vielen Anwendungen wichtig. Also wenn sie,
ja, wenn sie zum Beispiel Straßen- und Eisenbahnnetze planen,
ja, oder wenn sie, also Verkehrswegesysteme planen,
dann ist Planarität eine ganz wichtige Eigenschaft,
denn da,
wo es nicht planar ist,
brauchen sie immer Brücken oder irgendetwas in der Art
Es ist auch so, dass es eine ganze Reihe von Problemen gibt,
von denen wir zeigen können, dass sie einfacher sind, wenn der Graf Planar ist.
Also wenn wir ihnen in der Ebene Planar zeichnen können,
wir können sogar Aussagen machen über die Kantendichte,
wenn wir wissen, dass ein Graf Planar ist,
dann ist die Kantendichte nämlich geringer, was ja nachvollziehbar ist,
wenn sie keine Kreuzung, Überkreuzung malen dürfen,
dann haben sie weniger Kanten und das kann man auch explizit machen.
Wie viele Kanten das dann sind, das werden wir noch
später sehen, wenn wir uns mit algorithmischer Geometrie beschäftigen.
Da spielt das nochmal eine Rolle. Okay,
Planarität ist ganz wichtig und es hat die Mathematiker lange fasziniert.
Die Frage, kann ich das jetzt so malen,
dass ich das nicht überkreuzt oder geht das nicht
Und dann, das erste,
was man sich dann überlegen kann, ist, ja,
wir haben ja unsere Zerlegung jetzt in die Komponenten und offensichtlich
ist der Graf genau dann Planar zeichenbar, wenn seine Trie-Zusammenhangskomponenten-Planer zeichenbar sind.
Da können wir uns noch mal das Bild anschauen. Also,
wenn ich so zusammenhängende, nicht zusammenhängende Teile habe,
dann ist natürlich das ganze Planar zeichenbar,
wenn ich den Teilplaner zeichnen kann und den Teilplanar zeichnen kann.
Ist ja klar, weil die kann ich ja beliebig in der Ebene verteilen,
diese beiden Teile. Und wenn ich solche B-Zusammenhangskomponenten habe,
die durch einen Knoten getrennt sind, dann sind Ist das
der gesamte Graf zeichenbar, wenn diese B-Zusammenhangskomponenten zeichenbar sind?
Weil wir wissen ja schon, dass die Kanten hier in diesem Bereich sind,
die sind in diesem anderen Bereich und wir können die beiden
nur durch einen Knoten getrennten Bereiche oder verbundenen Bereiche getrennt voneinander einfach auf der Ebene zeichnen.
Und das Gleiche gilt für die Tri-Zusammenhangskomponenten auch. Sie können
den Teil zeichnen und den und dann können sie die beiden Teile miteinander entsprechend verbinden. Gut,
so und die Frage,
ob so eine Tri-Zusammenhangskomponente Planer als zeichenbar ist oder nicht,
die ist gelöst von einem russischen Mathematiker, von Kuratowski,
der hat nämlich gezeigt, dass ein Graf Gehe genau dann Planar ist,
wenn er keiner zu K5 oder K333 Isomorphen-Subdivisionen enthält.
Das ist ein relativ komplizierter Satz, ist aber eigentlich
ganz einfach. Ein K5 ist übrigens nach ihm benannt,
ist der vollständige Graf mit fünf Knoten.
Und ein K33 ist der vollständige Bi-Partite Graf mit zweimal drei Knoten.
Also drei Knoten hier, drei Knoten hier und jeder
ist mit jedem verbunden.
Das ist ein K33 und das hier ist der
Und so eine Subdivision ist nichts weiter als ein Teilgraf,
allerdings mit einer zusätzlichen Eigenschaft,
dass nämlich alle Pfade mit Grad 2 zu einer Kante verschmolzen werden.
Ja, also wenn sie so eine Perlenkette haben,
wo dann ganz viele Knoten vom Grad 2 sind,
dann können sie daraus eine lange Kette machen.
Und dann redet man von einer Subdivision.
Das ist also etwas anders als ein Teilgraf. Okay,
und der Koratowski hat gezeigt, also wenn das nicht vorkommt
, dann können wir das Planat zeichnen.
Und wir sehen schon, diese beiden hier,
die können wir nicht Planat zeichnen, das können sie ausprobieren.
Das ist ja hier ein ganz berühmtes Kinderspiel hier mit
drei Häusern
Dem Elektrizitäts, dem Wasser und dem Gaswerk, ja,
die man so mit Röhren verbröhren und Leitungen verbinden muss,
die sich nicht kreuzen, ohne irgendwann feststellt, das geht nicht.
Ja, also das sind, das ist klar,
die sind nicht planarzeichenbar und Kuratowski hat gezeigt, eigentlich wird man alles,
kann man alles zurückführen auf diese beiden Fälle, ja.
Was total faszinierend ist, weil wir haben ja eigentlich eine Aussage,
die sich auf die Ebene, auf eine Geometrie bezieht,
auf das R2, ne,
wir fragen nach der Zeichenbarkeit in zweidimensionalen und Kuratowski hat gezeigt,
dass das ein ein topologisches Kriterium ist.
Dass man, das hat überhaupt nichts mit Geometrie zu tun.
Das ist eine reine Topologiefrage, ob in dem Grafen sowas vorkommt oder nicht.
Und damit kann man das zeigen. Ja,
die Idee von Kuratowski, das ist also das Schlüssel-Theorien,
was man braucht, wurde dann von Tajan umgesetzt.
Tajan hat dann den Planaritätstest, den ersten, der in Linearzeit läuft, entwickelt.
Und die algorithmischen Komponenten, die wir gerade gesehen haben,
also diese Beat-Zusammenhangs-Komponente ist eben Teil dieses Planaritätstests.
Das ist der erste Schritt, na, ist eigentlich der
zweite Schritt, Im Planaritätstest. Also sie zerlegen Zusammenhangskomponenten,
sie zerlegen B-Zusammenhangskomponenten, dann würde man die Tri-Zusammenhangskomponenten zerlegen.
Da gibt es auch nochmal eine tiefen Suche. Und innerhalb
der Tri-Zusammenhangskomponenten prüfen sie dann, ob sie diese K5 und K3-3-Subtivision finden.
Ja, und auch das kann man in der Nährzeit tatsächlich tun.
Und das hat Tajan gezeigt und er war damit der Erste,
der in der Lage war, Planarität von Graphen, ein Problem,
was als extrem schwierig angesehen wurde, in Linearzeit zu lösen.
Ja, durch Angucken des Grafen. Das werden wir hier nicht weiter betreiben,
das wird alles das ist sehr, sehr schwierig und verwirrend.
Aber sie haben die Gedankengänge, die dahinter sind, hoffentlich gesehen.
Und falls sie mal über Planare Grafen stolpern in ihrem Leben,
dann wissen sie, wo sie nachgucken können.
So, im Folgenden, wir verlassen die Tiefensuche noch nicht.
Wir haben noch ein bisschen was vor. Und zwar werden wir
uns die Tiefensuche jetzt auf Beispielen oder auf Grafen anschauen,
die gerichtet sind. Wir haben jetzt noch zwei weitere Anwendungen,
die wir uns anschauen. Und zwar als erstes wieder etwas,
was wahrscheinlich Im Rückblick ist für sie,
und danach gucken wir uns noch was an,
was wahrscheinlich kein Rückblick sein wird.
Das erste, was wir uns anschauen, ist topologische Sortierung.
Das ist etwas, was man klassischerweise, das heißt,
es gehört zu den ersten Problemen, die man so auf Grafen sich anschaut.
Und zwar möchte man gerne den Knotennummern geben,
sodass der im gerichteten Graben so las,
die kannten in dem Grafen immer nur von einer kleinen Nummer zu einer großen Nummer führen.
Topologische Sortierung ist ein super wichtiges Problem, das brauchen Sie beim Scheduling.
Also wenn Sie viele Aufgaben haben Sie müssen diese ganzen Aufgaben
erledigen und es gibt Abhängigkeiten. Das berühmteste Beispiel ist immer so,
wir bauen ein Haus und dann können sie eben ein Fenster nur einbauen,
bevor der Rohbau steht und so weiter.
Da gibt es ja ganz viele Abhängigkeiten zwischen den Aufgaben.
Und dann, wenn sie dann sich einen Zeitplan überlegen,
wann passiert was bei diesem Bau,
dann brauchen sie eine topologische Sortierung der einzelnen Schritte.
Okay, das ist eben ein Beispiel und so eine
topologische Sortierung, die muss ja nicht existieren.
Die existiert natürlich dann nicht, wenn es so zyklische Abhängigkeiten gibt.
Deswegen ist das Erste, was man mal feststellt, ist,
wir müssen erstmal gucken, ob der Graf überhaupt topologisch logisch sortierbar ist.
Und dazu muss er ein Deck sein, ein Deck.
Das ist ein gerichteter arzyklischer Graf, ein Directed Ascycly Graph.
Gut, wann ist ein Graph ein Deck? Können wir
das leicht feststellen? Das können wir ganz leicht feststellen.
Und zwar gibt es ein einfaches Kriterium. Ein Graph ist
genau dann ein Deck, wenn er keine Rückhanden hat.
Warum ist das so? Naja, gehen wir mal davon
aus, der Graph hat Rückhanden.
Naja, wenn er eine Rückhande hat,
dann sehen wir natürlich sofort
einen Zyklus
Sehen wir sofort einen Zyklus, weil wir haben ja Baumkanten,
ja, die sind jetzt gerichtet, unsere Baumkanten.
Und wenn wir eine Rückhande haben, ja,
die geht zum Beispiel hier hoch, dann haben wir natürlich sofort einen Zyklus,
weil wir hier von diesem Knoten, uh,
zu irgendeinem guten V gehen und dann haben wir diese Rückhande hier von U nach V und die bildet natürlich einen Zyklus.
Da steht hier, also, angenommen,
wenn G ein Deck ist und mal angenommen,
es gibt eine Kante E, das von U nach V,
wird sich gerade wieder falsch rumgemalt, von nach V, ja,
die eine Rückhandel ist, ja, also vom Typ Back,
dann wissen wir, V ist Vorgänger von U,
ja, V ist Vorgänger von U in unserem Baum,
im DFS-Baum und dann haben wir auch einen Zyklus,
nämlich über die Baumkarten von V nach O und über die Rückhandel von UNAV.
Gut, und andersherum, warum können wir keinen Deck haben,
wenn wir einen Zyklus haben, mal angenommen,
wir haben in dem Graphen einen Zyklus,
da ist das Bild hier schon da, wir kommen,
wir besuchen diesen Zyklus irgendwie ja in der Tiefensuche
Und zu irgendeinem Zeitpunkt sehen wir den ersten Knoten V1 dieses Zyklus.
Egal welcher das ist. Und dann haben wir, wie gesagt,
irgendwie einen Zyklus über andere Knoten zu einem anderen letzten Knoten,
der heißt jetzt VR. Und weil das ja ein Zyklus ist,
gibt es eine Kante von VR hier zu dem Knoten V1.
So, das ist unser Zyklus. Hier sind also viele
Knoten und Kanten und die Kanten laufen alle immer in diese Richtung.
Und ja, was ist jetzt der Fall,
wenn wir den Knoten V1 in den ersten Knoten dieses Zyklus,
dann wissen wir, dass alle Knoten auf diesem Zyklus hier,
die sind alle weiß
Die müssen alle weis sein, weil das ist ja
der erste Knoten, den wir von dem Zyklus sehen.
Dann haben wir unser Theorien der weißen Fahne, was sagt,
dass alle nachfolgenden Knoten von V1, die weiß sind,
auch im DFS-Algorithmus, Nachfolger im DFS-Baum werden.
Das heißt, alle Knoten hier sind Nachfolger.
Damit ist auch VR ein Nachfolger von V1 in dem Baum.
Und damit ist klar, dass die Kante von VR nach V1
eine Rückkante sein muss. Kann keine Kreuzkante oder etwas anderes sein.
Also wenn C von V1 bis VR plus 1 ein Zyklus ist,
Und V1, der Knoten, der zuerst besucht wird,
dann gilt zum Zeitpunkt Discovery-Zeit von V1,
dass die Farbe von allen weiteren Knoten des Zyklus Weißes nach
dem Theorien der Weißen Pfade sind dann V2 bis VR Nachfolger von V1. Wenn VR abgearbeitet wird,
ist V1 noch grau.
Der ist dann grau, wenn der betrachtet wird,
dann sehen wir die Kante von VR nach V1.
Sie zeigt auf einem grauen Knoten, damit ist es
eine Rückkante. Aber das ist eigentlich auch, ja,
es ist relativ nachvollziehbar,
dass Zyklen natürlich nicht topologisch sortierbar sind und dass wir
dann gerade kein Deck haben
Okay, was ist jetzt eine topologische Sortierung,
eine topologische Sortierung in einem Grafen, ist eine bijektive Funktion,
die jedem Knoten eine Zahl von eins bis Anteil der Knoten zuordnet und die die Eigenschaft hat,
dass wenn zwei Knoten mit einer Kante verbunden sind,
also von V nach W, dann gilt,
dass P von V kleiner ist als P von W. Gut,
wie können wir die topologische Sortierung jetzt relativ leicht berechnen,
was auf alle Fälle sinnvoll ist,
sich mal Knoten anzugucken,
wo keine Kante hineingeht
Und das sind sogenannte Quellen, ja,
die haben den Indigree 0 oder Knoten, wo keine Kante herausgeht,
das sind sogenannte Senken, die haben den Outing-Green 0.
Für uns sind die Quellen gerade interessant. Okay, so,
zunächst mal können wir feststellen, dass wir NG eine topologische Sortierung haben,
dass sie existiert, wenn G kein Zyklus enthält, das heißt,
wenn es ein Deck ist, Warum ist das so? Naja,
mal angenommen, wir haben eine topologische Sortierung und angenommen,
wir haben einen Zyklus, dann widerspricht sich das natürlich,
denn P von V1,
dem ersten Knoten des Zyklus,
muss ja kleiner sein als P von V2,
weil dann eine Kante existiert von V1 nach V2 und das
ist dann kleiner als P von V3 und so weiter und dann kommen wir irgendwann wieder bei P von V1 raus und dann haben wir einen Widerspruch, das kann also gar nicht sein.
So, wenn wir keinen Zyklus haben,
müssen wir jetzt noch zeigen,
dass tatsächlich immer
eine topologische Sortierung existiert
Intuitiv ist es so, dass man das eigentlich glaubt,
dass man sagt, ja klar,
es gibt ja nichts, was irgendeiner Ordnung widersprechen würde.
Aber es ist nicht so trivial zu sehen, wie die andere Richtung.
Okay, wir müssen also zeigen, es gibt immer eine
topologische Sortierung und das machen wir in einem konstruktiven Beweis.
Wir werden einfach zeigen, wie wir sie berechnen können.
Und wenn wir dann zeigen, dass diese Berechnung korrekt ist,
dann müssen wir auch, dass die topologische Sortierung existiert.
Der erste Schritt unseres Verfahrens ist, dass wir
einen Knoten suchen mit Eingangsgrad null, also eine Quelle.
Wie können wir eine Quelle finden in einem Grafen?
Also so eine Quelle ist ja gerade so ein Knoten, wo nichts reingeht.
Ja, also wir haben hier V und da gehen viele Kanten raus,
aber es geht eben keine rein, ja.
Wie können wir die finden? Das ist relativ einfach,
was wir machen ist, wir bilden mal den transponierten Grafen.
Das heißt, wir drehen alle Kanten um, ja, in der Richtung.
Und dann in dem Augenblick wird eine Quelle zu einer Senke,
ja, also es laufen nur Knoten hinein.
Und wie können wir die jetzt finden?
Wir gehen einfach zu einem x-beliebigen Knoten in unserem Grafen,
irgendwo in unserem Grafen, haben wir einen Knoten X,
ja, und von X aus laufen wir einen x-beliebigen Pfad nachfolgenden Knoten.
Das machen wir hier. Wir wählen also einen beliebigen Knoten V
aus. Solange die Atjazzenzliste von Tee nicht leer ist,
nehmen wir eine beliebige Kante. Das ist jetzt hier
die erste x-beliebige Kante, die den Knoten V verlässt.
Und dann machen wir an einem Knoten V weiter.
Also in der Nomenklatur mit dem Algorithmus wäre das jetzt
eben V und im nächsten Schritt ist das jetzt,
das ist also die Kante von V nach W, die wir betrachten.
Und dann wird das jetzt zu V und dann würden wir
diesen Knoten angucken und so weiter.
So und irgendwann kommen wir mal zu einer Situation,
wo die Addizenzliste leer ist. Wenn die Addizenzliste leer ist,
dann haben wir quasi eine Senke gefunden, weil eine Lehrer-Addizenzliste bedeutet,
es gibt keine Kante, die diesen Knoten verlässt.
Gut, und das Einzige, was wir zeigen müssen,
ist, dass dieser Algorithmus sie auch terminiert, aber warum terminiert er?
Ja, der terminiert natürlich nach höchstens V-Schritten,
weil wenn wir mehr als V-Schritte haben, also Anzahl Knoten,
dann haben wir irgendeinen Knoten zweimal gesehen.
Ja, und wenn wir einen Knoten zweimal gesehen haben,
dann haben wir einen Zyklus gefunden.
Und Zyklus existiert nicht. Deswegen terminiert dieser Algorithmus auf
alle Fälle
So, und wie können wir jetzt die topologische Sortierung angeben?
Oder wie können wir zeigen, dass eine existiert?
Wir machen eine Induktion. Wir werden also immer sukzessive einen
Knoten entfernen. Und dann haben wir irgendwann mal einen Graf,
der nur aus seinem Knoten besteht.
Dann wissen wir, die topologische Sortierung existiert. Das ist ja,
da kriegt der eine Knoten die Nummer eins und alles ist gut.
Ja, das ist der Induktionsanfang. Und jetzt machen wir
einen Induktionsschritt. Und bei dem Induktionsschritt ist das Entscheidende,
dass wir uns einen Knoten raussuchen und zwar einen Knoten mit Indigree 0.
Das heißt, wir suchen uns einen Knoten raus,
der eine Quelle ist. Also da haben wir jetzt eine
Quelle und hier haben wir den ganzen Rest des Grafen.
So, das ist jetzt also der Graf-G-Strich und da
haben wir den einen Knoten-U rausgenommen aus dem Graf.
Ja, das ist ein Knoten, der Eingangsgrad null hat.
Es gibt also keine Kante, die hier irgendwie so hier
irgendwie so rumläuft hier rein, die gibt es nicht.
Okay. So, das ist so ein Knoten-U gibt,
haben wir gerade schon gezeigt, den muss es immer geben, ja.
Und der Graf-G-Strich hier, der hat ja eine,
ein Knoten weniger als der Graf,
den wir vorher hatten
Ja, und in der vollständigen Induktion machen wir es ja immer so,
dass wir ein Resultat schließen aus der Situation,
dass wir eine um eins verkleinerte Eingabe haben.
Und das haben wir hier, der Graf hat nämlich einen Knoten weniger.
Und auch Induktionsvoraussetzungen existiert jetzt hier eine topologische Ordnung und die nennen wir P-Strich.
Also da haben die Atome alle ihre Nummer. Und wenn
wir hier zwei beliebige Knoten drin haben und wir haben eine Kante,
dann wissen wir,
dass also P-Strich von dem Knoten hier X und wie hätten wir dann das Y,
dann wissen wir, dass das auf alle Fälle kleiner ist als das.
Ja, also P-Strich von dem YX es ist immer kleiner
als das B-Strich von dem Y, weil diese topologische Sortierung existiert für den Grafen.
Ja, wie können wir jetzt eine topologische Sortierung für den gesamten Grafen,
der gesamte Graf inklusive diesem Knoten, uh hier bestimmt,
das ist ganz einfach.
Wir sagen einfach, der Knoten-uh hier,
der kriegt in der neuen topologischen Sortierung den Wert 1.
Der kriegt die kleinste Nummer. Damit können wir nicht viel verkehrt machen,
weil da geht ja keine Kante rein. Und alle anderen Knoten,
die jetzt ja in dem G-Strich vorkommen, also diese hier,
die, wenn wir hier so einen Knoten X haben,
dann würden wir das P von X setzen auf eins plus das P-Strich von X.
Also der kriegt eine um eins höhere Nummer, als er vorher gehabt hat.
Das ist die Art und Weise, wie wir es definieren.
Okay, und dann können wir ganz leicht zeigen,
dass ein so definiertes P, eine topologische Sortierung ist.
Warum? Weil es gibt drei Fälle.
Entweder wir haben eine Kante von U nach V,
also von U nach V hier in den Grafen hinein.
Und wenn das so ist, dann wissen wir,
U hat ja die Nummer eins, dann ist sowieso das Minimum.
Eins ist also kleiner als eins plus P-Strich von V,
weil die P-Strich ist ja irgendwas zwischen eins N minus 1 oder V minus 1.
Und das ist die neue Nummer.
Also das heißt, solche Kanten erfüllen die topologische Sortierung.
Wenn wir eine beliebige Kante innerhalb dieses Graphen haben, ja,
da haben wir ja quasi zur Berechnung von Pi auf beiden Seiten eins dazu addiert.
Und weil das ja vorher eine topologische Sortierung war, ist es nachher auch eine.
Das heißt, Pi von V ist 1 plus Pi-Strich von V,
das ist kleiner als 1 plus Pi-Strich von W.
Und das ist das Pi von W, ja. Okay,
und ja, eine Kante, die von V nach U führt,
das war diese, die ich gerade schon wieder weggewischt habe,
so eine, ja, die kann eben nicht existieren,
weil der Knoten U ja, eine Quelle war.
Und da geht keine Kante rein, die gibt es also nicht. Okay,
damit haben wir eine topologische Sortierung und dieser Beweis hier ist,
wie gesagt, konstruktiv und hinter dem verbirgt sich ja ein Algorithmus, ein Verfahren.
Und für meinen Geschmack ist es auch der eleganteste Algorithmus, den man hat, um eine topologische Sortierung zu berechnen. Ja,
es ist so, finde ich, persönlich,
also der ist jetzt nicht schneller oder lang,
das ist ein Linearzeitalgorithmus, den man hier braucht,
was man da ja ganz einfach machen kann,
um das zu berechnen, ist,
dass man sich ein ein Array erzeugt, ja, ein,
das kann man Deck für Degreen nen oder für Indigree,
ist noch besser,
hört man Array und da hat man hier die entsprechenden Knoten,
V und für jeden Knoten speichert man hier den Eingangsgrad.
Wie viele Knoten gehen da rein? Und dann wird es hier
in diesem Array ein paar Einträge geben, die sind null,
da muss es mindestens einen geben, der null ist,
das ist nämlich da eine Quelle und diese Quelle,
da können wir da eine Nummer geben und zu diesem Knoten,
hier gibt es eine Addizenzliste, Für diesen Knoten V.
Und was wir jetzt einfach machen können, ist,
wenn wir den Knoten eine Nummer gegeben haben,
dann gehen wir die Addizenzliste durch und verringern hier die Indigreed-Sähler
und dann finden wir die nächsten Quellen.
Das ist einfach eine... Und dann können wir den nächsten nehmen,
der die Nummer 0 hat, der kriegt dann die nächste Nummer und so weiter.
Das ist quasi die direkte Umsetzung, dieses Beweis in einen Algorithmus.
Man kann das Ganze aber auch mit einer Tiefensuche machen,
und zwar, indem man sich die Finishing-Zeiten anschaut.
Also, das geht wie folgt. Und zwar bauen wir
uns eine Liste auf und die Liste,
die wir hier erzeugen,
wird eine topologische Sortierung der Knoten sein
Und wir machen das Ganze, ja, mit einer normalen Tiefensuche,
fürs wieder alles Schwarze, ist normale Tiefensuche.
Wir initialisieren alle Knoten, hier benutzen wir ja die Farbe, die sind alle weiß.
Wir haben eine Liste L, illitopologische Sortierung enthält,
die ist am Anfang leer. Wir betrachten alle Knoten,
die Knoten, die wir noch nicht gesehen haben,
die sind weiß und gerufen rekusiv die Funktion auf.
Wir färben den Knoten grau, weil wir ihn betrachtet haben,
wir sondieren die Addizenzliste, wenn der Knoten weiß,
das haben wir eine Baumkante, wir machen den rekusiven Aufruf.
Anderenfalls ist der Knoten eine Schauen wir,
ob die Farbe dieses Knotens grau ist,
wenn die Farbe dieses Knotens grau ist,
dann haben wir eine Rückkante detektiert, wenn wir eine Rückkante detektiert haben,
dann wissen wir, dass wir einen Fehler haben,
dann ist der Graf kein Deck.
Ja, wir haben einen Zyklus gefunden und dementsprechend können wir
den auch nicht topologisch sortieren. Dann gibt es auch eine Fehlermeldung,
anderenfalls läuft irgendwann mal der DFS durch,
wir haben alle Kanten sortiert, sondiert,
der Knoten wird schwarz gefärbt und das ist der Augenblick,
wo wir normalerweise die Finishing-Zeit festlegen.
Und in diesem Augenblick hängen wir den Knoten in die Liste und
das Entscheidende an dieser Inside-Operation hier ist, was man wissen muss,
ist, dass der Die Inside-Operation an der Liste vorne anfügt.
Also das Element U wird vorne in die Liste reingehängt.
Das steht ja auch, aber sicherheitshalber. Eine ältere Version
vom Script steht ja auch, glaube ich, noch nicht diese objektorientierte Variante.
Da steht, glaube ich, noch Inside-Head oder so,
um das nochmal klarzumachen. Gut. Tja,
das ist die eine Vorlesung aus meiner A&D-Vorlesung,
an denen sich alle erinnern, selbst nach 20 Jahren interessanter,
so lange mache ich die ja noch nicht.
Aber letztens hatte ich einen,
der hat das irgendwie vor x Jahren gehört,
der meinte ja, die Folie weiß ich noch.
Das Deck-Beispiel,
das ist auch aus dem Kommen,
habe ich
mich selber ausgedacht
Woran man sehen kann, dass das so funktioniert.
Hier geht es nicht um Häuser bauen,
sondern um ein täglicheres Problem, nämlich anziehen.
Und sie sehen hier diverse Dinge, die sie anziehen können.
Und ohne, dass sich diese Kanten hier diskutieren will,
nehme ich mal an, dass die alle korrekt sind.
Meistens gibt es nämlich mal irgendeinen, der irgendeinen dieser Kanten noch anzweifelt.
Und wenn sie dann diesen Graphen hier haben,
dann können sie darauf eine Tiefensuche laufen lassen.
Und jetzt wieder der eine wichtige Satz,
den ich jetzt schon zehnmal gesagt habe, Die Reihenfolge,
in der wir die Knoten besuchen, ist nicht deterministisch.
Es kann was Unterschiedliches rauskommen. Wir können mit jedem beliebigen
Knoten starten. Das heißt, die Discovery- und Finishing-Zeiten,
die hier immer an den Kanten, an den Knoten dranstehen hier,
die können unterschiedlich sein. Und trotzdem ist es so,
dass wenn sie eine Sequenz von den Knoten haben mit fallenden Finishing-Zeiten,
ist es immer eine topologische Sortierung.
Und in meinem Beispiel hier bin ich,
habe ich mir immer bei dem Hemd gestartet und dann kommt
hier die Krawatte und dann das Jackett und dann geht man
wieder hoch in der Gürtel
Ist dann eine Vorwärtskante und dann 8, 8, 8,
wo ist denn 8? Achso, dann geht man hier,
ist man dann bei dem Hemd zurück,
ist die Finishing-Zeit und dann hat man ja die Uhr 9,
10, dann fängt man hier oben an, 11,
12, dann kommt die Schuhe, 13, Finishing-Zeit,
14 und so weiter.
Und dann haben sie Discovery und Finishing-Zeiten und wenn sie dann eine Sequenz fallender Finishing-Zeiten betrachten,
sehen sie hier unten, dann haben sie eine topologische Sortierung,
die man immer sehr schön daran erkennt,
dass alle kannten, von links nach rechts verlaufen.
Ja, es gibt keine Kante in der umgekehrten Richtung
Gut, da muss man noch zeigen,
dass das auch richtig ist, wenn man so eine topologische,
also wenn man so eine Reihenfolge berechnet.
Und die Aussage ist jetzt also,
dass unser Algorithmus korrekt ist und etwas genauer formuliert,
könnte man sagen, dass wenn wir Knoten nach fallenden Finishing-Zeiten sortieren,
dann bekommen wir eine topologische Sortierung in einem Deck.
Das ist quasi das,
was sich hinter der Korrektheit,
dieses Algorithmus verbirgt
Okay, also, wir wissen, dass die Liste L,
die Knoten U in der Reihenfolge fallender Endzeiten,
also Finishing-Zeiten enthält, weil wir jeweils nach der Bearbeitung des Knotens,
wenn wir die Finishing-Zeit setzen,
den Knoten vorne in die Liste einfügen.
Okay, wir betrachten eine beliebige Kante,
UV und was wir zeigen müssen, ist,
dass die Finishing-Zeit von U immer größer ist als die Finishing-Zeit von V.
Bei jedem Beweis ist es immer sinnvoll, erst mal einmal darüber nachzudenken,
ob ein intuitiv klar ist, warum das so ist.
Ja, das ist immer besser, als wenn man gleich versucht,
so formal durchzugehen. Warum muss das intuitiv so sein?
Wir haben so eine Kante von U nach V und warum
muss die Finishing-Zeit von U immer größer sein als die von V?
Naja, dann können wir natürlich jetzt die ganzen Fälle durchgehen,
das werden wir gleich auch tun. Aber so intuitiv ist es natürlich so,
wenn wir diese Kante haben, dann können wir,
bevor wir die Finishing-Zeit von V festlegen,
bevor wir die Finishing-Zeit von U festlegen,
werden wir ja diese Kante irgendwie betrachten von U nach V.
Und dementsprechend werden wir irgendwie V bearbeiten,
bevor wir U endgültig abschließen können,
weil es
diese Kante gibt
Und deswegen muss natürlich die Finishing-Zeit von U immer größer sein
als sie von V. Das war jetzt das intuitive Argument.
Formell unterscheiden wir natürlich die Kantentypen. Wir haben entweder
eine Baumkante, wenn wir eine Baumkante haben,
dann sehen wir Erstknoten U und dann nach Knoten V.
Dann wird Knoten V vollständig abgearbeitet, dann entsprechend Knoten U abgeschlossen.
Wir können auch mit dem, das kann man aus dem Klammerungstheorium sofort sehen.
Und dann ist die Discovery-Zeit von U kleiner als die von
V und die ist kleiner als die Finishing-Zeit von U.
Die Intervalle überlappen nicht. Sie sind vollständig ineinander enthalten.
Und deswegen ist die Finishing-Zeit von U auch größer als
die Finishing-Zeit von
Wenn wir eine Vorwärts- oder eine Kreuzkante haben, dann wissen wir,
dass zu dem Zeitpunkt, wo wir den Knoten,
wo wir die Kante hier sehen, die Knoten V,
wo wir hingucken, schon schwarz ist.
Das war das Kriterium für eine Vorwärts- oder eine Kreuzkante,
dann ist der Knoten schwarz. Wenn der Knoten schwarz ist,
dann hat er ja schon eine Finishing-Zeit. Das heißt,
die Finishing-Zeit von V wurde schon gesetzt, zu dem Zeitpunkt,
wo wir die Kante sehen, wenn wir die Kante angucken,
da sondieren wir gerade die Liste,
die Adjazzenzliste von dem Knoten U und die ist dann auch nicht abgearbeitet,
deswegen ist die Finishing-Zeit noch nicht gesetzt und deswegen ist F&U
größer als
Bleibt nur noch der Fall der Rückkante,
den brauchen wir aber gar nicht betrachten, weil wir wissen ja,
dass in einem Deck Rückkanten nicht vorkommt.
Deswegen gibt es die Kanten nicht. Und deswegen kann das
entsprechend auch nicht vorkommen. Und deswegen müssen wir fallende Finishing-Zeiten,
liefern uns immer eine topologische Sortierung.
Gut, das dazu. Und das war quasi nochmal der
Rücksprung zu topologischen Sortierungen. Und nun schauen wir uns nochmal
den starken Zusammenhang an
Das ist auch was, was man typischerweise,
ich weiß ich gar nicht so ganz genau,
es kommt so ein bisschen darauf an,
bei wem man die Algorithmen und Datenstruktur macht.
Ich mochte das auch immer schon in den Algorithmen und Datenstrukturen.
Da zeige ich diesen Beweis auch schon. Es geht um einen starken Zusammenhang. Der Grund,
warum ich diesen Algorithmus nochmal zeige, ist,
ich will ihnen im Anschluss einen anderen Algorithmus zeigen für starken Zusammenhang,
der zwar etwas komplexer ist als dieser, aber einen großen Vorteil hat.
Dazu muss ich ihnen erstmal den Nachteil von diesem Algorithmus sagen,
dann sage ich ihnen auch, was der Vorteil des anderen ist.
Okay, Wiederholung starker Zusammenhang, was ist starker Zusammenhang?
Wir haben eine Pfadrelation, UV, das bedeutet,
es existiert ein Part von U nach V. Wenn wir eine Menge C von Knoten haben,
die jeweils paarweise mit Pfaden verbunden sind,
also für alle Knoten, UV, dieser Komponente C gilt,
es gibt einen Pfad von U nach V und einen von V nach U,
dann bezeichnen wir das als starke Zusammenhangskomponente.
Und das erste, was man dann zeigen muss, ist,
dass diese Struktur der starken Zusammenhangskomponenten auch korrekt und sinnvoll ist. Das heißt,
das könnte man entweder machen, indem man ja,
da müsste man jetzt so eine,
man könnte diese Relation noch erweitern in eine Relation, wo man sagt,
es gibt ein Pfad von UNAV und von V nach U,
ja, und dann könnte man zeigen,
dass das eine Äquivalenzrelation ist, wäre die eine Möglichkeit,
oder man macht es halt direkt und zeigt dann die Knotenmenge V,
zerfällt in eine Menge des Jungter starker Zusammenhangskomponenten.
Da muss man nur zeigen, dass keine Situation eintreten kann,
dass in einer starken Zusammenhang oder in zwei starken Zusammenhangskomponenten ein Knoten drin ist,
Ja, der also zu beiden gehört.
Also das ist die eine Situation, die nicht auftreten darf.
Wir haben eine Komponente C1 und eine Komponente C2 und hier in
der Mitte ist ein Knoten-V. Das Szenario darf es nicht geben,
weil wenn das das nicht gibt, dann wissen wir,
dass wir einen sauberen Zerfall des Grafen in starke Zusammenhangskomponenten gibt.
Aber den kann es natürlich auch nicht geben,
das ist ganz offensichtlich,
denn wir können einen beliebigen Knoten U nehmen hier aus dieser Komponente C1 und dann wissen wir,
weil ja U und V in der gleichen Komponente sind.
Es gibt also einen Pfad,
der uns von U nach V führt und es gibt auch
einen Pfad von V nach
Und wir können auch noch einen beliebigen Knoten hier in der Komponente C2 nehmen.
Und dann wissen wir, es gibt einen Pfad von W
nach V und es gibt einen Pfad von V nach W,
weil die ja auch in der gleichen Komponente sind.
Und deswegen gibt es automatisch auch einen Pfad,
der uns von U nach V und von V nach W
führt und wieder zurück von W nach V und von V nach U.
Und deswegen gibt es Pfade von U nach W und von W nach U. Und deswegen gehören W und U zur gleichen Komponente.
Und weil das für alle Knoten gilt,
sind C1 und C2
somit identisch
Damit ist, kann man zeigen, dass der Zerfall quasi in solchen Komponenten korrekt ist.
Ja, die nächste Eigenschaft, die man sehen kann,
ist, dass ein Graf und der Graf, der entsteht,
in dem man alle Kanten umdreht, die gleichen starken Zusammenhangskomponenten hat.
Das muss man nicht beweisen, aber es werden wir im Folgenden brauchen. Das heißt,
wenn wir alle Kanten umdrehen,
dann ist natürlich aufgrund der Symmetrie dieser Zusammenhangseigenschaft,
es muss ein Pfad von UNAV und von V nach U bestehen,
wenn ich alle Kanten umdrehe,
dann wird der Pfad von UNAV zu einem Pfad von V
nach U und umgekehrt,
entstehen natürlich genau die gleichen starken Zusammenhangskomponenten
Und dann die dritte Eigenschaft, die man dann sehen kann,
ist, dass wenn wir diese starken Zusammenhangskomponenten mal als Knoten verstehen,
wir fassen die zu allen großen Knoten zusammen,
dann ist es so, dass der dann resultierende Graf,
die Kanten, die wir dann haben,
sind halt genau die Kanten, die zwischen Knoten verlaufen,
die zu verschiedenen Zusammenhangskomponenten gehören, dass der ein Deck sein muss.
Wenn ich alle Knoten zusammennehme und sage, okay,
das ist die eine Komponente und das ist dann die nächste Komponente
hier und dann haben wir noch eine Komponente,
dann wird es zwischen denen ja immer noch Kanten geben
Kannst du eben wie einen Knoten hier geben, der hat noch eine Kante nach hier oder der hat auch noch eine Kante nach hier. Aber dass dieser Graf,
der dann entsteht,
wenn das einzelne Knoten sind und das in Kanten,
das muss ein Deck sein,
weil wenn wir hier einen Zyklus hätten,
ja, wenn wir einen Zyklus hätten,
dann könnten wir ja jeden Knoten von C1,
von jedem Knoten von C1 aus jeden in C2 erreichen und von C2 auch über C3 wieder jeden Knoten in C1. Deswegen muss dieser starke Zusammenhangsgraf,
den man hat, dieser G-SCC,
der Komponentengraf ein Deck sein.
Das sind drei ganz wichtige Eigenschaften von starkem Zusammenhang
So, hier ist ein Beispiel, an dem man den Algorithmus gleich
ganz gut verfolgen kann. Sie kennen schon die Nomenklatur,
wir haben also eine Reihe von Knoten, in den Knoten stehen wieder Discovery und Finishing-Zeiten.
Auch hier hätte man jeden beliebigen anderen Durchlauf wählen können,
ich habe den gewählt, den sie hier sehen,
also wenn mit diesem Knoten C angefangen, hier runtergegangen,
zu G, zu F, dann hat der nur einen gehende Kanten,
dann hat der hier die Finishing-Zeit 4 gekriegt,
zurück zu dem Knotens und dann geht der Wo ist jetzt eine 3,
4?
Achso, hier rüber, 5, 6, dann Finishing Zeit 7,
hier hoch, 8, 9, Finishing Zeit 10.
Und dann hat der Algorithmus hier neu angefangen bei dem noten B,
ist hier runtergegangen, nach 12, hier hoch,
nach 13 und dann hier die Finishing Zeiten gesetzt.
Die rot unterlegten Kanten, hier sind entsprechende Baumkanten.
Okay, jetzt können wir den Grafen transponieren.
Wir drehen einfach alle Kanten um. Ja, so,
die haben wir jetzt alle umgedreht. Und ich sage jetzt
noch nicht, was die Färbung und so das soll.
Das hat gleich was damit zu tun,
wenn ich Ihnen sage,
was rausgekommen ist
Und wenn wir aber jetzt den,
also die Komponenten sind auf alle Fälle identisch,
wenn wir jetzt den Komponentengraffen bilden,
dann hätten wir hier einen Knoten für die Komponente,
der enthält die Knoten A-B-E, dieser CD und so.
Und dann sehen wir bezogen auf den ursprünglichen Grafen hier oben,
gibt es Kanten zum Beispiel von dem Knoten A-B-E zu dem Knoten CD,
ja, diese Kante, die entsteht,
weil hier eine Verbindung zwischen B und C ist und so weiter.
Und dieser Grafie ist ein Deck,
ja, der muss ein Deck sein.
Und es ist natürlich auch einer, sehen hier keine
Zyklen.
Ja, okay
Es gibt zwei Varianten, zwei relativ bekannte Varianten,
wie man mit einer tiefen Suche eine, die starken Zusammenhangskomponenten berechnen kann.
Und die erste Variante, die funktioniert wie folgt,
die nutzt gerade diese Invertierung und wie geht sie folgt?
Wir machen zwei DFS-Durchläufe, dem ersten DFS-Durchlauf ordnen wir
die Knoten nach fallenden Finishing-Zeiten. Das kennen Sie schon,
haben wir eben gerade auch schon gemacht für die topologische Sortierung.
Und in dem zweiten Durchlauf starten wir dann einfach ein DFS
und der Clou ist jetzt, wenn wir das machen,
Und dann kriegen wir mit jedem Aufruf von DFS Visit
auf der Hauptebene genau eine starke Zusammenhangskomponente.
Okay, das heißt, der Algorithmus sieht insgesamt so aus,
dass es der Start der ersten DFS-Durchlaufs,
alle Knoten werden weiß, Zeit wird auf null gesetzt.
Dann laufen wir hier durch alle Knoten.
Wenn die weiß sind, besuchen wir die Knoten.
Wir starten den DFS Visit-Durchlauf und wir werden in diesem RAF
von V, werden wir die Finishing-Zeiten FV.
Da werden wir die Finishing-Zeiten reinschreiben,
weil wir die nämlich noch brauchen im anschließenden Algorithmus.
Da kommen die Finishing-Zeiten raus
Dann kommt der Schritt 5 in Word Edges.
Also jetzt müssen wir quasi alle Kanten umdrehen. Und wenn wir
alle Kanten umgedreht haben, dann machen wir einen neuen DFS-Durchlauf.
Wir initialisieren wieder, ihr fehlt eine Klammer. Wir initialisieren
wieder alle Knoten weiß. Dann setzen wir diese SCC-Nummer 110.
Das wird unser Zähler, mit dem wir die markieren.
Und jetzt sieht diese Schleife hier ein bisschen anders aus.
Normalerweise ist ja die Reihenfolge, in der wir die Knoten
besuchen, hier völlig egal.
Wir gehen irgendwie durch die Knotenmenge durch.
Das machen wir jetzt nicht
Und zwar gehen wir in einer ganz festen Reihenfolge dadurch,
und zwar mit fallenden Finishing-Zeiten, die Finishing-Zeiten, haben wir gespeichert.
Genau genommen haben wir in diesem Array-Pointer gespeichert auf die Knoten nach den Finishing-Zeiten,
also FV von Eastern, einen Zeiger auf einen Knoten.
Und wir nehmen fallende Finishing-Zeiten,
also die Vorschleife geht von der Knotenmenge runter bis eins,
wenn der Knoten noch nicht besucht wurde,
erhöhen wir die SCC-Nummer hier und markieren alles,
was wir sehen,
in diesem rekrusiven Aufruf
Ja. Okay, wie sieht der DFS-Visit-Aufruf aus? Das ist streng genommen, sind das zwei, die habe ich in einem verpackt. Es gibt den ersten Durchlauf,
da wird dieses Array FV initialisiert, ja,
also es ist ein Standard-DFS-Durchlauf und an dieser Stelle wird in FV von Time,
also es ist der Zeitzähler drin,
wird quasi der Verweis auf den Knoten U gesetzt,
dann können wir quasi danach in sortierter Reihenfolge auf die Knoten zugreifen,
ja,
und in dem anderen Durchlauf markieren wir einfach den Knoten hier als zugehörig zu dieser entsprechenden Komponente,
Beachten Sie, dass wir durch dieses Array,
dass Array ist eine geschickte Maßnahme, um das Sortieren einzusparen.
Wir müssen dich sortieren. Wir machen quasi implizit so eine Art Bucketsword.
Weil wenn wir sortieren müssten, dann hätten wir eine N-Log-in-Laufzeit.
Ja, wenn wir jetzt sagen würden,
wir machen jetzt hier einen Quick-Sword oder so auf den Tiefensuchzeiten,
dann hätten wir keine Linearzeit mehr.
Dann hätten wir N-Log-in oder sogar ein Quadrat für eine schlechten implementierten Quicksword.
Also Zeit für die Sortierung und die Tatsache,
dass wir in diesem Array direkt an die Position schon das Richtige reinschreiben,
sortieren wir implizit mit einem Linearzeit-Sortierverfahren.
Okay, gut, das dazu. Dann Kanten umdrehen,
ist, glaube ich, klar. Wir gehen durch die Attialzenzlisten
durch und für jede Attazenzliste bauen wir eine neue, transponierte Attazenzliste,
indem wir dann die Knoten da entsprechend einfügen und dann schalten wir das rum.
Ja, und mit dem Verfahren können wir in O
von V plus E-Zeit den starken Zusammenhang berechnen
Wir müssen uns noch anschauen, warum der korrekt ist,
aber weil wir jetzt gerade hier auf den Algorithmus drauf schauen,
wollte ich erst mal jetzt motivieren,
warum es sich überhaupt lohnt, noch einen anderen Algorithmus anzuschauen.
Warum das nicht ausreicht? Warum würde ich sagen, ist
doch ein toller Algorithmus. Zum einen muss man sagen,
der Algorithmus ist jünger als der, den ich Ihnen nachher zeigen werde.
Der besticht durch seine Einfachheit. Der ist einfach relativ simpel.
Das ist super leicht zu programmieren. Er hat nur einen
ganz gravierenden Haken und der gravierende Haken ist Schritt 5. Dieses Umdrehen der Kanten,
das ist eine ganz üble Geschichte,
weil normalerweise bei ganz vielen Anwendungen ist der Graf statisch,
ja, den können sie nicht einfach so leicht verändern.
Wenn sie natürlich ein Beispiel haben,
wo ihr Graf 15 Knoten und entsprechend ein paar Kanten hat,
dann können sie den einfach kopieren und die Kanten umdrehen oder so.
Das ist völlig unkritisch. Aber starker Zusammenhang interessiert uns
in der Regel auf sehr großen Grafen.
Ja, wir wollen sehr große Agrafen angucken, ja.
Wir wollen das Freundschaftsnetzwerk von Facebook analysieren. Ja,
wir haben Facebook Kanten, wo von einer Person steht,
den kenne ich, ja. Das heißt aber nicht,
dass bei dem anderen auch eine Kante steht,
wo sagt, den kenne ich, ja,
das geht nicht.
Es gibt nicht immer Rückhanden in solchen Netzwerken
Und dann haben sie Grafen mit 10, 100 Millionen Knoten und vielleicht Milliarden Kanten.
Und wenn ihnen dann einer sagt, der Algorithmus,
ja, dann drehen sie doch mal alle Kanten um.
Dann haben sie ein echtes Problem. Das heißt, die Grafen,
gerade große Grafen, sind typischerweise statisch und sie haben diese Rückhandeninformationen nicht.
Wenn sie sich zum Beispiel die Vernetzung von Wikipedia-Dokumenten angucken,
da haben sie Links, da haben sie haufenweise Links,
das ist nichts weiter als Kanten zwischen Dokumenten,
aber sie haben die Rücklinks nicht.
Und die Rücklinks können sie natürlich berechnen mit diesen Verfahren,
aber sie brauchen Unmengen an Speicher,
um das zu tun
Das ist der ganz große Nachteil,
warum dieser Algorithmus dann nichts eingesetzt werden kann.
Und wofür man dann den anderen Algorithmus braucht.
Er hat auch einen ganz tollen Vorteil, kann ich Ihnen
auch gleich sagen. Dieser Algorithmus hat nämlich die tolle Eigenschaft,
dass er die Komponenten in topologischer Sortierung ausgibt.
Das heißt, die kommen da in topologischer Sortierung aus.
Wir werden am Ende diesen Knoten als allererstes erkennen und dann
entsprechend weiß ich es gar nicht, den und dann den und dann den, ja.
Was für viele Anwendungen wiederum enorm praktisch ist,
weil sie die Komponenten dann intopologischer Sortierung weiter prozessieren
Wenn sie zum Beispiel riesige,
kürzeste Wegeprobleme lösen wollen und sagen sie, ja,
da gucke ich mir erst mal die Komponenten an und berechne da die kürzesten Wege.
Da können sie Zeit sparen, dann müssen sie danach die Knoten intopologischer Sortierung prozessieren.
Und der Algorithmus würde das liefern. Das ist ein echter Vorteil. Ja,
okay, was jetzt also noch fehlt, ist,
dass wir sehen, dass dieser Algorithmus tatsächlich auch korrekt ist,
dass wir das Ganze so tun können.
Und der Beweis ist wieder so ein bisschen kniffelig.
Also der Algorithmus ist leicht, aber zu sehen,
dass er korrekt ist, ist immer ein bisschen kniffelig.
Jetzt tun wir also das folgende. Wir gucken uns Seiten an,
Discovery und Finishing-Zeiten und über die können wir argumentieren,
dass der Algorithmus korrekt ist.
Dabei muss man jetzt an einer Stelle enorm aufpassen und zwar im folgenden,
wenn wir von Discovery und Finishing-Zeiten reden,
reden wir immer von den Discovery- und Finishing-Zeiten des ersten DFS-Durchlaufs.
Wir haben ja zwei. Also wir haben hier den ersten DFS-Durchlauf und
hier den zweiten und streng genommen haben die natürlich beide Discovery und Finishing-Zeiten,
aber für den Beweis interessieren uns immer nur Discovery und Finishing-Zeiten dieses ersten.
Also das heißt, alles immer,
wenn sie Discovery und Finishing-Zeiten hier lesen oder gleich hören,
bezieht sich das immer auf die Zeiten des ersten DFS-Durchlaufs
Auch wenn wir vielleicht gerade im Zweiten sind. Gut,
wir definieren uns noch was. Und zwar definieren wir uns
eine Discovery-Zeit für eine Menge von Knoten, indem wir einfach sagen,
das ist die minimale Discovery-Zeit aller Knoten da drin.
Und wir definieren uns auch eine Finishing-Zeit für eine Menge von Knoten,
indem wir sagen, das ist die maximale Finishing-Zeit der Knoten dieser Menge.
Ja, das entscheidende Lämmer, was wir hier haben,
ist das folgende. Wenn wir zwei Komponenten haben,
also zwei starke Zusammenhangskomponenten, die heißen jetzt C- und C-Strich
in einem Grafen
Unterschiedliche, ja, also klar, C- und C-Strich,
zwei verschiedene Komponenten. Dann gilt für alle Kanten,
die von U nach V verlaufen,
das und U ist in der ersten Komponente C und V ist in der zweiten Komponente C-Strich,
dass die Finishing-Zeit der Komponente C größer ist als die Finishing-Zeit der Komponente C-Strich.
Das haben wir in abgewandelt davor, haben gerade schon gesehen,
ja, als wir uns überlegt haben,
warum die topologische Sortierung korrekt ist.
Da haben wir ein fast ähnliches Argument,
ein ganz ähnliches Argument gesehen wie das hier,
das ist nämlich Fallende Finishing-Zeiten, etwas mit
Mit Richtungen von Kanten in Decks zu tun haben.
Und der Graf, den wir betrachten,
der Graf zwischen den Komponenten ist ja ein Deck.
So, das müssen wir erstmal zeigen, dass das so ist.
Und dazu brauchen wir einen folgenden Beweis. Wir machen jetzt
eine Fallunterscheidung danach, wann wir welche Komponente entdecken.
Also nach welchen Zeiten. Ich gucke immer so auf die Uhr,
weil wir nur noch so zwei, drei Minuten haben.
Ja, ganz kommen wir da jetzt nicht mehr durch.
Naja, ich fange mal an,
dann habe ich auch was zum Wiederholen nächstes Mal.
Also der erste Fall ist,
wir entdecken erst die Komponente C und dann die Komponente C-Strich
Und das ist hier dargestellt. Also wir kommen irgendwo
von hier oben in den Grafen. Ja,
das ist jetzt eine Baumkante und dann, zack,
finden wir den Knoten X und der Knoten X ist der erste Knoten,
der Komponente C in der Tiefensuche.
Und dann laufen wir irgendwie durch, finden dann alle Knoten
und die Komponente C-Strich wird offensichtlich später erkannt, gefunden.
Und wir haben ja so eine Kante, die läuft von U nach V.
So, und jetzt sagen wir, dass die Finishing-Zeit von
der Komponente C größer ist als der von DLC-Strich.
Warum ist das so
Das kann man auch relativ leicht sehen. Also,
wenn X in der Komponente ist und X ist der erste Knoten der Komponente,
dann ist D von X gleich D von C. Also,
das ist der Knoten, der das Minimum bestimmt.
Und zu dem Zeitpunkt, wo wir X erkennen,
sind alle anderen Knoten hier noch weiß,
denn die Komponente C-Strich haben ja noch gar nicht gesehen.
Ja, und die Komponente C haben wir auch noch nicht gesehen.
Und weil es diese Kante gibt von UNAV,
gibt es einen Pfad von X zu jedem beliebigen Knoten W in dieser Komponente C-Strich.
Und deswegen sind alle Knoten W von C-Strich Nachfahren,
Theorien der weißen Pfade, ja,
von dem Knoten X. Und deswegen ist die Finishing-Zeit von X Auch später,
als die Finishing-Zeit dieser ganzen Komponente wegen dem Klammerungstheorien.
Das ist der einfache Fall. Jetzt kann aber es aber trotzdem
noch passieren, dass wir erst einen Knoten von C-Strich sehen.
Und der Fall ist ein bisschen komplizierter und den müssen wir
uns dann am nächsten Montag anschauen.
Okay, das war es dann für heute