Datenstrukturen und Algorithmen SS07 Datum: 4.6.2007 Michael Belfrage [email protected] belfrage.net/eth Programm von Heute • Minimaler Spannbaum (MST) • Challenge der Woche • Fibonacci Heap Minimaler Spannbaum • Einführendes Beispiel: 1’100 Fr. 1’200 - Fr. r. 0F 20 40 0F r. 50 0F . Fr Fr. 0 0 7 r. 0 00 1’ 1700 F r. 40 0F r. 900 Fr. 900 Fr. 800 Fr. Es hat eine Anzahl Häuser, und man möchte diese alle miteinander vernetzen. (z.B. wegen einem LAN, elektrische Leitungen, etc.) Die Möglichen Verbindungen zwischen zwei Häusern ist oben abgebildet, zusammen mit den Kosten in [Fr.]. Bestimme die Menge von direkten Verbindungen, sodass alle miteinander direkt oder indirekt - verbunden sind, und die Kosten minimal sind. Minimaler Spannbaum • Diese Menge mit minimalen Kosten im vorherigen Beispiel kann man effizient bestimmen mithilfe Ideen aus der Graphentheorie: – Insbesondere mithilfe des minimalen Spannbaums • Es folgen einige Definitionen … Spannbaum • Definitionen: – Ein Spannbaum T eines ungerichteten und zusammenhängenden Graphen G, ist ein Baum, welcher aus allen Knoten und einigen (oder auch allen) Kanten von G besteht. – Ein minimaler Spannbaum (engl. MST = Minimal Spanning Tree) ist ein Spannbaum, dessen summierte Kantengewichte kleiner oder gleich deren aller anderen Spannbäume ist. Spannbaum • Informell: – Ein Spannbaum eines Graphen ensteht, wenn man einige Kanten auswählt, sodass man von jedem Knoten aus alle anderen erreichen kann. Es soll jedoch nicht möglich sein im Kreis zu laufen. • Beispiel: E B F A C D G C Spannbaum • Ein Graph kann mehrere Spannbäume haben, der folgende hat z.B. 16 verschiedene: Minimaler Spannbaum • MST – Ist der Graph gewichtet, kann man einen minimalen Spannbaum bestimmen. Die Summe der Kantengewichte soll dann minimal sein über alle möglichen Spannbäume • Beispiel: 11 9 B 2 F 7 17 C 4 D 12 4 10 A E 9 5 8 G C Minimaler Spannbaum • Schnellste Methoden zur Bestimmung eines minimalen Spannbaums: – Randomisierter Algorithmus • Karger, Klein, Tarjan [1995] • Erwartet lineare Laufzeit – Falls Gewichte kleine natürliche Zahlen • Fredman, Willard [1990] • lineare Laufzeit – Sonst existieren bisher nur Algorithmen die fast linear sind: • Chazelle [1999] • O(|E| · a(|E|, |V |)) (a ist die inverse Ackermann-Funktion) Minimaler Spannbaum • Oft sind diese „schnellen“ Algorithmen aber sehr aufwändig zu implementieren und lohnen sich erst dann, wenn die Laufzeit ein Problem wird. • Die klassischen Algorithmen sind im Vergleich um einiges einfacher zu verstehen und zu implementieren. (Hinzu kommt, dass viele der modernen Algorithmen auf Ideen der klassischen beruhen) Minimaler Spannbaum • Klassische Methoden zur Bestimmung eines minimalen Spannbaums: – Boruvka‘s Algorithmus • Otakar Borůvka, 1926 – Prim‘s Algorithmus • Vojtěch Jarník [1926], Robert C. Prim [1957] • Wiederentdeckt von Edsger W. Dijkstra [1959] – Kruskal‘s Algorithmus • Joseph Kruskal [1956] Minimaler Spannbaum • Eine naive Methode wäre z.B. Alle Spannbäume zu bestimmen, dann würde man sicher auch den minimalen finden. • Jedoch hat es meist sehr 10 16 17 14 viele Spannbäume. Diese Methode ist darum wohl 19 12 16 9 sehr ineffizient. 5 2 1 8 15 6 20 15 18 12 11 14 9 3 Kruskal‘s Algorithmus • Um das Problem effizient von Hand zu lösen, eignet sich Kruskal‘s Algorithmus: INPUT: gewichteter Graph G=(V,E,w) OUTPUT: Minimaler Spannbaum T=(V,E‘) Algorithmus (vereinfacht): T := G \ E sortiere E {aufsteigend nach Gewicht w} for all e=(u,v) in E do if {es gibt keinen Weg von u nach v} then e einfügen in T end Nebenbei: Kruskal‘s Algorithmus ist ein Greedy end Algorithmus, d.h. dass er jeweils die beste lokale return T Auswahl trifft in der Hoffnung das globale Optimum zu erreichen. Oft sind Greedy Algorithmen jedoch falsch, also aufpassen!! Kruskal‘s Algorithmus • Beispiel: 11 9 B 2 F 7 17 C 4 D 12 4 10 A E 9 5 8 G C Hier werden z.B. nacheinander eingefügt: (A, D), (B, D), (C, E), (F, G), (C, F ), (C, D) Kruskal‘s Algorithmus • Wieso ist dieser Ansatz korrekt? – Nehme an Kantengewichte seien versch. • • – – Lemma: Wenn wir die Menge der Knoten V in zwei disjunkte Teilmengen V1 und V2 aufteilen, so ist die kleinste Kante e welche V1 und V2 miteinander verbindet Teil des minimalen Spannbaums. Beweis: Nehme an e sei nicht Teil des Spannbaums. V1 und V2 müssen durch eine Kante verbunden sein. Falls dies nicht e ist, dann kann diese Kante f nur noch schwerer sein, womit der Spannbaum schwerer wird. Widerspruch. In jedem Schritt fügen wir immer die kleinste Kante hinzu welche zwei disjunkte Teilmengen V1 und V2 verbindet. Nach obigem Lemma sind diese Kanten also jeweils Teil des minimalen Spannbaumes. qed. Will man auch gleiche Kantengewichte zulassen, wird der Beweis einwenig mühsamer, aber die Idee bleibt dieselbe. V1 = {A, B, D} 11 14 2 E B F 7 17 C 4 D 12 5 10 A V2 = {C, E, F, G} 9 e 6 8 G C Kruskal‘s Algorithmus • Laufzeit O(|E| log |V |), da |V | − 1 ≤ |E| ≤ |V |(|V | + 1)/2 Algorithmus: T := G \ E sortiere E {aufsteigend nach Gewicht w} for all e=(u,v) in E do if {es gibt keinen Weg von u nach v} then e einfügen in T end end union-find Datenstrukturen können dies return T meist in konstanter Zeit beantworten (naiver Ansatz würde O(|V | · |E|) benötigen) • Fazit: O(|E| log |V |) Prim‘s Algorithmus • Prim baut den minimalen Spannbaum auf, indem er einen Knoten nach dem anderen einfügt. INPUT: gewichteter Graph G=(V,E,w) OUTPUT: Minimaler Spannbaum T=(V,E‘) Algorithmus: T := ein bel. Knoten von G while {T weniger als |V| Knoten hat} do e := kleinste Kante welche T mit G\T verbindet e einfügen in T end return T Prim‘s Algorithmus • Beispiel: 11 9 B 2 F 7 17 C 4 D 12 4 10 A E 9 5 8 G C Hier werden z.B. nacheinander eingefügt: (A, D), (D, B), (D, C), (C, E), (C, F ), (G, F ) Prim‘s Algorithmus • Korrektheit folgt aus dem gleichen Lemma wie schon bei Kruskal. • Langsamster Teil des Algorithmus ist dieser Algorithmus: T := ein bel. Knoten von G while {T weniger als |V| Knoten hat} do e := kleinste Kante welche T mit G\T verbindet e einfügen in T end return T • Wir brauchen also eine Datenstruktur, die effizient das Minimum bestimmen/löschen kann • Eine solche kennengelernte Datenstruktur ist ein Heap. Besonders effzient ist ein Fibonacci-Heap. • Siehe auch http://de.wikipedia.org/wiki/Algorithmus_von_Prim für eine mögliche Heap-Implementation. Prim‘s Algorithmus • Laufzeiten (ohne Begründung): – Mit Adjazenzmatrix 2 O(|V | ) – Mit Adjazenzliste und (normalem) Heap O((|V | + |E|) log |V |) – Mit Adjazenzliste und Fibonacci-Heap O(|E| + |V | log |V |) Boruvkas‘s Algorithmus • Ähnlich wie Prim, einfach ohne komplizierte Datenstruktur. • Baue MST auf indem man mehrere Bäume vereinigt, bis nur noch ein grosser vorhanden ist. INPUT: gewichteter Graph G=(V,E,w) OUTPUT: Minimaler Spannbaum T=(V,E‘) Algorithmus: T := {} //MST L := {} //Liste von Bäumen Teile G auf in |V| Teilbäume bestehend aus einem einzigen Knoten und füge diese in L hinein. while {L mehr als einen Baum hat} do for all Bäume S in L do Finde die kleinste Kante e welche S mit G\S verbindet. Füge e in T ein. end Verschmelze verbundene Bäume und aktualisiere L. end return T Boruvka‘s Algorithmus • Beispiel: 11 9 B 2 F 7 17 C 4 D 12 4 10 A E 9 5 8 G C L := {{A}, {B}, {C}, {D}, {E}, {F }} Boruvka‘s Algorithmus • Beispiel: 11 9 B 2 F 7 17 C 4 D 12 4 10 A E 9 5 8 G C L := {{A, B, D}, {C, E}, {F, G}} Boruvka‘s Algorithmus • Beispiel: 11 9 B 2 F 7 17 C 4 D 12 4 10 A E 9 5 8 G C L := {{A, B, C, D, E, F, G}} Boruvka‘s Algorithmus • Korrektheit folgt wiederum aus dem gleichen Lemma wie schon bei Kruskal und Prim. • Laufzeit: O(|E| log |V |) • Begründung – Anzahl Durchgänge O(log |V |) , da Bäume immer mind. doppelt so gross werden. – In jedem Durchgang jeweils alle Knoten und alle Kanten ansehen: O(|V | + |E|) = O(|E|) hier, da |V | − 1 ≤ |E| ≤ |V |(|V | + 1)/2 Maximalen Spannbaum • Jetzt seit ihr gefragt: – Wie konstruiert man einen maximalen Spannbaum? – Aufgabe 10.3a • Durch Modifikation von Kruskal – Aufgabe 10.3c • Durch Modifikation des Graphen Uralte Challenge • Zu zeigen: – Wenn n die Anzahl Wände sind im Museum, dann braucht es höchstens n/3 [abgerundet] Wachen. Alte Challenge der Woche • Ein sehr beliebtes Problem... • Schreibt ein Programm in Eiffel, welches seinen eigenen Quellcode ausgibt, i.e. mit io.put_string(“...“) • Dabei ist nicht eine Datei schon irgendwo abgespeichert auf der man zugreifen kann • Brauchen kann man (ohne zu modifizieren) alle Klassen der Eiffel-Base • Der Quellcode kann irgendetwas sein, Hauptsache das Programm gibt ihn aus • Und ja... keinen leeren Quellcode ;) The Challenge • Gegeben: – Einen Zeiger zu einem mittleren Knoten einer einfach verketten Liste • Aufgabe: – Lösche diesen Knoten a b a b c d e f d e f Fibonacci-Heap • Fredman, Tarjan [1984] • amort. konstante Laufzeit für praktisch alle Operationen bis auf deleteMin (i.e. das Minimum löschen) Fibonacci-Heap • Ein Fibonacci-Heap ist eine Sammlung von Bäumen, wobei der Schlüssel eines Kindes immer grösser oder gleich dem Elternschlüssel ist. • Somit ist das Minimum die Wurzel irgend eines dieser Bäume. 7 1 2 10 9 15 6 4 7 9 5 14 17 9 Fibonacci-Heap • Der Name Fibonacci kommt daher, da die Grösse eines Teilbaums mit Wurzel vom Grad k mindestens Fk+2 beträgt (wobei Fk die k-te Fibonacci Zahl ist) 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811… Fibonacci-Heap • Bäume vom Grad 1 enstehen aus zwei Bäumen vom Grad 0: 4 2 2 4 Fibonacci-Heap • Bäume vom Grad 2 enstehen aus zwei Bäumen vom Grad 1: 2 3 4 8 2 3 8 4 Fibonacci-Heap • Bäume vom Grad 3 enstehen aus zwei Bäumen vom Grad 2: 4 2 3 8 4 2 11 8 8 11 4 3 7 8 4 7 Fibonacci-Heap • Baum vom Grad 5: 2 5 3 12 18 21 14 13 17 18 14 28 29 24 23 27 28 14 18 24 14 13 17 18 14 8 11 4 3 7 8 4 Fibonacci-Heap • Minimum bestimmen: – Minimum Zeiger zeigt immer darauf. • Laufzeit somit konstant 7 10 6 4 9 7 9 15 1 2 5 14 17 9 4 Fibonacci-Heap • Einfügen: – Neuen Knoten in die Wurzelliste einfügen. (Minimum Zeiger ändern falls nötig) 7 7 10 6 4 9 7 9 15 1 2 5 14 17 9 4 Fibonacci-Heap • Minimum entfernen (3 Phasen) 1. die Kinder des minimalen Knotens abschneiden und zur Wurzelliste hinzufügen. Danach minimalen Knoten entfernen. 7 10 6 4 9 7 9 15 1 2 5 14 17 9 4 Fibonacci-Heap • Minimum entfernen (3 Phasen) 2. Verbinde Wurzeln vom selben Grad. (mithilfe eines Arrays, wo wir jeweils von links nach rechts die Wurzelknoten durchgehen, und bei einem leeren k-ten Eintrag (falls der aktuelle Wurzelknoten Grad k hat) einen Zeiger machen zum aktuellen Wurzelknoten, und bei einem vorhandenen Zeiger diesem folgen und die Wurzeln verbinden.) 7 2 10 6 4 9 7 9 15 5 4 14 17 9 Fibonacci-Heap • Minimum entfernen (3 Phasen) 3. Suche das neue Minimum unter den Wurzelknoten - maximal O(log(n)) Wurzelknoten 9 15 6 4 7 9 2 4 5 9 7 14 17 10 Fibonacci-Heap • Einen Schlüssel verkleinern – Verkleinere den Schlüssel des Knotens zuerst. – Ist der neue Schlüssel grösser/gleich dem Schlüssel des Elternknoten, dann muss nichts mehr gemacht werden. 9 15 6 4 7 9 2 4 5 9 7 14 17 8 10 Fibonacci-Heap • Einen Schlüssel verkleinern – Verkleinere den Schlüssel des Knotens zuerst. – Ist der neue Schlüssel hingegen kleiner als der Schlüssel des Elternknoten, dann schneidet man den Knoten ab und markiert den Elternknoten. Ist dieser auch schon markiert, schneidet man diesen auch ab. (dies geht so weiter bis man zur Wurzel kommt oder einen unmarkierten Knoten erreicht. Jeweils immer minimum Aktualiseren falls nötig) 9 15 6 4 7 9 2 4 5 9 7 14 17 3 10 Fibonacci-Heap • Einen Schlüssel löschen – Verkleinere den Schlüssel zuerst auf – ∞ – Lösche nun das Minimum 6 4 7 9 -∞ 9 15 2 4 5 9 7 14 17 10 Fibonacci-Heap • Frage an euch: – Aufgabe 10.4b • Warum können nicht alle Heap-Operationen eine konstante amortisierte Laufzeit haben (wenn man nur Vergleichsbasierte Operationen zulässt)? Fibonacci-Heap • Frage an euch: – Aufgabe 10.4a • Kann ein Fibonacci-Heap zu einer Struktur degenerieren, die genau noch einen Knoten in der Wurzelliste hat, an dem eine lineare Liste hängt? 4 9 11 13 15 Ende der Stunde. Questions?