Grundlagen der Informatik 2 – Algorithmik Vorlesung 1 und 5 Prof. Dr. Wolfram Conen [email protected] Raum: P -1.08 Version 0.9β , 1. Mai 2005 c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Inhalte • Dijkstra: Korrektheit, Datenstrukturen zur effizienten Implementierung (Priority Queues, Exkurs: Heap Sort, Radix Sort, Bucketsort); Minimal spannende Bäume (Union-Find) • Greedy-Algorithmen, Huffman-Codierung, Hashing • Algorithmen für uninformierte und informierte Suche nach Problemlösungen • NP-Vollständigkeit: TSP und andere schwere Probleme; Approximation • Strings und Suffix-Trees c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 2 Was wir nicht mehr tun können . . . • (Effiziente) randomisierte Algorithmen zur (exakten oder approximativen) Problemlösung • Andere Modelle der Berechenbarkeit (Termersetzungssysteme, DNA-Computer etc.) • Logik und formal nicht-erkennbare Wahrheiten (Gödel) • Moderne Logik und das Semantic Web (Description Logic, Temporal and Action Logics) • Weitere Grundlegende Algorithmen und Methoden der KI, z.B. zum maschinellen Lernen, zur symbolischen Wissensverarbeitung, zum Data Mining, zur Sprachverarbeitung etc. • . . . und viel, viel mehr, das auf den Inhalten unserer GIN1a/1b/2-Veranstaltungen aufbaut. c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 3 Ziele aus der Modulbeschreibung . . . • Erkennen der grundlegenden Bedeutung von mathematischen/theoretischen Instrumenten für das Finden und die Analyse von Problemlösungen. • Beherrschen der grundlegenden Begrifflichkeiten, der wichtigsten Resultate und der wesentlichen Beweisverfahren. • Überblicksartige Kenntnisse der grundlegenden theoretischen Resultate und Methoden, die wichtigen Einfluss auf weitere Felder der Informatik haben (z.B. auf Algorithmik, Sprachen, künstliche Intelligenz). c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 4 Literatur... Kaufen Sie erstmal nichts! Ich nenne Literatur bei den einzelnen Inhalten! Für die Jäger und Sammler unter ihnen (zum Nachschlagen für den Schrank) hier das meist gelobte Buch zu Algorithmen und Datenstrukturen: Cormen, Leierson, Rivest, Stein Introduction to Algorithms, MIT Press, 2001 (2. Auflage, 1202 Seiten, ca. 60 Euro) Für den Blick über den Tellerrand dieser Vorlesung: • Schöning: Ideen der Informatik, Oldenbourg, 2002 • Gritzmann, Brandenberg: Das Geheimnis des kürzesten Weges, Springer, 2. Aufl., 2003 c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 5 Unterlagen... Für Skriptversionen, Übungsblätter, aktuelle Nachrichten usw. guckst du wie gehabt hier: http://www.informatik.fh-gelsenkirchen.de/conen c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 6 Algorithmik: Dijkstra Dijkstra revisited (1) Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V BEGIN S ← {s}, Distanz(s) ← 0 /* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */ (1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR WHILE S 6= V DO /* ⇐= Hier auf alle Knoten erweitert! */ (2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ } (4) FOR ALL v ∈ V \S DO IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗ Satz 1. D IJKSTRAS Algorithmus arbeitet korrekt. Beweis: Wir zeigen, dass die folgenden Zusicherungen vor jeder Ausführung von (2) und am Ende gelten: (i) Für alle Knoten v ∈ S und alle Knoten w ∈ V \S gilt Distanz(v ) ≤ Distanz(w). (ii) Für alle Knoten v ∈ S gilt: Distanz(v ) ist die Länge eines kürzesten s-v -Weges in G. Falls Distanz(v ) < ∞, dann gibt es einen s-v -Weges der Länge Distanz(v ), dessen letzter Bogen (Vorgänger(v ),v ) ist (außer für v = s) und dessen Knoten alle in S liegen. (iii) Für alle Knoten w ∈ V \S ist Distanz(w) die Länge eines kürzesten s-w-Weges im Untergraph W mit den Knoten VW = S ∪ {w}. Falls Distanz(v ) < ∞, dann Vorgänger(w) ∈ S und Distanz(w) = Distanz(Vorgänger(w) + Bogenlänge(Vorgänger(w,v )). c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 7 Algorithmik: Dijkstra Dijkstra revisited (2) Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V BEGIN S ← {s}, Distanz(s) ← 0 /* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */ (1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR WHILE S 6= V DO /* ⇐= Hier auf alle Knoten erweitert! */ (2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ } (4) FOR ALL v ∈ V \S DO IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗ Beweis (Forts.): (Per Induktion, hier abgekürzt) Klarerweise gelten alle Bedingungen nach der Initialisierung (Induktionsanfang). Wir müssen also noch beweisen, dass (2),(3) und (4) die Gültigkeit der Bedingungen nicht verletzen. Anmerkung: Der Algorithmus terminiert natürlich: in jeder Runde wird ein Knoten aus V \S zu S hinzugefügt. Deshalb ist klar, dass die Gültigkeit von (ii) nach Abschluß des Algorithmus garantiert, dass alle kürzesten Wege gefunden wurden (denn das genau sicher (ii) zu jedem Zeitpunkt für alle Knoten aus S zu – und in S sind am Ende eben alle Knoten enthalten!) c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 8 Algorithmik: Dijkstra Dijkstra revisited (3) Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V BEGIN S ← {s}, Distanz(s) ← 0 /* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */ (1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR WHILE S 6= V DO /* ⇐= Hier auf alle Knoten erweitert! */ (2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ } (4) FOR ALL v ∈ V \S DO IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗ (Wir nehmen nun an, dass die Bedingungen vor der Ausführung von (2) gegolten haben und zeigen, dass sie dann auch nach (2),(3) und (4) gelten!) Sei nun v ∗der Knoten, der im Schritt (2) ausgewählt wird (das gilt für den ganzen Rest des Beweises!) Zu (i): Für jedes v ∈ S und jedes w ∈ V \S gilt Distanz(v ) ≤ Distanz(v ∗) ≤ Distanz(w) wg. (i) und der Art der Auswahl in (2), also gilt (i) nach (3) und (4) weiterhin. c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 9 Algorithmik: Dijkstra Dijkstra revisited (4) Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V BEGIN S ← {s}, Distanz(s) ← 0 /* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */ (1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR WHILE S 6= V DO /* ⇐= Hier auf alle Knoten erweitert! */ (2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ } (4) FOR ALL v ∈ V \S DO IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗ Hält (ii) nach (3) (und damit auch nach (4), denn dort wird nichts mehr an S verändert)? Um das zu prüfen, reicht es wg. der Gültigkeit von (iii) vor (3) zu zeigen, dass kein s − v ∗ -Pfad, der einen beliebigen Knoten w aus V \S enthält, kürzer sein kann, als Distanz(v ∗ ). Nehmen wir also an, dass ein solcher Pfad P mit w doch existieren würde und sei w der erste Knoten ausserhalb von S , auf den wir bei der Reise von s nach v ∗ treffen. Weil (iii) vor (3) galt, ist Distanz(w) ≤ Pfadkosten(P[s,w] ).1 Weil die Bogengewichte nicht-negativ sind, gilt Pfadkosten(P[s,w] ) ≤ Pfadkosten(P ) < Distanz(v ∗ ). Das impliziert aber Distanz(w) < Distanz(v ∗ ), im Widerspruch zur Wahl von v ∗ in (2) (hier wird versteckt nochmal (iii) verwendet: die Distanz von v ∗ war vor (2) minimal unter allen Knoten aus V \S ). 1P [s,w] ist der Teil des Weges P , der von s nach w führt, s. auch Übungen zu GIN1b (eindeutig, weil P ein Weg ist). c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 10 Algorithmik: Dijkstra Dijkstra revisited (5) Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V BEGIN S ← {s}, Distanz(s) ← 0 /* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */ (1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR WHILE S 6= V DO /* ⇐= Hier auf alle Knoten erweitert! */ (2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ } (4) FOR ALL v ∈ V \S DO IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗ Noch zu zeigen ist, dass (iii) nach (2),(3) und (4) gilt: Falls für ein w aus V \S in (4) der Vorgänger auf v ∗ und die Distanz auf Distanz(v ∗ ) + Bogenlänge(v ∗ ,w) gesetzt wird (also ein Update erfolgt), dann existiert ein s-w-Weg im Teilgraph H mit den Knoten VH = S ∪ {w} der Länge Distanz(v ∗ ) + Bogenlänge(v ∗ ,w) mit dem letzten Bogen (v ∗ , w) (achten sie wieder darauf, dass (iii) für v ∗ galt). Gilt nun nach dem Update (iii) weiter für alle w aus V \S ? Nehmen Sie an, dass es nach (3) und (4) ein w und einen s-w-Weg P im Teilgraph H mit VH = S ∪ {w} gibt, der kürzer ist, als Distanz(w) (also (iii) verletzt). P muß nun v ∗ enthalten (nur dieser Knoten wurde zu S hinzugefügt), sonst wäre (iii) bereits vor (3) verletzt gewesen (achten sie darauf, dass Distanz(w) niemals ansteigt)! c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 11 Algorithmik: Dijkstra Dijkstra revisited (6) Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V BEGIN S ← {s}, Distanz(s) ← 0 /* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */ (1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR WHILE S 6= V DO /* ⇐= Hier auf alle Knoten erweitert! */ (2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ } (4) FOR ALL v ∈ V \S DO IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗ Sei nun v der Vorgänger von w in P . Da v ∈ S ist, wissen wir wg. (i), dass Distanz(v ) ≤ Distanz(v ∗ ) (v ∗ wurde zuletzt ausgewählt, war also vor (2) in V \S und da galt ja (i) auch schon mit v ∈ V !) und dass Distanz(w) ≤ Distanz(v ) + Bogenlänge(v, w) (wäre es anders hätte (4) bereits in früheren Runden zu einem Update führen müssen!). Wir schließen: Distanz(w) ≤ Distanz(v ) + Bogenlänge(v, w) ≤ Distanz(v ∗ ) + Bogenlänge(v, w) ≤ Pfadkosten(P). Die letzte Ungleichung gilt, weil wg. (ii) Distanz(v ∗ ) die Länge einen kürzesten s-v ∗ -Weges in S ist und weil P einen sv ∗ -Weg und den Bogen (v, w) enthält. Aber natürlich steht Distanz(w) ≤ Pfadkosten(P ) im Widerspruch zur Annahme, dass P kürzer ist, als Distanz(w) – also kann es einen solchen Weg P nicht geben (also gilt (iii) nach (3) und (4)!). q.e.d. c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 12 Algorithmik: Heap und Co. (für Dijkstra) Bucket/Radix Sort, PQueues, Heaps [s. PPT-Präsentationen bzw. die zugehörigen PDF-Files] c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 13 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Zur Erinnnerung: Sei G = (V, E) ein ungerichteter Graph mit Kantenbewertung w. • G heißt zusammenhängend (oder verbunden), wenn jeder Knoten von jedem anderen Knoten erreichbar ist (bei gerichteten Graphen hieße das dann stark zusammenhängend). • Ein Graph ohne Kreise (oder Zyklen) heißt kreisfrei (oder azyklisch). • Ein zusammenhängender Graph ohne Zyklen heißt auch freier Baum (frei, weil es keine ausgezeichnete Wurzel gibt). Anmerkung: Wenn der Graph nicht zusammenhängend, aber kreisfrei war, dann kann man ihn auch als Wald von freien Bäumen ansehen. Übrigens: Eine Komponente ist ein maximal zusammenhängender Teilgraph von G, d.h. es gibt keine Kante in G, die einen Knoten, der nicht im Teilgraph ist, mit diesem Teilgraph verbindet. Mit anderen Worten: andere Knoten, als die die im Teilgraph enthalten sind, sind von Knoten dieses Teilgraphs aus nicht erreichbar. c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 14 Algorithmik: K RUSKAL Die einzelnen Komponenten eines Graphs induzieren eine Zerlegung der Knotenmenge (s. GIN1b-Folien). Wenn der Graph zusammenhängend ist, dann besteht er natürlich nur aus einer Komponente. c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 15 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Freie Bäume haben z.B. die folgenden interessanten Eigenschaften: (i) Ein freier Baum mit n ≥ 1 hat genau n − 1 Kanten. (ii) Wenn man einem freien Baum eine beliebige Kante hinzufügt, entsteht ein Zyklus Machen sie sich insbesondere die letzte Eigenschaft klar! Abstrakt ist es klar: • zwischen jedem Paar von Knoten, z.B. x und y , aus V gibt es bereits einen Weg (der Baum ist ja ein zusammenhängender Graph). • Wenn sie jetzt einen Kante einfügen, die x und y miteinander verbindet (unter der Annahme, dass es zwischen diesen beiden Knoten keine direkte Verbindung gab, ein solches Paar gibt es in einem Baum immer, wenn n > 2 ist), dann gibt es einen weiteren Weg von x nach y , also haben sie einen Kreis! c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 16 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Weiteres zur Erinnerung: • Ein Spannbaum zu einem zusammenhängenden Graphen G = (V, E) mit Kantenbewertungen bzw. Gewichten w ist ein freier Baum, der alle Knoten aus V enthält und dessen Kanten eine Teilmenge von E bilden. • Das Gewicht eines Spannbaum ist die Summe der Gewichte seiner Kanten. • Spannbäume mit einem Gewicht, das im Vergleich zu allen Spannbäumen von G minimal ist (d.h. es gibt keinen Spannbaum zu G mit kleinerem Gewicht) heißen minimale Spannbäume (natürlich kann es mehrere Spannbäume mit dieser Eigenschaft geben). c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 17 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Wir kennen bereits den Algorithmus von P RIM, der einen minimalen Spannbaum, ausgehend von einem ausgezeichneten Knoten s, findet. • Der Ablauf entspricht dem D IJKSTRA-Algorithmus • Als Distanzen werden aber die Gewichte von einzelnen Kanten mitgeführt, und nicht die Gewichte von Wegen! • Diese Gewichte für einen Knoten v aus V S sind die Gewichte der jeweils besten Kante, die aus S heraus direkt zum Knoten v führt [Beispiel in ihrem Mitschrieb] c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 18 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Der P RIM-Algorithmus konstruiert also nach und nach einen minimalen Spannbaum, der in jedem Schritt um eine Kante erweitert wird. Es geht aber auch anders . . . Definition 2. [Cut] Ein Cut eines Graphen ist eine Zerlegung (alternativ: Partition) der Knoten (zum Zerlegungsbegriff s. Übungsaufgabe 13 zu GIN1b) in zwei Mengen. Eine kreuzende Kante ist eine Kante, die einen Knoten der einen Menge mit einem Knoten der anderen Menge verbindet. Satz 3. [Cut Eigenschaft] Sei für G = (V, E) die Menge Z = {U, W } ein Cut der Knotenmenge V . Sei uw eine kreuzende Kante in G mit minimalen Kosten unter allen kreuzenden Kanten, also aus der Menge {u0w0|u0 ∈ U, w0 ∈ W }. Dann gibt es mindestens einen minimalen Spannbaum zu G, der uw enthält (i) und jeder minimal spannende Baum enthält eine minimale kreuzende Kante. (ii) [Beweise s. Übung, dieser Beweis (ebenso wie der nächste) findet sich z.B. im Sedgewick, Part 5] c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 19 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Satz 4. [Cycle Eigenschaft] Gegeben sei ein Graph G und ein Graph G0 der aus G durch Hinzufügen einer Kante e entsteht. Fügt man e zu einem minimalen spannenden Baum für G hinzu und löscht eine maximale Kante auf dem resultierenden Kreis, dann erhält man einen minimalen spannenden Baum für G0. c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 20 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Dieser Satz liefert die Grundlage für den Algorithmus von K RUSKAL, der für G = (V, E) (kleine) minimale Spannbäume nach und nach zusammenfügt, und zwar wie folgt: • Zu Beginn sei T ein Graph, der genau die Knoten V enthält, aber keine Kanten. • Die Kanten aus E werden nach Gewicht sortiert. • In jeder Runde eine Kante mit minimalem Gewicht ausgewählt und aus der Menge der noch nicht betrachteten Kanten entfernt. • Wenn die Kante zwei bisher getrennte Komponenten miteinander verbindet, dann wird sie in den Graphen eingefügt (und die Komponenten werden dadurch verschmolzen). • Ansonsten wird die Kante ignoriert (sie würde zu einem Kreis führen! Warum?) • Fertig ist man, wenn T nur noch aus einer einzigen Komponente besteht. c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 21 Algorithmik: K RUSKAL Kruskal Implementierung Nach und nach die Minima aus einer Menge zu entnehmen können wir schon! (PriorityQueue) Aber wie können wir geschickt feststellen, ob eine ausgewählte Kante zu einem Kreis führt oder zwei bisher getrennte Komponenten verbindet? • Jede ungerichtete Kante kann man als zwei gerichtete Bögen darstellen. • Die Bögen sind nichts anderes als geordnete Paare. • Wir können diese geordneten Paare als Teil einer Verbunden-mit-Relation betrachten. • Wir nehmen zudem an, dass jeder Knoten mit sich selbst verbunden ist. • ... und ergänzen die Relation um Paare, die aus der Transitivität der Relation entstehen. Wir betrachten die Kanten ja nach und nach. Zu jedem Betrachtungszeitpunkt bestimmt die Kantenmenge eine Relation, die die Knoten V in Äquivalenzklassen zerlegt. c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 22 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 • Jede Prüfung, ob eine Kante uv zu einem Kreis führen würde, können wir nun in die Frage übersetzen, ob u und v in der gleichen Äquivalenzklasse sind (also schon verbunden sind). • Wenn das nicht der Fall ist, dann fügen wir die Kante uv hinzu. Für unsere “Verbunden-mit”-Relation bedeutet dies, dass wir (u, v) und (v, u) hinzufügen – und alle Paare, die aus der Transitivität der Relation folgen. • Das brauchen wir aber gar nicht wirklich zu tun, denn u ist in einer Äquivalenzklasse U und v in einer Äquivalenzklasse V mit U 6= V . Durch Schaffen einer Verbindung zwischen U und V folgt mit der Transitivität, dass sich einfach eine neue Äquivalenzklasse U ∪ V bildet, die u und v enthält. • Dies entspricht genau dem Zusammenfügen zweier Komponten in T ! c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 23 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Wir können das Problem also auch mit Hilfe von Äquivalenzklassen betrachten: • Test, ob uv zu einem Kreis führt: sind u und v in der gleichen Äquivalenzklasse? • Verschmelzen von Komponenten: Vereinigung von Äquivalenzklassen Das kann man als Operationen auf Zerlegungen beschreiben (zur Erinnerung: eine Zerlegung P einer Menge M besteht aus Mengen, die überschneidungsfrei und nichtleer sind und vereinigt M ergeben) • z = find(P ,k): Liefert das Element der Zerlegung P , in dem sich k befindet • P = union(P ,z1,z2): Vereinigt z1 und z2 aus P und liefert das “neue” P zurück Beachten Sie, dass P ein Mengensystem ist, Elemente aus P also Mengen sind. Die folgende Implementierung verwendet allerdings Elemente dieser Mengen als Repräsentanten für die Mengen (so, wie jedes Element einer Äquivalenzklasse als Repräsentant der ganzen Klasse gewählt werden kann). c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 24 Algorithmik: K RUSKAL Union-Find Init(P) For i ← 0 to n do P[i] ← i Union(P,i,j) Random z in [0,1] if z = 0 then P[i] ← j else P[j ] ← i Find(P,i) if i =P[i] then return i else j ← Find(P,P[i]) P[i] ← j return j [s. auch die Informationen zu TARJAN] c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 25 Algorithmik: K RUSKAL Eine Verbesserung • Die obige Implementierung verwendet bereits die sogenannte Pfadkompression (s. Mitschrieb)2 • Sie wählt allerdings zufällig aus, ob zwei Komponenten i und j durch j oder durch i repräsentiert werden • Diese Wahl kann man auch bewußt treffen: – In einem weiteren Array wird gespeichert, wieviele Elemente sich hinter einem repräsentierenden Element verbergen – Dann wird die kleinere Menge zur größeren hinzugefügt (d.h. jedes Element zeigt dann auf den Repräsentanten der größeren Menge) – Natürlich muß man dann auch noch die Information zur Elementzahl updaten – Noch simpler (aber fast genau so gut und leichter zu analysieren) ist ein Ranking: für jeden Knoten gibt der Rank ein obere Grenze für die Höhe des Knotens an 2 die Elemente einer Äquivalenzklasse zeigen also direkt auf den Repräsentanten der Klasse (sonst könnte aus dem nach und nach erfolgenden Verschmelzen von Klassen eine “tiefe” Baumstruktur folgen, die es teurer macht, die Frage nach dem Repräsentanten der Klasse, in der ein gegebenes Element ist, zu beantworten, s. Übung.) c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 26 Algorithmik: K RUSKAL Init(P) For i ← 0 to n do P[i] ← i; rank[x] ← 0 Union(P,i,j) if rank[i] < rank[j ] then P[i] ← j else P[j ] ← i if rank[i] = rank[j ] then rank[i] ← rank[i] + 1. Find-Worst-Case: O(log n), Amortisationsanalyse für eine beliebige Folge von O(n) Union und Find Operationen führt zu O(n log∗ n) (das ist praktisch linear – s. Mitschrieb, Details s. z.B. Cormen et. al) c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 27 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Algorithmus K RUSKAL: Input: Zusammenhängender, ungerichteter Graph G = (V, E) mit Gewichten w und n Knoten Output: Minimaler Spannbaum T zu G Init(P ); Sei T ← (V, ∅); Füge alle Kanten aus E in die PQueue Q ein; kantenanzahl ← 0; while kantenanzahl < n − 1 do vw ← deleteMin(Q); a ← find(P ,v ); b ← find(P ,w); if a 6= b then insert(T ,vw); P ← union(P ,a,b); kantenanzahl ← kantenanzahl+1; end if end while c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 28 Algorithmik: K RUSKAL Spannende Bäume, Teil 2 Analyse des K RUSKAL-Algorithmus (n = Knotenanzahl, m = Kantenanzahl): • Initialisierung von P und T : O(n) • Initialisierung von Q: O(m log m) (geht natürlich auch in O(m)) • Schleife: – maximal m deleteMin O(m log m) – maximal m find und maximal n union Je nach Implementierung O(n log n + m) oder O(m log n) Da wir angenommen haben, dass G zusammenhängend ist, gilt m ≥ n − 1, insgesamt folgt also ein Aufwand von O(m log m). c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen Version 0.9β , 1. Mai 2005, Seite 29