Minimale Spannbäume – 1 Erinnerung: Ein (freier) Baum ist ein zusammenhängender (zh)und kreisfreier (ungerichteter) Graph G = (V, E). Algorithmen und Datenstrukturen 15. Vorlesung Nach Charakterisierungssatz gilt für Baum G = (V, E): (1) G hat |V |−1 Kanten. Karl-Heinz Niggl (2) Durch Hinzufügen einer neuen Kante (u, w) entsteht genau ein Kreis: 11. Juli 2006 u Bsp. FG KTuEA, TU Ilmenau AuD – 11.07.2006 FG KTuEA, TU Ilmenau Minimale Spannbäume – 2 w AuD – 11.07.2006 1 Minimale Spannbäume – 3 (3) Durch Streichen einer Kante e := (u, w) zerfällt G in zwei Komponenten: Def. Sei G = (V, E) ein zusammenhängender Graph. Ein Spannbaum von G ist ein Baum (V, T ) mit T ⊆ E. U := {v ∈ V | v ist von u aus in (V, E −e) erreichbar} Bsp. (V, T ) ist ein Baum von G W := {v ∈ V | v ist von w aus in (V, E −e) erreichbar} W U Bem. Jeder zusammenhängende Graph hat einen Spannbaum. Bsp. FG KTuEA, TU Ilmenau u AuD – 11.07.2006 w Iterativ: Solange ein Kreis (u, v1, . . . , vk−1, v, u) existiert, entferne eine Kreiskante, z.B. e = (v, u). Der ZH bleibt erhalten: In Wegen mit e ersetze e durch (v, vk−1, . . . , v1, u). 2 FG KTuEA, TU Ilmenau AuD – 11.07.2006 3 Minimale Spannbäume – 4 Minimale Spannbäume – 5 Def. Ein gewichteter Graph ist ein Tripel G = (V, E, c) mit Graph (V, E) und Gewichtsfunktion (Kostenfkt.) c : E → R. Def. Jeder Kantenmenge E ′ ⊆ E in einem gewichteten Graphen G = (V, E, c) wird durch P c(e) c(E ′) := 3 5 1 2 2 1 4 e∈E ′ 1 5 ein Gesamtgewicht zugeordnet. 4 3 Bsp. 3 3 1 2 1 4 2 1 5 3 1 2 2 3 1 5 4 3 1 2 Bsp. Gesamtgewicht 30 2 3 1 1 2 3 3 Gewichte modellieren z.B. die Distanz zwischen zwei Städten. 1 2 Ziel: Finde Spannbaum mit minimalen Kosten. FG KTuEA, TU Ilmenau AuD – 11.07.2006 4 FG KTuEA, TU Ilmenau AuD – 11.07.2006 5 Minimale Spannbäume – 6 Minimale Spannbäume – 7 Def. Ein Spannbaum (V, T ) eines zusammenhängenden Graphen G = (V, E, c) heißt minimaler Spannbaum, kurz MST (minimum spanning tree), falls gilt: Ziel: Algorithmus von Jarnı́k/Prim Berechnet bei Eingabe eines zusammenhängenden Graphen G = (V, E, c) einen minimalen Spannbaum von G in Zeit O((|V |+|E|)·log |V |). c(T ) = min{c(T ′) | (V, T ′) Spannbaum von G} Bsp. Zwei minimale Spannbäume mit Gesamtgewicht 18. 3 5 1 1 2 2 3 4 1 5 5 1 1 1 2 2 3 3 1 1 FG KTuEA, TU Ilmenau 2 Hier: Wähle eine Kante nach der anderen, wobei jeweils eine billigste Kante ausgewählt wird, die mit den bereits gewählten (T ) zusammenhängt und einen neuen Knoten erreicht (S). 3 3 1 5 3 3 1 1 4 3 2 2 2 4 Verwendetes Algorithmenparadigma: greedy ( gierig“) ” Allgemein: Baue eine Lösung Schritt für Schritt auf und treffe in jedem Schritt die (lokal) günstigste Entscheidung. 4 2 AuD – 11.07.2006 3 1 2 6 FG KTuEA, TU Ilmenau AuD – 11.07.2006 7 Minimale Spannbäume – 8 Minimale Spannbäume – 9 Algorithmus von Jarnı́k/Prim ohne Implementierungsdetails procedure jarnı́k/prim-mst-abstract(G = (V, E, c)) Wähle einen beliebigen Knoten u0 ∈ V . S ← {u0} ⊲ bisher erreichte Knoten ⊲ bisher gewählte Kanten T ←∅ Wiederhole (n−1)-mal (n := |V |) Finde w ∈ S und u ∈ V \S mit: c(w, u) ist minimal unter allen Werten c(w′, u′), w′ ∈ S, u′ ∈ V \S. S ← S ∪ {u} T ← T ∪ {(w, u)} return T FG KTuEA, TU Ilmenau AuD – 11.07.2006 8 Bsp. 1 3 5 1 2 2 3 4 1 5 5 1 1 1 1 2 → 2 3 5 3 1 1 → 2 3 3 3 1 1 2 FG KTuEA, TU Ilmenau 3 4 1 5 5 2 AuD – 11.07.2006 9 1 1 1 2 → 2 AuD – 11.07.2006 1 5 5 1 1 1 → 2 3 1 10 5 1 2 → 3 3 1 1 3 2 3 2 2 2 4 4 3 1 3 1 2 2 2 3 4 4 3 FG KTuEA, TU Ilmenau 2 3 3 1 3 1 → 2 3 3 3 1 3 3 1 2 2 4 3 2 4 3 1 2 2 4 2 1 Bsp. fortgesetzt 1 1 5 5 4 Minimale Spannbäume – 11 Bsp. fortgesetzt 5 1 1 4 Minimale Spannbäume – 10 3 2 2 3 4 FG KTuEA, TU Ilmenau 3 1 2 AuD – 11.07.2006 2 11 Minimale Spannbäume – 12 Minimale Spannbäume – 13 Bsp. fortgesetzt 3 5 Bsp. fortgesetzt 1 1 2 2 3 4 1 5 5 1 1 → 2 2 3 1 1 2 → 2 FG KTuEA, TU Ilmenau 1 12 2 2 5 3 2 3 1 1 AuD – 11.07.2006 2 → 2 FG KTuEA, TU Ilmenau 3 1 2 AuD – 11.07.2006 13 Wegen |Tn−1| = n−1 und Charakterisierungssatz (1) gilt dann T = Tn−1 für den nach Beh. exisitierenden MST T von G. 3 FG KTuEA, TU Ilmenau 1 Beh. Für i < n gilt: ∃ MST T von G mit T ⊇ Ti. 2 3 1 3 1 Nach Konstruktion gilt für i < n: (Si, Ti) ist ein Baum. Wir beweisen die folgende Schleifeninvariante: 4 → 5 Korrektheit: (jarnı́k/prim-mst-abstract(G)): Sei • Ti die Kantenmenge (Größe i) nach Runde i, • Si die Knotenmenge (Größe i + 1) nach Runde i. 4 1 1 Minimale Spannbäume – 15 Bsp. fortgesetzt 1 2 2 3 Minimale Spannbäume – 14 5 → 2 3 1 2 1 1 3 2 3 AuD – 11.07.2006 3 5 5 4 4 3 1 3 2 1 1 4 3 1 2 2 3 4 3 3 1 3 1 5 5 3 3 1 1 1 4 3 2 2 2 1 4 3 4 2 Beweis. Induktion nach i < n. i = 0 Dann gilt T0 = ∅, fertig nach Bem. 14 FG KTuEA, TU Ilmenau AuD – 11.07.2006 15 Minimale Spannbäume – 16 Minimale Spannbäume – 17 i → i+1 Nach I.V. gibt es einen MST T von G mit T ⊇ Ti. Werde e = (w, u) in Runde i+1 < n gewählt, d.h. Ti+1 = Ti ∪{e} mit w ∈ Si, u ∈ V \Si und c(e) ist diesbzgl. minimal. e ∈ T Dann gilt T ⊇ Ti+1, fertig. e ∈ / T Nach Satz existiert in T ein eindeutig bestimmter einfacher Weg(w, v1, . . . , vr−1, u). Dieser Weg beginnt in Si und endet außerhalb von Si. w e e’ u FG KTuEA, TU Ilmenau u Setze T ′ := (T ∪ {e})−e′. T ′ besteht aus n−1 Kanten, c(T ′) ≤ c(T ) und T ′ ist kreisfrei. (T ∪{e} hat genau einen Kreis, nämlich (w, v1, . . . , vr−1, u, w). Durch das Entfernen von e′ wird dieser Kreis zerstört.) Auf dem Weg existiert also eine Kante e′ = (w′, u′) mit w′ ∈ Si und u′ ∈ / Si . AuD – 11.07.2006 Aufgrund der Minimalität von c(e) muß c(e′) ≥ c(e) gelten, sonst wäre e in Runde i nicht gewählt worden. w Also ist T ′ ein Spannbaum von G mit T ′ ⊇ Ti+1. Da T ein MST ist, folgt c(T ′) = c(T ), d.h. T ′ ist auch ein MST. 16 FG KTuEA, TU Ilmenau Minimale Spannbäume – 18 AuD – 11.07.2006 17 Minimale Spannbäume – 19 Implementierung Annahme: G = (V, E, c) mit V = {1, . . . , n} ist in Adjazenzlistendarstellung gegeben. Die Kantengewichte c(e) stehen in den Adjazenzlisten bei den Kanten. In Runde i erhält man mittels heap-extract-min den neuen Knoten ui für S sowie die neue Kante (p(ui), ui) für T . Berechnung der d-Werte: Für jede Kante (ui, u) mit u ∈ / Si prüfe, ob c(ui, u) < aktueller Wert d(u). Für Knoten u ∈ V \S mit Nachbarn in S wollen wir wissen: 1) Distanz d(u) von u zu S := min{c(w, u) | w ∈ S}, 2) den (einen) Knoten p(u) := w ∈ S mit c(w, u) = d(u). Falls u ohne Nachbarn in S, gelte d(u) = ∞ und p(u) = NIL. Trick: Verwalte die Knoten u ∈ V \S mit Werten d(u) < ∞ als Prioritäten in einer Min-Priority-Queue PQ. Wenn d(u) = ∞ gilt, ist u (noch) nicht in PQ. FG KTuEA, TU Ilmenau AuD – 11.07.2006 4 5 → 3 6 4 ui 2 5 3 6 ui 2 7 7 Falls ja: d(u) ← c(ui, u) p(u) ← ui Die Priorität von u in PQ erniedrigt sich: heap-decrease-key! x ∈ PQ: x = (key[x], node[x], p[x]), key[x] = c(p[x], node[x]). 18 FG KTuEA, TU Ilmenau AuD – 11.07.2006 19 Minimale Spannbäume – 21 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: Minimale Spannbäume – 22 procedure jarnik/prim-mst(G = (V, E, c)) S ← {1} ⊲ Startknoten u0 = 1 T ←∅ ⊲ bisher gewählte Kanten empty(PQ) ⊲ leere Priority-Queue foreach Nachbar u von u0 do ⊲ nach Adjazenzliste von u0 min-heap-insert(PQ, (c(u0, u), u, u0)) Wiederhole (n − 1)-mal (n := |V |) (k, v, w) ← heap-extract-min(PQ) S ← S ∪ {v} ⊲ der neue Knoten T ← T ∪ {(w, v)} ⊲ die neue Kante ⊲ nach Adjazenzliste von v foreach Nachbar u von v do if u ∈ / S and u ∈ / PQ then min-heap-insert(PQ, (c(v, u), u, v)) if u ∈ / S und (cu, u, wu) in PQ mit c(v, u) < cu then heap-decrease-key(PQ, u, (c(v, u), v)) return T FG KTuEA, TU Ilmenau AuD – 11.07.2006 20 Tiefensuche in Digraphen – 1 Laufzeitanalyse: Realisiere Menge S über einen Bitvektor der Länge n = |V |, Zugriffszeit O(1), Menge T z.B. als Liste. Die Min-Priority-Queue PQ enthält maximal n−1 Einträge. Initialisierung benötigt Zeit O(deg(u0) · log n). Die while-Schleife wird (n−1)-mal durchlaufen, Kosten O(n) für die Schleifenorganisation. Im Schleifenrumpf benötigt jeder der Aufrufe heap-extract-min, min-heap-insert oder heap-decrease-key Zeit O(log n). Insgesamt (m := |E|): P n · O(log n) + O(deg(ui) · log n) 0≤i<n P deg(ui) = O((n+m)·log n) = O n log n+log n· 0≤i<n FG KTuEA, TU Ilmenau AuD – 11.07.2006 21 Tiefensuche in Digraphen – 2 1. Tiefensuche dfs(v) (depth-first-search) von v aus: Man muß verhindern: Mehrfache Entdeckung eines Knoten v Eingabeformat: Digraph G = (V, E) in Adjazenzlistenkodierung mit Knotenmenge V = {1, . . . , n} Dazu verwende Statusinformation neu, aktiv, fertig. Ziele: • Besuche alle von v aus erreichbaren Knoten und Kanten. • Sammle Strukturinformationen (Farbe weiß) v neu: – noch nie gesehen (Farbe rot) v aktiv: – dfs(v) gestartet, noch nicht beendet v fertig: – dfs(v) beendet (Farbe grau) Der Status wird in einem Array status[1..n] verwaltet. Parallel dazu (oder Teil davon) existiert Array nodes[1..n]. Ansatz: Besuche Knoten v und bearbeite nacheinander alle Nachbarn v1, . . . , vl von v. Aber sobald ein neuer Knoten vi entdeckt wird, rufe sofort rekursiv dfs(vi) auf. (Weitere Nachbarn von v kommen später dran.) Effekt: Entdeckung der Knoten geht vorrangig in die Tiefe. Initialisierung: Der Status aller Knoten ist anfangs neu. Knotennumerierung erfolgt in der Reihenfolge der Entdeckung: Array dfs num[1..n]: Tiefensuch-Numerierung. Mitzählen erfolgt in dfs count, mit 0 initialisiert. FG KTuEA, TU Ilmenau FG KTuEA, TU Ilmenau AuD – 11.07.2006 22 AuD – 11.07.2006 23 Tiefensuche in Digraphen – 3 Tiefensuche in Digraphen – 4 4 Input: Knoten v mit status(v) = neu. ⊲ Tiefensuche von v aus, rekursiv 1: procedure dfs(v) 2: dfs count ← dfs count+1 3: dfs num(v) ← dfs count 4: status(v) ← aktiv 5: foreach w ∈ suc(v) do ⊲ nach Adjazenzliste von v ⊲ w wird entdeckt 6: if status(w) = neu then dfs(w) 7: 8: status(v) ← fertig AuD – 11.07.2006 24 1 1 2 2 1 2 6 2 6 1 2 8 4 1 1 2 2 1 7→ 2 6 3 1 2 8 4 1 1 2 2 2 6 1 1 3 1 1 AuD – 11.07.2006 1 2 8 2 6 3 1 2 8 4 1 1 2 2 7→ 2 6 FG KTuEA, TU Ilmenau 1 3 1 7 2 8 5 1 1 2 2 2 6 5 3 9 1 1 10 9 1 4 2 5 3 2 1 10 9 1 1 1 2 1 2 1 8 2 6 3 5 1 1 2 2 3 1 2 2 6 10 26 FG KTuEA, TU Ilmenau 1 1 1 8 3 AuD – 11.07.2006 1 2 8 5 3 10 9 1 4 1 2 5 3 1 10 9 1 4 2 1 7 2 2 2 3 7→ 1 2 1 7 1 1 4 2 2 3 7→ 1 2 4 1 2 2 2 7 1 2 10 4 3 25 1 3 7→ 3 9 5 AuD – 11.07.2006 4 1 1 2 Tiefensuche in Digraphen – 6 3 2 3 10 4 2 1 1 1 2 3 5 2 9 1 2 1 1 4 2 8 2 7 1 2 1 1 3 7→ 10 4 2 1 7 2 2 2 3 7→ FG KTuEA, TU Ilmenau 1 2 9 2 1 7 1 1 1 2 4 1 1 2 2 2 3 1 7 1 1 2 4 1 4 2 1 7 1 1 2 3 7→ 3 3 2 3 Bsp. Tiefensuche in Digraphen – 5 4 1 2 2 Tiefensuche von v0 aus: Initialisiere dfs count mit 0 und den Status aller Knoten mit neu. Rufe dfs(v0) auf. FG KTuEA, TU Ilmenau 1 1 2 5 3 2 1 9 10 27 Tiefensuche in Digraphen – 7 4 1 1 2 2 1 7→ 2 6 3 1 7 2 8 4 1 1 2 2 1 2 6 1 7 1 2 8 4 1 1 2 2 2 6 FG KTuEA, TU Ilmenau 1 1 1 3 1 2 8 3 10 9 1 4 1 2 1 w, 10 1 4 2 Mindestens einmal Angenommen, es gäbe in G einen Weg v0 = v, v1, . . . , vr−1, w, aber dfs(w) wird nicht aufgerufen. O.E. habe dieser minimale Länge mit dieser Eigenschaft. 5 3 2 1 G Beweis. Höchstens einmal Bei Aufruf dfs(w) gilt status(w) = neu; dann wird der Status sofort auf aktiv gesetzt und nie wieder auf neu. 5 3 9 2 1 7 Lemma (Beobachtung 1 zu dfs(v)). Für jeden von v aus erreichbaren Knoten w, d.h. v wird dfs(w) genau einmal aufgerufen. 5 2 2 3 7→ 1 2 1 2 2 2 3 7→ 3 1 4 2 1 1 1 2 3 Tiefensuche in Digraphen – 8 =⇒ Aufruf dfs(vr−1) und irgendwann wird w ∈ suc(vr−1) entdeckt. Also wird dfs(w) aufgerufen, Widerspruch! 10 9 AuD – 11.07.2006 28 FG KTuEA, TU Ilmenau AuD – 11.07.2006 29 Tiefensuche in Digraphen – 9 Tiefensuche in Digraphen – 10 Def. Für jeden Knoten v in einem Digraphen G = (V, E) bezeichne Rv (für reachable) die Menge der von v aus erreichbaren Knoten, d.h. Bem. Zu jedem v ∈ V gehört also ein dfs-Aufrufbaum Tdfs(v) mit Wurzel v, der Tiefensuchbaum zu v. Seien v1, . . . , vl die Nachfolger von v, deren Status bei Aufruf dfs(v) noch neu ist, und seien vi1 , . . . , vik mit i1 < . . . < ik diejenigen Nachfolger mit Rv := {w ∈ V | v G w} Bem. Jeder Knoten w ∈ Rv \ {v} wird nach Lemma, in Abhängigkeit von der Reihenfolge der Nachfolger v1, . . . , vl(v), l(v) := outdeg(v), von v in der Adjazenzliste für v, von einem eindeutig bestimmten Knoten aus entdeckt, in Zeichen p(w). vij+1 ∈ / Tdfs(vi1 ) ∪ . . . ∪ Tdfs(vi AuD – 11.07.2006 30 ) für j = 1, . . . , k−1. Dann besitzt der Tiefensuchbaum zu v die folgende Gestalt: Der Aufruf dfs(w) erfolgt während der Abarbeitung von Aufruf dfs(p(w)). Insbesondere: dfs(w) beginnt nach Aufruf dfs(p(w)) und endet vor Ende des Aufrufs dfs(p(w)). FG KTuEA, TU Ilmenau j Tdfs(v): FG KTuEA, TU Ilmenau AuD – 11.07.2006 31 Tiefensuche in Digraphen – 11 Tiefensuche in Digraphen – 12 Bsp. Tiefensuchbaum im Graphen (mit Pfeilen von p(w) zu w, die Entdeckungsrichtung) und Tdfs(v0) mit v0 := 1. 1 4 1 1 2 2 1 2 6 6 1 1 2 8 4 1 4 2 1 7 5 1 2 3 2 3 3 2 9 die Menge der Kanten, die aus Knoten in Rv ausgehen. Bem. Es gilt |Rv | ≤ |Ev |+1. 3 Lemma (Beobachtung 2 zu dfs(v)). Ein Aufruf dfs(v) benötigt Zeit O(|Ev |), d.h. ist linear in |E|. 10 Satz (Charakterisierung von Tdfs(v)). w ∈ Tdfs(v) ⇐⇒ bei Aufruf dfs(v) existiert ein Weg v aus Knoten mit Status neu. Beweis. Induktion über #(rekursive Aufrufe von dfs). FG KTuEA, TU Ilmenau Ev := {(u, w) ∈ E | u ∈ Rv } 5 2 1 Def. Für einen Knoten v (in G) bezeichne AuD – 11.07.2006 G w Beweis. Der Zeitaufwand für die Bearbeitung eines Aufrufs dfs(v) ohne die rekursiven Aufrufe ist O(1+outdeg(v)). Also benötigt Aufruf dfs(v) insgesamt: P O 1+outdeg(w) = O(|Ev |) w∈Rv 32 FG KTuEA, TU Ilmenau Tiefensuche in Digraphen – 13 Bew. O(|V |) sind Initialisierungskosten. Nach Aufruf dfs(v) kann man über eine Liste für Rv in Zeit O(|Rv |) das statusArray wieder auf neu setzen. Def. Die transitive Hülle eines Digraphen G = (V, E) ist der Digraph TH(G) := (V, TH(E)) mit G w}. AuD – 11.07.2006 Bem. Auch Breitensuche leistet dies. 2. Globale Tiefensuche in G: 1: procedure global-dfs(G) 2: dfs count ← 0 3: for v ← 1 to n do 4: status[v] ← neu 5: 6: 7: for v ← 1 to n do if status[v] = neu then dfs(v) ⊲ Status-Initialisierung ⊲ v noch nicht erreicht! ⊲ Starte dfs von v aus Bem. dfs-Aufrufe verändern die status-Werte. Durch Zeilen 6-7 werden insgesamt alle Knoten in G erreicht! Folg. TH(E) ist in Zeit O(|TH(E)|) berechenbar. FG KTuEA, TU Ilmenau 33 Tiefensuche in Digraphen – 14 Folg (Strukturinformation 1). Mit Tiefensuche kann man • in Zeit O(|V |+|Ev |) jede Menge Rv ermitteln, P • nacheinander für jeden Knoten, in Zeit O(|V |+ v∈V |Ev |) alle Mengen Rv ermitteln. TH(E) := {(v, w) | v, w ∈ V, v AuD – 11.07.2006 34 FG KTuEA, TU Ilmenau AuD – 11.07.2006 35 Tiefensuche in Digraphen – 15 Tiefensuche in Digraphen – 16 Lemma (Laufzeit). global-dfs(G) für einen Digraphen G = (V, E) benötigt Zeit O(|V |+|E|), d.h. Linearzeit! Bew. Ein Aufruf dfs(v) benötigt Zeit O(1+outdeg(v)) und jeder Knoten wird genau einmal besucht. Gesamtzeit: P O 1+outdeg(v) = O(|V | + |E|) v∈V Bsp. Alle Tiefensuchbäume mit dfs-Nummern 4 1 1 1 2 2 1 2 6 6 1 1 2 3 2 3 3 7 4 1 2 2 1 1 7 5 2 FG KTuEA, TU Ilmenau 9 5 3 2 8 4 1 10 9 8 10 AuD – 11.07.2006 36 Strukturinformation 2: Kreisfreiheitstest Durch Erweiterung der Prozedur dfs(v) um ein globales Flagbit kreis gesehen (mit false initialisiert) erhält man einen Linearzeittest auf Kreisfreiheit: ⊲ Tiefensuche von v aus, rekursiv procedure dfs-e(v) ... foreach w ∈ suc(v) do ⊲ nach Adjazenzliste von v if status(w) = aktiv then ⊲ w Kreis entdeckt! kreis gesehen ← true if status(w) = neu then ⊲ w wird entdeckt dfs-e(w) ... FG KTuEA, TU Ilmenau Tiefensuche in Digraphen – 17 4 1 1 2 2 1 Bsp. 2 6 1 3 1 2 3 1 7 1 2 8 1 4 2 2 1 Tiefensuche in Digraphen – 18 5 3 Satz =⇒ v Satz =⇒ v 10 Im Aufruf dfs−e(1) wird dfs−e(2) aufgerufen und Kante (2, 1) inspiziert. Knoten 1 ist noch aktiv, Kreis wird entdeckt! Satz (Kreisfreiheit). 1. Rv enthält einen Kreis ⇐⇒ nach Aufruf dfs-e(v) gilt kreis gesehen = true. 2. G enthält einen Kreis ⇐⇒ nach global-dfs-e(G) gilt kreis gesehen = true. AuD – 11.07.2006 u →G w, dfs−e(w) wurde vor dfs−e(u) aufgerufen. G w G u, fertig. G =⇒ Im Aufruf dfs − e(v) numeriere (in Gedanken) mit f num(w) ∈ {1, . . . , |V |} die Knoten w in der Reihenfolge der Beendigung der Aufrufe dfs−e(w). 6 4 1 1 Bsp. mit f-Nummern 5 1 1 3 2 1 2 8 3 10 1 4 2 2 1 7 1 2 3 2 8 1 4 2 2 6 Beweis. Aussage 2.) folgt aus Aussage 1.) Zu 1). FG KTuEA, TU Ilmenau 37 ⇐= Gelte kreis gesehen = true nach Aufruf dfs-e(v). =⇒ ∃u ∈ Tdfs−e(v) ∃w ∈ suc(u) : status[w] = aktiv 2 9 AuD – 11.07.2006 FG KTuEA, TU Ilmenau AuD – 11.07.2006 3 2 1 9 10 7 9 Es gilt: u →G w =⇒ f num(u) > f num(w). Fertig. 38 5 39 Topologische Sortierung in Digraphen– 1 Topologische Sortierung in Digraphen – 2 Strukturinformation 3: Topologische Sortierung global-dfs findet eine topologische Sortierung! Def. Eine topologische Sortierung eines azyklischen Digraphen G ist eine Bijektion f : V → {1, . . . , n} mit: Idee: Wissen: Die f-Nummern sind strikt fallend entlang Kanten. Definiere daher (mit n := |V |): ∀(v, w) ∈ E(G) : f (v) < f (w) f (v) := (n+1) − f num(v) Zur Berechnung der f-Nummern muß man nur dfs-e geringfügig erweitern. Sortierung der Knoten: f −1(1), f −1(2), . . . , f −1(n). 6 4 1 1 Bsp. mit f-Nummern 2 1 3 5 1 7 2 1 2 3 10 1 4 2 2 1 6 8 1 4 2 Folg (Topologisches Sortieren in Digraphen). Die Berechnung einer topologischen Sortierung für azyklische Digraphen G = (V, E) benötigt Linearzeit: 5 3 1 2 8 3 1 9 10 7 9 O(|V |+|E|) mit topologischer Sortierung: 5, 10, 4, 9, 1, 6, 2, 8, 7, 3 FG KTuEA, TU Ilmenau AuD – 11.07.2006 40 Topologische Sortierung in Digraphen – 3 Bem. dfs-ts-Aufrufe verändern die f count-Werte. AuD – 11.07.2006 AuD – 11.07.2006 41 Tiefensuche in ungerichteten Graphen – 1 Beendete Aufrufe mitzählen: in f count, mit 0 initialisiert. 1: procedure dfs-ts(v) ⊲ Tiefensuche von v aus, rekursiv 2: dfs count ← bfs count+1 3: dfs num(v) ← dfs count 4: status(v) ← aktiv 5: foreach w = suc(v) do ⊲ nach Adjazenzliste von v ⊲ w wird entdeckt 6: if status(w) = neu then dfs-ts(w) 7: 8: f count ← f count+1 9: f num(v) ← f count 10: status(v) ← fertig FG KTuEA, TU Ilmenau FG KTuEA, TU Ilmenau Im folgenden sei G = (V, E) ein ungerichteter Graph. Erinnerung: 1) Für Knoten u, v ∈ V schreiben wir u ∼G v, falls u mit v durch einen Weg in G verbunden ist. 2) ∼G ist eine Äquivalenzrelation auf V . 3) [v]∼G := {u | u ∼G v} heißt Äquivalenzklasse (Zusammenhangskomponente) von G mit Repräsentant v. Ziele: • Zusammenhangskomponenten finden • Spannbaum für jede Zusammenhangskomponente finden • Kreisfreiheitstest Bem. Diese Aufgaben kann auch Breitensuche erledigen. 42 FG KTuEA, TU Ilmenau AuD – 11.07.2006 43 Tiefensuche in ungerichteten Graphen – 2 Tiefensuche in ungerichteten Graphen – 3 Input: Knoten v mit status(v) = neu. 1: procedure udfs(v) ⊲ Tiefensuche von v aus, rekursiv 2: dfs count ← dfs count+1 3: dfs num(v) ← dfs count 4: status(v) ← aktiv 5: foreach Nachbar w von v do ⊲ Adjazenzliste! ⊲ w wird entdeckt 6: if status(w) = neu then udfs(w) 7: 8: status(v) ← fertig Tiefensuche von v0 aus: Initialisiere dfs count mit 0 und den Status aller Knoten mit neu. Rufe udfs(v0) auf. FG KTuEA, TU Ilmenau AuD – 11.07.2006 44 Tiefensuche in ungerichteten Graphen – 4 5: 7: for v ← 1 to n do if status[v] = neu then udfs(v) 2. Bei Aufruf udfs(v) gilt: Die Kanten (w, u), wo udfs(u) unmittelbar aus udfs(w) aufgerufen wird, bilden einen (gerichteten) Baum mit Wurzel v, in Zeichen Tudfs(v). Dies ist der Spannbaum von [v]∼G . 3. w ∈ Tudfs(v) ⇐⇒ bei Aufruf udfs(v) existiert ein Weg v ∼G w aus Knoten mit Status neu. 4. Die Laufzeit (mit Initialisierung) eines Aufrufs udfs(v) beträgt O(|V | + |Ev |), wobei Ev die Menge der Kanten in [v]∼G ist. FG KTuEA, TU Ilmenau AuD – 11.07.2006 45 Tiefensuche in ungerichteten Graphen – 5 Globale Tiefensuche in (ungerichtetem) Graphen G: 1: procedure global-udfs(G) 2: dfs count ← 0 3: for v ← 1 to n do 4: status[v] ← neu ⊲ Status-Initialisierung 6: Satz (Tiefensuche bei ungerichteten Graphen). 1. Aufruf udfs(v) entdeckt genau die Knoten in [v]∼G . ⊲ v noch nicht erreicht! ⊲ Starte udfs von v aus Kreisfreiheitstest in ungerichteten Graphen. Zudem: Finden eines Kreises, wenn es einen gibt. Erinnerung: Ein Kreis in G ist ein Weg (v0, v1, . . . , vk ) mit k ≥ 3, v0 = vk , ∀i ∈ {1, . . . , k} : vi−1 6= vi+1 sowie vk−1 6= v1. Ein Hin und her“ (. . . , u, v, u, . . . ) über dieselbe Kante ” (u, v) = (v, u) ist also nicht erlaubt! Wir merken uns die Vorgänger in einem Array p[1..n], das mit Nullen initialisiert wird. Satz. Der Aufruf global-udfs(G) erzeugt eine Reihe von Bäumen Tudfs(v). Deren Knotenmengen sind genau die ZHKn [v]∼G von G. Die Laufzeit ist linear: O(|V |+|E|) Durch Erweiterung der Prozedur udfs(v) um ein globales Flagbit kreis gesehen (mit false initialisiert) erhält man einen Linearzeittest auf Kreisfreiheit: FG KTuEA, TU Ilmenau FG KTuEA, TU Ilmenau AuD – 11.07.2006 46 AuD – 11.07.2006 47 Tiefensuche in ungerichteten Graphen – 6 1: 2: 3: 4: 5: 6: 7: 8: 9: Tiefensuche in ungerichteten Graphen – 7 procedure udfs-e(v) ⊲ Tiefensuche von v aus, rekursiv ... foreach Nachbar w von v do ⊲ Adjazenzliste! if status(w) = aktiv ∧ w 6= p[v] then kreis gesehen ← true if status(w) = neu then ⊲ w wird entdeckt p[w] ← v ⊲ Vorgänger eingetragen! udfs-e(w) ... Viel Erfolg in der Klausur! Schöne Ferien! Satz. G enthält einen Kreis ⇐⇒ nach global-dfs-e(G) gilt kreis gesehen = true. Wenn Kante (v, w) wie in Zeile 4-5 gefunden wird, so bildet diese mit w ∼G v einen Kreis. FG KTuEA, TU Ilmenau AuD – 11.07.2006 48 A&D-Klausur: Relevant sind 1. Definitionen von grundlegenden Begriffen/Konzepten: • Lernen und knapp, aber präzise hinschreiben können Bsp. Kreise in ungerichteten Graphen 2. Zentrale Resultate der Vorlesung: • Vestehen, lernen und spielerisch damit umgehen können (wichtig für Fragenkatalog!) 3. Beweise bzw –skizzen/Ideen (Konstruktionen) dazu: • Verstehen und knapp, aber präzise hinschreiben können Bsp. Satz Untere Schranke 4. Wichtige Aufgabentypen im Stil der Übungen • Grundlegende Techniken (z.B. Mastertheorem) bzw. Algorithmen etc. beherrschen und anwenden können FG KTuEA, TU Ilmenau AuD – 11.07.2006 50 FG KTuEA, TU Ilmenau AuD – 11.07.2006 49