So, guten Morgen. Willkommen bei Algorithmen und Datenstrukturen.
Wir befinden uns im Kapitel der Graf-Algorithmen. Wir schauen uns gerade
die Tiefensuche an und wir haben uns in der letzten Vorlesung
eine erste Anwendung angeschaut. Das ist die Frage,
ob wir in einem Graf die Knoten topologisch sortieren können.
Und wir haben gesehen, dass wir das mit der Tiefensuche machen können.
Also wir konnten erstens feststellen, ob das überhaupt geht.
Das war nämlich immer dann der Fall, wenn es keine Rückhanden gibt.
Und dann konnten wir auch sehen, wie wir das machen.
Wir müssen nämlich nur über die Finishing-Zeiten merken, uns merken für jeden Knoten.
Und wenn wir dann die Knoten sortieren nach fallender Finishing-Zeit,
die steht hier unten immer dort runter, dann sehen wir,
dass wir eine topologische Sortierung der Knoten bekommen.
Das können wir an dem Bild hier sofort optisch verifizieren,
dass das so ist, weil alle Kanten verlaufen hier nur von links nach rechts.
Das war die erste Anwendung, die wir gesehen haben.
Und die hängt sehr stark zusammen mit der zweiten Anwendung,
die wir uns angeschaut haben dann.
Hier geht es jetzt um starken Zusammenhang. Das haben wir
so in der letzten Hälfte der Vorlesung gemacht.
Und die Frage, die wir haben, ist die folgende.
Wir haben also einen gerichteten Graf und wir wollen diesen Grafen zerlegen in seine Zusammenhangskomponenten.
Und eine Zusammenhangskomponente ist dadurch charakterisiert, dass alle Knoten,
die zu der Zusammenhangskomponente gehören, jeweils über Pfade miteinander verbunden sind oder erreichbar sind.
Also immer für alle Paare, UV,
gibt es einen Pfad von U nach V und von V nach,
oh, das ist eine Zusammenhangskomponente.
Dann haben wir als nächstes gesehen, dass es ein paar Eigenschaften gibt,
dieser Zusammenhangskomponenten, also insbesondere ganz wichtig, die Zusammenhangskomponenten zerlegen, die Knotenmenge des Jungt.
Das heißt, jede Komponente enthält eine Menge von Knoten.
Jeder Knoten kommt nur in einer Komponente vor. Dass das so ist,
ist ganz naheliegend, weil wenn ein Knoten wirklich in zwei Komponenten vorkäme,
dann würde jeder Knoten der ersten Komponente mit jedem,
also würden wir von jedem Knoten der ersten Komponente jeden anderen Knoten der zweiten Komponente erreichen können und umgekehrt und dann wären das die gleichen Komponenten.
Dann haben wir gesehen, dass ein invertierter Graf,
also da, wo wir alle Kanten umdrehen, Die gleichen starken Zusammenhangskomponenten hat.
Und dann haben wir gesehen, dass wenn wir einen Komponentengrafen bilden,
das heißt, ein Graf,
in dem jeder Knoten einen Zusammenhangskomponente ist und die sind durch Kanten verbunden,
wenn es noch eine Kante gibt zwischen diesen Knoten,
dass so ein Komponentengraf ein Deck sein muss, also ein arzyklischer Graf.
Wenn der ein Zyklus hätte, dann würden die Knoten gegenseitig
voneinander erreichbar sein und dann hätten wir eine größere starke Zusammenhangskomponente.
Ja, hier war ein entsprechend passendes Bild dazu,
ein kleiner Graf mit acht Knoten mit einer diversen Anzahl von Kanten und hier bleiben unterlegt.
Sehen Sie die starken Zusammenhangskomponenten, dieses entsprechenden Grafen.
Also hier eine mit drei Knoten. Jeder Knoten ist von jedem
erreichbar. Die sind hier so zyklisch mit Kanten verbunden.
Und die beiden können wir voneinander erreichen, lieber einen auch und den auch.
Wenn wir ihn transponieren, ist es immer noch so,
daran ändert sich nichts.
Hier sehen wir den Komponentengrafen des ursprünglichen Grafens hier oben.
Und hier drin stehen auch die Namen der Knoten,
die dazu gehören. Also das ist ja A,
B und E.
Und wir sehen Kanten
Genau dann, wenn Kanten verlaufen zwischen den entsprechenden Knoten,
von den Zusammenhangskomponenten. Und dieser Grafie ist a-zyklisch,
der muss arzyklisch sein, wenn der einen Zyklus hätte,
dann würden die entsprechenden Knoten hier zu einer großen Zusammenhangskomponente gehören.
Okay, soweit hatten wir das Problem charakterisiert und dann habe ich Ihnen
einen Algorithmus vorgestellt, der die starken Zusammenhangskomponenten berechnen kann.
Der Algorithmus basiert auf der Tiefensuche und er hat quasi zwei Durchläufe,
also er benötigt zwei Durchläufe und zwischendurch werden alle Kanten invertiert.
Die werden alle einmal rumgedreht, so wie wir das
gerade in dem Beispiel schon gesehen haben
Der Algorithmus nochmal, wir fangen an. Hier oben starten wir
die erste Tiefensuche. Wir markieren alle Knoten als weiß.
Wir besuchen alle Knoten nacheinander. Wir schauen, ob der Knoten
noch weiß ist. Dann haben wir ihn noch nicht betrachtet
und rufen den rekusiv, rufen dann rekusiv unsere Tiefensuche auf.
Und während wir das tun, werden wir in dieser Tiefensuche
einen Array FV hier initialisieren. Dieses Array FV ermöglicht uns
über den Index auf die Knoten zuzugreifen, die eine entsprechende Finishing-Time haben.
Also wenn da so ein Knoten, uh, Definishing-Zeit,
7 hat, dann könnten wir in diesem Array mit dem Index 7 auf diesen entsprechenden Knoten zu greifen.
Das ist das Ziel von diesem Array hier. Und die rekusive Routine an der Stelle sieht dann so aus,
also die klassische Tiefensuchtroutine, der Knoten wird grau gefärbt,
wir gehen in die Addizenzliste rein und sondieren, die ganzen Knoten,
wenn ein Knoten noch weiß ist, dann rufen wir rekusiv die Funktion auf.
Wenn wir alle Knoten besucht haben,
färben wir den Knoten schwarz, jetzt kommt der Zeitpunkt,
wo wir die Finishing-Zeit vergeben und das passiert hier,
das ist also nur für den Durchlauf hier,
das der Richtige ist, hier wird die Zeit,
jetzt die Uhr um einen Schritt weiter gedreht und dann wird hier dieses Array entsprechend gesetzt mit diesem Knoten-U, an der Position Time.
Okay, als nächsten Schritt bauen wir den Grafum,
wir drehen alle Kanten um, das macht die Funktion in Word Edges,
die muss ich glaube ich jetzt nicht nochmal zeigen und dann machen wir die zweite Tiefensuche,
alle Knoten werden wieder weiß gefärbt, wir haben eine Variable,
die uns die Nummer der aktuellen starken Zusammenhangskomponente speichert hier,
dann haben wir diese Vorschleife,
wo wir alle Knoten besuchen und während wir normalerweise keine genaue Reihenfolge vorgeben.
Wir sagen einfach, okay, wir besuchen irgendwie die Knoten,
müssen nur alle sein, also so eine For-Each-Leife.
Ist es jetzt so, dass wir die Knoten zwar auch alle besuchen,
aber in einer ganz speziellen Reihenfolge,
und das machen wir durch diese Vorschleife mit der Variablen I,
die jetzt von der Gesamtzahl aller Knoten rückwärts zählt, bis eins.
Wir verwenden, dass Array mit den Finishing-Zeiten hier, und gucken dann,
ob dieser Knoten die Farbe weiß hat und dann besuchen wir den.
Dann rufen wir die Funktion rekusiv auf und laufen dann entsprechend
hier in den in die Tiefensuche rein. Und das Einzige,
was diese Tiefensuche jetzt macht, ist, dass alle Knoten,
die gefunden werden, bis wir wieder an diesem Punkt angekommen sind,
werden markiert mit dieser starken Zusammenhangskomponente.
Also die Annahme ist, dass wir in diesem mit diesem
rekrusiven Call an dieser Stelle quasi genau eine Zusammenhangskomponente erwischen.
Und dann sind wir quasi fertig. Sein ganz faszinierender Algorithmus
. Immerhin ist er in der Lage in Linearzeit,
also in O von V plus E,
die einen starken Zusammenhangskomponenten des Grafens zu berechnen
So, jetzt müssen wir uns anschauen, warum das hier
eigentlich korrekt ist, warum wir so vorgehen können.
Und da haben wir in der letzten Vorlesung schon einen Teil des Beweises gesehen.
Und zwar hängt der jetzt wieder zusammen mit den Finishing-Zeiten,
was wir nämlich sehen können. Und das ist eigentlich fast das Gleiche,
also fast der gleiche Beweis oder die gleiche Idee,
wie das, was wir gerade gesehen haben bei der topologischen Sortierung,
meine topologischen Sortierung als gleiche Argument.
Nämlich hier formuliert für die starken Zusammenhangskomponenten, wenn wir zwei Knoten haben,
Entschuldigung, wenn wir zwei starke Zusammenhangskomponenten haben, C- und C-Strich.
Und die sind nicht identisch. Und dann gilt für alle Kanten UV,
die zwischen diesen Komponenten laufen, also die von U nach V,
von C nach C-Strich laufen,
dass die Finishing-Zeit der gesamten Komponente C größer sein muss als die Finishing-Zeit der Komponente C-Strich.
Und das ist genau, wenn sie statt den Komponenten hier Knoten einsetzen,
dann haben sie genau die Aussage, die wir eben für die topologische Sortierung haben.
Das heißt, was wir hier machen, ist,
wir sorgen einfach dafür, dass wir die Knoten so durchlaufen,
dass zumindest bezüglich der Komponenten Und das war ja ein Deck,
der Komponentengraf.
Wir eine ganz spezielle Reihenfolge haben. Und diese Reihenfolge wird uns sichern,
dass wir beim zweiten DFS-Durchlauf mit jedem Aufruf immer genau eine starke Zusammenhangskomponente finden.
So, den Beweis mache ich jetzt nicht nochmal,
den haben wir in der letzten Vorlesung schon gesehen.
Und jetzt sind wir im Prinzip auch genau da,
wo wir beim letzten Mal aufgehört haben.
Das war jetzt also eine etwas längere Wiederholung,
aber wir waren ja auch so mittendrin,
dann schadet das manchmal ja auch nicht
Okay, jetzt wollen wir sehen, warum der Algorithmus korrekt ist.
Hier ist die Aussage, die Korrektheit unseres SCC-Algorithmus.
G ist also ein gerichteter Graf. Und dann bestimmt also unser Algorithmus,
Strongly connected components, die starken Zusammenhangskomponenten in dem Grafen gehe.
Was wir machen ist, wir machen eine vollständige Induktion über
die Schleife der Zeilen 8 bis 11, das sich nochmal zeigen.
Das ist hier. Also wir werden jetzt,
das ist der Zahl, den wir uns anschauen.
Wir wissen schon mit unserem ersten Beweis, mit dem Lämmer,
dass wir hier oben einfach dafür sorgen,
dass wir die Knoten nach Finishing-Zeiten sortiert haben.
Das wissen wir schon. Und jetzt schauen wir uns diese
zweite Vorschleife hier an und wir gucken uns immer an,
was passiert, wenn wir hier den DFS-Algorithmus aufrufen.
Ja, also für jeden, für jede Vorschleife,
wenn wir hier den Aufruf machen, schauen wir, was genau passiert.
Also und dann zeigen wir, dass immer dann,
wenn wir hier den Aufruf starten und dann wieder zurückkommen,
wir eine weitere starke Zusammenhangskomponente bestimmt haben.
Okay, so. So, was machen wir?
Also wir machen jetzt die Durchläufe dieser Schleife,
jetzt sind wir im Kartendurchlauf, also K minus 1
Durchläufe sind schon passiert. Und wir machen einen Induktionsbeweis. Wir gehen also davon aus,
die ersten paar minus 1 DFS-Bäume, die wir erzeugt haben,
also jedes Mal erzeugen wir einen DFS-Bau mit jedem Durchlauf,
die waren alle starke Zusammenhangskomponenten.
Also die sind irgendwo hier im Graf verteilt,
das war also hier K minus 1,
das ist der DFS-Baum von K minus 2 und dann gibt
es noch K minus 3 und so weiter.
Also jeder Aufruf hat bis zum K minus 1 genau eine starke Zusammenhangskomponente identifiziert.
Sie sehen also mit diese grauen ovalen Objekte hier sind quasi immer die Zusammenhangskomponenten.
Und das, was sie hier an Graf sehen,
ist immer der Baum, der Tiefensuchbaum, der entsteht.
So, jetzt sind wir in dieser besagten Schleife.
Und wir haben jetzt einen Knoten gefunden, der weiß ist.
Und den färben wir jetzt grau. Das ist dieser Knoten-U.
Das ist der nächste Knoten, den wir jetzt betrachten werden.
Und jetzt müssen wir den Beweis zeigen,
dass wenn jetzt die Tiefensuche losläuft,
dann wird sie irgendwie einen gewissen Baum hier betrachten,
dass dieser Baum exakt eine starke Zusammenhangskomponente beschreibt.
Das müssen wir zeigen
Okay, wie machen wir das? Also der Zeitpunkt,
den wir uns betrachten, ist da so der Zeitpunkt X,
wo wir in der Schleife diesen Knoten-U auswählen.
Wenn wir den auswählen, dann gilt Folgendes.
Wir haben noch nicht die Funktion aufgerufen, die DFS-Visit-Funktion.
Wir haben gesagt, den nehmen wir jetzt als nächstes.
Und jetzt würden wir die Funktion aufrufen. Zu dem Zeitpunkt
gibt es in unserem Graphen nur schwarze und weiße Knoten.
Der ist jetzt hier schon grau gezeichnet,
weil wir jetzt als nächstes den grau färben würden.
Aber bevor der Aufruf stattfindet, gibt es nur schwarze und weiße Knoten.
Wir können auch sagen, direkt danach gibt es nur einen grauen Knoten,
nämlich diesen einen Knoten-U, dem wir jetzt den DFS-Aufruf gestartet haben.
Das ist ganz wichtig, weil als erstes zeigen wir nämlich jetzt mal,
dass die Zusammenhangskomponente, zu der dieser Knoten-U gehört,
dass die auf alle Fälle eine Teilmenge des Baums ist.
Also jeder Knoten, der dazugehört,
der ist auch in dem Baum auf alle Fälle enthalten.
Das kann man ganz schnell sehen, dass das so sein muss,
denn wir haben ja unser Theorien der weißen Pfade.
Das hilft uns jetzt, weil wir wissen, in dem Baum,
also in dem Grafen gibt es nur schwarze Knoten und weiße Knoten.
Die schwarzen Knoten sind alle Knoten, die zu anderen starken Zusammenhangskomponenten gehören.
Die können also gar nicht zu C gehören.
Also C ist ja die, wo U dazu gehört.
Und da können die nicht dazu gehören. Die haben ja
vorher schon abgearbeitet. Das heißt, alle Knoten,
die zu C gehören, müssen noch weiß sein,
weil grau gibt es nicht, bis auf diesen Knoten-U,
den wir jetzt hier gerade betrachten.
So, also es gibt keine grauen Knoten.
So, das bedeutet, die sind alle weiß.
Und was wir auch wissen, ist, es ist ja
eine starke Zusammenhangskomponente. Das heißt, es gibt Pfade.
Von der U aus können wir natürlich alle Knoten der
starken Zusammenhangskomponente erreichen
Sonst wäre es ja keiner nicht stark zusammenhängend. Und das bedeutet für jeden Knoten,
der zu der gleichen starken Zusammenhangskomponente wie U gehört, wissen wir,
der Knoten ist weiß und es gibt einen Pfad,
der führt uns von U nach W.
So, und wenn dem so ist,
dann wissen wir mit dem Theoreten der weißen Pfade,
dass dieser Knoten W auf alle Fälle ein Nachfolger wird.
Es wird ein Nachfolger des Knotens U. Und wenn es
ein Nachfolger wird, dann ist er eben Teil dieses
entsprechenden Kartenbaums des DFS-Algorithmus
Das heißt, wir haben jetzt schon gezeigt,
dass die Komponente auf alle Fälle eine Teilmenge ist, dieses Baums.
Es könnte aber noch sein, dass dieser Baum größer ist,
dass der hier noch in irgendwo weiterläuft, in andere Bereiche rein,
die aber nicht zu dieser starken Zusammenhangskomponente gehören.
Könnte ja sein. Das wäre dann so,
dann würde hier vielleicht eine Kante hier runtergehen,
aber es gäbe keine Kante mehr,
die uns hier hochführen würde zu, uh,
dann würde das nicht funktionieren.
Jetzt müssen wir also den zweiten Teil des Beweises führen.
Wir müssen zeigen, dass der Baum auch eine Teilmenge der
starken Zusammenhangskomponente ist. Und das ist natürlich der schwierigere Teil,
dass das tatsächlich so ist
So, das machen wir hier. Also, wir müssen zeigen,
der Baum, der Kartebaum ist eine Teilmenge von C. Ich
habe das jetzt noch ein bisschen weiter hochgeschoben.
Wir sehen hier noch BK-1, also da oben sind noch die alten Bäume,
die alle abgearbeitet sind. Und hier ist jetzt unser Baum BK. Und die Art und Weise,
wie wir uns das jetzt vorstellen,
also sie sehen hier keine Richtung in den Kanten,
aber wie wir uns das jetzt vorstellen können, ist,
also hier ist der Knoten-U, mit dem starten wir unsere Tiefensuche,
die findet noch andere Knoten irgendwo hier,
aber die starke Zusammenhangskomponente, die hört hier schon auf,
die ist kleiner.
So, wenn das so ist, dann,
Also U gehört ja zu der starken Zusammenhangskomponente C,
so haben wir sie definiert.
Dann haben wir so einen Pfad. Und irgendwann verlassen wir die ja,
die Komponente C und gehen zu irgendeiner anderen Komponente C-Strich.
Irgendwann muss so ein Wechsel stattfinden von C nach C-Strich.
Und dann landen wir plötzlich in der falschen Zusammenhangskomponente.
Und wir müssen hier zeigen, dass diese Kante hier nicht existieren kann,
dass das nicht auftreten kann, diese Situation.
Okay, also, wir machen mal wieder einen Widerspruchsbeweis.
Wir sagen mal angenommen, es gibt einen Knoten W und
W gehört zu dem Baum.
Ja, wie sehen wir hier unten?
Der gehört zu dem Baum. Und W gehört aber zu
einer anderen starken Zusammenhangskomponente, die heißt C-Strich.
Das ist dieser graue Bund
Hier, ja. Und C-Strich ist eine andere starke Zusammenhangskomponente
als C zu der U gehört. Okay,
wenn es so einen Knoten gibt, dann hätten wir verloren.
Den dürfen wir nicht finden, weil wir wollen ja sicherstellen,
dass alle Knoten, die im Baum sind, auch zu starken Zusammenhangskomponenten zielgehören. So,
von allen Knoten W, die es da so geben mag,
die diese Eigenschaft haben, wählen wir wieder den,
der eine minimale Fahrtlänge hat zum Knoten U. Das machen wir so wie bei dem,
vielleicht in der breiten Suche, haben wir es auch so gemacht,
Widerspruchsbeweis und wir erzeugen den Widerspruch an der Stelle,
wo wir das erste Mal den Widerspruch haben können bezüglich der Pfadlänge.
Wir laufen da sehr runter und W ist der Knoten,
der am dichtesten an U ist,
der quasi nicht mehr zu der gleichen Komponente gehört,
wie der ursprüngliche Knoten
Oh. Okay, was gilt jetzt für diesen Knoten weh?
Also das erste Mal können wir schon mal feststellen,
der Knoten weh, der gehört nicht zu einer starken Zusammenhangskomponente,
die wir vorher gefunden haben.
Ja, also zu BK minus 1 oder BK minus 2 oder so.
Zu denen kann der nicht gehören, denn die Knoten,
die hier oben sind in den anderen starken Zusammenhangskomponenten,
die sind ja alle schwarz, die haben wir ja komplett abgearbeitet.
Aber W ist ja ein Nachfolger von U und mit dem Theorien der weißen Pfade.
Wissen wir, weh muss natürlich ein weißer Knoten sein.
Also es kann kein Knoten sein, der hier oben zu
den Komponenten gehört, weil der ist ja weiß.
Das heißt, weh könnte höchstens noch ein Knoten sein,
der zu einer Komponente gehört, die wir eigentlich erst später entdecken wollen.
Ja, aber nicht zu einer, die wir schon entdeckt haben.
Das geht nicht. Gut, also hier steht das nochmal zum Zeitpunkt X,
existiert also ein weißer Pfad von U nach W und wir
betrachten jetzt diese Kante von V nach W.
Das ist also V ist einfach der Vorgänger auf diesem weißen Pfad.
Und wir haben den Knoten so gewählt,
dass er möglichst dicht dran ist an U. Das heißt,
der Knoten V, der muss noch zur Komponente dazugehören.
Der ist noch dabei und will eben nicht mehr
Okay, jetzt haben wir aber so eine Kante,
die geht hier runter. Das heißt, es gibt
eine Kante von V nach W in dem transmonierten Graf.
Wir haben ja vorher alle Kanten transsponiert.
Und jetzt kommt das Argument mit den Finishing-Zeiten.
Jetzt brauchen wir die finnische Zeit. Und jetzt sagen wir,
aha, das ist ja eine Kante,
die ist ja auch repräsentiert in unserem Deck der Komponenten.
Und wir wissen, dass die Finishing-Zeit von C
jetzt kleiner sein muss als die Finishing-Zeit von C-Strich.
Ja, warum? Naja, weil wir haben ja gerade
den Knoten-U hier entdeckt
Und der Knoten hier ist ja noch nicht entdeckt.
Das kommt also zu einem späteren Zeitpunkt. Und die Finishing-Zeit offensichtlich später.
Ja? Okay. So, das nutzen wir jetzt gleich aus.
Wir sagen jetzt, okay, diese Komponente hier,
die hat ja eine Wurzel, die hat irgendwie einen Knoten,
wo wir diese Komponente das erste Mal entdecken werden.
Und das ist der Knoten-S. Ja, den nennen wir jetzt einfach mal so.
Und der Knoten definiert die Finishing-Zeit für diese ganze Komponente.
Und jetzt wissen wir, die Finishing-Zeit von unserem Knoten,
uh, die ist kleiner oder gleich die Finishing-Zeit
der ganzen Komponente hier
Und die ist aber kleiner als die Finishing-Zeit der neuen Komponente C-Strich.
Und das entspricht der Finishing-Zeit des Knotens S. Es ist weiß,
der muss bei sein, weil der gehört ja zu der Komponente,
die wir noch nicht entdeckt haben.
So, der ist weiß und deswegen wird zum Zeitpunkt X
in der Schleife U nicht ausgewählt, sondern es,
weil S hat ja die größere Finishing-Zeit und wir haben die,
wir betrachten die Knoten nach fallenden Finishing-Zeiten.
Das ist die Art und Weise, jetzt gehen wir
zum Rück zum Algorithmus, um es zu sehen.
Hier, In dieser Vorschleife betrachten wir die Knoten nach fallenden Finishing-Zeiten.
Deswegen wird in dieser Vorschleife nicht der Knoten-U ausgewählt,
sondern es wird der Knoten S ausgewählt,
weil der einfach die kleinere, die größere Finishing-Zeit hat.
Deswegen wird er ausgewählt. Und damit haben wir unseren Widerspruch. Damit haben wir gezeigt,
dass zum Zeitpunkt XS ausgewählt wird und nicht U. Das heißt,
dieser Knoten-U kann nicht existieren und das ganze Szenario kann nicht existieren.
Und weil das so ist, da gibt es keinen Knoten
weh, für den diese Eigenschaft gilt.
Das W, also zum Baum gehört, aber nicht zur Komponente.
Und damit haben wir gezeigt, dass Knoten des Baums zur Komponente gehören.
Und damit haben wir gezeigt, dass der Baum eine Teilmenge
der Komponente ist. Und damit sind die beiden identisch.
Also der Baum ist identisch mit der Komponente. Ja,
das ist der Beweis. Also der ist schon nicht so
einfach. Da musst du mal ein bisschen drüber nachdenken,
um wirklich zu verstehen, wie das funktioniert.
Und am besten kann man das auch nochmal an so einem Beispiel sehen.
Jetzt zeige ich Ihnen das gleiche Beispiel nochmal wie eben,
aber jetzt ist es eben voll mit Daten,
die von den Algorithmen, von dem Algorithmus kommen.
Wir beginnen also hier oben, das ist der ursprüngliche Graf
und in dem ursprünglichen Grafen lassen wir jetzt die Tiefensuche einmal laufen.
Und hier in den Knoten sehen Sie,
wie vorher auch schon,
immer eine Discovery und eine Finishing-Zeit
Das Faszinierende ist, die Tiefensuche legt ja nicht fest,
bei welchem Knoten wir beginnen oder in welcher Reihenfolge wir die Kanten betrachten.
Und es spielt auch keine Rolle. Egal,
bei welchem Knoten wir anfangen und egal in welcher Reihenfolge wir
die Kanten durchlaufen, werden wir hier einfach verschiedene Finishing-Zeiten zuordnen.
Ich habe jetzt eine willkürliche Zuordnung gemacht. Ich bin jetzt
bei dem Knoten. Ich habe bei dem Knoten C angefangen.
Der hat die Discovery-Zeit 1. Und sie sehen an den Zahlen sofort,
wo meine tiefen Suche langgelaufen ist,
also von C aus ist er zu G gegangen,
Discovery Zeit 2, dann zu F, 3,
die kannten andersrum, da kommt er nicht lang.
Also das Finishing Zeit 4 zurück zu G,
dann hat er hier Haar entdeckt, 5 des Finishing Zeit 6,
da kommen wir nicht weiter.
Zurück Finishing Zeit 7, hier wieder hoch,
dann hat er D gefunden, 8, 9, zurück 10.
Und da war der, das war die Tiefensuche einmal abgeschlossen.
Wir sind wieder in der aufrufenden Schleife oben.
Wir wählen den nächsten Knoten, das war bei mir willkürlich B.
Ja, der hat die Discovery Zeit 11,
da gehen wir dann zu E12 zu A13,
das Finishing Zeit 14, 15, 16.
Alle Knoten sind Magia, die erste,
der erste DFS Durchlauf ist abgeschlossen
So, in Rot sehen Sie nochmal die Baumkanten,
nur damit Sie wissen, warum das irgendwie rot ist.
So, jetzt kommt der Zeitpunkt, wo wir alle Kanten umdrehen.
Ja, das ist unser neuer Graf hier, ja.
Und mit Dunkelgrau markiert sehen Sie jetzt innerhalb der Komponenten,
die Komponenten kennen wir natürlich eigentlich noch nicht, die sind hier immer noch unterlegt.
Innerhalb der Komponenten sehen Sie jeweils den Knoten mit der maximalen Finishing-Zeit.
Also innerhalb dieses Bereichs, von den dreien ist es
der Knoten B, der hat die maximale Finishing-Zeit.
Von den beiden hier ist es der, der G,
der hat die Sieben und von den beiden ist es der
Knoten C und hier ist es der Knoten H. Also das sind die Knoten mit maximalen Finishing-Zeiten.
Wenn der Algorithmus jetzt also losläuft, dann wird er als allererstes
den Knoten B wählen, weil B ist der Knoten mit maximaler Finishing-Zeit. So,
da geht er in die Tiefensuche rein und wenn sie jetzt auf das Bild schauen,
dann sehen sie, warum der Algorithmus funktioniert, weil sie sehen,
B gehört zu einer Komponente, in denen zwar,
da sind zwar Kanten, die zu anderen Komponenten führen,
aber alle Komponenten, aber alle Kanten zeigen in diese Komponente hinein,
wo B ist.
W ist also bezüglich des Investors vertierten Grafens quasi eine Senke,
ein Knoten, in dem nur Kanten hineinlaufen.
Also nicht nur B, also der Knoten,
der diese Komponente in dem Komponentengrafen repräsentiert.
In dem ursprünglichen Grafen ist es eine Quelle.
Also weil wir alle Kanten umgedreht haben. So,
da laufen nur welche rein. Und das bedeutet,
wenn wir von B aus einer Tiefensuche starten,
dann können wir die Komponente gar nicht verlassen.
Es gibt keine Kante, die uns hier rausführt.
Da laufen wir also hier los und jetzt gehen wir,
jetzt ist es völlig egal, wie wir es machen.
Wir gehen einfach mit der Tiefensuche da durch und markieren diese drei Knoten.
Als nächstes würden wir jetzt den Knoten 10 auswählen.
Der hat hier die Nummer 10. Und jetzt sehen wir,
wenn wir den angucken, der hat durchaus Kanten,
die hier herein zeigen, genauso wie hier auch.
Die tun uns nicht, das haben wir schon gelernt,
weil von C aus können wir hier die Komponente nicht verlassen.
Aber diese Komponente hat Kanten, die die Komponente verlassen,
zum Beispiel diese Kante hier. Und der Trick an der Sache ist,
wir gucken hier allerdings nur auf Komponenten, die wir schon abgearbeitet haben.
Diese Knoten sind die, die dann zu dem Zeitpunkt schon
schwarz wären. Deswegen gibt es zwar so eine Kante,
aber diese Kante werden wir nicht langlaufen,
weil den Teil des Graphen haben wir es schon betrachtet.
Das heißt, wir gucken uns an C&D an und dann haben wir
diese Komponente entdeckt und dann würden wir als nächstes G finden und würden Gäbe arbeiten.
Und jetzt gilt das Gleiche. Also G hat verweise in
andere Komponenten, aber die haben wir schon entdeckt.
Und dann kommt zum Schluss der Knoten Haar. Und jetzt
sehen Sie hier noch die Komponentennummern. Das ist die K 1,
2, 3, 4. Genau wie in dem Beweis.
Das ist also der erste, die erste Komponente, die entdeckt wird.
Die zweite, die dritte und die vierte,
die entdeckt wird
Und jetzt sehen Sie noch eine Eigenschaft, die diesen Algorithmus auszeichnet.
Eine ganz tolle Eigenschaft. Der Algorithmus entdeckt nämlich nicht nur die starken Zusammenhangskomponenten,
sondern er entdeckt sie sogar in einer ganz speziellen Reihenfolge.
Er entdeckt sie nämlich in der tokologischen Sortierung des Komponentengrafen.
Und das ist für vier Anwendungen total nützlich,
weil sie häufig dann quasi in der topologischen Sortierung diese Komponenten weiterverarbeiten,
wenn sie andere Sachen machen.
Das ist also noch eine ganz wichtige Eigenschaft,
die einfach ein Abfallprodukt der Vorgehensweise ist.
Das hat man nicht so genau geplant,
aber das resultiert einfach darauf,
die aus der Art und Weise,
wie man das macht
Ja, okay, das war, war es quasi zu starken Zusammenhang.
Ich habe schon gesagt, der Algorithmus hat ein paar Nachteile
auf großen Grafen, bei großen Grafen ist der unschön,
also invertieren von Kanten, ist nicht so schön.
Und auch zwei DFS-Durchläufe ist nicht so schön.
Das sind zwei Nachteile, die der Algorithmus hat.
Es gibt einen Algorithmus, der mit einer tiefen Suche auskommt.
Da muss man aber sagen, der hat auch Nachteile gegenüber diesem.
Man muss dann abwägen, was einem wichtiger ist. Dieser Algorithmus braucht,
wie gesagt, nur einen Durchlauf und hat nicht den Vorteil,
dass die Komponenten hier intopologischer Sortierung ausgegeben wird.
Okay, vielleicht braucht man das auch nicht,
dann ist es nicht so schlimm.
Der hat allerdings den einen Haken, der benötigt einen zusätzlichen Speicher.
Da brauchen sie noch einen zusätzlichen Stapel, um sich Knoten zu merken.
Und da haben sie auf der Seite wieder einen Nachteil.
Dieser Algorithmus braucht keinen zusätzlichen Speicher. Okay,
sie brauchen das Array einmal, um diese Finishing-Zeiten zu speichern.
Aber ansonsten brauchen sie sonst keinen weiteren Speicher. Gut,
okay, also starker Zusammenhang ist also lösbar mit Tiefensuche in linearer Zeit.
Gut, also mit der Tiefensuche könnten wir jetzt noch viele
tolle Dinge machen, das werden wir jetzt nicht mehr tun
Zu wenig Zeit in dieser Vorlesung, uns da jetzt noch länger aufzuhalten
. Wir werden also jetzt uns anderen Graf-Algorithmen widmen. Und das Nächste,
was wir uns anschauen, ist das Problem der minimalen Spannbäume.
Das wollen wir lösen, dieses Problem. Und auch dieses Problem betrachtet man,
ja, nicht, weil man vielleicht unbedingt immer im Leben auf Spannbäume trifft,
sondern weil man hier eine Menge lernen kann.
Das hier ist ein Problem, welches für uns eigentlich das
erste Optimierungsproblem ist, was wir lösen werden in dieser Vorlesung.
Bisher haben wir mit allen Algorithmen entweder Strukturen aufgeklärt, wie eben gerade. Ja,
wir haben dann gesagt, okay, wo ist jetzt ein Zusammenhangskomponente,
wir haben was sortiert, da gab es dann eine richtige Antwort,
die sortierte Folge, aber sonst nichts anderes.
Und hier haben wir quasi ein Optimierungsproblem,
was man schon in dem Begriff der Minimalität sieht.
Wir suchen unter einer Menge von Objekten,
das sind bei uns Spannbäume, ich werde denen gleich sagen,
was das ist, den minimalen Spannbaum.
So, was ist denn so ein Spannbaum,
so ein Spannbaum, der sieht wie folgt aus,
wir haben also einen jetzt ungerichteten Graf für diese Anwendung,
GVE,
hier ist mein Beispiel,
ein Graf und wir haben Kantengewichte
Das heißt, an jeder Kante des Graphen steht eine Zahl dran.
Und das ist ein, können Sie sich vorstellen wie ein Gewicht.
So, und was wir jetzt machen wollen, ist,
wir wollen einen Baum erzeugen und dieser Baum soll die Eigenschaft haben,
dass er alle Knoten miteinander verbindet.
Ja, und das können wir jetzt ja machen und das
Rote hier ist so ein Beispiel für so einen Baum.
Dieser rote Baum hier, diese Kanten, das ist ein Baum,
wir haben keine Zyklen, verbindet alle Knoten miteinander.
Da gibt es ja jetzt ganz viele Möglichkeiten, das zu machen.
Also ich könnte ja zum Beispiel diese Kante hier löschen und
dafür diese Kante nehmen.
Da hätte ich auch wieder
einen Baum,
der alle Knoten
Miteinander betrachtet. Und unter all diesen Spannbäumen,
heißen manchmal auch spannende Bäume, ja,
also all diesen Spannbäumen, suchen wir jetzt den,
wo einfach die Summe der Gewichte von den Kanten,
die wir betrachten, minimal ist, ja,
also wir wollen die mit minimalem Gewicht.
Die Kanten, die nehmen wir und wenn wir dann den Baum haben,
wo die Summe über diese Kantengewichte minimal ist,
dann haben wir den minimalen Spannbaum gefunden, ja.
Okay, also das Problem wollen wir lösen,
können berechtigterweise nach Beispielen oder Anwendungen fragen, wo man solche Probleme hat.
Wahrscheinlich fallen Ihnen ein paar Sachen ein.
Also all so ein typischer Fall ist so im Telekommunikationsbereich,
wenn Sie also so Netzwerke aufbauen und Sie müssen sicherstellen,
dass gewisse Komponenten alle irgendwie miteinander vernetzt sind.
Und Sie haben vielleicht unterschiedliche Kosten dafür, diese Vernetzung durchzuführen.
Also vielleicht ist es besonders teuer, den Knoten D mit F zu verbinden,
ist vielleicht ein großer Berg dazwischen, keine Ahnung.
Und es ist vielleicht besonders günstig, C mit I zu verbinden,
weil da ist gerade schon ein U-Bahn-Tunnel und dann können Sie
direkt ihre Zelefonleitung noch in reinlegen.
Also es sind unterschiedliche Kosten verbunden und sie suchen einfach das Netz,
was alle Knoten miteinander verbindet, mit minimalen Kosten.
Oder sie suchen was mit minimaler Streckenlänge. Könnte ja auch sein.
Dann würden sie hier Kilometer dran schreiben und dann sagen sie,
die wollen das Netz haben mit der minimalen Streckenlänge.
Gut, also das ist das Problem, was wir lösen wollen.
Und wir werden dieses Lösen mit einer ganz speziellen Art von Algorithmen und zwar mit sogenannten Greedy-Algorithmen.
Greedy-Algorithmen sind ganz faszinierende Verfahren. Greedy, für die,
die es jetzt nicht ad hoc wussten, heißt gierig.
Also das ist ein Algorithmus, der in schwieriger Art und Weise die Lösung zusammensucht.
Und das bedeutet, nichts weiter als er fällt Entscheidungen,
der Algorithmus. Und diese Entscheidung wird er niemals wieder zurücknehmen.
Also er wird einmal entscheiden für unsere Anwendung,
die Kante, die gehört jetzt zu meinem minimalen Spannbaum.
Und wenn er einmal gesagt hat, die gehört dazu,
dann bleibt das auch so. Dann wird er nicht sagen,
ach nee, war nicht so gut,
ich nehme doch mal lieber diese Kante, sondern er wird nur einmal sagen,
die gehört dabei dazu und dann nehmen wir die.
Das sind Greedy Algorithmen, Greedy Algorithmen werden sehr gerne eingesetzt,
weil die sind natürlich schnell.
Sie gucken sich alle Kanten an, fällen für jede Kante
eine Entscheidung,
Die Frage ist natürlich,
sind die dann auch korrekt und für ein Optimierungsproblem,
wie dieses hier, wisst der Algorithmus dann korrekt,
wenn er auch die minimale Lösung findet, also den minimalen Spannbaum wirklich findet.
Und Greedy Algorithmen sind in den vielen Fällen nicht korrekt,
in dem Sinne, dass sie eben keine Garantie geben,
die optimale Lösung zu finden.
Häufig sind es eben dann Heuristiken. Das heißt,
sie finden eine gute Lösung, aber nicht garantiert eine optimale Lösung.
Das spannendbare Problem hier hat die Eigenschaft,
dass Greedy Algorithmen, wenn man sie richtig macht,
auch tatsächlich die optimale Lösung finden können und man kann das
nachweisen,
dass das so ist
Okay, ja, ist das hier ein minimaler Spannbaum oder nicht?
Es ist schon mal, es ist nicht so ganz so schnell zu sehen.
Man muss ein bisschen gucken, es ist nicht so ganz
so offensichtlich, ob das jetzt einer ist oder nicht.
Wir werden gleich so ein Kriterium sehen,
aber auch das ist nicht so einfach und schnell händisch auszuprobieren.
Es zeigt aber schon, es ist zumindest nicht trivial,
dieses Problem zu lösen. Okay, gut. So,
wie sieht jetzt unsere Greedy-Strategie aus? Also unsere gierige Strategie,
die sieht so aus, dass wir sagen, wir wollen nach und nach eine Kante hinzunehmen
Und die Kante, für die müssen wir immer sicherstellen,
dass sie auch zu einem minimalen Spannbaum gehört.
Wir sagen, du Kante, du bist jetzt dabei,
dich nehmen wir. Und weil wir diese Entscheidung nicht zurücknehmen dürfen,
wir haben ja einen Greedie-Algorithmus,
der entscheidet und dann macht er nie wieder die entgegengesetzte Entscheidung,
weil wir sie nicht zurücknehmen dürfen, müssen wir zu diesem Zeitpunkt wissen,
dass diese Kante auch wirklich zu einem minimalen Spannbaum gehört.
Sonst hätten wir einen Fehler produziert. Auf der anderen Seite ist,
wenn wir das nachweisen können, wissen wir sofort,
dass unser Algorithmus korrekt ist. Wenn wir wissen,
jede einzelne Entscheidung für jede Kante ist korrekt,
die Kante gehört zu einem minimalen Spannbaum,
dann wissen wir auch,
dass der Algorithmus korrekt ist
Ist. So, was wir also brauchen, sind sogenannte
sichere Kanten, eine sichere Kante, ist wie folgt definiert.
Wir haben mal eine Menge von Kanten,
die zu einem, zu irgendeinem minimalen Spannbaum gehören.
Ja, also da gibt es jetzt so einen Spannbaum,
den sehen wir noch nicht komplett,
sondern wir sehen nur ein paar Kanten daraus, ja.
So, und eine Kante von UNAV ist für A sicher,
also für diese Teilmenge,
bezeichnen wir die Kante als sicher, genau dann,
wenn wir die Menge A um diese Kante erweitern können und
dann haben wir wieder die Teilmenge eines minimalen Spannbaums, ja.
Dabei Also gilt es jetzt zu beachten,
dass so ein minimaler Spannbaum nicht notwendigerweise eindeutig ist. Ja,
wenn sie Söhne kannten, ich weiß jetzt nicht so genau,
ob wir hier, haben wir jetzt gerade kein Beispiel,
wo man, also mal stell dich mal vor,
hier würde auch eine 4 stehen.
Dann könnte natürlich diese Kante weggenommen werden und dann diese Kante
dazugenommen werden, hätten sie auch wieder einen Minimalspann.
Aha, gegen BC. Ja, genau.
Das ist ein super Beispiel. Also wenn sie sehen,
wenn wir BC wegnehmen und aha hinzunehmen,
haben wir wieder ein, haben wir wieder einen minimalen Spannbarm.
Also es gibt verschiedene Möglichkeiten. Und diese Definition hier,
die besagt nicht, dass wir hier,
hier steht ein minimaler Spann, warum irgendein minimaler Spannbaum.
Das heißt nicht, dass wir nachher den gleichen haben.
Wir müssen nur wieder einen minimalen Spannbaum haben, der diese Menge umfasst.
Ja, das zweite, also was wir jetzt sehen,
ist erst mal, wie viele Kanten brauchen wir?
Das ist einfach. Wir müssen V,
Betrag von V viele Knoten miteinander verbinden, über Kanten.
Also haben wir Betrag von V minus eins,
viele Kanten,
die wir auswählen müssen
Okay, so, hier ist der Algorithmus. Wie immer
verbirgt sich das Geheimnis nachher hinter einer so einer Funktion.
Wir gehen wie folgt vor. Wir haben unsere Menge A,
das ist am Anfang eine leere Menge, also G ist
unser Graf und W ist die Gewichtsfunktion für die Kanten.
Und jetzt haben wir eine Wildschleife, solange der Betrag von A
kleiner ist als Betrag von V minus 1. Das heißt,
die Menge A verbindet noch nicht alle Knoten miteinander.
Rufen wir eine Funktion auf, die heißt fine safe edge,
also die findet jetzt eine sichere Kante und die braucht als Parameter natürlich den Graf und die Menge A,
denn das Es ist wichtig,
also die sichere Kante ist definiert für die Menge A,
also ausgehend von A, sagen wir, eh,
dürfen wir hinzunehmen und haben dann wieder ein Minimalspann.
Okay, so,
und dann fügen wir einfach diese Kante A zu der Menge E,
zu der Menge A hinzu,
haben die neue Menge A und dann wiederholen wir das und
wir suchen die nächste sichere Kante und die nächste und die
nächste und die nächste und dann haben wir irgendwann am Ende
Betrag von V minus 1 viele Kanten ausgewählt und dann
haben wir in der Menge A hoffentlich einen minimalen Spannbaum gefunden
Der Algorithmus ist korrekt, vorausgesetzt, diese Funktion ist korrekt,
wenn diese Funktion uns eine sichere Kante liefert,
ist der Gesamtalgorithmus korrekt,
weil die Definition der sicheren Kante ist genauso,
dass wir die Kanten immerhin zunehmen können und damit sichergestellt ist,
dass wir noch einen minimalen Spannbaum finden können und jetzt können sie sich hier eine Schleifen in Variante definieren,
sehen sofort, dass der Algorithmus korrekt sein muss.
Also müssen wir uns nur mit der Frage herumschlagen,
wie finden wir
solche sicheren Kanten
Um die zu finden, brauchen wir eine Definition von einem
anderen Objekt, was häufig in Grafen vorkommt,
was man sich sehr häufig anguckt.
Das sind sogenannte Schnitte. Und hier sehen wir so einen Schnitt.
Ein Schnitt macht Folgendes, der teilt die Knotenmenge in zwei Mengen auf.
Es sind in diesem Beispiel die schwarzen Knoten und die hellgrauen Knoten.
Das ist mein Schnitt, der geht hier so durch.
Das ist diese geschlängelte Linie hier. Das heißt nicht immer,
dass man so eine geschlängelte Linie malen kann.
Also ein Schnitt sagt erstmal nur aus,
die Knotenmenge wird in zwei Mengen geteilt
Und graue oder weiße. Okay,
das ist also die Definition eines Schnitts,
also ein Schnitt ist also ein Tupel von zwei Mengen,
das ist also die Menge S und die Menge V ohne ist.
Also ist eine Partition der Knotenmenge und es ist also eine Teilmenge
der Knoten und V ohne es sind dann die anderen.
So, das ist unser Schnitt. Für die Kanten in
unserem Grafen gibt es jetzt zwei Möglichkeiten.
Entweder sie verlaufen auf der einen Seite des Schnitts,
zum Beispiel zwischen den beiden oder zwischen den beiden,
oder sie laufen über den Schnitt wie die.
Und die sind für uns besonders interessant. Wir sagen also
eine Kante E von UNAV, die kreuzt den Schnitt genau dann,
wenn der Knoten U in der Menge Es ist.
Und wenn der guten V in der Menge V ohne Es
ist. Da wir hier einen ungerichteten Grafen haben,
spielt es keine Rolle, ob wir jetzt UV oder VU sagen.
Okay, wenn das nicht so ist,
also wenn die beide auf der gleichen Seite sind,
dann sagen wir, dass die Kante den Schnitt respektiert.
Okay. So, das ist erst mal zu schnitten.
Und jetzt kommt die Definition, an denen wir gleich
unsere sicheren Kanten identifizieren können. Was wir machen ist,
wir schauen uns so einen Schnitt an und da gibt es
jetzt ja eine Menge von Kanten, die den Schnitt schneiden.
Also diese und diese und diese und diese und diese
So, die Kanten gucken wir uns an und jetzt nehmen
wir die mit minimalem Gewicht über diesen Schnitt. So,
ein Schnitt S, eine, eine Kante E,
die den Schnitt kreuzt, das wollte ich sagen,
ja, das bin ich gerade selber irritiert.
Hier müsste eigentlich, Ihr müsst einfach,
dass eigentlich das Paar des Tubel stehen,
das wäre ein bisschen schöner, aber ist egal.
Also mit der Menge können wir natürlich auch den Schnitt bezeichnen. Also eine,
den Schnitt kreuzende Kante E heißt leichte Kante, genau dann,
wenn W von E unter allen den Schnitt kreuzenden Kanten minimal ist.
Also in diesem Beispiel ist die leichte Kante diese hier,
zwischen C und D,
weil die hat das Gewicht 7 und das ist das niedrigste Gewicht entlang aller Kanten,
die wir hier sehen, die den Schnitt kreuzen.
Okay, das ist eine leichte Kante. Gut,
jetzt kommt das Theorie, mit dem wir
die sicheren Kanten erkennen können
Was wir sagen ist, wir haben also einen zusammenhängenden Grafen,
G und eine Gewichtsfunktion W und A ist eine Teilmenge eines minimalen Spannbaums.
Jetzt betrachten wir einen Schnitt und der soll so sein,
dass er alle Kanten in der Menge A respektiert.
Wir sagen dann auch, er respektiert die gesamte Menge.
Also wir haben eine Anzahl von Kanten schon ausgewählt,
die gehören zu einem minimalen Spannenbaum.
Und jetzt machen wir irgendwie einen Schnitt, aber wir achten darauf,
dass dieser Schnitt keine Kante von denen durchschneidet,
die wir schon ausgewählt haben.
Das ist das einzige Kriterium,
was wir berücksichtigen müssen.
Ansonsten ist alles okay
Ja, also wir dürfen den Schnitt beliebig wählen.
Er darf nur keine Kante aus der Menge A von den Kanten schneiden,
die wir schon, oder kreuzen, die wir schon ausgewählt haben.
So, wenn wir so einen Schnitt haben und wir haben eine Kante E,
die diesen Schnitt kreuzt, und das ist eine leichte Kante,
dann wissen wir, dass die Kante sicher ist.
Ja, also andersrum gesprochen,
wenn wir jetzt hier eine Menge A ausgewählt haben,
das wären die rot markierten Kanten, ja,
wir haben einen Schnitt und der Schnitt respektiert Ähm,
hier die Menge der roten Kanten.
Es gibt keine rote Kante, die den Schnitt schneidet.
Dann können wir die Kanten betrachten, die hier drüber laufen.
Wir nehmen die leichte Kante oder eine leichte Kante,
kann ja auch mehrere geben. Das wäre in diesem Fall
diese hier. Und dann wissen wir, die ist sicher.
Die dürfen wir zur Menge hinzunehmen und wir werden garantiert noch einen minimalen Spannbaum finden können.
Okay, sie haben ja eben als Beispiel gesagt,
da hatten wir ja diese Kante und haben gesagt,
die gehörte bei meinem Beispiel zum minimalen Spannbaum.
Und diese, haben sie gesagt, könnten wir ersetzen.
Und jetzt sehen Sie an dem Beispiel,
dass man das tatsächlich auch an einem Schnitt sehen könnte,
wenn wir den Schnitt so gewählt hätten,
Hätten wir die drei Kanten hier geschnitten.
Dieser Schnitt hätte auch die Kantenmenge A respektiert.
Hätten wir auch machen können, wäre völlig legitim gewesen.
Und entlang dieser Kante gibt es entlang dieses Schnitts,
gibt es drei Kanten, diese und diese und diese.
Und sie sehen, es gibt zwei leichte Kanten.
Es gibt diese leichte, diese Kante hier mit Gewicht 8
und die beide minimal. Ich habe die hier oben gewählt
. Ich hätte zu diesem Zeitpunkt auch die wählen dürfen.
Die sind beide leicht gewesen. Dann hätte ich
den anderen spannenden Baum bekommen
Okay, so, warum gilt das? Jetzt müssen wir
noch einen Beweis führen, dass das tatsächlich so ist,
also dass diese leichten Kanten tatsächlich immer sicher sind,
um wir sie zum Spannbaum hinzunehmen können.
Okay, also was ist die Situation? Wir haben also
einen minimalen Spannbaum, den nennen wir mal Tee.
Ja, also sie sehen hier, sehen sie den minimalen Spannbaum.
Das sind alle gezeichneten Linien hier. Also die gibt es
vielleicht noch mehr Kanten in dem Graphen, aber die,
die sie sehen, die durchgezogenen Linien, die gehören alle zu dem minimalen Spannbaum.
Tee kennen wir aber noch gar nicht.
Wir kennen den nicht, wir kennen nur
die Menge
A, das sind die Knoten, die Kanten,
die wir schon ausgewählt haben, von denen wir sagen,
die gehören auf alle Fälle dazu.
Das wären hier die rot unterlegten. Das ist die Menge A.
Also diese Kante haben wir ausgewählt und die und die und die und die und die.
Diese hier und diese und diese, die haben wir noch nicht ausgewählt.
Gut, das ist die Situation und jetzt brauchen wir irgendwie
einen Schnitt, den können wir jetzt irgendwie wählen.
Also mein Schnitt, der hat jetzt diese vier schwarzen Knoten
und diese vier diese fünf grauen Knoten.
Und beachten Sie, das Einzige, was wir sicherstellen müssen,
ist, dass dieser Schnitt keine rote Kante schneidet.
Ich hatte das auch irgendwie anders machen können. Ich hätte
auch nur diesen Knoten hier abschneiden können, diesen einen.
Oder hier irgendwie so lang oder so. Ist ja alles
okay. Nee, da natürlich nicht. So hätte ich
vielleicht noch schneiden können. Aber die Roten durfte ich,
darf ich nicht schneiden. Okay, so. Okay,
ich habe also jetzt einen Schnitt und ich habe meine Kante
eh und das ist die leichte Kante.
Also es könnte sein, dass diese Kante eh schon zu
T gehört, zu dem minimalen Spannbaum.
Das ist alles super
Da habe ich schon alles gezeigt, was nötig ist,
dann ist die ja sicher, dann gehört die ja zu einem minimalen Spannbaum.
Jetzt könnte es aber sein, dass die vielleicht nicht dazugehört.
Mal angenommen, die Kante E gehört nicht zu meinem minimalen Spannbaum.
Deswegen ist die hier gestrichelt gemalt in meinem Bild.
Die ist gestrichelt, weil, das ist zwar eine Kante,
in dem Graf gehört aber nicht zu dem Baumtee.
So, die kommt dir jetzt vor. Die Kante,
die dazugehört, ist E-Strich, die gehört zu meinem minimalen Spannbaum.
Nicht die. Okay, jetzt haben wir ein Problem,
weil wir wollen ja sagen,
dass wenn wir zu der Menge der roten Karten diese Kante dazunehmen,
können wir immer noch einen minimalen Spannbaum.
Wir werden offensichtlich nicht Tee erzeugen können,
weil dann hätten wir ja hier
einen Zyklus
Das hatten wir keinen Baum mehr, ja. Okay,
so, wir gucken uns an, was passiert.
Also wir haben diese Kante E. Wir betrachten den Spannbaum,
den wir vorher hatten, den Minimalen hier.
Und wir sagen, gut, diese Kante E,
die wird jetzt einen Zyklus erzeugen. Es muss die,
weil jeder Knoten ist ja über den Baum mit jedem anderen verbunden.
Er verbindet alle Knoten miteinander, der Spannbaum. Und die
Kante E gehört nicht zu dem Spannbaum. Das heißt,
die wird irgendwie, das ist so eine Art Kurzschluss.
Die verbindet zwei Knoten, die sonst über einem längeren Pfad
hier miteinander verbunden sind.
So, jetzt habe ich so
einen Zyklus hier
Und bei so einem Zyklus ist es jetzt so,
wenn ich einen Zyklus habe und ich habe eine Kante,
die einen Schnitt kreuzt, dann muss es auch noch
eine zweite Kante geben, die den Schnitt kreuzt.
Weil mit der einen Kante komme ich von grau nach schwarz und dann laufe ich hier lang,
dann kann ich eine gewisse Zeit lang bei den schwarzen Knoten bleiben,
aber irgendwann muss ich zurück zu den grauen Knoten,
damit ich den Zyklus schließen kann, zu dem Knoten zurück.
Das heißt, jeder Zyklus kann sein, dass ein Zyklus den Schnitt komplett respektiert,
aber wenn er eine kannte, wenn eine Kante kreuzt,
dann gibt es auch noch eine zweite, die kreuzt.
So, die zweite kannte die Kreuz, das ist eh Strich.
Die ist irgendwo in dem Graf, gibt es noch eine
Kante e-Strich, die muss auch noch den Schnitt kreuzen.
Die muss es geben und die gehört zu unserem ursprünglichen Spannbaum.
Jetzt machen wir Folgendes. Das heißt, wir bauen jetzt
einen neuen Spannbaum. Wir sagen einfach, okay,
wir nehmen einfach unseren Spannbaum, also alle schwarzen Kanten hier.
Wir werfen e-Strich raus und nehmen eh dazu. Das erste,
was wir jetzt sehen, ist, wir haben wieder einen Spannbaum.
Wir wissen auch nicht, ob er minimal ist,
aber wir haben auf alle Fälle wieder einen Spannbaum,
weil wir haben das Entfernen dieser Kante,
den Baum in zwei Teilbäume zerlegt.
Und durch ihn zunehmend dieser Kante haben wir die beiden Teilbäume
wieder zusammengefügt. Wir haben also wieder einen Baum.
Okay, wie schwer ist dieser Baum-T-Strich? Naja,
wir nehmen einfach das Gewicht von Tee,
das Gewicht von Tee, von dem ursprünglichen Baum.
Und dann wissen wir, wir müssen das Gewicht der Kante E-Strich abziehen.
Das wird subtrahiert, weil die Kante haben wir ja aus dem Baum entfernt.
Und das Gewicht erkannte E, das nehmen wir dazu.
Die Kante eh nehmen wir dazu und deswegen
wird das Gewicht addiert
Das ist also das neue Gewicht, also W von t-w
von E-Strich plus W von E. Das ist das Gewicht unseres
neuen Spannbaums T-Strich. So, und wir sagen jetzt,
dieses Gewicht ist kleiner als das Gewicht des ursprünglichen Spannbaums.
Warum ist das so? Das sehen wir sofort daran,
dass E, unsere Kante eh eine leichte Kante war,
weil bezüglich dieses Schnittes war eh ja eine Kante mit minimalem Gewicht.
Und E-Strich kreuzt auch diesen Schnitt. Das heißt,
das Gewicht von E hier kann höchstens so groß sein,
wie das von E-Strich. Das heißt,
W von E ist kleiner, gleich wie von I-Strich.
Ja, und deswegen ist unser neuer Baum T-Strich auf
alle Fälle auch ein minimaler Spannbaum.
Gut,
die Menge
Ist komplett in T-Striche enthalten. Also die roten Kanten haben wir alle nicht entfernt.
Deswegen ist die Kante eh eine sichere Kante für die Menge A
und wir können den Baum entsprechend um E ergänzen und würden auf diesem Wege auch einen minimalen Spannbaum finden.
Ja, das ist das Theorium der sicheren Kanten,
auf dem quasi alle Spannbaum-Algorithmen funktionieren. Das ist eigentlich ganz interessant,
wir werden gleich zwei Varianten sehen,
wie wir spannend wollen wir berechnen können,
die funktionieren beide mit diesem Theorien, wenn sie die ursprüngliche Literatur angucken,
also irgendwann gibt es ja mal so ein wissenschaftliches Paper,
wo dann jemand diesen Algorithmus das erste Mal aufgeschrieben hat,
dann finden Sie da nichts von irgendwelchen sicheren Kanten oder so.
Ja, da gibt es einen anderen komplizierten Beweis,
mit dem gezeigt wird, dass der Algorithmus korrekt ist.
Diese Beweistechnik, über die sicheren Kanten ist später entstanden,
weil man einfach gemerkt hat, die Algorithmen funktionieren komplett anders,
aber wir können quasi mit dem gleichen Ansatz erkennen,
dass beide Algorithmen korrekt sind.
Okay, gut, jetzt müssen wir einfach nur Algorithmen entwerfen,
von denen wir, wo wir sicherstellen,
dass wir sichere Kanten auswählen und wir wissen auch schon,
wie wir das machen.
Wir suchen uns einfach so Schnitte raus und entlang des Schnitts nehmen wir einfach
die Kante mit minimalem Gewicht und dann nehmen wir die dazu.
Und wie wir diese Schnitte auswählen, Können wir jetzt machen,
wie wir wollen? Hauptsache, die respektieren einfach die Kanten,
die wir vorher schon vorher schon ausgewählt haben. Okay,
so, also wir können direkt die Korrektheit unseres Algorithmus folgern,
aus dem Theorien der sicheren Kanten folgt, die Korrektheit der Greedy-Strategie.
Wir wissen, dass Fine-Safe-Edge in der Lage ist,
uns wirklich eine sichere Kante zu liefern.
Und deswegen ist der Algorithmus dann korrekt, sieht man,
also wenn G, also ein ungerichteter Graf ist und wir haben
eine Gewichtsfunktion W und A ist eine Teilmenge des minimalen Spannbaums,
dann würde ja A quasi einen Wald definieren.
Das ist ja noch kein Baum, weil wir haben ja nur
einen Teil des Grafen ausgewählt und wir haben quasi einen Wald.
Ich habe ein neues Beispiel, es wird jetzt ein bisschen komplizierter,
damit man alle möglichen Sachen mal zeigen kann.
Hier ist wie ein Graf und sie sehen,
hier gibt es eine Reihe von Kanten,
sie sind jetzt alle Kanten in dem Graf und die dicken Kanten sind jetzt die Ausgewählten.
Und sie sehen zu irgendeinem Zeitpunkt, das ist die Menge A hier.
Zu irgendeinem Zeitpunkt haben wir eine Reihe von Kanten ausgewählt,
die gehören alle dazu. Die dünnen Kanten haben wir
noch nicht ausgewählt und wir sehen einen Wald.
Und diesen Wald müssen wir noch ergänzen durch weitere Kanten,
um den minimalen Spannbaum zu erhalten
So, was wir jetzt machen können, ist, wir sagen,
wir nehmen C, eine Zusammenhangskomponente aus diesem Wald heraus,
also es ist die Gelbe hier, die wählen wir jetzt aus,
irgendeine, ja, und wir nehmen eine leichte Kante,
die C mit den anderen Komponenten verbindet,
mit irgendeiner anderen, also wir teilen das hier,
das ist unser Schnitt, das Rote, ist der Schnitt,
damit trennen wir C von dem Rest des Graphen,
dieser Schnitt,
der respektiert offensichtlich die Menge A,
weil wir haben ja quasi einen Baum genommen aus der Menge A und haben den ausgeschnitten,
wenn die hier noch eine schwarze Kante wäre,
dann wäre der Baum
ja größer gewesen
Okay, der Schnitt respektiert also A. Und da nehmen wir jetzt
eine Kante E raus, das ist eine leichte Kante.
Und die verbindet halt C mit irgendeiner anderen Komponente C-Strich,
zum Beispiel mit der,
dann wäre die Kante irgendwie diese oder diese oder diese,
weiß ich nicht, eine von denen.
Und dann wissen wir, dann wissen wir,
dass eh so eine sichere Kante ist und wir die Kantenmenge erweitern können.
Und sehen wir sofort aus unserem Theorien der sicheren Kanten,
also der Schnitt C, V ohne C,
respektiert A und E ist eine leichte Kante für
den Schnitt,
also ist eh auch sicher
So, und mit der Technik können wir jetzt Algorithmen entwerfen.
Hier sehen Sie eigentlich schon eine Strategie, die man machen kann. Man könnte sagen,
ja, ich habe jetzt also immer so eine Menge,
dann suche ich mir so einen Baum und verbinde den mit irgendeinem anderen.
Klar, können wir machen. Das Einzige,
was wir jetzt noch machen müssen,
wir müssen noch überlegen, wie wir etwas geschickt vorgehen,
damit wir möglichst schnell auch immer wissen,
was hier eine sichere Kante ist.
Weil wenn wir das jetzt ungeschickt machen,
müssen wir mal alle Erkannten angucken und zu sagen,
was ist denn jetzt eine leichte Kante und wo geht denn was über einen Schnitt und so.
Das wäre Umständlich. Also wir müssen jetzt
ein bisschen aufpassen, dass das Verfahren auch effizient wird
So, der erste Algorithmus stammt von Kruskal und Kruskal
hat sich Folgendes überlegt und hat gesagt, okay,
also wir haben jetzt eine Menge A ausgewählt,
das ist jetzt ein Wald, haben wir gerade gesehen,
dem Beispiel, so gewisse Knoten sind ineinander verbunden, andere nicht.
Und wir wissen, mit jeder Kante werden zwei Bäume zu einem Knoten verschmolzen.
Am Anfang wäre noch keine Kante ausgewählt. Jeder Knoten wäre
für sich ein Baum, ein Baum mit einem Knoten. So,
das ist die Anfangssituation und was wir jetzt machen ist,
wir sagen einfach, naja, wir wissen schon,
wir müssen ja die Kanten nehmen mit möglichst niedrigem Gewicht
Also, warum machen wir es nicht so? Wir sortieren die einfach mal vorher.
Wir nehmen alle Kanten, wir sortieren die nach aufsteigendem Gewicht.
Und dann betrachten wir die einfach in dieser Reihenfolge,
diese Kanten, ja, und wenn so eine Kante,
dann tatsächlich zwei Bäume miteinander verbindet, die vorher nicht verbunden sind,
dann wissen wir, dann gibt es so einen Schnitt,
dann können wir nämlich den einen Baum trennen von dem Rest.
Und weil diese Kante ja,
weil die ja sortiert sind nach aufsteigender Größe,
wissen wir, dass es keine mehr geben kann,
die die beiden noch verbindet mit einem kleineren Gewicht,
weil die hätten wir ja dann ja vorher genommen,
die sind ja sortiert nach aufsteigendem Gewicht
Das heißt, die können wir nehmen und dann wissen wir,
okay, die ist okay, die ist sicher,
was ist mal eine leichte Kante für einen Schnitt und damit ist die auch sicher.
Gut, der Schnitt trennt dann jeweils, also diesen Baum
von dem Rest des Grafen und dann nehmen wir die.
Jetzt kann es aber blöderweise sein,
dass diese Kante natürlich auch den innerhalb des gleichen Baums verläuft.
Also wenn ich jetzt diese Schwarzen alle ausgewählt habe,
jetzt kommt die unter den hellgrauen, unter den helldünnen Kanten hier,
das sind ja die, die noch nicht betrachtet wurde,
kommt jetzt die nächste Kante bezüglich des aufsteigenden Gewichts und vielleicht ist das diese Kante.
Wenn das diese Kante ist, ist alles gut.
Die verbindet jetzt zwei verschiedene Bäume miteinander. Die muss dann
eine leichte Kante sein, weil wir wissen,
dass es ja die mit minimalem Gewicht ist von allen dünnen Kanten,
die wir ja noch haben.
Also ist es offensichtlich eine leichte Kante. Könnte natürlich auch sein,
dass es diese Kante ist. Ja, mal angenommen,
das ist jetzt die nächste. Das ist jetzt doof,
ja, weil die dürfen wir natürlich nicht hinzunehmen,
weil dann haben wir ja keinen Baum mehr.
Ja, das geht nicht. Das heißt,
wir müssen für die Kanten schnell entscheiden, ob sie quasi zwischen
zwei Bäumen verlaufen oder ob sie innerhalb eines Baums verlaufen.
Wenn sie innerhalb eines Baums verlaufen, dürfen wir sie
nicht hinzunehmen,
Wenn Sie sich jetzt fragen, warum eigentlich nicht,
ist ja auch vielleicht irgendwie eine sichere Kante für irgendeinen Schnitt,
kann man sagen, nein, das kann keine sichere Kante sein,
weil wenn die Kante innerhalb eines Baums verläuft,
dann gibt es gar keinen Schnitt, ja,
der sicher ist, weil wenn sie diese Kante durchschneiden wollen,
dann müssen sie auch eine Kante des Baums durchschneiden.
Ja, und das dürfen sie ja nicht. Okay,
so, also, wir brauchen eine Datenstruktur,
die uns möglichst schnell sagt,
ob zwei Knoten zum gleichen Baum gehört oder zu
unterschiedlichen Bäumen gehört
Diese Datenstruktur gibt es, die braucht man auch noch an
verschiedenen anderen Stellen in der Informatik, also an nicht unerhebliche Datenstruktur.
Und wir haben jetzt so eine Situation ungefähr wie beim Heepsword.
Eigentlich wollen wir uns ja diese Spannbäume angucken und jetzt kommen wir
auf so ein Datenstrukturproblem, was wir erst mal lösen müssen.
Wir müssen irgendwie gucken, wie wir mit diesen Bäumen umgehen.
Die Datenstruktur, die wir brauchen, heißt das Joint Set-Datenstruktur
und das Joint Set-Datenstrukturen bestehen aus drei Operationen.
Sie haben eine Makeset-Operation, damit wird eine Menge erzeugt
Das passiert nur einmal ganz am Anfang,
also nicht so wie ein Hieb,
wo man immer wieder was einwickelt und löscht,
sondern es ist eine Datenstruktur, die ich sowieso in eine Einbahnstraße,
das geht nur in eine Richtung.
Wir haben mal so ein Make-Set, da machen wir eine
Menge von Mengen, dann sind die erstmal alle da.
Dann gibt es eine Union-Operation mit Union,
werden zwei Mengen zu einer Menge zusammengefügt, ja,
und dann gibt es eine Findset-Operation und Findset sucht quasi ein repräsentatives Element.
Und das Wichtige ist, dass es diese Findset-Operation immer
das Gleiche repräsentative Element finden muss
Okay, so, mal angenommen, wir haben diese drei Funktionen,
diese Datenstruktur, dann können wir sofort Krustkals Algorithmus aufschreiben.
Der sieht dann so aus, also die Menge A ist am Anfang leer.
Dann erzeugen wir für jeden Knoten des Graphen eine Menge.
In der Menge ist jetzt nur der eine Knoten drin.
Diese Mengen, die repräsentieren jetzt die Bäume in den Spannbaumwald,
also in dem Wald, den wir immer erzeugen während der Algorithmus läuft.
So, dann brauchen wir eine Sortierfunktion.
Irgendeine dürfen sich aussuchen, die auf der Kantenmenge arbeitet und
die Kanten sortiert
Nach aufsteigendem Gewicht. Dann kommt unsere Vorschleife,
wo wir die Kanten nach aufsteigenden, oh, ist noch ein Fehler,
es müsste G.e heißen, es ist noch alte Notation,
wo wir die Kanten nach aufsteigender Sortierung betrachten, ja,
steht ja extra dabei, ordert by increasing wait,
also erst die Kanten mit kleinem Gewicht und dann werden die immer größer,
dann prüfen wir, ob die beiden Knoten,
die diese Kante verbindet, UNV, ob die zur gleichen Menge gehören,
also ob sie zum gleichen Baum gehören, ja,
und wenn die nicht zum gleichen Baum gehören,
dann wissen wir, das ist eine sichere Kante, Weil,
das ist ja von allen verbleibenden Kanten die letzte, ja,
die noch da ist und das ist die, Entschuldigung, das ist von allen verbleibenden Kanten, die mit minimalem Gewicht, weil die anderen sind ja nach Gewicht sortiert, haben nur größeres Gewicht.
So, dann wissen wir, dass es dies sicher,
dann nehmen wir also zu der Menge A,
diese Kante hinzu und jetzt müssen wir in der Datenstruktur noch schreiben oder uns merken,
dass diese Knoten U&V jetzt zu der gleichen,
zum gleichen Baum gehört,
die haben wir ja jetzt miteinander verbunden und das machen wir mit einer Union-Operation,
da werden die beiden Mengen verbunden,
die den U&V angehören
Okay, dass der Algorithmus korrekt ist, sieht man sofort.
Einfach aus unserer ganzen, aus unserem Konstrukt der sicheren Kanten.
Aufgrund der aufsteigenden Sortierung ist UV eine leichte Kante und
die Korrektheit folgt damit aus unserem Korrala.
Wir wissen noch nicht, wie wir diese Datenstruktur bauen.
Deswegen wissen wir auch noch nicht, wie viel Zeit wir brauchen,
um die Mengen zu bauen, um den Repräsentanten zu finden,
um zwei Mengen miteinander zu vereinigen.
Aber gehen wir mal davon aus, wir hätten dafür eine Datenstruktur,
dann könnten wir sagen, wie lange das dauert.
Dann wäre die Laufzeit von Cruise Skull für einen Graphen mit
N-Knoten und M
Mal die Zeit für Makeset, diese Vorschleife. Wir müssen für jeden Knoten
einmal ein Makeset aufrufen. Plus O von M Login,
das ist die Zeit zum Sortieren der Kanten und irgendeiner darf mir jetzt gleich sagen,
warum das nicht M-Log M, sondern M-Login ist.
Das ist nämlich kein Tippfehler. So,
plus dann haben wir zweimal M mal Tee von Fineset.
Hier bearbeiten wir alle Kanten. Ja,
das ist also M mal und zwei Aufrufe von Fineset,
also zweimal M mal die Zeit von Fineset plus.
Und hier haben wir N minus 1 mal Union.
Das ist komisch, ne? Also es ist eine Vorschleife
hier und die läuft über alle Knoten. Entschuldigung,
die läuft über alle Kanten und trotzdem haben wir ja nicht M mal die Zeit von Union,
sondern N minus eins mal die Zeit von Union.
Noch eine merkwürdige Stelle, die es zu analysieren gilt.
So, weiß irgendjemand, warum das hier N ist und nicht M?
Wir haben M-Kanten bezüglich der Anzahl der Knoten,
können das maximal wie viele sein
Bei Endknoten ein Quadrat, ja. Also mehr Kanten kann es
nicht geben in einem Grafen, ja, in maximal Endquadrat.
Logorithmus von Endquadrat. Das ist zweimal Login. Und dann ist es im O-Kalkül weg, ja. Deswegen,
wir haben, wir werden noch andere Algorithmen sehen,
den Dike-Salgorithmus, den sie vielleicht schon kennen,
die eigentlich hier auf Kanten arbeiten in diesen Datenstrukturen, ja,
aber wenn das im Logorithmus erscheint, wissen wir automatisch,
dass es immer nur ein Login ist,
weil wir maximal quadratisch viele Kanten haben,
Okay, kann mir denn irgendjemand sagen,
warum wir nur N-minus 1 Junior-Operationen haben und nicht M-Union-Operationen?
Ja, was meinte ich? Genau, das ist der Grund. Also die Vorschleife hier,
die wir zwar M mal durchlaufen, aber diese IF-Bedingung hier,
die ist nur N-minus 1 mal wahr, weil mit jedem,
mit jeder Operation hier verbinden wir zwei Knoten miteinander,
wir wählen eine Kante aus und wir wissen ja schon,
dass diese Menge A hier am Ende nur N-minus 1
viele Kanten haben wird
Okay, das ist die Laufzeit. Das ist noch nicht sehr befriedigend.
Wir wissen noch nicht, was hier quasi einzusetzen ist.
Und deswegen müssen wir uns jetzt also mit dieser Datenstruktur auseinandersetzen.
Oh, vorher noch ein schönes Beispiel,
damit man auch mal sieht, wie der Algorithmus läuft.
Wir haben den Beispielgraf von eben, ganz am Anfang,
ist noch keine Kante ausgewählt.
Die Kanten sind sortiert nach aufsteigenderem Gewicht.
Die erste Kante, die wir auswählen, ist die.
Ja, das ist die Kante mit Gewicht 1.
Da gibt es nur die eine mit Gewicht 1.
Also die können wir auch alle Fälle hinzunehmen.
Dann haben wir Kanten mit Gewicht 2. Hier gibt es mehrere
Möglichkeiten. Also die hat Gewicht 2, die hat Gewicht
2. Ich habe jetzt diese ausgewählt
Wenn wir diese Kante betrachten, dann fragen wir,
zu welchem Baum gehört denn der Knoten C?
Der gehört nur zu sich alleine. Der hat noch
gar keinen, der ist noch nicht mit nichts verbunden.
Und zu welchem Baum gehört der Knoten I? Der gehört auch
nur zu sich alleine. Der hat auch noch keine weiteren Knoten.
Und deswegen gehören die zu unterschiedlichen Bäumen und dann können wir
den beide miteinander verbinden. Das geht eine Zeit lang so weiter,
das müssen wir gar nicht jeden Schritt lang machen.
Als nächstes betrachten wir diese Kante hier, G und F
gehören zu unterschiedlichen Bäumen, werden also miteinander verbunden.
Ja, und dann kommt hier zum Beispiel A und B
, gehört auch zu unterschiedlichen Bäumen, werden miteinander verbunden.
Und dann kommt hier C und F, hätten auch auch,
die haben beide Gewicht hier, die hatten auch in unterschiedlicher Reihenfolge,
anderer Reihenfolge hinzugenommen werden, können wir Zufall.
Jetzt passiert zum ersten Mal was anderes.
Hier zwischen I und G haben wir eine Kante mit Gesicht,
Gewicht 6. Aber I und G gehören bereits zum gleichen Baum.
Also I ist hier rum, bereits mit G verbunden.
Das heißt, die Findset-Operation von I und G
wird den gleichen Repräsentanten liefern und wird übrigens sagen,
die beiden gehören zum gleichen Baum.
Und weil dem so ist, können wir diese Kante,
diese Kante nicht zur Menge auf in dem Engel aufgenommen.
Dann kommt die Menge hier oben mit Gewicht 7,
die wird aufgenommen. Diese Kante hier hat wieder die Eigenschaft,
dass sie zwei Knoten verbindet,
die zum gleichen Baum gehört,
wird also nicht aufgenommen
Ja, die Wahrscheinlichkeit, dass es passiert,
die steigt jetzt, ist ja auch logisch.
Jetzt kommen ja die Kanten mit den hohen Gewichten,
die wollen wir ja eigentlich in dem Spannmamok gar nicht sehen.
Jetzt betrachten wir die hier und die wird aufgenommen in den Grafen.
Hier ist übrigens wieder der Zeitpunkt. Also ich wähle die,
kann sie hier, die verbinde zwei Teilbäume,
die nicht miteinander verbunden sind,
diesen Teilbaum und diesen ganzen Rest hier und danach sind sie verbunden.
Danach gucke ich mir diese Kante an,
ja, jetzt sind die beiden Teilbäume schon miteinander verbunden und
ich wähle sie nicht aus
Hier hat der Zufall quasi gerade entschieden, wie der Baum aussieht.
Ich hätte auch diese Kante nehmen können. Die ist auch,
ist ja auch minimal, ist ja nur bezüglich der Reihung zufällig,
ob jetzt gerade die oder die genommen hätte.
Dann hätte ich einen anderen Baum gehabt. Wer die
dazu gekommen. Okay, jetzt habe ich die genommen,
dann kommt die hier dazu und jetzt sehe ich,
jetzt ist mein Baum schon vollständig.
Ich habe schon alle Knoten miteinander verbunden. Ich könnte zu
diesem Zeitpunkt den Algorithmus auch abbrechen. Ich wüsste schon,
ich habe einen spannenden Baum gefunden, könnte ich schon anhalten.
Meiner läuft jetzt noch weiter. Wir gucken,
was die Kanten an und die verbinden natürlich immer nur den
gleichen Baum und wir sind eigentlich fertig und dann das Ganze erledigt.
So, jetzt kommt die Jetzt kommt unsere Datenstruktur, die wir brauchen.
Also die Union Find oder das Joint Set-Datenstruktur.
Manchmal ist es die Union Find aufgrund der beiden Operationen,
Union und Find. Hier ist die ganze Idee hinter Union und Find
. Was wir machen ist, wir sagen einfach,
wir werden so eine Menge repräsentieren durch einen Baum.
Unser Baum, also Bäume hatten wir bisher immer so,
dass wir sie von der Wurzel zu den Blättern durchlaufen haben.
Dieser Baum ist ein bisschen anders. Die einzige Geschichte,
die wir machen, ist, wir laufen zu Wurzeln
Weil wir wollen einen Repräsentanten finden und
der eindeutige Repräsentant in einem Baum ist natürlich die Wurzel.
Der Knoten ist ja immer der Gleiche. Das heißt,
wir müssen einen Baum bauen, wo die Kanten zur Wurzel zeigen.
Das ist eigentlich sehr angenehm, weil jeder Knoten hat ja
nur einen Vorgänger in einem Baum.
Das heißt, die Datenstruktur ist ganz einfach.
Wir müssen uns für jeden Knoten nur einen Vorgänger merken.
So, und dann sieht es so aus.
Und die Wurzel, die hat die Eigenschaft,
dass der Vorgänger auf sich selbst zeigt.
Dann müssen wir, dann markieren wir damit die Wurzel.
So, und während der Algorithmus läuft,
dann haben wir vielleicht diese Situation.
Jetzt haben wir
zwei Mengen
Der Baum repräsentiert eine Menge. Und wenn Sie jetzt zum Beispiel fragen würden,
gehören B und E zur gleichen Menge,
dann würden Sie so vorgehen, dass Sie sagen,
okay, was ist denn die Wurzel des Baumes,
wo B dazu gehört, dann laufen Sie hoch und sagen,
das ist c und dann sagen Sie,
was ist denn die Wurzel des Baums,
zu dem E gehört und dann laufen Sie hoch und sagen,
das ist auch zehn, dann wissen Sie, aha,
gehört zurgleichen Menge.
Wenn Sie fragen, gehört denn H und D zur gleichen Menge,
dann bestimmen Sie die Wurzel, das sind C und F,
sind unterschiedlich, gehören also nicht zur gleichen Menge.
Gut, das wäre die Feindoperation. Die sagt uns
den Repräsentanten, also die Wurzel davon. Ja,
und was machen wir, wenn wir zwei Mengen vereinigen wollen?
Da müssen wir einen großen Baum erzeugen
Okay, ist kein Problem. Wir nehmen einen der beiden
Bäume und hängen den einfach unter die Wurzel des anderen.
Hängen den hier so drunter. Der Baum hier muss übrigens nicht
binär sein. Das spielt für uns überhaupt keine Rolle.
Es muss kein binärer Baum sein. Der könnte auch viele
Kinder haben. So, den hängen wir jetzt hier drunter.
Und jetzt haben wir einen großen Baum. Und wenn wir jetzt zum Beispiel fragen,
gehören H und B zur gleichen Menge, dann würden wir sagen,
H, hat die Wurzel F und D,
hat die Wurzel F und jetzt gehören sie zueinander.
Jetzt haben wir die beiden Mengen vereinigt. Okay, super
. Das ist die Datenstruktur. Und mit der können wir
das Problem lösen
Aber ich kann Ihnen aber sagen,
die ist noch nicht so richtig effizient, ja,
wenn das doof läuft,
dann haben wir so ganz lange Kaskaden irgendwie da drin und das ist unschön.
Deswegen brauchen wir noch einen Trick, damit der Algorithmus schnell wird.
Und hier sind die Tricks. Es gibt zwei Tricks,
um das Ganze schnell zu machen. Es sind zwei Heuristiken.
Das sind Heuristiken, weil sie garantieren uns keine spezielle Eigenschaft.
Also wir können nicht garantieren, dass in etwas passiert,
sondern sie werden tendenziell das Verfahren erstmal beschleunigen, ja.
Die erste Heuristik heißt Union by Rank
Die bezieht sich auf die Union-Operation. Und was wir da einfach machen,
ist, wenn wir sagen, wir haben zwei Bäume,
ja, und wir müssen die jetzt vereinigen,
dann haben wir ja die Möglichkeit zu sagen,
wir machen C zu einem Kind von F,
das habe ich hier gemacht.
Man könnte aber auch F zu einem Kind von C machen,
wäre auch in Ordnung. In beiden Fällen hätten wir
einen Baum. Wenn die Bäume unterschiedlich groß sind,
also unterschiedlich tief sind, dann machen wir es einfach so,
dass wir den kleineren Baum unter den Größeren hängen.
Weil wir müssen ja versuchen, möglichst nicht so tiefe Bäume zu kriegen,
weil je flacher die sind, umso besser.
Das ist der erste Trick, den wir machen.
Hier sieht man den nicht, weil beide die gleiche
Höhe haben
Die zweite Heurestick heißt Path Compression, also Pfadkomprimierung und die
findet beim Findset statt. Und was wir jetzt machen,
ist, mal angenommen, wir fragen, das ist unser Baum.
Hier sind noch, wo die Dreiecke sind, sind noch weitere Bäume dann.
Also weitere Teilbäume drunter. Und wenn wir Findset aufrufen von,
ah, dann laufen wir den Weg hier hoch bis zur Wurzel F.
Und jetzt sagen wir uns, ja,
wenn wir sowieso hier schon hochlaufen, diesen ganzen Weg,
dann haben wir uns diese Knoten alle angeguckt.
Warum machen wir in dem Augenblick den Baum nicht einfach ein bisschen dichter?
Wir schieben den so zusammen. Und wir sagen einfach,
entlang dieses Weges wird jeder Knoten einfach ein Kind der Wurzel
Ja, also eh ist schon Kind der Wurzel,
aber David 1 und C wird 1 und B wird 1 und A
wird 1. Jetzt werden die alle Kinder der Wurzel.
Und der Baum ist natürlich viel besser als der.
Wenn wir jetzt zum Beispiel C anfragen,
dann brauchen wir noch einen Schritt und haben die Wurzel gefunden.
Und auch wenn wir ein Kind von C anfragen,
haben wir natürlich den Pfad verkürzt.
Das ist die Pfadkomprimierung, die man durchführt.
Das sind die zwei Heuristiken, die man einsetzt.
Und es ist offensichtlich erstmal, die werden beide dafür sorgen,
dass die Bäume nicht so tief sind,
dass sie also möglichst alle Knoten nah an der Wurzel sind.
Wie viel es bringt,
ist erstmal noch unklar
Ja, jetzt müssen wir noch Pseudo-Code dafür angeben.
Das ist ganz einfach hier. Wir machen eine Makeset-Operation,
so eine Makeset-Operation. Achso, unsere Datenstruktur ist so,
wir haben für jeden Knoten X einfach einen Vorgänger.
P, ein Parameter P, also ein Attribut-P, wollte ich sagen.
Und wir merken uns noch eine Zahl und zwar den Rang,
also Rank. Der Rang ist eine Schranke für die Höhe
des Teilbaums unter X. Wir wüssten eigentlich ganz gerne,
wie hoch der Baum ist, der unter uns ist,
damit wir hier entscheiden können,
welchen Baum hängen wir unter welchen anderen
Werden das aber nicht perfekt berechnen können, während der Algorithmus läuft, aber wir merken uns so eine Schranke dafür.
Wir wissen, also es ist nicht mehr als das. Gut,
so, wenn wir jetzt Mengen bauen, dann sagen wir,
okay, wir haben so ein Objekt X und dann sagen wir X,
der Vorgänger von XSX, dadurch bauen wir diese Schleife,
um die Wurzel zu markieren und der Rang ist null,
das wissen wir am Anfang,
also der hat ja noch keine Kinder da drunter.
Jetzt bauen wir die Funktion, die verknüpft,
das ist die Linkfunktion, das ist eine Funktion,
die wir nachher gar nicht mehr sehen werden,
die Linkfunktion nimmt zwei Knoten X und Y, das müssen eigentlich Wurzeln sein.
Und die macht jetzt die Union bei Rank-Heuristik. Das heißt,
wenn der Rang von X größer ist, ist der von Y,
dann machen wir X zu einem Kind von Y. Und anderem,
weil es machen wir Y zu einem, nee, andersrum, ne?
Da machen wir Y zu einem Kind von X.
Und sonst machen wir X zu einem Kind von Y.
Wenn die Ränge gleich sind, naja, dann ist egal.
Also dann haben wir uns ja hier entschieden offensichtlich für den Els-Teil.
Ja, ist egal, wie wir es machen. Was wir dann aber tun,
ist, dass wir sagen, okay, der Rang von Y,
der wird jetzt um eins größer, weil wir wissen,
wenn die Bäume gleich sind und wir hängen den einen darunter,
dann wird er in der Höhe wachsen um eins.
Dann korrigieren wir den Rang hier und addieren hier eins drauf. So,
wenn die Union-Operation, die macht jetzt nichts weiter,
als die Wurzeln zu suchen, wenn wir jetzt sagen X und Y,
das sind zwei Mengen, für einige die mal, dann sagen wir,
okay, wir suchen die Wurzeln, das macht Feindset,
sucht die beiden Wurzeln und dann benutzen wir unsere Link-Funktion, um die Bäume zusammenzuhängen.
Okay, jetzt brauchen wir nur noch die Feindset-Funktion und da
habe ich zwei Varianten für sie, dürfen sich die aussuchen,
die sie schöner finden
Es gibt ein Rekusives und ein iteratives Findset und ich kann
Ihnen sagen, an dem rekusiven Findset verzweifeln die allermeisten,
um zu verstehen, wie die Pfadkomprimierung funktioniert.
Deswegen steht hier das andere. Das ist natürlich viel eleganter,
aber ehrlich gesagt, das ist ein bisschen schwieriger.
Also rekusiv machen wir folgendes, solange X-Ungleich XP ist,
also solange wir nicht eine Schleife sehen,
dass X auf sich selber zeigt mit seinem Vorgängerzeiger,
rufen wir Rekusiv Findset auf und setzen den Vorgänger von X
auf das Ergebnis dieser Routine.
Und dann geben wir am Ende X.p zurück
Okay, das ist die rekusive Variante des Algorithmus.
Hier wird quasi direktlosive Aufruf gestartet, die Wurzel gefunden.
XP kriegt entsprechend die Wurzel zugeordnet und danach sind wir fertig.
So, die iterative Variante, die ist ein bisschen leichter zu verstehen.
Also wir merken uns in eher die Wurzel,
also in eher suchen wir die Wurzel.
Am Anfang ist das X und dann haben wir die Wahlschleife,
solange also eher ungleich R.P., sind wir noch nicht an der Wurzel angekommen.
Gehen wir mit R zum Vorgänger von P. Und diese Wahlschleife,
wenn die terminiert, sind wir also den ganzen Baum hochgeklettert,
haben wir die Wurzel gefunden. Jetzt laufen wir da noch
mal hoch. Es ist die zweite Wildschleife hier,
solange also x ungleich er ist und wir noch nicht an der Wurzel angekommen.
Dann merken wir uns in PX den Vorgänger von X.
Wir setzen den Vorgänger von X auf R und nachher
wird X zu dem ehemaligen Vorgänger von X. So,
und dann machen wir den nächsten Knoten.
Das ist die Pfadkomprimierung hier, der Schritt.
Und am Ende geben wir die Wurzel zurück.
Was man an dem Findset sieht, ist,
es wird nicht der Versuch unternommen,
den Rang zu korrigieren
Bei der Pfadkomprimierung könnte es ja sein, dass der Rang
kleiner wird, weil die Knoten alle nach oben rutschen,
wenn der Rang ja kleiner werden.
Das könnte man versuchen zu korrigieren, aber das ist zu aufwendig.
Also, weil dann müssten wir den ganzen Baum analysieren und
das können wir nicht. Deswegen wird der Rang nicht korrigiert
. Und das ist der Grund, warum der Rang,
wo stand es? Nee, wo stand es?
Warum der Rang nur eine obere Schranke ist? Also,
warum der Parameter, das Attribut hier nur eine Oberschranke ist.
Okay, so, jetzt müssten wir die Datenstruktur analysieren und
die Laufzeitanalyse machen
Laufzeitanalyse dieser Datenstruktur, ist nicht nur zu schwierig für diese Vorlesung.
Sie ist ehrlich gesagt auch fast schon für die Hauptstudiumsvorlesung zu schwierig.
Die ist richtig schwierig. Also da kann man sich lange mit beschäftigen,
wie das genau funktioniert. Ich sage Ihnen, was rauskommt hier. Was da rauskommt,
ist, dass wenn Sie, ich muss Ihnen gleich noch sagen,
was amortisiert bedeutet, also wenn Sie N-Makeset-Operationen machen,
so endet ja die Anzahl der Knoten für unsere,
für unseren minimalen Spannbau, N-Minus-1-Union-Operation und M-Feind-Operation,
Dann ist die Gesamtzeit dafür, für alle diese Operationen,
ist O von M. mal Alpha von N. Alpha ist eine spezielle Funktion aus der Mathematik.
Und zwar ist es die Inverse der Ackermann-Funktion.
Kennt irgendeiner von Ihnen die Ackermann-Funktion? Ah, okay.
Die Ackermann-Funktion ist eine Funktion, die einfach dadurch charakterisiert ist,
dass sie ein unheimlich starkes Wachstum hat.
Alpha ist die Inverse, was natürlich bedeutet, sie hat ein
unheimlich flaches Wachstum. Und für uns ist eigentlich nur relevant,
dass Alpha eigentlich kleiner gleich vier ist, für alle X,
die für uns relevant sind. Wer behauptet,
dass für die Knotenanzahl er einen Grafen konstruiert,
mit mehr als 10 hoch 80 Knoten,
der wird keinen Rechner haben, mit dem er das tun kann,
weil die Anzahl der Atome im Universum nur auf 10 Uhr 100 geschätzt werden.
Also insofern werden sie das nicht machen. Also insofern können wir
davon ausgehen, für alles Relevante ist es kleiner gleich vier.
Aber im mathematischen Sinne ist die Alpha-Funktion nicht konstant.
Sie ist nicht konstant, sie wächst. Das heißt,
sie finden immer wieder Werte, wo die dann auch noch
wieder größer wird.
Aber sie wächst einfach
unheimlich langsam
So, und jetzt, wenn Sie diese Laufzeitabschätzung sehen,
dann sehen Sie schon,
warum das wahrscheinlich nicht so trivial ist, so was rauszufinden.
Also da muss man eine Menge, Menge rechnen,
um das zu zeigen, dass das tatsächlich so ist.
Den Begriff muss ich noch erklären, amortisierte Laufzeit.
Die Laufzeit, die Sie hier sehen,
ist ja die gesamte Laufzeit der Ausführung dieser ganzen Operation.
Also en mal das, Andrews 1, mal das und en mal das.
Aus dieser Schranke können Sie nicht ableiten, wie lange eine individuelle Funktion dauert.
Die dauert natürlich nicht länger wie die Gesamtzeit. Okay,
aber Sie können nicht sagen, aha,
also dann kostet das hier wahrscheinlich irgendwie N durch M-Zeit oder sowas.
Das wissen Sie nicht, das können Sie nicht sagen.
Aber insgesamt, wenn Sie das alles machen,
Und dann haben sie diesen Laufzeitbedarf.
Und das sind amortisierte Laufzeitbetrachtungen. Das heißt,
sie sind nicht in der Lage, eine präzise Abschätzung für einen Aufruf zu machen.
Aber sie wissen, sie machen das N-mal und dafür können sie die Laufzeit abschätzen.
Sobald man aus der einfachen Algorithmen-Theorie herauskommt, werden fast alle Algorithmen amortisiert analysiert.
Also mit dem einfachen Betrachtung der Laufzeiten, so wie wir hier machen,
kann man einfach komplexere Algorithmen nicht mehr betrachten.
Das braucht man dann. Okay, ist für uns hier
nicht weiter wichtig. Wir wissen, alle Union-Feind-Operationen brauchen O
von M mal AlphaN-Zeit.
Und das bedeutet der Kruskal-Algorithmus
O von M-Log-in, das ist der Sortierteil. Plus O von M mal AlphaN,
das ist der Teil für das hier, im Allverein.
Und das ist im, weil das Alpha von Enfie langsamer
wächst als der Logarithmus, ist das O von M-Log-in.
Ja, die Gesamtzeit, um das zu berechnen.
Was man hier sieht, ist schon ein bisschen ärgerlich.
Die Zeit wird nur durch das, für das Sortieren verbraucht
an dieser Stelle. Dafür brauchen wir die ganze Zeit.
Und wenn wir schneller sortieren können, wäre unser Algorithmus auch schneller.
Und wir wissen ja, es gibt ja Szenarien,
in denen wir schneller sortieren können
Ja, wir haben ja verschiedene Verfahren gesehen. Wenn die zum Beispiel die Kantengewichte,
wenn die ganz zahlig sind in einem speziellen Bereich,
dann können wir halt mit entsprechenden Radik-Sort etc.,
die können wir einsetzen und haben dann hier lineare Laufzeit,
ja, beim Sortieren.
Und das ist natürlich super. Dann wäre die Gesamtlaufzeit
gegeben durch O von M mal AlphaN,
AlphaN ist für uns als Informatikersicht konstant, ja.
Das heißt, wir hätten eigentlich lineare Laufzeit für unseren Algorithmus, fast lineare Laufzeit.
Okay, also der Kruska-Algorithmus ist definitiv Methode der Wahl,
wenn sie Kantengewichte haben, die sie schnell sortieren können.
Ja, dann sollte man so auf alle Fälle vorgehen.
Oder vielleicht haben sie die sogar schon sortiert.
Das gibt es auch. Vielleicht ist ihr Graf einfach so
gegeben, dass die Kantengewichte schon sortiert sind.
Dann sollten sie natürlich auf alle Fälle groß geiles Algorithmus verwenden.
So, es gibt noch ein zweites Verfahren. Oh,
es gibt noch ein zweites Verfahren. Ich erzähle dir noch
die Idee, die schaffen wir auf alle Fälle noch.
Und zwar ist das der Algorithmus von Prim,
den wir auch wieder ganz schnell sehen können,
dass der korrekt ist, indem wir einfach unsere sicheren Kanten bemühen.
Die Menge A ist am Anfang leer und wir wählen einen beliebigen Knoten
R Und jetzt werden wir folgendes machen.
Wir werden jetzt um eher herum den Spannbaum wachsen lassen.
Wir bauen da immer was dran. Das macht Prims-Algorithmus.
In jeder Iteration wird ein zusammenhängender minimaler Spannbaum, um eine Kante erweitert.
Wir schauen uns also den Schnitt an. Der Trend,
den Baum, den wir haben von dem ganzen Rest,
wir nehmen die nächste Kante,
die leichte Kante entlang dieses Schnitts und bauen damit einen weiteren Knoten an den Spannbaum an.
Damit wir das effizient machen,
haben wir eine Prioritätswateschlange und da sind alle Knoten drin,
die noch nicht mit A verbunden sind.
Und das Priorität ist das Kantengewicht,
was wir brauchen,
um den Knoten anzubauen
Und damit können wir quasi auch direkt ganz schnell immer herausfinden,
was die leichte Kante ist. Und dann speichern wir uns
die Vorgängerrelation wie bei einer Breitensuche und dann haben wir unsere
Menge A und dann haben wir unseren Spannbaum gefunden.
Das ist der Pseudo-Code, den gucken wir uns dann aber
in der nächsten Woche an.
Das war es dann für heute