Organisatorisches VL-14: Graphalgorithmen I • Vorlesung: Gerhard Woeginger (Zimmer 4024 im E1) Sprechstunde: Mittwoch 11:15–12:00 • Übungen: Tim Hartmann, David Korzeniewski, Björn Tauer Email: [email protected] (Datenstrukturen und Algorithmen, SS 2017) Gerhard Woeginger • Webseite: http://algo.rwth-aachen.de/Lehre/SS17/DSA.php • Nächste Vorlesung: Dienstag, Juni 20, 16:15–17:45 Uhr, Aula 1 SS 2017, RWTH DSAL/SS 2017 VL-14: Graphalgorithmen I 1/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 2/48 Bedeutung der Graphen in der Informatik Graphen treten in vielen Informatikanwendungen auf: Beispiele Graphalgorithmen I I (Computer-)Netzwerke I Darstellung von topologischen Informationen (Karten, . . . ) • Terminologie • Repräsentation von Graphen I Darstellung von elektronischen Schaltungen I Vorranggraphen (precedence graph), Ablaufpläne, . . . • Breitensuche (BFS) • Tiefensuche (DFS) • Finden von Zusammenhangskomponenten I Semantische Netze (z. B. Entity-Relationship-Diagramme) DSAL/SS 2017 VL-14: Graphalgorithmen I In dieser Vorlesung werden wir uns auf fundamentale Graphalgorithmen konzentrieren. 3/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 4/48 Was ist ein Graph? (1) Was ist ein Graph? (2) Gerichteter Graph Beispiel Ein gerichteter Graph (auch: digraph) G ist ein Paar (V , E ) mit I I einer endlichen Menge V von Knoten (vertices) und einer Menge E ⊆ V × V von geordneten Paaren von Knoten, die gerichtete Kanten (arcs) genannt werden. I V = {A, . . . , F } I E = {(A, B), (A, D), (B, E ), (C , E ), (C , F ), (D, B), (E , D), (F , F )} A C B Kanten Ungerichteter Graph Knoten Ein ungerichteter Graph G ist ein Paar (V , E ) mit I einer endlichen Menge V von Knoten (vertices) und I einer Menge E von ungeordneten Knotenpaaren, die Kanten (edges) genannt werden. DSAL/SS 2017 VL-14: Graphalgorithmen I D 5/48 Terminologie (1) F E DSAL/SS 2017 VL-14: Graphalgorithmen I 6/48 Terminologie (2) I Knoten u ist adjazent zu Knoten v , wenn (u, v ) ∈ E I Knoten u ist inzident zu Kante e = (u, v ) I Kante (u, u) heisst Schlinge (loop) I Der Grad deg(u) ist die Anzahl der zu u inzidenten Kanten I In-Grad deg+ (u); Aus-Grad deg− (u) I Graph ist k-regulär, falls alle Knoten Grad k haben Handshake Lemma In einem ungerichteten Graphen G = (V , E ) gilt: X Teilgraph Ein Teilgraph (subgraph) eines Graphen G = (V , E ) ist ein Graph G 0 = (V 0 , E 0 ) mit: I V 0 ⊆ V und E 0 ⊆ E ∩ (V 0 × V 0 ). I Ist V 0 ⊂ V und/oder E 0 ⊂ E , so heisst G 0 echter (proper) Teilgraph I Ist E 0 = E ∩ (V 0 × V 0 ), so ist G 0 der durch V 0 induzierte Teilgraph A B C D E F deg(v ) = 2|E | v ∈V Lemma In gerichtetem Graphen gilt: X deg+ (v ) = v ∈V DSAL/SS 2017 VL-14: Graphalgorithmen I X deg− (v ) v ∈V 7/48 DSAL/SS 2017 VL-14: Graphalgorithmen I Teilgraph in rot 8/48 Terminologie (3) Terminologie (4) Symmetrischer Graph Vollständiger (und symmetrischer) Digraph auf vier Knoten: Der gerichtete Graph G heisst symmetrisch, wenn aus (v , w ) ∈ E immer auch (w , v ) ∈ E folgt. Zu jedem ungerichteten Graphen gibt es einen korrespondierenden symmetrischen Digraphen. A B D E Vollständiger Graph Ein Graph G heisst vollständig, wenn jedes Paar von Knoten mit einer Kante verbunden ist. Kn ist der ungerichtete vollständige Graph mit n Knoten. Transponieren Der transponierte Graph von G = (V , E ) ist gegeben durch G T = (V , E 0 ) mit (v , w ) ∈ E 0 gdw. (w , v ) ∈ E . DSAL/SS 2017 VL-14: Graphalgorithmen I 9/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 10/48 Pfade und Kreise (1) Spaziergang, Pfad Ein Spaziergang (walk) von einem Knoten v zu einem Knoten w ist eine Folge v0 , v1 , v2 , . . . , vk−1 , vk , sodass: Pfade, Kreise, Zusammenhang I Startknoten v0 = v und Endknoten vk = w I (vi , vi+1 ) ist Kante in E , für 0 ≤ i ≤ k − 1 Ein Spaziergang mit lauter paarweise verschiedenen Knoten (das heisst, mit vi 6= vj für i 6= j) heisst simpel oder einfach oder Pfad. Die Länge eines Spaziergangs ist die Anzahl der durchlaufenen Kanten. Kreis Ein Kreis (cycle) ist ein nicht-leerer Pfad bei dem Startknoten mit Endknoten zusammenfällt. Graph ist kreis-frei (azyklisch), wenn er keine Kreise als Teilgraphen hat. DSAL/SS 2017 VL-14: Graphalgorithmen I 11/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 12/48 Pfade und Kreise (2) A B Zusammenhang (1) C Erreichbarkeit Ein Knoten w ist vom Knoten v aus erreichbar, wenn es einen Pfad von v nach w gibt. Schlinge D E Zusammenhang (im ungerichteten Graphen) F Kreis I G heisst 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. A B E D B und C F F sind Beispiele für Spaziergänge. E D B und C F sind Pfade. DSAL/SS 2017 VL-14: Graphalgorithmen I 13/48 Zusammenhang (2) DSAL/SS 2017 I G heisst stark zusammenhängend (strongly connected), wenn jeder Knoten von jedem anderen aus erreichbar ist. I G heisst schwach zusammenhängend, wenn der zugrunde liegende ungerichtete Graph (in dem alle Kanten ungerichtet gemacht worden sind) zusammenhängend ist. C B A I F J Eine starke Zusammenhangskomponente von G ist ein maximaler stark zusammenhängender Teilgraph von G. G H D I Jeder ungerichtete Graph kann eindeutig in Zusammenhangskomponenten aufgeteilt werden. I Jeder gerichtete Graph kann eindeutig in starke Zusammenhangskomponenten aufgeteilt werden. DSAL/SS 2017 14/48 Zusammenhang (3a) Zusammenhang (im gerichteten Graphen) I VL-14: Graphalgorithmen I VL-14: Graphalgorithmen I E Ein ungerichteter Graph: Wie lauten die Zusammenhangskomponenten? 15/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 16/48 Zusammenhang (3b) Zusammenhang (4) C B A I F J G H D E Ein nicht-stark-zusammenhängender Digraph, aufgeteilt in seine maximalen stark zusammenhängenden Teilgraphen. Die Zusammenhangskomponenten. DSAL/SS 2017 VL-14: Graphalgorithmen I 17/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 18/48 Zusammenhang (5) G ist Baum ⇐⇒ G zusammenhängend und G kreis-frei Anmerkungen I Ein Baum mit n Knoten hat n − 1 Kanten. I Ein ungerichteter Graph mit n Knoten und n − 2 oder weniger Kanten kann nicht zusammenhängend sein. I Ein ungerichteter Graph mit n Knoten und n oder mehr Kanten muss einen Zyklus enthalten. DSAL/SS 2017 VL-14: Graphalgorithmen I Repräsentation von Graphen 19/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 20/48 Repräsentation von Graphen: Adjazenzmatrix Repräsentation von Graphen: Adjazenzliste Sei G = (V , E ) ein Graph mit • |V | = n • |E | = m • V = {v1 , . . . , vn } Adjazenzliste In der Darstellung als Array von Adjazenzlisten gibt es ein durch die Nummer des Knoten indiziertes Array, das verkettete Listen (Adjazenzlisten) enthält. Der i-te Arrayeintrag enthält alle Kanten mit Startknoten vi . Adjazenzmatrix In der Adjazenzmatrix-Darstellung ist der Graph durch eine n × n Matrix A gegeben, wobei A(i, j) = 1 wenn (vi , vj ) ∈ E A(i, j) = 0 wenn (vi , vj ) ∈ /E I Wenn G ungerichtet ist, ist A symmetrisch (A = AT ). Dann muss nur die Hälfte der Matrix gespeichert werden. I Platzbedarf: Θ(n2 ). DSAL/SS 2017 VL-14: Graphalgorithmen I I 21/48 Darstellung eines ungerichteten Graphen Ist G ungerichtet, dann wird jede Kante zweimal gespeichert I Kanten, die in G nicht vorkommen, benötigen keinen Speicherplatz I Platzbedarf: Θ(n + m). DSAL/SS 2017 VL-14: Graphalgorithmen I Darstellung eines gerichteten Graphen A A B C E DSAL/SS 2017 D 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 A B C D E B A D C D E E B E B D C B C B A D F E Adjazenzliste Adjazenzmatrix VL-14: Graphalgorithmen I A B C D E F B E F B D F D E Adjazenzliste 22/48 23/48 DSAL/SS 2017 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 VL-14: Graphalgorithmen I 24/48 Graphendurchlauf (1) Viele Algorithmen (die wir später kennenlernen werden) untersuchen jeden einzelnen Knoten und jede einzelne Kante eines Graphen. Es gibt verschiedene Graphendurchlaufstrategien (traversal strategies), die jeden Knoten (oder jede Kante) genau einmal besuchen: Graphendurchlauf DSAL/SS 2017 VL-14: Graphalgorithmen I 25/48 I Breitensuche (BFS) I Tiefensuche (DFS) I Es handelt sich um Verallgemeinerungen der Strategien zur Baumtraversierung I In diesem allgemeineren Szenario müssen wir uns aber alle bereits besuchten Knoten explizit merken I Im folgenden arbeiten wir mit der Adjazenzlisten-Darstellung I Algorithmen auf dieser Basis kosten O(|V | + |E |) Zeit DSAL/SS 2017 VL-14: Graphalgorithmen I 26/48 Graphendurchlauf (2) Beispiele I Finden von (schwachen oder starken) Zusammenhangskomponenten I Topologische Sortierung I Kritische-Pfad-Analyse I Finden von 2-Zusammenhangskomponenten (biconnected components) I und viele weitere . . . DSAL/SS 2017 Breitensuche VL-14: Graphalgorithmen I 27/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 28/48 Breitensuche Breitensuche: Beispiel Breitensuche (Breadth-First Search, BFS) A Am Anfang sind alle Knoten als unbesucht (WHITE) markiert. Die Breitensuche beginnt in einem beliebigen Knoten v . B Zugrundeliegende Strategie: I I I I F Markiere den aktuellen Knoten v als aktiv (GRAY). Für jede Kante (v , w ) mit unbesuchtem Nachfolger w : I A Suche gleichzeitig von allen derartigen Knoten w aus weiter. Keinerlei Backtracking. F C G E Erforsche alle folgenden unbesuchten Knoten A D D B G G Markiere Knoten v als besucht (BLACK). VL-14: Graphalgorithmen I 29/48 F E DSAL/SS 2017 C E Fertig! VL-14: Graphalgorithmen I 30/48 Eigenschaften der Breitensuche (1) 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 () ) { // naechster noch u n ve ra rb e itet er Knoten int v = wait . dequeue () ; foreach ( w in adjList [ v ]) { if ( color [ w ] == WHITE ) { // neuer unbesuchter Knoten color [ w ] = GRAY ; // w ist noch zu verarbeiten wait . enqueue ( w ) ; } } color [ v ] = BLACK ; // v ist abgeschlossen } } VL-14: Graphalgorithmen I C Erforsche alle folgenden unbesuchten Knoten Breitensuche: Implementierung DSAL/SS 2017 E D B G B Anmerkung: Die schwarzen Knoten sind genau jene Knoten, die vom Startknoten aus erreichbar sind 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 C A Beginn der Breitensuche F DSAL/SS 2017 D Notation • Für einen Knoten v ∈ V bezeichnet d(v ) den Abstand vom Startknoten zum Knoten v (das heisst: die Anzahl der Kanten auf dem kürzesten Weg vom Startknoten nach v ) • Wenn ein Knoten w in die Queue gegeben wird, so färben wir die dafür verantwortliche Kante (v , w ) gelb. Knoten v ist der Vater von Knoten w . 31/48 I BFS besucht die Knoten in einer Reihenfolge mit ansteigendem Abstand vom Startknoten I Erst wenn alle Knoten mit Abstand d verarbeitet worden sind, werden die Knoten mit Abstand d + 1 angegangen I Die Suche terminiert, wenn für einen Abstand d keine Knoten auftreten. DSAL/SS 2017 VL-14: Graphalgorithmen I 32/48 Eigenschaften der Breitensuche (2) 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) I Die gelben Kanten induzieren den Breitensuchbaum I Breitensuchbaum hat Startknoten als Wurzel Tiefensuche Theorem (Komplexität der Breitensuche) Die Zeitkomplexität von BFS ist O(|V | + |E |). Der Platzbedarf von BFS ist Θ(|V |). DSAL/SS 2017 VL-14: Graphalgorithmen I 33/48 Tiefensuche DSAL/SS 2017 A Am Anfang sind alle Knoten als unbesucht (WHITE) markiert. Zugrundeliegende Strategie: I I I I I F Suche rekursiv von w aus. Das heisst: Erforsche Kante (v , w ), besuche den neu entdeckten Knoten w , und forsche von dort aus, bis es nicht mehr weiter geht. Dann backtracke von w nach v . C A D B G E F E Erforsche einen Knoten D B C G A D B G G Überprüfe die Kante, ohne aber Nachfolger w zu besuchen. Markiere Knoten v als besucht (BLACK). F C E Erforsche einen Knoten Anmerkung: Auch bei DFS sind die schwarzen Knoten genau jene Knoten, die vom Startknoten aus erreichbar sind DSAL/SS 2017 A Beginn der Tiefensuche Für jede Kante (v , w ) mit bereits besuchtem Nachfolger w : I D B Markiere den aktuellen Knoten v als aktiv (GRAY). Für jede Kante (v , w ) mit unbesuchtem Nachfolger w : I 34/48 Tiefensuche: Beispiel (1) Tiefensuche (Depth-First Search, DFS) I VL-14: Graphalgorithmen I VL-14: Graphalgorithmen I 35/48 DSAL/SS 2017 F C E Sackgasse! Backtracke und erforsche den nächsten Knoten VL-14: Graphalgorithmen I 36/48 Tiefensuche: Beispiel (2) A A D B F Tiefensuche: Beispiel (3) C E F A D B F C A E F B E F C A G DSAL/SS 2017 F C G E Erforsche den nächsten Knoten A D D B G G E B ist eine Sackgasse Backtracke und erforsche den nächsten Knoten VL-14: Graphalgorithmen I F C E F Beide nächsten Knoten wurden bereits gefunden 37/48 DSAL/SS 2017 C E Fertig! VL-14: Graphalgorithmen I 38/48 Eigenschaften der Tiefensuche (1) 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 ; } I DFS erforscht einen Pfad so weit wie möglich (dann backtracking) I Die zu verarbeitenden Knoten werden in LIFO-Reihenfolge abgearbeitet Es gibt zwei mögliche Verarbeitungszeitpunkte für jeden Knoten: I 1. Wenn der Knoten entdeckt wird 2. Wenn der Knoten abgeschlossen wird void dfsSearch ( List adjList [ n ] , int n , int start ) { int color [ n ]; for ( int i = 0; i < n ; i ++) { // I ni ti al i si er un g color [ i ] = WHITE ; } dfsRec ( adjList , n , start , color ) ; } VL-14: Graphalgorithmen I E B Tiefensuche – Implementierung 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 C C wurde bereits gefunden Backtracke und erforsche den nächsten Knoten D G D ist eine Sackgasse Backtracke und erforsche den nächsten Knoten DSAL/SS 2017 C D B G G Nächster Zustand wurde bereits gefunden Backtracke und erforsche den nächsten Knoten A D B B G Nächster Zustand wurde bereits gefunden Backtracke und erforsche den nächsten Knoten A D Theorem (Komplexität der Breitensuche) Die Zeitkomplexität von DFS ist O(|V | + |E |). Der Platzbedarf von DFS ist Θ(|V |). 39/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 40/48 Eigenschaften der Tiefensuche (2) 1 2 3 4 5 6 7 8 9 10 11 12 Eigenschaften der Tiefensuche (3) void dfsRec ( List adjList [ n ] , int n , int start , int & color [ n ]) { color [ start ] = GRAY ; time = time+1; left[start] = time; foreach ( next in adjList [ start ]) { if ( color [ next ] == WHITE ) { dfsSRec ( adjList , n , next , color ) ; } } color [ start ] = BLACK ; time = time+1; right[start] = time; } Fakt Zwei Intervalle [left[v],right[v]] und [left[u],right[u]] sind entweder disjunkt, oder das eine Intervall enthält das andere Fakt Knoten v ist Vorfahre vom Knoten u im DFS-Baum, gdw. left[v] < left[u] < right[u] < right[v] gdw. zum Zeitpunkt left[v] ein Pfad von v nach u existiert, der (bis auf v ) nur weisse Knoten enthält • Knoten v ist von Zeitpunkt left[v] bis Zeitpunkt right[v] aktiv DSAL/SS 2017 VL-14: Graphalgorithmen I 41/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 42/48 Eigenschaften der Tiefensuche (4) DFS-Klassifikation von Kanten I Baum-Kanten (tree-edges) treten im DFS-Baum auf I Rückwärts-Kanten (back-edges) gehen von Knoten u zu Vorfahren v I Vorwärts-Kanten (forward-edges) gehen von einem Vorfahren v zum Knoten u I Quer-Kanten (cross-edges) sind alle restlichen Kanten Finden von Zusammenhangskomponenten Fakt In einem ungerichteten Graphen ist jede Kante entweder Baum-Kante oder Rückwärts-Kante DSAL/SS 2017 VL-14: Graphalgorithmen I 43/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 44/48 Finden von Zusammenhangskomponenten (1) Finden von Zusammenhangskomponenten (2) Problem Bestimme alle Zusammenhangskomponenten für ungerichteten Graphen. 1 2 3 4 5 6 7 8 9 10 11 12 13 Lösung I I Konstruiere den zugehörigen symmetrischen Digraphen Verwende Tiefensuche: I I I Beginne bei einem beliebigen Knoten Finde alle anderen Knoten (und alle 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 |). DSAL/SS 2017 VL-14: Graphalgorithmen I 45/48 Finden von Zusammenhangskomponenten (3) 1 2 3 4 5 6 7 8 9 10 11 DSAL/SS 2017 void connComp onents ( List adjLst [ n ] , int n , int & cc [ n ]) { int color [ n ] , ccNum = 0; for ( int v = 0; v < n ; v ++) { // I ni ti al i si er un g color [ v ] = WHITE ; } for (int v = 0; v < n; v++) { if (color[v] == WHITE) { // weitere Komponente dfsSearch ( adjLst , n , v , ccNum++, cc) ; } } } DSAL/SS 2017 VL-14: Graphalgorithmen I 46/48 Organisatorisches 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 ; } VL-14: Graphalgorithmen I // Ausgabe in cc : cc [ v ] = Komponente von Knoten v • Nächste Vorlesung: Dienstag, Juni 20, 16:15–17:45 Uhr, Aula 1 • Webseite: http://algo.rwth-aachen.de/Lehre/SS17/DSA.php 47/48 DSAL/SS 2017 VL-14: Graphalgorithmen I 48/48