Elementare Graphenalgorithmen I Elementare Graphenalgorithmen I Übersicht Datenstrukturen und Algorithmen Vorlesung 14: Elementare Graphenalgorithmen I 1 Graphen Terminologie Repräsentation von Graphen 2 Graphendurchlauf Breitensuche Tiefensuche Finden von Zusammenhangskomponenten Joost-Pieter Katoen Lehrstuhl für Informatik 2 Software Modeling and Verification Group http://www-i2.rwth-aachen.de/i2/dsal10/ 15. Juni 2010 Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen 1/36 Übersicht Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen 2/36 Die Bedeutung von Graphen Graphen werden in vielen (Informatik-)Anwendungen verwendet: 1 2 Graphen Terminologie Repräsentation von Graphen Beispiele Graphendurchlauf Breitensuche Tiefensuche Finden von Zusammenhangskomponenten I (Computer-)Netzwerke I Darstellung von topologischen Informationen (Karten, . . . ) I Darstellung von elektronischen Schaltungen I Vorranggraphen (precedence graph), Ablaufpläne, . . . I Semantische Netze (z. B. Entity-Relationship-Diagramme) Wir werden uns auf fundamentale Graphalgorithmen konzentrieren. Joost-Pieter Katoen Datenstrukturen und Algorithmen 3/36 Joost-Pieter Katoen Datenstrukturen und Algorithmen 4/36 Elementare Graphenalgorithmen I Graphen Elementare Graphenalgorithmen I Was ist ein gerichteter Graph? (I) Graphen Was ist ein gerichteter Graph? (I) Beispiel Gerichteter Graph I V = { A, . . . , F } Ein gerichteter Graph (auch: Digraph) G ist ein Paar (V , E ) mit I E = { (A, B), (A, D), (B, E ), (C , E ), (C , F ), (D, B), (E , D), (F , F ) } I einer Menge Knoten (vertices) V und I einer Menge (geordneter) Paare von Knoten E ⊆ V × V , die (gerichtete) Kanten (edges) genannt werden. I A C B Kante Knoten Falls E eine Menge ungeordneter Paare ist, heißt G ungerichtet. D Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen 5/36 F E Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen 6/36 Terminologie bei Graphen (I) Terminologie bei Graphen (II) Teilgraph Vollständiger Graph Ein Teilgraph (subgraph) eines Graphen G = (V , E ) ist ein Graph G 0 = (V 0 , E 0 ) mit: Der Graph G ist vollständig, wenn jedes Paar von Knoten mit einer Kante verbunden ist. I V 0 ⊆ V und E 0 ⊆ E . I Außerdem ist E 0 ⊆ V 0 × V 0 wegen der Grapheigenschaft von G 0 . I Ist V0 ⊂ V und E0 ⊂ E , so heißt G0 Adjazent Knoten w ist adjazent zu v , wenn (v , w ) ∈ E . echter (proper) Teilgraph. Symmetrischer Graph Transponieren Der Graph G heißt symmetrisch, wenn aus (v , w ) ∈ E folgt (w , v ) ∈ E . Transponiert man G (transpose graph), so erhält man G T = (V , E 0 ) mit (v , w ) ∈ E 0 gdw. (w , v ) ∈ E . I Zu jedem ungerichteten Graphen gibt es korrespondierenden symmetrischen Digraphen. Joost-Pieter Katoen Datenstrukturen und Algorithmen I 7/36 In G 0 ist die Richtung der Kanten von G gerade umgedreht. Joost-Pieter Katoen Datenstrukturen und Algorithmen 8/36 Elementare Graphenalgorithmen I Graphen Elementare Graphenalgorithmen I Terminologie bei Graphen (III) Graphen Pfade und Zyklen (I) Weg, Pfad A C B Ein Weg von Knoten v nach w ist eine Folge von Kanten (vi , vi+1 ): v0 v1 v2 . . . vk−1 vk , so dass v0 = v und vk = w Ein Weg, bei dem alle vi 6= vj für i 6= j verschieden sind, heißt Pfad. D F E I Länge eines Pfades(Weges) ist die Anzahl der durchlaufenen Kanten. Teilgraph Erreichbarkeit A B Knoten w heißt erreichbar von v , wenn es einen Pfad von v nach w gibt. Zyklus D E Ein Zyklus ist ein nicht-leerer Weg bei dem der Startknoten auch Endknoten ist. Vollständiger (und symmetrischer) Digraph auf vier Knoten Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen 9/36 Pfade und Zyklen (II) A B I Ein Zyklus der Form vv heißt Schleife (loop, self-cycle). I Ein Graph ist azyklisch, wenn er keine Zyklen hat. Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen 10/36 Zusammenhängende Graphen (I) C Beim ungerichteten Graphen G: Zusammenhang Schleife D E I G heißt zusammenhängend, wenn jeder Knoten von jedem anderen Knoten aus erreichbar ist. I Eine Zusammenhangskomponente (connected component) von G ist ein maximaler zusammenhängender Teilgraph von G. I In einer Ansammlung von Graphen heißt ein Graph maximal, wenn er von keinem anderen dieser Graphen ein echter Teilgraph ist. F Zyklus A B E D B und C F F wären hier Beispiele für Wege. E D B und C F sind Pfade. Joost-Pieter Katoen Datenstrukturen und Algorithmen 11/36 Joost-Pieter Katoen Datenstrukturen und Algorithmen 12/36 Elementare Graphenalgorithmen I Graphen Elementare Graphenalgorithmen I Zusammenhängende Graphen (II) Graphen Ungerichtete, zusammenhängende Graphen Beim gerichteten Graphen G: C B A Zusammenhang I I G heißt stark zusammenhängend (strongly connected), wenn jeder Knoten von jedem anderen aus erreichbar ist. I G heißt schwach zusammenhängend, wenn der zugehörige ungerichtete Graph (wenn man alle Kanten ungerichtet macht) zusammenhängend ist. I Eine starke Zusammenhangskomponente von G ist ein maximaler stark zusammenhängender Teilgraph von G. I Ein nicht-verbundener Graph kann eindeutig in verschiedene Zusammenhangskomponenten aufgeteilt werden. Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen F J G D E Ein ungerichteter Graph; Was sind die Zusammenhangskomponenten? 13/36 Ungerichtete, zusammenhängende Graphen Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen 14/36 Starke Zusammenhangskomponenten C B A H I F J G D Zusammenhangskomponenten H E Ein nicht-zusammenhängender Digraph, aufgeteilt in seine maximalen zusammenhängenden Teilgraphen. Die Zusammenhangskomponenten. Joost-Pieter Katoen Datenstrukturen und Algorithmen 15/36 Joost-Pieter Katoen Datenstrukturen und Algorithmen 16/36 Elementare Graphenalgorithmen I Graphen Elementare Graphenalgorithmen I Repräsentation von Graphen – Adjazenzmatrix Repräsentation von Graphen – Adjazenzliste Adjazenzliste Sei G = (V , E ) mit |V | = n, |E | = m und V = { v1 , . . . , vn }. Adjazenzmatrix Die Adjazenzmatrix-Darstellung eines Graphen ist durch eine n × n Matrix A gegeben, wobei A(i, j) = 1, wenn (vi , vj ) ∈ E , sonst 0. I Graphen Wenn G ungerichtet ist, ergibt sich symmetrisches A (d. h. A = AT ). Dann muss nur die Hälfte gespeichert werden. ⇒ Platzbedarf: Θ(n2 ). Bei der Darstellung als Array von Adjazenzlisten gibt es ein durch die Nummer des Knoten indiziertes Array, das jeweils verkettete Listen (Adjazenzlisten) enthält. I Der i-te Arrayeintrag enthält alle Kanten von G, die von vi „ausgehen“. I Ist G ungerichtet, dann werden Kanten doppelt gespeichert. I Kanten, die in G nicht vorkommen, benötigen keinen Speicherplatz. ⇒ Platzbedarf: Θ(n + m). Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen 17/36 Darstellung eines ungerichteten Graphen A A B C D E B C E D B A D C D E E B E B Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphen Darstellung eines gerichteten Graphen A D C B A D F E 0 1 0 0 1 1 0 1 1 1 0 1 0 1 0 0 1 1 0 1 1 1 0 1 0 Datenstrukturen und Algorithmen B E F B D F D E Adjazenzliste Adjazenzmatrix Joost-Pieter Katoen A B C D E F C B Adjazenzliste 18/36 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 1 1 0 0 0 0 0 1 0 0 1 Adjazenzmatrix 19/36 Joost-Pieter Katoen Datenstrukturen und Algorithmen 20/36 Elementare Graphenalgorithmen I Graphendurchlauf Elementare Graphenalgorithmen I Übersicht Graphendurchlauf Graphendurchlauf (I) Viele Algorithmen untersuchen jeden Knoten (und jede Kante). 1 2 Graphen Terminologie Repräsentation von Graphen Es gibt verschiedene Graphendurchlaufstrategien (traversal strategies), die jeden Knoten (oder jede Kante) genau einmal besuchen: Graphendurchlauf Breitensuche Tiefensuche Finden von Zusammenhangskomponenten Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphendurchlauf 21/36 Graphendurchlauf (II) I Tiefensuche I Breitensuche I Es handelt sich um Verallgemeinerungen von Strategien zur Baumtraversierung. I Nun müssen wir uns aber alle bereits gefundenen Knoten merken. I Algorithmen auf dieser Basis sind in O(|V | + |E |). Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphendurchlauf 22/36 Breitensuche Breitensuche (Breadth-First Search, BFS) Am Anfang seien alle Knoten als „nicht-gefunden“ (WHITE) markiert. Die zugrundeliegende Strategie ist: Beispiele I Finden von (starken) Zusammenhangskomponenten, I Topologische Sortierung, I Kritische-Pfad-Analyse, I Finden von 2-Zusammenhangskomponenten (biconnected components), I I I I I . . . und viele weitere . . . Joost-Pieter Katoen Datenstrukturen und Algorithmen Markiere den aktuellen Knoten v als „gefunden“ (GRAY). Für jede Kante (v , w ) im Graph G mit „nicht-gefundenem“ Nachfolger w : 23/36 Suche „gleichzeitig“ von allen solchen w aus weiter. Kein Backtracking. I Markiere Knoten v als „abgeschlossen“ (BLACK). I Man erhält die Menge aller Knoten, die vom Startknoten aus erreichbar sind. Joost-Pieter Katoen Datenstrukturen und Algorithmen 24/36 Elementare Graphenalgorithmen I Graphendurchlauf Elementare Graphenalgorithmen I Breitensuche – Beispiel Breitensuche – Implementierung 1 A 2 D A D 3 4 B B G G 5 6 7 F C E F Beginn der Breitensuche C E 8 9 Erforsche alle folgenden nicht-gefundenen Knoten 10 A A D D 11 12 B B G 13 G 14 15 F C F E C 16 E 17 Erforsche alle folgenden nicht-gefundenen Knoten Fertig! 18 19 20 Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphendurchlauf 25/36 Eigenschaften der Breitensuche I I void bfsSearch(List adjList[n], int n, int start) { int color[n]; Queue wait; // zu verarbeitende Knoten for (int i = 0; i < n; i++) { color[i] = WHITE; // noch nicht gefunden } color[start] = GRAY; // start ist noch zu verarbeiten wait.enqueue(start); while (!wait.isEmpty()) { // nächster noch unverarbeiteter Knoten int v = wait.dequeue(); foreach (w in adjList[v]) { if (color[w] == WHITE) { // neuer ("ungefundener") Knoten color[w] = GRAY; // w ist noch zu verarbeiten wait.enqueue(w); } } color[v] = BLACK; // v ist abgeschlossen } } Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphendurchlauf Tiefensuche (Depth-First Search, DFS) Am Anfang seien alle Knoten als „nicht-gefunden“ (WHITE) markiert. Die zugrundeliegende Strategie ist: Nachdem alle Knoten mit Abstand d verarbeitet wurden, werden die mit d + 1 angegangen. Die Suche terminiert, wenn in Abstand d keine Knoten auftreten. I I I Die Tiefe des Knotens v im Breitensuchbaum ist seine kürzeste Kantendistanz zum Startknoten. I Die zu verarbeitenden Knoten werden als FIFO-Queue (first-in first-out) organisiert. I Es gibt eine einzige „Verarbeitungsmöglichkeit“ für v , nämlich, wenn es aus der Queue entnommen wird. Markiere den aktuellen Knoten v als „gefunden“ (GRAY). Für jede Kante (v , w ) im Graph G mit „nicht-gefundenem“ Nachfolger w : I I I I Theorem (Komplexität der Breitensuche) Die Zeitkomplexität ist Θ(|V | + |E |), der Platzbedarf Θ(|V |). Datenstrukturen und Algorithmen 27/36 Suche rekursiv von w aus, d. h.: Erforsche Kante (v , w ), besuche w , forsche von dort aus, bis es nicht mehr weiter geht. Dann backtracke von w nach v . Für jede Kante (v , w ) in G mit gefundenem Nachfolger w : I Joost-Pieter Katoen 26/36 Tiefensuche Knoten werden in der Reihenfolge mit zunehmenden Abstand vom Startknoten aus besucht. I Graphendurchlauf „Überprüfe“ die Kante, ohne aber w zu besuchen. I Markiere Knoten v als „abgeschlossen“ (BLACK). I Man erhält wieder die Menge aller Knoten, die vom Startknoten aus erreichbar sind. Joost-Pieter Katoen Datenstrukturen und Algorithmen 28/36 Elementare Graphenalgorithmen I Graphendurchlauf Elementare Graphenalgorithmen I Tiefensuche – Beispiel (I) A Tiefensuche – Beispiel (II) D B F A B E F Beginn der Tiefensuche A F A F B E F A E F Sackgasse! Backtracke und erforsche den nächsten Knoten Erforsche einen Knoten Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphendurchlauf 29/36 F C wurde bereits gefunden Backtracke und erforsche den nächsten Knoten A C 3 G 4 E 7 Erforsche den nächsten Knoten 8 9 A D B G 12 G 13 14 F C E F Beide nächsten Knoten wurden bereits gefunden Joost-Pieter Katoen C G E B ist eine Sackgasse Backtracke und erforsche den nächsten Knoten 30/36 void dfsRec(List adjList[n], int n, int start, int &color[n]) { color[start] = GRAY; foreach (next in adjList[start]) { if (color[next] == WHITE) { dfsSRec(adjList, n, next, color); } } color[start] = BLACK; } D 11 B F Graphendurchlauf 1 D B G E B G E D Elementare Graphenalgorithmen I 6 C A Datenstrukturen und Algorithmen 5 F C E C Nächster Zustand wurde bereits gefunden Backtracke und erforsche den nächsten Knoten Joost-Pieter Katoen 2 B D G Tiefensuche – Implementierung A D F D ist eine Sackgasse Backtracke und erforsche den nächsten Knoten Tiefensuche – Beispiel (III) A E B G C C D B G Nächster Zustand wurde bereits gefunden Backtracke und erforsche den nächsten Knoten D G C E C A D B G Erforsche einen Knoten D B A D G C Graphendurchlauf C 15 E 16 Fertig! Datenstrukturen und Algorithmen 17 31/36 void dfsSearch(List adjList[n], int n, int start) { int color[n]; for (int i = 0; i < n; i++) { // Initialisierung color[i] = WHITE; } dfsRec(adjList, n, start, color); } Joost-Pieter Katoen Datenstrukturen und Algorithmen 32/36 Elementare Graphenalgorithmen I Graphendurchlauf Elementare Graphenalgorithmen I Eigenschaften der Tiefensuche Graphendurchlauf Finden von Zusammenhangskomponenten (I) Problem I Erforsche einen Pfad so weit wie möglich, bevor man backtrackt. I Das entspricht der Reihenfolge der rekursiven Aufrufe. I Die zu verarbeitenden Knoten werden in LIFO-Reihenfolge geprüft. Es gibt zwei „Verarbeitungsmöglichkeiten“ für einen Knoten: I Finde die Zusammenhangskomponenten des ungerichteten Graphen G. Lösung I 1. Wenn der Knoten entdeckt wird. 2. Wenn der Knoten als „abgearbeitet“ markiert wird (und alle seine Nachfolger entdeckt werden). I I ⇒ Diese letztgenannte Möglichkeit macht Tiefensuche beliebt. I Theorem (Komplexität der Tiefensuche) I Zeitkomplexität: Θ(|V | + |E |), Platzkomplexität: Θ(|V |). Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphendurchlauf 33/36 Finden von Zusammenhangskomponenten (II) 1 2 3 4 5 6 7 8 9 10 11 12 Datenstrukturen und Algorithmen Beginne bei einem beliebigen Knoten. Finde alle anderen Knoten (und Kanten) in der selben Komponente mit DFS. Wenn es weitere Knoten gibt, wähle einen und wiederhole das Verfahren. I Man erhält einen Tiefensuchwald. I Die Zeitkomplexität ist Θ(|V | + |E |). Joost-Pieter Katoen Datenstrukturen und Algorithmen Elementare Graphenalgorithmen I Graphendurchlauf 34/36 Finden von Zusammenhangskomponenten (III) // Ausgabe in cc: cc[v] = Komponente von Knoten v void connComponents(List adjLst[n], int n, int &cc[n]) { int color[n], ccNum = 0; for (int v = 0; v < n; v++) { // Initialisierung color[v] = WHITE; } for (int v = 0; v < n; v++) { if (color[v] == WHITE) { // weitere Komponente dfsSearch(adjLst, n, v, ccNum++, cc); } } } Joost-Pieter Katoen Konstruiere den zugehörigen symmetrischen Digraph (mit 2 · |E | Kanten). Verwende Tiefensuche: 1 2 3 4 5 6 7 8 9 10 11 35/36 void dfsSearch(List adjLst[n], int n, int start, int &color[n], int ccNum, int &cc[n]) { color[start] = GRAY; cc[start] = ccNum; // speichere Nummer der Komponente von v foreach (next in adjLst[start]) { if (color[next] == WHITE) { dfsSearch(adjLst, n, next, color, ccNum, cc); } } color[start] = BLACK; } Joost-Pieter Katoen Datenstrukturen und Algorithmen 36/36