2.6 Datenstrukturen für Graphen Anwendungen überall. Ungerichtete Kanten drücken symmetrische Beziehungen aus, gerichtete Kanten auch asymmetrische Beziehungen. G = (V, E) V endliche Knotenmenge (vertices, nodes), n := |V |, E Kantenmenge (edges), m := |E|. Ungerichtete Kante {v, w }, gerichtete Kante (v, w ). wenn Verwechslungen ausgeschlossen sind, auch (v, w ). v und w adjazent, wenn zwischen ihnen eine Kante verläuft. v und e inzident, wenn v Endknoten von e. . – Seite 147/726 1 1 2 4 5 3 6 8 2 7 4 5 3 6 7 8 d(v) Grad (degree) = Anzahl ind(v ) Ingrad = Anzahl eingehender Kanten. inzidenter Kanten. d(5) = 3 outd(v ) Outgrad = Anzahl ausgehender Kanten. ind(5)= 1, outd(5)= 2 (1,2,5,7,3) ungerichteter Weg (1,2,3,7,5) gerichteter Weg der Länge 4. der Länge 4. . – Seite 148/726 Wege heißen einfach, wenn höchstens Anfangs- und Endknoten übereinstimmen. Kreise sind einfache Wege mit Anfangsknoten = Endknoten und Länge > 1 für gerichtete Graphen und Länge > 2 für ungerichtete Graphen. Graphen ohne Kreise heißen azyklisch. . – Seite 149/726 Ungerichtet Gerichtet u ≈ v :⇔ u ≈ v :⇔ ∃ Weg zwischen u und v . ∃ Weg von u nach v und ∃ Weg von v nach u. – ∀v ∈ V : v ≈ v . – ∀u, v ∈ V : u ≈ v ⇒ v ≈ u. – ∀u, v, w ∈ V : u ≈ v, v ≈ w ⇒ u ≈ w. Also ist ≈ in beiden Fällen eine Äquivalenzrelation ⇒ V zerfällt in disjunkte Äquivalenzklassen, d. h. V = V1 ∪˙ · · · ∪˙ Vk , alle Knoten in Vi sind äquivalent, aber keine Knoten aus Vi und Vj sind äquivalent, falls i 6= j . . – Seite 150/726 Ungerichtete Graphen: Äquivalenzklassen heißen Zusammenhangskomponenten (connected components), sie sind die nicht vergrößerbaren Mengen von paarweise verbundenen Knoten. Im Beispiel: V1 = {1, 2, 3, 4, 5, 7}, V2 = {6, 8}. Zusammenhängende, azyklische, ungerichtete Graphen heißen auch Bäume. Gerichtete Graphen: Äquivalenzklassen heißen starke Zusammenhangskomponenten (strongly connected components), sie sind die nicht vergrößerbaren Mengen von Knoten, so dass es von jedem Knoten zu jedem anderen einen gerichteten Weg gibt. Im Beispiel: V1 = {1, 2, 3, 5, 7}, V2 = {4}, V3 = {6}, V4 = {8}. . – Seite 151/726 Wenn wir Kanten (i, j) als i ≤ j interpretieren, bilden genau die azyklischen Graphen eine partielle Ordnung. Deren Knotenmenge kann topologisch sortiert werden. (s. Kap. 2.3). Adjazenzmatrix A: A(i, j) = 1, falls (i, j) ∈ E (gerichtet) oder {i, j} ∈ E (ungerichtet), A(i, j) = 0 sonst. Speicherplatz für n2 Bits (Arraydarstellung möglich). Adjazenzlisten: Array der Knoten, für Knoten i Liste der j mit (i, j) ∈ E (gerichtet) oder {i, j} ∈ E (ungerichtet). Speicherplatz n für das Array und m bzw. 2m Listeneinträge. . – Seite 152/726 Operation: Adjazenzmatrix: Ist (i, j) ∈ E ? O(1) Berechne d(i). Θ(n) Adjazenzlisten: O(d(i)) Θ(d(i)) Meistens Darstellung durch Adjazenzlisten. Traversieren von Graphen und Erzeugung einer nützlichen Kantenpartition Tiefensuche (DFS, depth first search) und Breitensuche (BFS, breadth first search). . – Seite 153/726 Tiefensuche in ungerichteten Graphen Informell: Versuche einen Weg so lang wie möglich auszudehnen, ohne Kreise zu schließen. Wenn es nicht weitergeht, Backtracking, neuer Start am Vorgänger (← Stack für aktuellen Weg). Kanteneinteilung: Baumkanten (Treekanten) erreichen Knoten erstmalig, alle anderen Rückwärtskanten (Backkanten), da sie im Baum zurückführen. DFS(v), gestartet bei leerem Stack, erreicht alle w mit v ≈ w und ordnet ihnen eine DFS-Nummer zu. Es wird also die Zusammenhangskomponente von v bearbeitet, d. h. Neustart bei allen anderen Knoten nötig: . – Seite 154/726 Datenstrukturen: Graph beschrieben durch Adjazenzlisten. Array P (Predecessor) für T -Vorgänger, mit Nullen initialisiert. Array num der Länge n für DFS-Nummern, mit Nullen initialisiert. Listen für die Mengen T (Treekanten) und B (Backkanten), zu Beginn leer. Variable i für Integers, zu Beginn 0. → O(n) Rahmenprogramm: Für x ∈ V : if num(x) = 0 then P (x) := 0 und DFS(x). . – Seite 155/726 DFS(v): i := i + 1, num(v) := i. (Die nächste freie Nummer wird an v vergeben.) w noch nicht gelesen Durchlaufe Adj(v) ↓ für w:Falls num(w) = 0, (v, w) → T, P (w) := v , DFS(w). Falls num(w) 6= 0 und w = P (v), tue nichts. → es ist (w, v) in T Falls num(w) 6= 0, num(w) < num(v) und w 6= P (v), (v, w) → B . Falls num(w) 6= 0, num(w) > num(v), tue nichts. Die Kanten in T und B sind gerichtet! . – Seite 156/726 T - Kanten 1 2 8 ignoriert 1 1 Adj(1) 3 2 2 B -Kanten 3 3 3 4 7 2 Adj(2) 5 13 4 5 7 6 4 7 6 6 4 5 7 Adj(3) 125 7 5 9 8 2 Adj(4) 8 Adj(5) 23 Neustart 8 Adj(6) Adj(7) Adj(8) Stop 23 6 . – Seite 157/726 Analyse: – Jede Kante {v, w} wird genau einmal von v und genau einmal von w betrachtet (→ O(n + m)). – Jede Kante {v, w} wird in genau einer Richtung entweder T - oder B -Kante. – Die T -Kanten bilden einen Wald (Menge von Bäumen), dessen Bäume die Knoten der Zusammenhangskomponenten zusammenfassen. . – Seite 158/726 Jeder DFS(v)-Aufruf gibt DFS-Nummer an v , danach kein neuer DFS(v)-Aufruf. Jede Adjazenzliste wird genau einmal durchlaufen (→ Behauptung 1). Sei {v, w} ∈ E , o. B. d. A. wird v früher erreicht → DFS(v) startet → w wird über {v, w} erstmals gefunden → (v, w) ∈ T . → w wird anders gefunden, dann num(w) > num(v). → w wird anders gefunden, dann entweder – num(w) < num(v) und w 6= P (v) → (v, w) ∈ B oder – num(w) > num(v). . – Seite 159/726 Weiterhin Fall num(w) > num(v). . . DFS(w) innerhalb von DFS(v), also wird v in Adj(w) vor w in Adj(v) gefunden → (w, v) ∈ B , da v 6= P (w) und num(v) < num(w). Später wird w in Adj(v) gefunden → (v, w) 6∈ B , da num(v) < num(w) (→ Behauptung 2). Knoten bei Neustart im Rahmenprogramm erhält keinen T -Vorgänger. Alle anderen erhalten genau einen T -Vorgänger. Die T -Kanten sind azyklisch, da (v, w) ∈ T ⇒ num(v) < num(w) (→ Behauptung 3). Ungerichtete Graphen sind genau dann azyklisch, wenn DFS keine B -Kante erzeugt. . – Seite 160/726 Schematisierung des zeitlichen Ablaufs für {v, w} ∈ E : Wenn DFS(w) vor DFS(v) begonnen wird, wird DFS(w) später als DFS(v) beendet. DFS(v ) w in Adj(v ) gefunden Möglichkeiten und Klassifikation von DFS(w ) unmöglich, da v in Adj(w ) v in Adj(w) gefunden → (w, v) ∈ T → (v, w) ∈ B → (w, v) ∈ B → (v, w) ∈ T . – Seite 161/726 Tiefensuche in gerichteten Graphen Informell: T -Kanten wie bisher, bei Start in v kommen alle von v aus erreichbaren Knoten in den T -Baum mit Wurzel v . Weitere Einteilung: B -Kanten führen im Baum zu einem Vorgänger. F -Kanten (Forward) führen im Baum zu einem Nachfolger. C -Kanten (Cross) sind die restlichen Kanten, sie führen innerhalb eines Baumes zu einem früheren Teilbaum oder zu einem früher konstruierten Baum. . – Seite 162/726 Formal: 0 α(v) = 1 2 bis DFS(v) begonnen wird (äquivalent num(v) = 0), während DFS(v), nach Beendigung von DFS(v). Es wird w in Adj(v) gefunden. (1) num(w) = 0 → (v, w) T -Kante. (2) num(w) 6= 0, num(w) > num(v) → (v, w) F -Kante, da w innerhalb DFS(v) entdeckt wurde. (3) num(w) 6= 0, num(w) < num(v), α(w) = 1 → (v, w) B -Kante, da v innerhalb DFS(w) entdeckt wurde. (4) Sonst, d. h. num(w) 6= 0, num(w) < num(v), α(w) = 2 → (v, w) C -Kante, da v nach Beendigung von DFS(w) entdeckt wurde. . – Seite 163/726 7 7 4 4 2 3 3 1 1 6 5 13 5 12 6 10 11 8 9 9 8 14 2 Adj(1) 3 8 Adj(2) Adj(3) Adj(4) Adj(5) 4 5 9 7 17 7 3 13 Adj(6) Adj(7) 19 Adj(8) 6 Adj(9) Neustart Jede Kante wird einmal verarbeitet → O(n + m). . – Seite 164/726 Schematisierung des zeitlichen Ablaufs für (v, w): DFS(v) w in Adj(v) gefunden Möglichkeit für DFS(w) (v, w) ∈ C (v, w) ∈ B (v, w) ∈ F (v, w) ∈ T Unmöglich. Wenn DFS(w) später als DFS(v) beginnt, wird DFS(w) nicht durch DFS(v) unterbrochen. . – Seite 165/726 Breitensuche Informal: Vom Startpunkt aus werden alle w ∈ Adj(v) zuerst nummeriert und in eine Queue geschrieben. Solange die Queue nicht leer ist, wird BFS für das nächste Element der Queue aufgerufen. 10 13 11 5 6 2 1 12 7 8 3 4 9 Die Knoten werden in der Reihenfolge ihres Abstandes vom Startknoten gefunden. O(n + m) . – Seite 166/726