WS 2009/10 Diskrete Strukturen Prof. Dr. J. Esparza Lehrstuhl für Grundlagen der Softwarezuverlässigkeit und theoretische Informatik Fakultät für Informatik Technische Universität München http://www7.in.tum.de/um/courses/ds/ws0910
Kapitel IV Graphentheorie Graphentheorie Grundlagen Bäume Eigenschaften von Graphen Graphen-Algorithmen Matchings 2
Wir präsentieren einige Grundverfahren der algorithmischen Graphentheorie. Diese Verfahren werden als ``Bausteine in vielen Algorithmen verwendet. Die Verfahren beantworten auch einige Grundfragen: Welche Knoten sind aus einem gegebenen Knoten erreichbar? Welcher ist der kürzeste Weg zwischen zwei gegebenen Knoten? Wie findet man einen optimalen Spannbaum? 3 (gewichtete Graphen)
Suchverfahren 4 Verfahren zum Durchlaufen aller Knoten eines Graphen, die aus einem gegebenen Knoten erreichbar sind. e i p o h d b root a c j q f k n r m g l
Generischer Algorithmus Suche Zwei Datenstrukturen: Tabelle: speichert für jeden Knoten die Information, ob er schon besucht wurde, und eventuell auch zusätzliche Informationen. Initialisierung: nur die Wurzel ist besucht worden. Worklist: enthält die schon besuchten, aber noch nicht bearbeiteten Knoten. Initialisierung: die Worklist enthält nur die Wurzel. 5
Generischer Suchalgorithmus 6 Initialisierung; while die Worklist nicht leer ist wähle einen Knoten v aus der Worklist; falls v mindestens einen unbesuchten Nachbarn hat wähle einen unbesuchten Nachbarn u von v; markiere u als besucht; (trage die zus. Inf. für u in die Tabelle ein); trage u in die Worklist ein andernfalls entferne v aus der Worklist
13 o h 8 n 7 r 17 14 18 i p 9 e j 4 b 15 11 k q d 16 3 root a c 1 2 f 5 m g l 12 6 10 7
Suchvarianten Suchstrategie: Strategie zur Wahl von v. Kaotische Suche: Wähle v beliebig. Breitensuche: FIFO-Strategie (First In First Out). Wähle v als der Knoten, der zuerst in die Worklist kam (unter denjenigen, die zur Zeit in der Worklist sind). Tiefensuche: LIFO-Strategie (Last In First Out). Wähle v als der Knoten, der zuletzt in die Worklist kam (unter denjenigen, die zur Zeit in der Worklist 8 sind).
Beispiel o h n r i p e j b k q d root a c f m g l 9
Mögliche zusätzliche Informationen, die in der Tabelle für jeden besuchten Knoten gespeichert werden können: Vorgänger Nachbarn, aus dem der Knoten besucht wurde. (Im Algorithmus, der Vorgänger von u ist v.) Suchtiefe 0 für die Wurzel (1 + Suchtiefe des Vorgängers) für die anderen Knoten. Suchnummer 10 Laufende Nummer für die besuchten Knoten.
Erweiterter Suchalgorithmus Initialisierung; nã 1; n[s] Ã n; d[s] Ã 0; für alle v2 V\{s} n[v],d[v] Ã 1; while die Worklist nicht leer ist wähle einen Knoten v aus der Worklist; falls v mindestens einen unbesuchten Nachbarn hat wähle einen unbesuchten Nachbarn u von v; markiere u als besucht; nã n+1; n[u]ã n; d[u] Ã d[v]+1; pred[u] Ã v; trage u in die Worklist ein andernfalls entferne v aus der Worklist 11
Satz: 12 Sei G=(V,E) mit Wurzel s eine Eingabe für den generischen Suchalgoritmus. 1. Der Suchalgorithmus terminiert nach höchstens V + E Durchläufe der while-schleife. 2. Nach Terminierung sind alle aus s erreichbaren Knoten von G als besucht markiert. 3. Wenn G zusammenhängend ist, dann bilden die Vorgänger-Kanten {v, pred[v]}, v V \ {s} einen Spannbaum von G.
Beweis: 1. Wir haben: Jeder Knoten von G kommt höchstens einmal in die Worklist. Jede Ausführung der while-schleife nimm eine Kante {u,v} mit v markiert und u noch nicht markiert, und markiert u, oder entfernt einen Knoten aus der Worklist. Jede Kante {u,v} wird höchstens einmal genommen. Es folgt, dass die while-schleife höchstens V + E 13 Mal ausgeführt wird.
Beweis: 2. Sei v einen aus s erreichbaren Knoten. Dann gibt es einen Pfad s=v 0 v 1 v n =v. Der Beweis ist durch Induktion über n. Basis: n=0. Dann v=s und s wird in der Initialisierung als besucht markiert. Schritt: n>0. Aus der Induktionsannahme folgt, dass v n-1 irgendwann als besucht markiert wird. Es folgt, dass irgendwann v n-1 in die Worklist kommt und irgendwann aus der Worklist entfernt wird (nach Terminierung ist die Worklist leer). Wenn v n-1 aus der Worklist entfernt wird, sind alle Nachbarn von v n-1 schon markiert. 14
Beweis: 3. Für jede Kante {v, pred[v]} gilt: pred[v] ist aus s durch Vorgänger-Kanten erreichbar. pred[v] hat eine kleinere Suchnummer als v. Es folgt, dass der Graph der Vorgänger-Kanten zusammenhängend ist, und dass die Vorgänger- Kanten keinen Kreis bilden. Damit ist dieser Graph ein Baum. Da G zusammenhängend ist und 2. gilt, gibt es für jeden Knoten v von G eine Kante {v, pred[v]}. Damit 15 ist der Baum ein Spannbaum.
Breitensuche 13 o h 8 n 7 r 17 14 18 i p 9 e j 4 b 15 11 k q d 16 3 root a c 1 2 f 5 m g l 12 6 10 16
Spannbaum der Breitensuche. a c d b f g n h e l k m o i j 17 q r p
Breitensuche Lemma: In einer Breitensuche für G = (V,E) gilt: 1. Wenn u vor v aus der Worklist kommt, dann gilt d[u] d[v]. 2. Zu jedem Zeitpunkt gibt es eine Zahl k, so dass für alle Knoten v in die Worklist gilt: k d[v] k+1. 3. Wenn {u,v} 2 E, dann d[u] d[v]+1. 18
Breitensuche Satz: Am Ende einer Breitensuche für G = (V,E) gilt: 1. d[v] ist die Länge des kürzesten s-v-pfades in G. Wenn es keinen s-v-pfad gibt, dann d[v]=1 2. Für alle erreichbaren Knoten v, der s-v-pfad im Spannbaum der Suche ist ein kürzester s-v-pfad. 19
Breitensuche 20 Beweis von 1: Es gibt einen s-v-pfad der Länge d[v]: Sei s=v 0 v 1 v n =v der s-v-pfad im Spannbaum. Die Länge des Pfades ist n. Für alle 0 i <n, gilt: d[v i+1 ] = d[v i ]+1. Mit d[s]=0 folgt d[v]=n. Für jeden s-v-pfad s = v 0 v 1 v n = v gilt: d[v] n. Es gilt (Lemma, dritter Teil): d[v] = d[v n ] d[v n-1 ]+1 d[v n-2 ]+2 d[v 0 ]+k = k.
Breitensuche Beweis von 2: Folgt aus dem Beweis von 1: der s-v-pfad im Spannbaum hat Länge d[v], und diese ist auch die Länge des kürzesten s-v-pfades. 21
Eine Implementierung von Breitensuche Eingabe: Graph G = (V,E), Startknoten s V. Ausgabe: Felder d[v], pred[v] für alle v V. for all v V do // bearbeite alle Knoten aus V begin if v = s then // wenn wir am Startknoten sind d[v] 0; // setze seinen Abstand auf 0 else d[v] ; // setze alle anderen Abstände auf pred[v] NULL; // setze alle Vorgänger auf undefiniert end 22 Fortsetzung nächste Seite
Eine Implementierung von Breitensuche 23 Q new QUEUE; // legt ein neues Objekt vom Typ Warteschlange an Q.INSERT(s); // fügt das Element s in die Warteschlange ein while not Q.IsEMPTY() do // teste, ob noch Elemente in der Queue begin v Q.DEQUEUE(); // hole nächstes Element aus der Queue for all u (v) do // betrachte alle Nachbarn von v if d[u] = then // der Knoten u ist noch unbesucht begin d[u] d[v] + 1; // sein Abstand = Abstand Vorg. + 1 pred[u] v; // sein Vorgänger ist v Q.INSERT(u); // setze ihn in die Warteschlange end end
Tiefensuche 17 o h 16 n 15 r 14 24 i p 7 11 8 10 e j 9 b k q 6 5 d 18 root a c 1 2 f 3 m g l 13 12 4
Spannbaum der Tiefensuche a f c l k g n q i p m h b e j r o d 25
Eine Implementierung von Tiefensuche Eingabe: Graph G = (V,E), Sartknoten s V. Ausgabe: Feld pred[v] für alle v V. for all v V do // bearbeite alle Knoten aus V pred[v] NULL; // setze alle Vorgänger auf undefiniert 26 Fortsetzung nächste Seite
Algorithmus Tiefensuche (Fortsetzung) S new STACK; // legt ein neues Objekt vom Typ STACK an v s; repeat if u (v) \ {s} mit pred[u] = NULL then // nicht besuchter Nachf.? S.PUSH(v) // setze Element auf den Stack pred[u] v; // das Element ist Vorgänger von u v u; // mache mit u weiter else if not S.IsEMPTY() then v S.POP(); // hole Element vom Stack else v NULL; until v = NULL; // wiederhole bis Stack leer 27
Kürzeste Wege 28 Gegeben sind ein Graph G = (V,E) und eine Gewichtsfunktion w : E R + {+ }. O. B. d. A. sei G vollständig und damit auch zusammenhängend. Sei u = v 0, v 1,..., v n = v ein Pfad in G. Die Länge dieses Pfades ist n 1 i 0 w( v, v ). i i 1 d(u, v) sei die Länge eines kürzesten Pfades von u nach v.
Kürzeste Wege Problemstellungen: Gegeben u, v V, berechne d(u, v). Gegeben u V, berechne für alle v V die Länge d(u, v) eines kürzesten Pfades von u nach v (sssp, single source shortest path). Berechne für alle (u, v) V die kürzeste Entfernung d(u, v) (apsp, all pairs shortest path). 29
Dijkstras Algorithmus für sssp. F V 30 for all v d[v] F do // d: aktuell kürzeste Pfade p[v] null // p: Vorgängerliste d[s] = 0; while F do bestimme u F for all v F \ {u}; // der Startknoten F mit d[u] minimal; (u) do if d[v] > d[u] + w(u,v) then d[v] p[v] d[u] + w(u,v) u
Satz: Dijkstras Algorithmus berechnet d(s,v) für alle v 2 V; der Zeitaufwand ist O( V 2 ). Beweis: Zeitbedarf ist aus dem Algorithmus ersichtlich. Es ist auch klar, dass nach Terminierung d[v] d(s,v) für alle Knoten v gilt. Für jeden Knoten v, sei t v der Zeitpunkt unmittelbar vor der Entfernung von v aus F. Wir zeigen: zum Zeitpunkt t v gilt d[v] d(s,v). Daraus folgt, dass d[v] d(s,v) auch nach Terminierung gilt, und damit d[v]=d(s,v). Der Beweis ist durch Widerspruch: 31
Beweis (Forts.): Annahme: Es gibt einen Knoten v, für den zum Zeitpunkt t v gilt: d[v] > d(s,v). O.B.d.A. wählen wir v so, dass t v minimal ist. Sei W einen kürzesten s-v-weg. Wir betrachten den Zeitpunkt t v. Sei y der erste Knoten in W, der zu F gehört (mög. y = v). Sei x der Vorgänger von y in W (mög. x= s). Es gilt d[v] d[y], weil v als nächstes von F entfernt wird. 32
Beweis (Forts.): Darüber hinaus: 1. d[x] = d(s,x). Weil x schon aus F entfernt wurde, und wegen der Minimalität von v. 2. d[y]=d(s,y). Da W minimal ist, gilt d(s,y)=d(s,x)+d(x,y). Als x von F entfernt wurde, wurde ein Update von d[y] durchgeführt, mit dem Ergebnis d[y] d(s,x)+d(x,y). Damit gilt d[y] d(s,y), und so d[y]=d(s,y). 3. d(s,y) d(s,v). Weil W ein kürzester s-v-weg ist, und W den Knoten y besucht. Aus (1)-(3) folgt: d[y] = d(s,y) d(s,v) d[v]. Damit gilt d[y] d[v], und so d[v]=d[y] und d[v]=d(s, v), in Widerspruch zur Annahme. 33
Dijkstras Algorithmus Beispiel: 34
Dijkstras Algorithmus Beispiel: 4 35
Dijkstras Algorithmus Beispiel: 2 4 36
Dijkstras Algorithmus Beispiel: 2 4 37
Dijkstras Algorithmus Beispiel: 2 4 38
Minimale Spannbäume Beispiel: Eine Firma plant eine Vernetzung seiner 5 Computer-Zentren. Die Leasing-Gebühren für die Verbindung zweier Zentren verursachen Kosten. Welche Verbindungen sollen geleast werden, so dass bei minimalen Kosten alle Zentren miteinander kommunizieren können. Lösung: Wir modellieren das Problem als gewichteten Graphen und konstruieren einen 39 Spannbaum mit minimaler Gewichtsumme.
Minimale aufspannende Bäume Beispiel: d a 1200 c b e 40
Minimale aufspannende Bäume Eine Lösung des vorhergehenden Beispiels. Gewichtsumme: 3600 d a 1200 c b e 41
Minimale Spannbäume Der Algorithmus von Kruskal: Eingabe: zusammenhängender Graph G = (V,E) Gewichtsfunktion w: E R. Ausgabe: Spannbaum (V,ET) mit minimalem Gewicht. 42 Sortiere die Kanten von E nach Gewicht in aufsteigender Reihenfolge; sei L die sortierte Liste. ET Ã ;; while L ist nicht leer entferne die erste Kante k von L; wenn (V, ET [ {k}) kreisfrei dann ETÃ ET [ {k}
Der Algorithmus von Kruskal 43 Beispiel: 2 3 1 a b c d 3 1 2 5 4 3 3 e f g h 4 2 4 3 i 3 j 3 k 1 l Iteration Kante Gewicht 1 {c,d} 1 2 {k,l} 1 3 {b,f} 1 4 {c,g} 2 5 {a,b} 2 6 {f,j} 2 7 {b,c} 3 8 {j,k} 3 9 {g,h} 3 10 {i,j} 3 11 {a,e} 3
Traversierung von Wurzelbäumen Seien T 1,, T nt die Teilbäume von T von links nach rechts. a Trav(T) j e b k f l c g m d h i Ausgabe Wurzel; Trav(T 1 ); ; Trav(T nt ). Ergebnis: a,b,e,j,k,n,o,p,f,c,d,g,l,m,h,i 44 n o p
Traversierung von Wurzelbäumen Seien T 1,, T nt die Teilbäume von T von links nach rechts. a Trav(T) j e b k f l c g m d h i Trav(T 1 ); Ausgabe Wurzel; Trav(T 2 ); ; Trav(T nt ). Ergebnis: j,e,n,k,o,p,b,f,a,c,l,g,m,d,h,i 45 n o p
Traversierung von Wurzelbäumen Seien T 1,, T nt die Teilbäume von T von links nach rechts. a Trav(T) j e b k f l c g m d h i Trav(T 1 ); ; Trav(T nt ); Ausgabe Wurzel. Ergebnis: j,n,o,p,k,e,f,b,c,l,m,g,h,i,d,a 46 n o p
Preorder-Traversierung von Wurzelbäumen Procedure preorder (Eingabe: Wurzelbaum T) v Wurzel von T; Gebe Inhalt von v aus for all u (v) von links nach rechts do // bearbeite alle Kinder begin T Teilbaum von T mit u als Wurzel preorder(t); // rekursiver Aufruf der Traversierung end 47
Postorder-Traversierung von Wurzelbäumen Procedure postorder (Eingabe: Wurzelbaum T) v Wurzel von T; for all u (v) von links nach rechts do // bearbeite alle Kinder begin T Teilbaum von T mit u als Wurzel postorder(t); // rekursiver Aufruf der Traversierung end Gebe Inhalt von v aus 48
Inorder-Traversierung von Wurzelbäumen Procedure inorder (Eingabe: Wurzelbaum T) v Wurzel von T; 49 if IsLeaf(v) then Gebe Inhalt von v aus else begin l erstes Kind von v von links nach rechts; T Teilbaum von T mit l als Wurzel; inorder(t); // rekursiver Aufruf der Traversierung Gebe Inhalt von v aus for all u (v) \ { l } von links nach rechts do begin T Teilbaum von T mit u als Wurzel; inorder(t); end end