Teil III: Graphenalgorithmen ! ! ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume Flüsse in Netzwerken Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-1 Kommunikationsnetze (1) Menge von Rechnern, die mit Leitungen verbunden sind. Beispiele Knoten (Rechner) Kante (Leitung) Vollständig vernetzte Struktur Ringstruktur Prof. Dr. O. Bittel, HTWG Konstanz Sternstruktur Busstruktur Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-2 Kommunikationsnetze (2) Zuverlässigkeit von Kommunikationsnetzen ! lässt sich bemessen durch: ! Verbindungszusammenhang: Minimale Anzahl von Verbindungen, bei deren Ausfall die Kommunikation zwischen irgendwelchen Knotenpaaren unterbrochen wird. ! Knotenzusammenhang: Minimale Anzahl von Knoten, bei deren Ausfall die Kommunikation zwischen irgendwelchen Knotenpaaren unterbrochen wird. ! Algorithmus → Flüsse in Netzwerken. Routing-Problem ! Bestimme von einem Rechnerknoten den günstigsten Weg zum Zielknoten. Die Güte einer Verbindungskante ist dabei beispielsweise durch seine Paketkapazität definiert. ! Algorithmus → kürzeste Wege in Graphen Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-3 Routenplanung Problemstellung ! Finde in einem Straßennetz die kürzeste Verbindung (Länge bzw. Fahrtzeit) von einer Stadt A zu einer Stadt B. 1 6 7 1 8 8 3 1 3 6 2 0 4 4 1 2 2 7 1 1 4 5 ! Algorithmus → kürzeste Wege in Graphen Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-4 Netzplanung Problemstellung ! Zwischen n Orten soll ein Versorgungsnetz (Kommunikation, Strom, Wasser, etc.) aufgebaut werden, so dass je 2 Orte direkt oder indirekt miteinander verbunden sind. Die Verbindungskosten zwischen 2 Orte seien bekannt. ! Gesucht ist ein Versorgungsnetz mit den geringsten Kosten. 7 2 1 2 1 5 6 1 2 4 2 4 1 2 2 Orte mit Verbindungskosten Günstigstes Versorgungsnetz ! Algorithmus → minimal aufspannende Bäume Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-5 Zyklenfreiheit bei gerichteten Graphen (1) ! Beispiel: Ausschnitt aus Vererbungsgraph für stream-Klassen in C++ ios istream ostream iostream fstream strstream ! Beispiel: include-Graph in C mod.h Dictionary.h set.h Prof. Dr. O. Bittel, HTWG Konstanz string.h Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-6 Zyklenfreiheit bei gerichteten Graphen (2) ! Beispiel: Montageanleitung für Schrank Füße an Boden montieren Linke Seitenwand montieren Rechte Seitenwand montieren Decke montieren Scharniere an Türen montieren Linke Türe an linke Seitenwand montieren Rechte Türe an rechte Seitenwand montieren ! Algorithmus → topologische Sortierung Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-7 Internet-Suchmaschinen (1) ! Web-Seiten mit ihren Verweisen A C B D ! Problemstellung: Indexierung Besuche alle Internetseiten, extrahiere Schlüsselwörter und speichere diese in einen Index. ! Algorithmus → Tiefensuche Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-8 Internet-Suchmaschinen (2) Problemstellung: Relevanz von Internetseiten ! Bei Eingabe eines Suchbegriffs durchsuchen die Suchmaschinen ihren Index. Die gefundenen Internetseiten werden üblicherweise nach Relevanz sortiert ausgegeben. ! Problem: Stelle die Relevanz von Internetseiten fest. PageRank-Algorithmus von Page und Prin (s. [Turau]) 1) Berechne für jede Internetseite w den PageRank r(w) nach folgender Formel: l(v) = Anzahl der ausgehenden Links von v α ∈ [0,1] ist Dämpfungsfaktor v1 … w vn 2) Wiederhole Schritt 1) solange, bis r(w) für alle Seiten hinreichend konvergiert. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-9 Internet-Suchmaschinen (3) Beispiel: ! Für das Beispiel-Netz ergeben sich mit α = 0.5 folgende Gleichungen: r(A) = 0.5 + 0.5*r(B) r(B) = 0.5 + 0.5*r(A)/2 r(C) = 0.5 + 0.5*(r(A)/2 + r(D)) r(D) = 0.5 + 0.5*r(C) A C B D ! Beginnt man mit den Initialisierungswerten r(w) = 1 und wendet man die Gleichungen iterativ an, ergeben sich bereits nach 4 Iterationen stabile Werte: Iterationen r(A) r(B) r(C) r(D) 1 1 1 1 1 2 1 0.75 1.25 1.0625 3 0.875 0.71875 1.071875 1.0359375 4 0.859375 Prof. Dr. O. Bittel, HTWG Konstanz 0.71484375 1.072265625 1.0361328125 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-10 Teil III: Graphenalgorithmen ! ! ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume Flüsse in Netzwerken Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-11 Gerichteter Graph ! Ein gerichteter Graph G = (V, E) besteht aus einer Menge V von Knoten (engl. vertices) und einer Menge E ⊆ V×V von Kanten (engl. edges). ! Eine Kante ist ein geordnetes Paar von Knoten (v,w). ! Die Kanten sind gerichtet: die Kante (v,w) geht von Knoten v nach Knoten w. In den graphischen Darstellungen werden Pfeile verwendet. ! Kanten der Form (v,v) heißen Schlingen. ! Gerichtete Graphen werden auch Digraphen genannt. v3 v4 v2 v1 v5 V = { v1 , v2 , v3 , v4 , v5 } E = { (v1,v2), (v2,v3), (v3,v1), (v3,v4), (v4,v2), (v4,v5) } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-12 Ungerichteter Graph ! Bei einem ungerichteter Graph G = (V, E) haben die Kanten keine Richtung. ! Eine Kante ist gegeben durch ein ungeordnetes Paar von Knoten (v,w). Es gilt (v,w) = (w,v). ! Ein ungeordnetes Paar von Knoten (v,w) ist dasselbe wie die 2-elementige Menge von Knoten {v, w}. Es muss v ≠ w sein (keine Schlingen!). ! Die Darstellung soll jedoch einfach bleiben. Daher werden wir sowohl für gerichtete als auch ungerichtete Kanten dieselbe Notation (v,w) verwenden. v3 Knoten v4 v2 Kante v1 v5 V = { v1 , v2 , v3 , v4 , v5 } E = { (v1,v2), (v1,v3), (v2,v3), (v2,v4), (v3,v4), (v5,v4) } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-13 Adjazenz und Grad ! Ein Knoten w ist adjazent zu (benachbart zu) einem Knoten v, falls (v,w) eine Kante des (gerichteten bzw. ungerichteten) Graphen ist. v w v w ! Bei einem ungerichteten Graphen ist die Adjazenzbeziehung immer symmetrisch. Bei einem gerichteten Grahen ist die Adjazenzbeziehung im allg. unsymmetrisch. ! Bei einer gerichteten Kante (v,w) wird w auch Nachfolger von v und v Vorgänger von w genannt. Bei einer ungerichteten Kante (v,w) heißen v und w auch Nachbarn. ! In einem ungerichteten Graphen ist der Grad eines Knoten die Anzahl seiner Nachbarn. ! Bei einem gerichteten Graphen wird die Anzahl der Vorgänger Eingangsgrad und die Anzahl der Nachfolger Ausgangsgrad genannt. v v - Der Knoten v hat 5 adjazente Knoten. - Der Knoten v hat 2 Vorgänger und 3 Nachfolger. - Der Grad von v ist damit 5. - Der Eingangsgrad von v ist damit 2 und der Ausgangsgrad ist 3. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-14 Weg ! Ein Weg (engl. path) ist eine Folge von Knoten v1, v2, …, vn, wobei n ≥ 1 und (vi, vi+1) für 1 ≤ i < n Kanten im Graphen sind. ! Die Länge des Weges v1, v2, …, vn ist gleich der Anzahl der Kanten und damit n-1. n darf 1 sein; dann hat der Weg die Länge 0. ! Ein Weg heißt einfacher Weg, falls alle Knoten bis auf Anfangs- und Endknoten unterschiedlich sind. v3 v3 v4 v2 v1 v5 v1 Ein einfacher Weg der Länge 3: v1, v2, v3, v4. Prof. Dr. O. Bittel, HTWG Konstanz v4 v2 v5 Ein (nicht einfacher) Weg der Länge 5: v1, v2, v5, v4, v2, v3 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-15 Zyklus ! Ein Weg v1, v2, …, vn in einem gerichteten Graph heisst Zyklus (cycle), falls Anfangsknoten v1 und Endknoten vn identisch sind und n ≥ 2 ist (d.h. wenigstens eine Kante). ! Ein Weg v1, v2, …, vn in einem ungerichteten Graph heisst Zyklus (cycle), falls Anfangsknoten v1 und Endknoten vn identisch sind, n ≥ 4 ist (d.h. wenigstens drei Kanten) und alle Kanten unterschiedlich sind. ! Zyklen sind einfach, wenn der Weg einfach ist. ! Zwei Zyklen sind gleich, falls sich ihre Wege (jeweils ohne Endknoten) durch eine zyklische Verschiebung unterscheiden. ! Zyklenfreie Graphen werden auch azyklisch (acyclic graph) genannt. v3 v3 v1 v2 v3 Ein einfacher Zyklus (in einem gerichteten Graph) der Länge 3: v1 , v 2 , v 1 Prof. Dr. O. Bittel, HTWG Konstanz v4 v2 v1 v5 Ein einfacher Zyklus der Länge 3: v1 , v 2 , v 3 , v 1 v4 v2 v1 v5 Ein nicht-einfacher Zyklus der Länge 6: v1 , v 2 , v 3 , v 1 , v 5 , v 4 , v 1 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-16 Teilgraph ! Ein Graph G' = (V', E') ist ein Teilgraph von Graph G = (V, E), falls V' ⊆ V und E' ⊆ E ! Enthält darüber hinaus G' alle Kanten aus E, die Knoten aus V' verbinden, dann heißt G' der durch V' induzierte Teilgraph von G, d.h. E' = {(u,v) ∈ E / u,v ∈ V'} v5 v3 v4 v3 v4 v3 v4 v1 v2 v1 v2 v1 v2 Graph G Prof. Dr. O. Bittel, HTWG Konstanz Teilgraph von G Der durch V = {v1, v2, v3, v4} induzierte Teilgraph von G Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-17 Zusammenhang ! Ein ungerichteter Graph heißt zusammenhängend (connected), falls es von jedem Knoten einen Weg zu jeden anderen Knoten gibt. ! Eine Zusammenhangskomponente eines ungerichteten Graphen G ist ein maximal zusammenhängender Teilgraph. v Graph G = (E,V) ist zusammenhängend. Prof. Dr. O. Bittel, HTWG Konstanz Der durch V \ {v} induzierte Teilgraph (d.h. G ohne Knoten v und ohne Kanten mit v als Endknoten) besteht aus genau zwei Zusammenhangskomponenten. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-18 Strenger und schwacher Zusammenhang ! Ein gerichteter Graph heißt streng zusammenhängend (strongly connected), falls es von jedem Knoten einen Weg zu jedem anderen Knoten gibt. ! Wenn bei einem gerichteter Graph G der entsprechende ungerichtete Graph zusammenhängend ist, dann wird G auch schwach zusammenhängend genannt. (weakly connected). Gerichteter Graph, der schwach aber nicht stark zusammenhängend ist. ! Strenge und schwache Zusammenhangskomponenten werden analog zu Zusammenhangskomponenten bei ungerichteten Graphen definiert. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-19 Wald und Baum ! Ein ungerichteter und azyklischer Graph heißt auch Wald. ! Ein zusammenhängender Wald heißt auch Baum. Wald Baum ! Ein Wurzelbaum ist ein gerichteter, azyklischer Graph, bei dem genau ein Knoten – die sogenannte Wurzel – den Eingangsgrad 0 hat und alle anderen Knoten den Eingangsgrad 1 haben. Bei einer Kante (v,w) heisst v auch Elternknoten von w und w Kindknoten von v. Knoten ohne Kinder heißen auch Blätter. In der graphischen Darstellung zeigen die Kanten üblicherweise nach unten. Die im Kap. 1 eingeführten Suchbäume sind Wurzelbäume. Wurzel v w Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-20 Gewichteter Graph ! Ist jeder Kante (v,w) eine reelle Zahl c(v,w) als Kosten oder Gewicht zugeordnet, spricht man von einem gewichteten Graphen. ! Sind darüber hinaus die Gewichte ≥ 0, dann heißt der Graph auch Distanzgraph. 7 2 1 2 1 5 6 2 4 2 Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-21 Teil III: Graphenalgorithmen ! Anwendungen ! Definitionen ! Datenstrukturen für Graphen - - - - ! ! ! ! ! ! Adjazenzmatrix Adjazenzliste Kantenliste Implementierungshinweise für Java Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume Flüsse in Netzwerken Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-22 Adjazenzmatrix für ungewichtete Graphen A[i][j] = 1, falls es eine Kante von vi und nach vj gibt 0 sonst v1 v1 v2 v0 v2 v0 v4 v3 [0] [1] [2] [3] [4] v4 v3 [0] [1] [2] [3] [4] [0] 0 1 0 0 1 [0] 0 1 0 0 0 [1] 1 0 1 0 1 [1] 0 0 1 0 1 [2] 0 1 0 1 1 [2] 0 0 0 1 1 [3] 0 0 1 0 1 [3] 0 0 0 0 0 [4] 1 1 1 1 0 [4] 1 0 0 1 0 Beachte: Die Adjazenzmatrizen sind bei einem ungerichteten Graphen symmetrisch und bei einem gerichteten Graphen im allgemeinen unsymmetrisch. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-23 Adjazenzmatrix für gewichtete Graphen A[i][j] = c(v,w), falls es eine Kante von Knoten v nach w gibt ∞ sonst v1 v1 v2 7 2 2 v0 v2 7 6 1 3 v0 6 1 3 4 4 v4 v3 -3 [0] [1] [2] [3] [4] -3 v4 v3 [0] [1] [2] [3] [4] [0] ∞ 2 ∞ ∞ 4 [0] ∞ [1] 2 ∞ 7 ∞ 1 [1] ∞ ∞ 7 ∞ 1 [2] 0 7 ∞ 3 6 [2] ∞ ∞ ∞ 3 6 [3] ∞ ∞ 3 ∞ -3 [4] 4 6 -3 ∞ 1 2 ∞ ∞ ∞ [3] ∞ ∞ ∞ ∞ ∞ [4] 4 ∞ ∞ -3 ∞ Beachte: Die Adjazenzmatrizen sind bei einem ungerichteten Graphen symmetrisch und bei einem gerichteten Graphen im allgemeinen unsymmetrisch. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-24 Adjazenzliste ! bei einem ungerichteten Graphen wird für jeden Knoten eine linear verkettete Liste mit allen Nachbarknoten (Adjazenzliste) abgespeichert. ! ungerichtete Kanten werden bei beiden Endknoten eingetragen. v1 v2 v0 v4 v3 [0] 1 4 [1] 0 2 4 [2] 1 3 4 [3] 2 [4] 0 1 2 ! Bei einem gerichteten Graphen werden zwei getrennte Listen verwaltet: eine Vorgänger-Liste und eine Nachfolger-Liste. ! Bei einem gewichteten Graphen wird noch zusätzlich das Kantengewicht eingetragen. ! Um die Dynamisierbarkeit der Datenstruktur zu verbessern, kann statt eines Feldes auch eine dynamische Datenstruktur wie ein binärer Suchbaum verwendet werden. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-25 Kantenliste ! Speichere die Menge aller Kanten in einer sortierten Suchstruktur (sortiertes Feld oder Suchbaum). ! Als Ordnungsrelation auf Kanten ist eine lexikographische Ordnung naheliegend: (v, v') ≤ (w, w') falls v < w oder v = w und v' ≤ w' ! Beispiel: sortiertes Kanten-Feld v1 v2 [0] [1] [2] … v0 v4 v3 [11] i 0 0 1 1 1 2 2 2 3 4 4 4 j 1 4 0 2 4 1 3 4 2 0 1 2 Alle Nachbarknoten zu v2 sind effizient auffindbar. ! Bei einem ungerichten Graphen wird für eine Kante von v nach w sowohl (v,w) als auch (w,v) gespeichert. ! Bei einem gerichteten Graphen wird die Kantenliste in Vorgänger- und Nachfolger-Liste aufgeteilt. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-26 Dünn- und dichtbesetzte Graphen ! Falls |E| = O(|V|) (dünnbesetzter Graph; sparse graph), dann ist die Speicherung als Adjazenzliste oder Kantenliste Speicherplatz sparender und effizienter in der Laufzeit. ! Falls |E| = O(|V|2) (dichtbesetzter Graph; dense graph), dann ist die Speicherung als Adjazenzmatrix Speicherplatz sparender und effizienter in der Laufzeit. ! Vollständiger Graph (alle Knotenpaare sind durch eine Kante verbunden) ist dichtbesetzt. ! Manhattan-Graph (Graph für Mahattan mit Kreuzungen als Knoten und Strassen als Kanten) ist dünnbesetzt. ! Es gilt: |E| = |V|*(|V|-1)/2 = O(|V|2) . ! Es gilt: |E| ≈ 2*|V| = O(|V|) . Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-27 Implementierungshinweise für Java: indizierter Zugriff (1) ! Knoten sind durchnummeriert: 0, 1, 2, ..., n-1 ! Nehme Knotennummer als Index eines Feldes ! Adjazenzmatrizen lassen sich als einfache zweidimensionale Felder realisieren: int n = 10; // Anzahl der Knoten int [ ][ ] adjcencyMatrix = new int[n][n]; adjcencyMatrix[1][2] = 1; // Kante von 1 nach 2 eintragen ! Adjazenzlisten lassen sich als Feld von Listen realisieren: int n = 10; // Anzahl der Knoten List<List<Integer>> adjacencyList = new ArrayList<>(n); for (int i = 0; i < n; i++) adjacencyList.add( new LinkedList<>() ); adjacencyList.get(1).add(2); // Kante von 1 nach 2 eintragen adjacencyList[0] Statt die adjazenten Knoten in Listen zu speichern, ließen sich sich auch Sets (bei ungewichteten Graphen) oder Maps (bei gewichteten Graphen) einsetzen. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen [1] 2 ... [8] [9] SS 2017 3-28 Implementierungshinweise für Java: indizierter Zugriff (2) ! In der Praxis kommt es häufig vor, dass die Graph-Knoten nicht von 0 bis n-1 nummeriert sind, sondern beispielsweise als Strings gegeben sind. Stuttgart 90 km Ulm 150km München ! Die Schlüssel können dann mit Hilfe einer Map (TreeMap oder HashMap) in eine interne Nummerierung von 0 bis n-1 umgerechnet werden: - Stuttgart -> 0 - Ulm -> 1 - München -> 2 ! Mittels der internen Nummerierung lässt sich dann eines der indizierten Zugriffsverfahren von der vorhergehenden Seite verwenden. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-29 Implementierungshinweise für Java: Zugriff mit einer Map (1) ! Eine Map ordnet jedem Knoten eine Adjazenzliste zu. ! Die Adjazenzlisten sind ebenfalls Maps und ordnen jedem adjazenten Knoten ein Gewicht zu (bei ungewichteten Graphen könnten entweder die Gewichte auf 0 oder 1 gesetzt werden oder aber eine Set verwendet werden). ! Knoten können vom Typ Integer (Knoten sind nummeriert) oder von einem anderen Typ sein (z.B. String). ! Maps können entweder HashMaps oder TreeMaps sein (siehe Beispiel nächste Seite). Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-30 Implementierungshinweise für Java: Zugriff mit einer Map (2) ! Beispiel: int n = 7; // Anzahl Knoten Map<Integer, Map<Integer, Integer>> adjacencyList = new TreeMap<>(); for (int i = 0; i < n; i++) adjacencyList.put(i, new TreeMap<>()); adjacencyList.get(4).put(3, 43); // Kante von 4 nach 3 mit Gewicht 43 eintragen adjacencyList.get(4).put(6, 46); // Kante von 4 nach 6 mit Gewicht 46 eintragen adjacencyList.get(4).put(2, 42); // Kante von 4 nach 2 mit Gewicht 42 eintragen 5 6 4 3 1 3 6 46 2 42 43 ! Graph als TreeMap ! Mit Knoten als Schlüssel und Adjazenzliste als Nutzdaten ! Die Adjazenzlisten sind ebenfalls TreeMaps. 2 0 Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-31 Darstellung der Algorithmen in Pseudo-Code (1) ! Um einfach und von der konkreten Datenstruktur für Graphen unabhängig zu bleiben, wird bei der Formulierung der Algorithmen folgender PseudoCode benutzt: ! Ungerichtete und gerichtete Graphen: for ( jeden adjazenten Knoten w von v ) { … } for ( jeden Knoten v ) { … } for ( jede Kante (v,w) ) { … } ! Ungerichtete Graphen: for ( jeden Nachbarn w von v ) { … } ! Gerichtete Graphen: for ( jeden Nachfolger w von v ) { … } Prof. Dr. O. Bittel, HTWG Konstanz for ( jeden Vorgänger w von v ) { … } Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-32 Darstellung der Algorithmen in Pseudo-Code (2) ! Die hier in Pseudo-Code dargestellten Schleifen lassen sich in Java mit den besprochenen Datenstrukturen (Adjazenzmatrix, Adjazenzliste und Kantenliste) einfach realisieren. Pseudo-Code: Java: ... for ( jeden Knoten v ) { … } // Map-basierte Adjazenzliste für ungewichteten Graphen: Map<Vertex, Set<Vertex>> adjacencyList = new TreeMap<>(); for ( jeden Nachfolger w von v ) { … } // adjacencyList wie oben definiert Prof. Dr. O. Bittel, HTWG Konstanz for (Vertex v : adjacencyList.keySet() ) { ... } for (Vertex w : adjacencyList.get(v) ) { ... } Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-33 Darstellung der Algorithmen in Pseudo-Code (3) ! Bei der Formulierung der Algorithmen sollen auch die Datenstrukturen möglichst einfach gehalten werden. ! Oft muss für jeden Knoten eine Information gespeichert werden. ! Im Pseudo-Code werden einfachheitshalber Felder und Indizierung gewählt. Sind die Knoten durchnummeriert, dann kann der Pseudo-Code direkt in Java übernommen werden. Bei einem beliebigen Knotentyp kann eine Map (bzw. Set für ein boolsches Feld) gewählt werden. Pseudo-Code: Java: int[ ] inDegree; ... for ( jeden Knoten v ) { inDegree[v] = Anzahl der Vorgänger; if (inDegree[v] == 0) ... } Map<Vertex, Integer> inDegree = new ...; ... for ( jeden Knoten v ) { inDegree.put(v, Anzahl der Vorgänger); if (inDegree.get(v) == 0) ... } for ( jeden Nachfolger w von v ) { if (--inDegree[w] == 0) ... } for ( jeden Nachfolger w von v ) { inDegree.put(w, inDegree.get(w)-1); if (inDegree.get(w) == 0) ... } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-34 Teil III: Graphenalgorithmen ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen - Tiefensuche - Breitensuche ! ! ! ! ! Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume Flüsse in Netzwerken Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-35 Rekursive Tiefensuche (depth-first search) void visitDF(Vertex v, Graph g) { boolean[ ] besucht; besucht mit false initialisieren; visitDF(v, g, besucht); visitDF startet von Knoten v eine Tiefensuche im Graph g. Mit dem boolschen Feld besucht wird gespeichert, ob ein Knoten bereits besucht worden ist. } Wichtig zur Vermeidung von Endlosschleifen. void visitDF(Vertex v, Graph g, boolean[ ] besucht) { besucht[v] = true; visitDF besucht Knoten v im Graphen g, wobei die bereits besuchten Knoten im Feld besucht abgespeichert sind. visitDF ist rekursiv. // Bearbeite v: println(v); for ( jeden adjazenten Knoten w von v ) if ( ! besucht[w]) // w noch nicht besucht visitDF(w, g, besucht); } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-36 Beispiel für rekursive Tiefensuche ! Tiefensuche mit Start bei Knoten 1 1 2 Besuchsreihenfolge: Aufrufstruktur 1, 0, 4, 2, 3, 5 1 0 0 1 5 4 3 0 2 4 4 1 2 3 5 1 3 4 4 2 4 ! die Nachbarn eines Knoten werden in lexikographischer Reihenfolge durchlaufen. ! Bereits besuchte Nachbarknoten sind durch eine gestrichelte Kante verbunden und gehören nicht zur Aufrufstruktur. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-37 Tiefensuchbaum ! Die Aufrufstruktur bildet mit den besuchten Knoten den sogenannten Tiefensuchbaum. 1 2 Tiefensuchbaum Aufrufstruktur 1 0 0 1 5 4 3 0 4 0 4 4 1 2 3 5 1 3 4 4 2 Prof. Dr. O. Bittel, HTWG Konstanz 2 1 2 5 3 4 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-38 Iterative Tiefensuche mit einem Keller void visitDF(Vertex v, Graph g) { boolean[ ] besucht; besucht mit false initialisieren; visitDF(v, g, besucht); } visitDF startet von Knoten v eine Tiefensuche. visitDF besucht Knoten v im Graphen g, wobei die bereits besuchten Knoten im Feld besucht abgespeichert werden. void visitDF(Vertex v, Graph g, boolean[ ] besucht) { Stack<Vertex> stk; stk.push(v); Die Tiefensuche wird erreicht durch die LIFOOrganisation des Kellers. while( ! stk.empty() ) { v = stk.pop(); if ( besucht[v] ) continue; Um die gleiche Besuchsreihenfolge wie bei der rekursiven Funktion zu erreichen, müssen in der forSchleife die Nachbarn in umgekehrter Reihenfolge bearbeitet werden. besucht[v] = true; // Bearbeite v: println(v); } for ( jeden adjazenten Knoten w von v ) if ( ! besucht[w] ) stk.push(w); } Prof. Dr. O. Bittel, HTWG Konstanz Im Keller stk werden alle Knoten verwaltet, die als nächstes zu besuchen sind. Beachte: Gleiche Knoten können mehrfach eingekellert werden. Das ließe sich durch eine weitere Knotenmarkierung verhindern: - nicht besucht, - nicht besucht und im Keller, - besucht. Allerdings würde sich eine andere Besuchsreihenfolge wie bei der rekursiven Funktion ergeben. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-39 Breitensuche - Beispiel 2 1 0 5 2 4 3 0 4 1 5 3 1) Beginne mit Knoten 1. 2) Besuche alle Knoten, die über genau eine Kante von Knoten 1 erreichbar sind. 2 Besuchsreihenfolge: 0 5 1 1, 0, 2, 4, 3, 5 4 3 3) Besuche alle Knoten, die über genau 2 Kanten von Knoten 1 erreichbar sind. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-40 Breitensuche (breadth-first search) mit einer Schlange void visitBF(Vertex v, Graph g) { boolean[ ] besucht; besucht mit false initialisieren; visitBF(v, g, besucht); } visitBF startet von Knoten v eine Breitensuche im Graphen g. void visitBF(Vertex v, Graph g, boolean[ ] besucht) { Queue<Vertex> q; q.add(v); while( ! q.empty() ) { v = q.remove(); if ( besucht[v] ) continue; visitBF besucht Knoten v im Graphen g, wobei die bereits besuchten Knoten im Feld besucht abgespeichert werden. In der Schlange q werden alle Knoten verwaltet, die als nächstes zu besuchen sind. Die Breitensuche wird erreicht durch die FIFOOrganisation der Schlange besucht[v] = true; // Bearbeite v: println(v); } for ( jeden adjazenten Knoten w von v ) if ( ! besucht[w] ) q.add(w); } Prof. Dr. O. Bittel, HTWG Konstanz Beachte: Gleiche Knoten können mehrfach in die Schlange eingereiht werden. Das ließe sich durch eine weitere Knotenmarkierung verhindern: - nicht besucht, - nicht besucht und in der Schlange, - besucht. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-41 Tiefen- bzw. Breitensuche bei mehreren Zusammenhangskomponenten ! Hat ein Graph mehrere (strenge) Zusammenhangskomponenten, dann ist es nicht garantiert, dass es von jedem Knoten v einen Weg zu jedem anderen Knoten w gibt. ! Sollen alle Knoten besucht werden, dann muss visitDF (Tiefensuche) bzw. visitBF (Breitensuche) mehrere Male mit allen noch nicht besuchten Knoten als Startknoten aufgerufen werden. void visitAllNodes() { boolean[ ] besucht; besucht mit false initialisieren; for (jeden Knoten v) if (! besucht[v] ) visit(v, g, besucht); } visit kann visitDF oder visitBF sein. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-42 Tiefensuchwald bei Tiefensuche mit mehreren Zusammenhangskomponenten ! Bei der Tiefensuche in einem Graphen mit mehreren Zusammenhangskomponenten entsteht dann der sogenannte Tiefensuchwald. void visitDFAllNodes() { Gerichteter Graph (mit mehreren strengen Zusammenhangskomponenten): boolean[ ] besucht; besucht mit false initialisieren; 1 3 2 for (jeden Knoten v) if (! besucht[v] ) visitDF(v, g, besucht); 5 4 6 } Tiefensuchwald (Knoten werden nach ihrer Nummerierung aufsteigend besucht): 3 1 2 4 6 5 Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-43 Analyse ! Hier: Analyse der Tiefensuche über den kompletten Graphen. Bei der Breitensuche ergibt sich dieselbe Komplexität. void visitDFAllNodes() { boolean[ ] besucht; besucht mit false initialisieren; for (jeden Knoten v) if (! besucht[v] ) visitDF(v, g, besucht); ! Jeder Knoten wird genau 1-mal besucht. ! Jede Kante wird genau 2-mal bei ungerichteten Graphen bzw. genau 1-mal bei gerichteten Graphen in einer der for-Schleifen besucht. Verwendet man die Adjazenzlistendarstellung, ist damit der Aufwand für den Durchlauf aller for-Schleifen höchtens O(|E|). ! Damit ergibt sich mit Adjazenzlistendarstellung insgesamt: } void visitDF(Vertex v, Graph g, boolean[ ] besucht) { besucht[v] = true; // Bearbeite v: println(v); for ( jeden adjazenten Knoten w von v ) if ( ! besucht[w]) // w noch nicht besucht visitDF(w, g, besucht); } T = O(|V| + |E|) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-44 Teil III: Graphenalgorithmen ! ! ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume Flüsse in Netzwerken Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-45 Definition topologische Sortierung ! Eine Folge aller Knoten eines gerichteten Graphen G v0, v1, v2, …, vn-1 heißt topologische Sortierung, falls für alle Knoten u, v folgende Bedingung gilt: falls es einen Weg von u nach v gibt, dann steht in der Folge u links von v. ! Beispiel: 4 1 7 6 3 2 5 topologisch sortiert: 2 1 3 5 4 6 7 ! Anschaulich: Unter Einhaltung der Adjazenzbeziehung lässt sich der Graph so auf eine Linie „verbiegen“, dass die Pfeile nur nach rechts gehen. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-46 Eigenschaften und Anwendungen Eigenschaften: ! Falls der Graph einen Zyklus enthält, dann existiert keine topologische Sortierung. 1 4 2 3 ! Falls ein Graph topologisch sortiert werden kann, dann ist im allgemeinen die topologische Sortierung nicht eindeutig. Beispiele für Anwendungen: ! Stelle fest, ob es eine Durchführungsreihenfolge für die Aktivitäten oder auch Prozesse in einem Vorranggraphen gibt. ! Prüfe, ob Vererbungsgraph oder include-Graph zyklenfrei ist. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-47 Algorithmus für topologische Sortierung topSort liefert zu einem gerichteten Graphen g eine topologische Sortierung. List<Vertex> topSort(DiGraph g) { List<Vertex> ts; // topologisch sortierte Folge int[ ] inDegree; // Anz. noch nicht besuchter Vorgänger Queue<Vertex> q; for (jeden Knoten v) { inDegree[v] = Anzahl der Vorgänger; if (inDegree[v] == 0) q.add(v); } while (! q.empty() ) { v = q.remove(); ts.add(v); for ( jeden Nachfolger w von v ) if (--inDegree[w] == 0) q.add(w); } } if (ts.size() != Anzahl Knoten in g) return null; // Graph zyklisch; else return ts; Prof. Dr. O. Bittel, HTWG Konstanz Idee: ! Speichere für jeden Knoten v: inDegree[v] = Anzahl der noch nicht besuchten Vorgänger. ! Speichere alle noch nicht besuchten Knoten v, für die InDegree[v] == 0 gilt, in einer Schlange q. q enthält also alle Knoten, die als nächstes besucht werden dürfen. ! Besuche als nächstes den vordersten Knoten in der Schlange q und entferne ihn aus q ! Topologische sortierte Folge wird in Liste ts abgespeichert. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-48 Analyse ! Für jeden Knoten w kann inDegree[w] höchstens einmal gleich 0 werden. Damit kommen in die Queue q höchstens |V| Knoten. Die while-Schleife läuft damit höchstens |V|-mal. List<Vertex> topSort(DiGraph g) { List<Vertex> ts; // topologisch sortierte Folge int[ ] inDegree; // Anz. noch nicht besuchter Vorgänger Queue<Vertex> q; for (jeden Knoten v) { inDegree[v] = Anzahl der Vorgänger; if (inDegree[v] == 0) q.add(v); } ! Jede Kante wird höchstens einmal in einer der for-Schleifen besucht. Verwendet man die Adjazenzlistendarstellung, ist damit der Aufwand für den Durchlauf aller for-Schleifen höchtens O(|E|). while (! q.empty() ) { v = q.remove(); ts.add(v); for ( jeden Nachfolger w von v ) if (--inDegree[w] == 0) q.add(w); } ! Damit ergibt sich mit Adjazenzlistendarstellung insgesamt: T = O(|V| + |E|) } Prof. Dr. O. Bittel, HTWG Konstanz if (ts.size() != Anzahl Knoten in g) return null; // Graph zyklisch; else return ts; Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-49 Teil III: Graphenalgorithmen ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege - - - - - - Problemstellung UngewichteteGraphenunderweiterteBreitensuche DistanzgraphenundDijkstra-Algorithmus GewichteteDigraphenundMoore-Ford-Algorithmus NetzpläneunderweitertetopologischeSorCerung AllekürzesteWegeingewichtetenDigraphenmitFloyd-Algorithmus ! Minimal aufspannende Bäume ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-50 Problemstellung kürzeste Wege (1) ! Im allgemeinen gibt es zwischen zwei Knoten u und v mehrere Wege. ! Ein Weg von u nach v heißt kürzester Weg, falls der Weg minimale Länge hat. ! Weglänge bei gewichtetem Graph ist Summe der Kantengewichte. ! Weglänge bei ungewichtetem Graph ist Anzahl der Kanten. ! Die Distanz d(u,v) zwischen u und v wird als die Länge eines kürzesten Weges definiert. Falls es keinen Weg von u nach v gibt, ist d(u,v) = ∞. 1 6 7 1 8 8 3 1 3 6 2 0 1 2 4 Prof. Dr. O. Bittel, HTWG Konstanz 2 Weg von 4 nach 7 mit Länge 15. 4 7 Kürzester Weg von 4 nach 7 mit Länge 2. Damit ist Distanz d(4,7) = 2. 1 1 4 5 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-51 Problemstellung kürzeste Wege (2) ! Länge eines Weges muss nicht die wörtliche Bedeutung haben. ! Die Gewichte (Kosten) der Kanten und damit die Weglängen können in Abhängigkeit von der Anwendung ganz unterschiedliche Bedeutungen haben. Beispiele: - Streckenlänge - Zeitspannen - Kosten - Profit: Gewinn/Verlust (Gewichte können positiv und negativ sein) - Wahrscheinlichkeiten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-52 Verschiedene Problemvarianten (1) Single pair shortest path: Kürzeste Wege zwischen zwei Knoten: (2) Single-source shortest-path problem: Kürzeste Wege zwischen einem Knoten und allen anderen Knoten (3) All pairs shortest path: Kürzeste Wege zwischen allen Knotenpaaren. ! Für Problem (1) kennt man keine bessere Lösung, als einen Algorithmus für Lösung (2) zu nehmen, der abgebrochen wird, sobald der Zielknoten erreicht wird. ! Problem (3) kann auf Problem (2) zurückgeführt werden. Bei dicht besetzten Graphen (d.h. viele Kanten) gibt es jedoch eine effizientere Lösung, die zudem auch negative Kantengewichte zulässt. ! Problemstellungen lassen sich für verschiedene Graphtypen (gerichtet oder ungerichtet, gewichtet oder ungewichtet) algorithmisch unterschiedlich lösen. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-53 Verschiedene Algorithmen Problemvariante Algorithmus A Single-source shortest-path für ungewichtete Graphen Erweiterte Breitensuche B Single-source shortest-path für Distanz-Graphen (nur positive Gewichte) Algorithmus von Dijkstra; A*-Verfahren C Single-source shortest-path für gewichtete Digraphen (auch negative Gewichten) Algorithmus von Moore und Ford D Single-source shortest-path für gewichtete, azyklische Digraphen mit einem Start- und einem Endknoten (Netzpläne) Erweiterte topologische Sortierung E All pairs shortest path für gewichtete Digraphen (auch negative Gewichte) Algorithmus von Floyd ! A ist ein Spezialfall von B. Wendet man den Dijkstra-Algorithmus auf Graphen mit Kantengewichten 1 an, ergibt sich eine Breitensuche. ! D ist ein Spezialfall von C. Der auf Variante D zugeschnittene Algorithmus ist jedoch wesentlich effizienter als der Algorithmus für Variante C. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-54 Teil III: Graphenalgorithmen ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege - - - - - - Problemstellung UngewichteteGraphenunderweiterteBreitensuche DistanzgraphenundDijkstra-Algorithmus GewichteteDigraphenundMoore-Ford-Algorithmus NetzpläneunderweitertetopologischeSorCerung AllekürzesteWegeingewichtetenDigraphenmitFloyd-Algorithmus ! Minimal aufspannende Bäume ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-55 Erweiterte Breitensuche ! Single-source shortest-path: Gesucht sind alle kürzesten Wege von einem Startknoten s zu allen anderen Knoten. ! Beginne bei s und durchlaufe Graph mit Breitensuche. ! Speichere für jeden besuchten Knoten v die Distanz d[v] zu Startknoten s. ! Speichere Vorgängerknoten p[v] im kürzesten Weg nach v. p stellt ein Baum mit Wurzel s dar. ! Der kürzeste Weg von s nach v ergibt sich in umgekehrter Reihenfolge: v, p[v], p[p[v]], …, p[…p[p[v]]…] = s Startknoten s = 4 Beispiel: 0 0 1 4 3 2 1 2 3 1 4 6 5 0 7 7 (1) Prof. Dr. O. Bittel, HTWG Konstanz 4 6 5 2 6 5 7 (2) (3) 3 v 0 1 2 3 4 5 6 7 d[v] ∞ ∞ ∞ ∞ 0 ∞ ∞ ∞ p[v] - - - - - - - - (1) v 0 d[v] ∞ p[v] - 1 2 3 4 5 6 7 1 1 ∞ 0 1 1 ∞ 4 4 - - 4 4 - (2) v 0 d[v] 2 p[v] 1 1 2 3 4 5 6 7 1 1 2 0 1 1 2 4 4 2 - 4 4 5 (3) Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-56 Algorithmus für erweiterte Breitensuche Eingabe: Startknoten s und ungewichteter Graph G Ausgabe: Distanzfeld d und Vorgängerfeld p void shortestPath (Vertex s, Graph G, int[ ] d, Vertex[ ] p) { for ( jeden Knoten v ) { d[v] = ∞; p[v] = undef; } d[s] = 0; // Distanz für Startknoten 4 3 2 v 0 1 2 3 4 5 6 7 d[v] ∞ ∞ ∞ ∞ 0 ∞ ∞ ∞ p[v] - - - - - - - - 6 7 0 1 4 while (! q.empty() ) { v = q.remove(); for ( jeden adjazenten Knoten w von v ) { if (d[w] == ∞) { // w noch nicht registriert d[w] = d[v] + 1; p[w] = v; q.add(w); } } } Prof. Dr. O. Bittel, HTWG Konstanz 1 5 Queue<Vertex> q; q.add(s); } 0 2 6 5 7 3 v 0 d[v] ∞ p[v] - 1 2 3 4 5 6 7 1 1 ∞ 0 1 1 ∞ 4 4 - - 4 4 - 0 1 4 5 7 2 6 3 v 0 d[v] 2 p[v] 1 Algorithmen und Datenstrukuren – Graphenalgorithmen 1 2 3 4 5 6 7 1 1 2 0 1 1 2 4 4 2 - 4 4 5 SS 2017 3-57 Analyse der Breitensuche ! Für jeden Knoten w kann d[w] höchstens einmal von ∞ auf einen endlichen Wert gesetzt werden. Damit kommen in die Queue q höchstens |V| Knoten. Die while-Schleife läuft damit höchstens |V|-mal. ! Jede Kantenrichtung wird höchstens einmal in einer der for-Schleifen besucht. Verwendet man die Adjazenzlistendarstellung, ist damit der Aufwand für den Durchlauf aller for-Schleifen gleich O(|E|). ! Damit ergibt sich mit Adjazenzlistendarstellung insgesamt: T = O(|V| + |E|) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-58 Teil III: Graphenalgorithmen ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege - - - - - - Problemstellung UngewichteteGraphenunderweiterteBreitensuche DistanzgraphenundDijkstra-Algorithmus GewichteteDigraphenundMoore-Ford-Algorithmus NetzpläneunderweitertetopologischeSorCerung AllekürzesteWegeingewichtetenDigraphenmitFloyd-Algorithmus ! Minimal aufspannende Bäume ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-59 Algorithmus von Dijkstra – Idee (1) ! Single-source shortest-path: Gesucht sind alle kürzesten Wege von einem Startknoten s zu allen anderen Knoten im einem Distanz-Graphen (Gewichte sind positiv). ! Ähnlich wie bei der Breitensuche werden alle Knoten, die als nächstes besucht werden können, in einer Liste K (Kandidatenliste) gehalten. ! Aus der Kandidatenliste wird immer der Knoten mit der kleinsten Distanz als nächster besucht. 1 6 7 1 8 8 3 1 2 0 2 4 3 2 6 4 1 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 6 2 2 ∞ ∞ ∞ ∞ p[v] - 0 0 0 0 - - - 7 1 Kandidatenliste Bereits besucht 2 d[v] Distanz zwischen v und Startknoten s = 0 4 Prof. Dr. O. Bittel, HTWG Konstanz Als nächstes wird Knoten 1 mit Distanzwert 1 besucht. 5 p[v] Vorgängerknoten im kürzesten Weg nach v Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-60 Algorithmus von Dijkstra – Idee (2) ! Wird ein neuer Knoten v besucht, dann kommen eventuell neue Nachbarknoten zur Kandidatenliste. ! Bei allen Nachbarknoten wird geprüft, ob ein Weg über v einen kürzeren Weg ergibt. 1 6 7 1 1 6 8 7 8 3 1 2 0 2 4 3 2 6 4 1 1 8 8 3 1 7 2 Knoten 1 1 0 2 wird besucht 5 4 5 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 6 2 2 ∞ ∞ ∞ ∞ p[v] - 0 0 0 0 - - - - 4 3 2 d(2) = 5 6 4 1 7 Knoten 3 1 wird besucht 5 … 4 5 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 5 2 2 6 ∞ ∞ ∞ p[v] - 0 1 0 0 1 - - - Knoten 5 ist zur Kandidatenliste neu dazu gekommen. Der Distanzwert von Knoten 2 hat sich von 6 auf 5 verbessert, da der Weg über Knoten 1 kürzer ist als der bisherige. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-61 Algorithmus von Dijkstra Eingabe: Startknoten s und Distanzgraph G void shortestPath (Vertex s, DistanzGraph G, int[ ] d, Vertex[ ] p ) { (1) (2) (3) (4) } kl = ∅; // Kandidatenliste for (jeden Knoten v) { d[v] = ∞; p[v] = undef; } d[s] = 0; // Startknoten kl.insert(s); Ausgabe: Distanzfeld d und Vorgängerfeld p ! In der Kandidatenliste sind alle Knoten abgespeichert, die als nächstes besucht werden können. ! Die Zugriffsoperationen auf die Kandidatenliste sind rot gekennzeichnet. while (! kl.empty() ) { ! Mögliche Implementierungen werden auf lösche Knoten v aus kl mit d[v] minimal; der nächsten Folie diskutiert. for ( jeden adjazenten Knoten w von v ) { if (d[w] == ∞) // w noch nicht besucht und nicht in Kandidatenliste kl.insert(w); if (d[v] + c(v,w) < d[w]) { Distanzwert für w verbessert sich. p[w] = v; Kürzester Weg nach w geht nun über v. d[w] = d[v] + c(v,w); } Falls in Zeile (3) w neu in kl eingefügt } wurde, wird d[w] von ∞ zu einem endlichen } Wert verbessert. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-62 Eigenschaften ! Die d-Werte der besuchten Knoten in Zeile (2) sind monoton steigend. Begründung: die d-Werte der anderen Kandidaten können nicht kleiner sein als der d-Wert von v. In Zeile (4) kann auch kein kleinerer d-Wert als der d-Wert von v entstehen, da die Gewichte der Kanten c(v,w) positiv sind. while (! kl.empty() ) { Knoten v wird besucht. (2) lösche Knoten v aus kl mit d[v] minimal; for ( jeden adjazenten Knoten w von v ) { if (d[w] == ∞) // w noch nicht besucht und nicht in Kandidatenliste (3) kl.insert(w); if (d[v] + c(v,w) < d[w]) { p[w] = v; (4) d[w] = d[v] + c(v,w); } } } ! Daher: sobald ein Knoten v besucht wird (Zeile (2)), ist sein kürzester Weg bekannt. ! Wird ein kürzester Weg von einem Startknoten s zu einem bestimmten Zielknoten z gesucht, dann kann das Verfahren abgebrochen werden, sobald v = z besucht wird. ! Ein Knoten wird höchstens einmal in die Prioritätsliste eingefügt. Daher kl.size() ≤ |V|. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-63 Analyse (1) Kandidatenliste als Index-Heap ! Speichere Kandidaten in einem Index-Heap (siehe Kap. 2, Prioritätslisten), wobei als Priorität der Distanzwert aus Feld d genommen wird. ! Ein Index-Heap bietet eine effiziente Realisierung folgender Operationen an: - insert(v, d) in O(log|V|): fügt Element v mit Priorität d ein. Aufruf in (1) und (3). - v = delMin() in O(log|V|): löscht Element mit kleinster Priorität. Aufruf in (2). - change(v, dNeu) in O(log|V|): ändert den Vorrangwert des Elements v in dNeu. Aufruf in (4). Analyse des Dijkstra-Algorithmus mit Kandidatenliste als Index-Heap ! Jede Kantenrichtung wird genau einmal in einer der for-Schleifen betrachtet. Da (3) und (4) in O(log|V|) geht, ist für alle for-Schleifen insgesamt O(|E| log|V|) notwendig. ! Hinzu kommt höchstens |V|-mal die Ausführung von (2), was O(|V| log|V|) bedeutet. ! Insgesamt: T = O(|E| log|V|) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-64 Analyse (2) Kandidatenliste als unsortiertes Feld ! Speichere Kandidaten in einem unsortierten Feld. ! Damit benötigt die Operation in Zeile (2) (Minimum bestimmen) O(|V|) und die Operationen in (1), (3) und (4) benötigen O(1). Analyse des Dijkstra-Algorithmus mit Kandidatenliste als unsortiertes Feld ! Die while-Schleife wird höchstens |V|-mal durchgeführt. ! Für (2) ist O(|V|) notwendig. ! Jede Kantenrichtung wird genau einmal in einer der for-Schleifen betrachtet. Da (3) und (4) in O(1) geht, ist für alle for-Schleifen insgesamt O(|E|) notwendig. ! Insgesamt: T = O(|V|2) Fazit: ! Ist der Graph dicht besetzt, d.h. |E| = O(|V|2), dann ist die Variante mit Kandidatenliste als unsortiertes Feld besser. ! Ist der Graph dünn besetzt, d.h. |E| = O(|V|), dann ist die Variante mit Kandidatenliste als Prioritätsliste besser. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-65 Beispiel zu Dijkstra-Algorithmus (1) 1 6 7 1 1 6 8 7 1 2 0 2 4 3 2 6 4 1 8 8 8 3 1 3 1 7 2 Knoten 0 1 2 wird besucht 5 4 0 v 0 1 2 3 4 5 6 7 8 d[v] 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ p[v] - - - - - - - - - 1 6 7 2 6 4 1 7 1 5 4 4 5 3 5 1 8 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 6 2 2 ∞ ∞ ∞ ∞ p[v] - 0 0 0 0 - - - - 8 Knoten 1 wird besucht 3 1 2 0 2 4 Prof. Dr. O. Bittel, HTWG Konstanz 3 6 1 2 Knoten 3 4 7 wird besucht 1 5 4 5 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 5 2 2 6 ∞ ∞ ∞ p[v] - 0 1 0 0 1 - - - Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-66 Beispiel zu Dijkstra-Algorithmus (2) 1 6 7 1 1 6 8 7 2 1 0 2 4 3 2 6 4 1 8 8 8 3 1 3 Knoten 4 7 2 1 wird besucht 1 0 2 5 4 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 5 2 2 6 ∞ ∞ ∞ p[v] - 0 1 0 0 1 - - - 1 6 7 1 8 2 6 4 1 7 1 5 4 4 5 3 5 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 5 2 2 6 3 ∞ ∞ p[v] - 0 1 0 0 1 4 - - 8 3 Knoten 6 wird besucht 2 1 0 2 4 Prof. Dr. O. Bittel, HTWG Konstanz 3 2 6 4 1 Knoten 7 7 wird besucht 1 5 4 5 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 5 2 2 6 3 4 ∞ p[v] - 0 1 0 0 1 4 6 - Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-67 Beispiel zu Dijkstra-Algorithmus (3) 1 6 7 1 8 8 3 2 1 0 2 4 3 2 6 4 1 7 1 Kürzeste Wege für Startknoten s = 0 sind nun bekannt für die Zielknoten: 1, 3, 4, 6, 7. Kürzester Weg von 0 nach 7 ist 0, 4, 6, 7 mit Distanz d = 4. 5 4 5 v 0 1 2 3 4 5 6 7 8 d[v] 0 1 5 2 2 6 3 4 5 p[v] - 0 1 0 0 1 4 6 7 Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-68 A*-Algorithmus: Verbesserung des Dijkstra-Algorithmus ! Ist der kürzeste Weg zu genau einem Zielknoten z gesucht und lässt sich die Distanz von einem Knoten v zum Zielknoten z durch eine Schätzfunktion h(v,z) abschätzen, dann lässt sich der Algorithmus von Dijkstra optimieren. ! Statt den Knoten v mit d[v] minimal als nächstes zu besuchen zu besuchen, wird der Knoten v besucht mit d[v] + h(v,z) minimal. ! Damit werden Knoten v bevorzugt, die näher zum Zielknoten liegen. ! Sind die Knoten des Graphen Punkte in der Euklidischen Ebene und die Gewichte gleich den Euklidischen Abständen (Luftlinienabstand), dann lässt sich als Schätzfunktion h(v,z) der Euklidische Abstand zwischen v und z wählen. z … h(1,z) h(2,z) … 1 … 1 s Prof. Dr. O. Bittel, HTWG Konstanz 4 2 Wird der kürzeste Weg von s nach z gesucht, wird zuerst Knoten 2 besucht, weil dieser näher zu z liegt. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-69 A*- Algorithmus boolean shortestPath (Vertex s, Vertex z, DistanzGraph G, int[ ] d, Vertex[ ] p ) { kl = ∅; // Kandidatenliste for (jeden Knoten v) { d[v] = ∞; p[v] = undef; } d[s] = 0; // Startknoten kl.insert(s); Ausgabe: Distanzfeld d und Vorgängerfeld p Eingabe: Startknoten s, Zielknoten z und Distanzgraph G while (! kl.empty() ) { lösche Knoten v aus kl mit d[v] + h(v,z) minimal; Änderungen gegenüber if (v == z) // Zielknoten z erreicht dem Diskstra-Algorithmus return true; sind blau gekennzeichnet. for ( jeden adjazenten Knoten w von v ) { if (d[w] == ∞) // w noch nicht besucht und nicht in Kandidatenliste kl.insert(w); if (d[v] + c(v,w) < d[w]) { p[w] = v; d[w] = d[v] + c(v,w); } } } return false; } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-70 Korrektheit des A*- Algorithmus ! Das A*-Verfahren liefert immer einen kürzesten Weg, falls der Zielknoten z erreichbar ist und die Schätzfunktion h folgende Eigenschaften erfüllt: (1) h ist zulässig: 0 ≤ h(u,v) ≤ d(u,v) für alle Knoten u, v (h ist positiv und unterschätzt die Distanz d) (2) h ist monoton: h(u,v) ≤ c(u,w) + h(w,v) für alle Knoten u, v, w v h(u,v) u c(u,w) h(w,v) w ! Die Euklidische Abstandsfunktion erfüllt die beiden oberen Bedingungen. ! Die Schätzfunktion h(u,v) = 0 erfüllt trivialerweise beide Bedingungen. Man erhält dann genau den Algorithmus von Dijkstra. ! In der Literatur wird die Schätzfunktion auch Heuristik genannt (Heuristik = Strategie zur Lösungssuche) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-71 Vergleich A*- und Dijkstra-Algorithmus Dijkstra-Algorithmus A*-Algorithmus 5 7 4 7 1 5 6 3 2 1 4 5 2 s 3 3 8 z 1 2 s 3 z 4 4 ! Schätzfunktion h und Kantengewicht c sind als Euklidischer Abstand definiert. ! Im Beispiel wird kürzester Weg von Startknoten s nach Zielknoten z berechnet. ! Reihenfoge der besuchten Knoten in rot. ! Berechnete kürzeste Wege sind blau markiert. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-72 Schlechtes Verhalten von A* bei Sackgassen ! A* geht direkt auf das Ziel z zu, läuft aber in eine Sackgasse. z s ! A* besucht nun einen Weg, der sich anfangs vom Ziel entfernt und daher nicht früher betrachtet wurde. s Prof. Dr. O. Bittel, HTWG Konstanz z Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-73 Teil III: Graphenalgorithmen ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege - - - - - - Problemstellung UngewichteteGraphenunderweiterteBreitensuche DistanzgraphenundDijkstra-Algorithmus GewichteteDigraphenundMoore-Ford-Algorithmus NetzpläneunderweitertetopologischeSorCerung AllekürzesteWegeingewichtetenDigraphenmitFloyd-Algorithmus ! Minimal aufspannende Bäume ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-74 Graphen mit negativen Gewichten (1) ! Hat ein Graph auch negative Gewichte, dann ist die Existenz eines kürzesten Weges nicht gesichert, da Zyklen mit negativer Länge existieren können. ! Durch mehrfaches Ablaufen solcher Zyklen kann die Weglänge beliebig klein (negativ) werden. 1 -4 1 u 1 1 1 v Zyklus mit Länge = 1 + 1 – 4 + 1 = -1 Damit kann die Weglänge von u nach v beliebig klein werden. ! Die Eindeutigkeit von einfachen Wegen ist jedoch garantiert (d.h. Knoten dürfen nicht mehrfach auftreten). ! Für die Bestimmung kürzester, einfacher Wege mit beliebigen Gewichten sind bis heute keine effizienten Algorithmen bekannt. ! Daher: Einschränkung auf beliebig gewichtete Digraphen ohne Zyklen negativer Länge. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-75 Graphen mit negativen Gewichten (2) ! Der Algorithmus von Dijkstra arbeitet bei Graphen mit negativ gewichteten Kanten im allgemeinen nicht korrekt: Mit Algorithmus von Dijkstra berechneter Weg mit Länge = 0.8. 2 0.1 s=1 0.1 1 3 1 5 8 -10 1 z=9 -20 1 1 1 4 6 7 Kürzester Weg von s nach z mit Länge = -16. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-76 Algorithmus von Moore und Ford - Idee ! Single-source shortest-path: Gegeben ist ein gewichteter Digraph (Gewichte können negativ sein). Gesucht sind alle kürzesten Wege zwischen einem Startknoten s und allen anderen Knoten, sofern der Graph keine negativen Zyklen enthält. ! Ausgangspunkt ist der Algorithmus von Dijkstra. ! Beim Algorithmus von Dijkstra wird jeder Knoten v höchstens einmal besucht und zwar dann, wenn der kürzeste Weg zu v bekannt ist. ! Kommen aber negative gewichtete Kanten vor, dann muss jeder Knoten im allgemeinen mehrere Male besucht werden und zwar immer dann, wenn sich ein noch kürzerer Weg ergibt. ! Die Reihenfolge, in der die Knoten besucht werden, spielt nun keine Rolle mehr. Es wird daher eine Schlange statt einer Vorrangswarteschlange gewählt. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-77 Algorithmus von Moore und Ford void shortestPath (Vertex s, WeightedDiGraph G, int[ ] d, Vertex[ ] p) { Queue<Vertex> kl = ∅; Ausgabe: Distanzfeld d und Vorgängerfeld p for (jeden Knoten v) { d[v] = ∞; p[v] = undef; } d[s] = 0; // Startknoten kl.add(s); (1) (2) (3) (4) (5) Eingabe: Startknoten s und gewichteter und gerichteter Graph g while (! kl.empty()) { v = kl.remove(); for ( jeden adjazenten Knoten w von v ) { if (d[v] + c(v,w) < d[w]) { p[w] = v; d[w] = d[v] + c(v,w); if (! kl.contains(w) ) kl.add(w); } } } } Prof. Dr. O. Bittel, HTWG Konstanz In der Kandidatenliste sind alle Knoten abgespeichert, die als nächstes besucht werden können. Die Zugriffsoperationen auf die Kandidatenliste sind rot gekennzeichnet. Distanzwert für w verbessert sich. Kürzester Weg nach w geht nun über v. w muss nochmals besucht werden. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-78 Beispiel zu Moore- Ford-Algorithmus 2 1 s=1 2 5 3 5 1 1 1 9 -8 6 Nächster besuchter Knoten ist rot gekennzeichnet. 7 v 1 2 3 4 5 6 7 8 9 d[v] 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ 1 5 1 3 6 2 4 -4 3 3 -3 4 -5 -6 -5 Prof. Dr. O. Bittel, HTWG Konstanz 1 1 1 4 8 -10 v 1 2 3 4 5 6 7 8 9 p[v] - - - - - - - - 1 1 1 2 3 4 8 5 6 6 8 3 7 5 8 Kandiatenliste kl 1 2, 3, 4 3, 4, 8 4, 8, 5 8, 5, 6 5, 6, 9 6, 9, 8 9, 8, 3, 7 3, 7, 9 7, 9, 5 9, 5, 8 8 9 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-79 Analyse des Moore- Ford-Algorithmus ! Enthält der Graph keine Zyklen mit negativer Länge, dann wird jeder Knoten maximal (|V|-1)-mal in die Schlange kl eingefügt (Zeile (5) im Algorithmus). (Begründung siehe [Turau]). ! Wird ein Knoten |V|-mal eingefügt, dann muss es ein Zyklus mit negativer Länge geben und es könnte im Algorithmus eine entsprechende Ausgabe erfolgen. ! Würde jeder Knoten genau einmal in die Schlange kl eingefügt werden, wäre der Aufwand für alle for-Schleifen gerade O(|E|). Denn jede Kante im Graph wird genau einmal in einer der for-Schleifen betrachtetet. (Zeile (5) kl.contains(w) kann mit Hilfe eines Booleschen Felds in O(1) realisiert weden). ! Da nun aber jeder Knoten bis zu (|V|-1)-mal in die Schlange kl eingefügt werden kann, erhält man insgesamt: T = O(|E|*|V|). Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-80 Teil III: Graphenalgorithmen ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege - - - - - - Problemstellung UngewichteteGraphenunderweiterteBreitensuche DistanzgraphenundDijkstra-Algorithmus GewichteteDigraphenundMoore-Ford-Algorithmus NetzpläneunderweitertetopologischeSorCerung AllekürzesteWegeingewichtetenDigraphenmitFloyd-Algorithmus ! Minimal aufspannende Bäume ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-81 Netzpläne ! Netzpläne sind azyklische Digraphen mit genau einem Start und einem Endknoten. ! Die Knoten (außer Start- und Endknoten) stellen oft gewisse Aktivitäten dar und sind mit einem Gewicht (Aufwand wie z.B. Zeitangaben) versehen. ! Kritischer Pfad ist ein Weg vom Start- zum Ziel-Knoten mit maximalen Aufwand. ! Beispiel Projektplan: - Die Knoten sind Tätigkeiten, die in einem Projekt durchgeführt werden müssen. - Die Gewichte geben die Zeitdauer der Aktivitäten an. - Die gerichteten Kanten legen Reihenfolgenbeziehungen fest. - Die Länge eines kritischen Pfades gibt die frühestmögliche Projektfertigstellung an. 4 2 3 1 2 4 6 9 Start 2 0 3 6 3 1 1 5 2 Ende 11 8 2 7 10 Netzplan und kritischer Pfad mit Zeitaufwand 15 Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-82 Bestimmung eines kritischen Pfades ! Werden die an den Knoten notierten Gewichte an die einmündenden Kanten mit umgekehrtem Vorzeichen „verschoben“, erhält man einen gewichteten, azyklischen Digraphen. ! Die Bestimmung des kritischen Pfades lässt sich dann zurückführen auf die Berechnung eines kürzesten Weges vom Start- zum Zielknoten. Start 0 -4 2 -2 -2 -3 1 -2 3 4 -3 -6 -1 5 -2 6 8 7 -1 -1 -2 -2 9 0 Ende 0 11 10 Gewichteter, azyklischer Digraph mit kürzestem Weg der Länge -15. ! Prinzipiell ließe sich der kritische Pfad mit dem Algorithmus von Moore und Ford bestimmen. ! Für den Spezialfall der Netzpläne ist jedoch ein auf toplogischer Sortierung basierender Algorithmus wesentlich effizienter. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-83 Erweiterte topologische Sortierung (1) ! Single-source shortest-path für Netzpläne: Gewichteter, azyklischer Digraph (Gewichte können negativ sein) mit genau einem Startknoten s. Gesucht sind alle kürzeste Wege zwischen s und allen anderen Knoten. ! Besuche die Knoten wie bei der topologischen Sortierung. Dazu werden in der Schlange alle noch nicht besuchten Knoten gehalten, deren Vorgänger bereits besucht worden sind. ! Bei jedem Knotenbesuch werden dann wie beim Algorithmus von Moore und Ford für alle Nachfolgerknoten die Distanzen und der gefundene kürzeste Weg gegebenenfalls aktualisiert. ! Analyse (wie bei der topologischen Sortierung): T = O(|E|+|V|). Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-84 Erweiterte topologische Sortierung (2) Ausgabe: Distanzfeld d und Vorgängerfeld p void allShortestPath (WeightedDiGraph g, int[ ] d, Vertex[ ] p) { int[ ] inDegree; // Anz. noch nicht besuchten Vorgänger Queue<Vertex> q; Eingabe: Gewichteter Digraph G mit genau einem Startknoten. for (jeden Knoten v) { d[v] = ∞; p[v] = undef; inDegree[v] = Anzahl der Vorgänger; if (inDegree[v] == 0) { q.add(v); d[v] = 0; } } int k = 0; while (! q.empty() ) { v = q.remove(); k++; for ( jeden Nachfolger w von v ) { if (d[v] + c(v,w) < d[w]) { p[w] = v; d[w] = d[v] + c(v,w); } if (--inDegree[w] == 0) q.add(w); } } if (q.size() > 1) { println(“Fehler; mehr als ein Startknoten”); return; } Programmteile, um die die topologische Sortierung erweitert wurde, sind blau gekennzeichnet. Prof. Dr. O. Bittel, HTWG Konstanz if (k != n) println( “Fehler; Graph ist zyklisch”); } Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-85 Beispiel zu erweiterten topologischen Sortierung Start 0 -4 2 -2 -2 -3 -2 3 0 1 2 3 4 5 6 7 8 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ -3 -4 -5 -6 -8 -7 -11 -9 -13 5 9 ∞ 10 11 ∞ ∞ -12 -11 -14 -15 -14 -15 -3 -6 -1 1 v d[v] 4 -2 6 8 7 -1 -1 -2 -2 9 0 0 Ende 11 10 v 0 1 2 3 4 5 6 7 8 9 10 11 inDegree 0 1 1 2 1 1 1 1 1 2 2 2 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 Kand.Liste q 0 1, 2 2 3 4, 5 5, 6 6, 7, 8 7, 8 8 9, 10 10 11 ! Nächster besuchter Knoten ist rot gekennzeichnet. ! p-Feld wurde weggelassen Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-86 Teil III: Graphenalgorithmen ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege - - - - - - Problemstellung UngewichteteGraphenunderweiterteBreitensuche DistanzgraphenundDijkstra-Algorithmus GewichteteDigraphenundMoore-Ford-Algorithmus NetzpläneunderweitertetopologischeSorCerung AllekürzesteWegeingewichtetenDigraphenmitFloyd-Algorithmus ! Minimal aufspannende Bäume ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-87 Idee des Floyd-Algorithmus ! All pairs shortest-path: Gegegeben ist ein gewichteter Digraph (Gewichte können negativ sein). Gesucht sind kürzeste Wege zwischen allen Knotenpaaren, sofern der Graph keine negativen Zyklen enthält. ! Der Algorithmus basiert im wesentlichen auf dem Optimalitätsprinzip von Bellman, das für Graphen ohne Zyklen negativer Länge gilt. Für jeden Knoten u auf einem kürzesten Weg von v nach w gilt: d(v,w) = d(v,u) + d(u,w). D.h. Teilwege von kürzesten Wege müssen ebenfalls kürzeste Wege sein. v u w ! Der Floyd-Algorithmus arbeitet nach dem Prinzip des dynamischen Programmierens. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-88 Einschub - dynamisches Programmieren (1) ! Problem: Bei rekursiven Funktionen kann es zu mehrfacher Berechnung des gleichen Funktionswerts kommen. ! Beispiel: Fibonacci-Funktion int fib(int n) { if (n == 0) return 0; else if (n == 1) return 1; else return fib(n-1) + fib(n-2); } Aufrufstruktur für fib(4): fib(4) fib(3) fib(2) fib(1) fib(2) fib(1) fib(1) fib(0) fib(0) ! Bei Aufruf von fib(4) wird 2-mal fib(2), 3-mal fib(1) und 2-mal fib(0) berechnet. ! Bei Aufruf von fib(24) wird 2-mal fib(22), 3-mal fib(21), 5-mal fib(20), 8-mal fib(19), 13-mal fib(18) etc. berechnet. ! Es lässt sich zeigen, dass bei Aufruf von fib(n) insgesamt etwa (2/√5)*φn+1 – 1 Aufrufe stattfinden, wobei φ = (1+√5)/2 ≈ 1.62 (φ wird auch „Goldener Schnitt“ genannt.) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-89 Einschub - dynamisches Programmieren (2) Dynamisches Programmieren: ! Ausgangspunkt: Rekursiv definierte Funktion. ! Berechne rekursive Funktion mit Iteration und Tabelle statt Rekursion: ! Beginne mit den Basisfällen und wende dann Rekursionsschritte „aufwärts“ an, wobei Zwischenergebnisse in einer Tabelle zur späteren Verwendung abgespeichert werden. ! Dynamisches Programmieren geht auf ein Verfahren zur Optimierung technischer Prozesse von Bellman 1957 zurück. Beispiel: Fibonacci-Funktion int[ ] fib = new int[N+1]; Tabelle fib[0] = 0; fib[1] = 1; Basisfälle for (int n = 2; n <= N; n++) fib[n] = fib[n-1] + fib[n-2]; Rekursionsschritt aufwärts angewendet Analyse der Fibonacci-Funktion: ! dynamisch programmiertes fib: O(n) ! rekursives fib: O(1.6n) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-90 Einschub - dynamisches Programmieren (3) Weiteres Beispiel aus der Kombinatorik: ! Eine Auswahl (Teilmenge) von k verschiedenen Elementen aus einer Menge von n Elementen heißt Kombination. ! Die Anzahl der Kombinationen schreibt man in der Mathematik auch als: Dieser Ausdruck ist nur für n ≥ k ≥ 0 definiert. ! Beispiel: die Anzahl der möglichen Lottokombinationen ist gleich . Rekursive Formulierung Rekursionsfall: Um eine k-elementige Teilmenge aus einer Menge mit n Elementen auszuwählen, kann entweder das n-te Element weggelassen oder dazu genommen werden. Basisfälle: Bei k = 0 gibt es nur eine Teilmenge, nämlich die leere Teilmenge. Bei k = n ist die Menge selbst die einzige Teilmenge. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-91 Einschub - dynamisches Programmieren (4) Iterative Berechnung mit Tabelle int[ ][ ] komb= new int[N+1][N+1]; for (int n = 0; n <= N; n++) { komb[n][0] = 1; // Basisfall (1) komb[n][n] = 1; // Basisfall (2) } Tabelle zum Speichern der Zwischenergebnisse: komb[n][k] = // Rekursionsschritt: for (int n = 2; n <= N; n++) for (int k = 1; k < n; k++) komb[n][k] = komb[n-1][k] + komb[n-1][k-1]; Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-92 Einschub - dynamisches Programmieren (5) ! Beispiel: für N = 4 ergibt sich die Tabelle komb wie folgt: n\k 0 1 0 1 1 1 1 2 1 2 1 3 1 3 3 1 4 1 4 6 4 + 2 3 4 Basisfälle Rekursionsfall 1 komb[n][k] = komb[n-1][k] + komb[n-1][k-1]; Pascalsches Dreieck! ! Bemerkung: Für die Berechnung von komb[n][k] würde es genügen nur die jeweils letzte Zeile der Matrix zu speichern. Daher eindimensionales Feld statt Matrix. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-93 Kürzeste Wege mit dynamischer Programmierung (1) ! Nochmals die Problemstellung - Berechne kürzeste Wege zwischen allen Knotenpaaren in einem gewichteten, gerichteten Graphen. Graph darf keine Zyklen negativer Länge enthalten. - Knoten sind durchnummeriert: v0, v1, …, vn-1. ! Formulierung des Problems als Funktion - Dk(i, j) = Distanz (Länge eines kürzesten Weges) von vi nach vj, wobei nur Wege von vi nach vj betrachtet werden, die über Knoten aus {v0, v1, …, vk} gehen. - Dn-1 löst unser Problem! ! Rekursive Formulierung - Basisfall k = -1 (nur die direkte Verbindung von vi nach vj ist erlaubt): D-1(i, j) = c(vi, vj) (Gewicht der Kante (vi, vj)) - Rekursionfall (k ≥ 0): Dk(i, j) = min { Dk-1(i, j) , Dk-1(i, k) + Dk-1(k, j) } Länge eines kürzesten Wegs von vi nach vj, ohne über vk zu gehen. Länge eines kürzesten Wegs von vi nach vj über vk setzt sich zusammen aus kürzestem Weg von vi nach vk und kürzestem Weg von vk nach vj. (Optimalitätsprinzip von Bellman) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-94 Kürzeste Wege mit dynamischer Programmierung (2) ! Iterative Berechnung von Dk mit Tabelle: - Dk(i, j) lässt sich für jedes k durch eine 2-dimensionale Matrix D[i][j] darstellen. D-1 wird gemäß Basisfall initialisiert; D0 ergibt sich aus D-1 nach dem Rekursionsfall, D1 aus D0, usw. Dn-1 ist die Lösung unseres Problems. - Es genügt, Dk nur für den jeweils aktuellen Wert von k abzuspeichern. Dk(i, j) = min { Dk-1(i, j) , Dk-1(i, k) + Dk-1(k, j) } = min { Dk-1(i, j) , Dk(i, k) + Dk(k, j) } (denn Dk(i, k) = Dk-1(i, k) und Dk(k, j) = Dk-1(k, j)) // Tabelle int[ ][ ] D = new int[n][n]; // Starte mit D-1: for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) if (i == j) D[i][j] = 0; else D[i][j] = c(vi, vj); } for (int k = 0; k < n; k++) // Berechne Dk: for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (D[i][j] > D[i][k] + D[k][j]) D[i][j] = min {D[i][j] , D[i][k] + D[k][j] }; D[i][j] = D[i][k] + D[k][j]); Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-95 Speicherung der kürzesten Wege ! Zur Speicherung der gefundenen Wege wird eine Vorgängermatrix P eingesetzt: P[i][j] = Vorgängerknoten von vj im kürzesten Weg von vi nach vj. ! Damit kann der kürzeste Weg von Start s nach Ziel z mit z beginnend in eine Liste von hinten nach vorne eingetragen werden: List<Integer> getShortestPath (int s, int z, Vertex[ ][ ] P) LinkedList<Integer> shortestPath; Vertex v = z; // Ziel while ( v != s ) { shortestPath.addFirst(v); v = P[s][v]; } shortestPath.addFirst(s); kürzester Weg von s nach z. return shortestPath; } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-96 Algorithmus von Floyd void computeAllShortestPath (WightedDiGraph G, int[ ][ ] D, Vertex[ ][ ] P) { // Initialisiere D und P: for (int i = 0; i < n; i++) { Ausgabe: for (int j = 0; j < n; j++) { Distanzmatrix D und Vorgängermatrix P P[i][j] = undef; if (i == j) Eingabe: D[i][j] = 0 Gewichteter Digraph G mit n Knoten. else { D[i][j] = c(vi, vj); // ∞, falls keine Kante (vi,vj) existiert if (D[i][j] != ∞) P[i][j] = i; } } } } for (int k = 0; k < n; k++) // Berechne Dk: for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (D[i][j] > D[i][k] + D[k][j]) { D[i][j] = D[i][k] + D[k][j]; P[i][j] = P[k][j]; } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-97 Analyse des Floyd-Algorithmus ! Enthält der Graph Zyklen mit negativer Länge, dann wird ein Diagonalelement von D negativ. Der Algorithmus kann leicht um eine passende Abprüfung mit entsprechender Ausgabe erweitert werden. ! Analyse: die dreifach geschachtelten for-Schleifen ergeben: T = O(|V|3). ! Vergleich zu Dijkstra-Algorithmus: ! Ist der Graph dünn-besetzt (d.h. |E| = O(|V|) und alle Gewichte positiv, dann kann der Dijkstra-Algorithmus mit einem Index-Heap auch für jeden Knoten aus V als Startknoten durchgeführt werden. Damit: T = O(|V||E|log(|V|) = O(|V|2log(|V|). Also: Dijkstra-Algorithmus besser als Floyd-Algorithmus. ! Ist der Graph dicht besetzt und alle Gewichte positiv, dann würde das |V|-malige Durchführen des Dijkstra-Algorithmus (mit unsortierter Prioritätsliste) ebenfalls zu T = O(|V|3) führen. Dann ist aber aufgrund der einfacheren Datenstruktur der Floyd-Algorithmus besser als der Dijkstra-Algorithmus. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-98 Beispiel zu Floyd-Algorithmus v0 v1 5 4 -2 1 -1 1 v2 -2 v4 0 5 ∞ ∞ 0 -1 ∞ 1 0 ∞ ∞ -2 1 ∞ ∞ 4 ∞ ∞ 0 ∞ ∞ -2 ∞ -1 0 0 5 ∞ ∞ 0 -1 ∞ 1 0 ∞ ∞ -2 1 6 ∞ D-1 4 0 2 - 1 3 - P-1 4 ∞ ∞ 0 5 ∞ -2 ∞ -1 0 0 5 4 4 3 ∞ 0 -1 ∞ -2 ∞ 1 0 ∞ -1 ∞ ∞ -2 0 -1 1 6 5 5 0 D0 0 - 1 3 - 4 0 2 0 1 3 - P0 Prof. Dr. O. Bittel, HTWG Konstanz v3 -1 0 5 4 ∞ 0 -1 ∞ 1 0 ∞ -1 -2 1 6 5 D1 0 0 1 3 - 4 0 2 0 1 1 3 1 P1 4 3 ∞ -2 ∞ -1 0 -3 5 0 0 3 2 ∞ 0 -1 ∞ 1 0 ∞ -1 -2 1 4 3 D2 0 0 1 1 1 3 - 4 0 2 2 0 1 1 3 1 P2 4 1 ∞ -2 ∞ -1 0 -3 5 0 0 -1 0 -2 1 3 0 1 -1 4 D3 0 0 1 1 1 1 - 4 2 2 2 2 3 1 3 3 2 -1 0 -2 3 4 1 3 -2 4 -1 0 -3 5 0 D4 0 0 P3 Algorithmen und Datenstrukuren – Graphenalgorithmen 1 1 1 1 - 4 4 4 4 2 2 2 2 3 1 3 3 0 0 0 0 1 1 1 1 - P4 SS 2017 3-99 Teil III: Graphenalgorithmen ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume - - - - Problemstellung AlgorithmusvonPrim AlgorithmusvonKruskal Union-Find-Strukturen ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-100 Problemstellung (1) Definition Sei G = (V,E) ein ungerichteter, gewichteter Graph. Dann ist B ein minimal aufspannender Baum, falls B folgende Eigenschaften erfüllt: (1) B = (V, E‘) mit E‘ ⊆ E; d.h. B ist ein Teilgraph von G mit gleicher Knotenmenge (2) B ist ein Baum; d.h. ein azyklischer, zusammenhängender Graph (3) Die Summe der Kantengewichte von B ist minimal; d.h. es gibt keinen anderen Baum, der Eigenschaften (1) und (2) erfüllt und eine kleinere Kantengewichtssumme hat. Beispiel 7 3 2 3 1 5 6 4 1 4 1 2 2 Ungerichteter Graph G Prof. Dr. O. Bittel, HTWG Konstanz 2 1 Minimal aufspannender Baum von G Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-101 Problemstellung (2) Bemerkung ! Ein azyklischer (d.h. kreisloser) zusammenhängender Graph ist ein Baum: man nehme einfach einen beliebigen Knoten als Wurzel und „verschiebe die anderen Knoten nach unten“. 1 2 1 3 6 4 5 6 4 3 2 5 ! Ein Baum garantiert, dass es zwischen je zwei Knoten immer genau einen Weg gibt. Er spannt damit die Knotenmenge auf. ! Der minimal aufspannende Baum muss nicht eindeutig sein. Typische Anwendung ! Zwischen n Orten soll ein Versorgungsnetz (Kommunikation, Strom, Wasser, etc.) aufgebaut werden, so dass je 2 Orte direkt oder indirekt miteinander verbunden sind. Die Verbindungskosten zwischen 2 Orte seien bekannt. ! Gesucht ist ein Versorgungsnetz mit den geringsten Kosten. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-102 Teil III: Graphenalgorithmen ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume - - - - Problemstellung AlgorithmusvonPrim AlgorithmusvonKruskal Union-Find-Strukturen ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-103 Idee des Algorithmus von Prim ! Der Algorithmus von Prim ist eine leichte Modifikation des Algorithmus von Dijkstra. ! Zu jedem Zeitpunkt bilden die bereits besuchten Knoten einen minimal aufspannenden Baum. Anfangs ist noch kein Knoten besucht. Der minimal aufspannende Baum wird wie bei den Algorithmen für kürzeste Wege in einem Vorgängerfeld gehalten. ! Alle Knoten, die als nächstes besucht werden können, werden in einer Kandidatenliste gehalten. Zu Anfang wird mit einem beliebigen Knoten als Kandidat begonnen. ! Von den Kandidaten wird derjenige Knoten als nächster besucht, der über die billigste Kante zu erreichen ist. Beachte den Unterschied zum Dijkstra-Algorithmus: Beim Algorithmus von Djikstra wird immer der Knoten mit der kürzesten Distanz zum Startknoten s besucht. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-104 Beispiel zu Algorithmus von Prim (1) 7 2 3 3 Zu Beginn ist Knoten 1 einziger Kandidat. 3 2 1 1 6 5 5 6 1 Kandidatenknoten sind rot unterlegt. 4 4 2 Knoten 1 wird besucht 7 2 3 Alle noch nicht besuchten Nachbarn von Knoten 1 werden Kandidaten. 1 Bereits besuchte Knoten und ausgewählte Kanten sind blau unterlegt und bilden den bisher gefundenen minimal aufspannenden Baum. 3 3 2 1 1 6 5 5 6 2 Prof. Dr. O. Bittel, HTWG Konstanz 4 4 Knoten v Vorgängerfeld p[v] 1 - 2 - 3 - 4 - 5 - Algorithmen und Datenstrukuren – Graphenalgorithmen 6 SS 2017 3-105 Beispiel zu Algorithmus von Prim (2) Knoten 6 wird besucht, da Knoten 6 über die günstigste Kante zu erreichen ist. 7 2 3 3 3 2 1 1 6 5 5 6 1 4 Knoten v Vorgängerfeld p[v] 1 - 2 - 3 - 4 - 5 - 6 1 5 - 6 1 4 2 Knoten 2 wird besucht. (Alternativ hätte man auch Knoten 4 besuchen können.) 7 2 3 3 3 2 1 1 5 5 6 Prof. Dr. O. Bittel, HTWG Konstanz 6 2 1 Knoten v Vorgängerfeld p[v] 1 - 2 6 3 - 4 - 4 4 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-106 Beispiel zu Algorithmus von Prim (3) Knoten 4 wird besucht. 7 2 3 3 3 2 1 1 6 5 5 6 1 4 Knoten v Vorgängerfeld p[v] 1 - 2 6 3 4 4 6 5 4 6 1 2 6 3 4 4 6 5 - 6 1 4 2 Knoten 3 wird besucht. 7 2 3 3 3 2 1 1 5 5 6 Prof. Dr. O. Bittel, HTWG Konstanz 6 2 1 4 Knoten v Vorgängerfeld p[v] 1 - 4 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-107 Beispiel zu Algorithmus von Prim (4) Knoten 5 wird besucht. 7 2 3 3 3 2 1 1 6 5 5 6 2 1 4 Knoten v Vorgängerfeld p[v] 1 - 2 6 3 4 4 6 5 4 6 1 4 Fertig, da alle Knoten besucht! Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-108 Algorithmus von Prim void minimumSpanningTree(Graph G, Vertex[ ] p ) { kl = ∅; // Kandidatenliste boolean[ ] istImBaum; // Knoten ist im aufspannenden Baum for (jeden Knoten v) { Eingabe: Gewichteter Graph G. c[v] = ∞; p[v] = undef; istImBaum[v] = false; } Ausgabe: Vorgängerfeld p, das den minimal s = 1; c[s] = 0; // irgendeinen Startknoten wählen aufspannenden Baum darstellt. kl.insert(s); while (! kl.empty() ) { In der Kandidatenliste kl sind alle Knoten lösche Knoten v aus kl mit c[v] minimal; abgespeichert, die als nächstes besucht istImBaum[v] = true; werden können. for (jeden Nachbarknoten w von v) if ( ! istImBaum[w]) { if (c[w] == ∞) // w noch nicht in Kandidatenliste kl.insert(w); if ( c(v,w) < c[w] ) { // c[w] verbessert sich c[v] gibt für jeden Kandidat das Gewicht der billigsten Kante an, mit der er von den bereits p[w] = v; besuchten Knoten erreicht werden kann. Für c[w] = c(v,w); die noch nicht besuchten Knoten, die noch } keine Kandidaten sind, ist c[w] = ∞. } } } if (istImBaum ist nicht true für alle Knoten) println("es existiert kein aufspannder Baum); } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-109 Analyse des Algorithmus von Prim ! Mit der gleichen Begründung wie beim Algorithmus von Dijkstra gilt: (1) Kandidatenliste als einfaches (unsortiertes) Feld T = O(|V|2) (2) Kandidatenliste als Prioritätsliste T = O(|E| log|V|) Fazit: ! Ist der Graph dicht besetzt, d.h. |E| = O(|V|2 ), dann ist die Variante (1) besser. ! Ist der Graph dünn besetzt, d.h. |E| = O(|V|), dann ist die Variante (2) besser. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-110 Teil III: Graphenalgorithmen ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume - - - - Problemstellung AlgorithmusvonPrim AlgorithmusvonKruskal Union-Find-Strukturen ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-111 Idee des Algorithmus von Kruskal ! Es wird ein Wald von minimal aufspannenden Bäumen verwaltet. (Wald = Menge von Bäumen) ! Start: Zu Anfang besteht der Wald aus allen Bäumen mit jeweils genau einem Knoten. ! Vereinigungsschritt: - Suche die billigste Kante k, die zwei unterschiedliche Bäume aus dem Wald verbindet. Vereinige die 2 Bäume mit der Kante k zu einem größeren Baum. - Es werden solange Vereinigungsschritte durchgeführt, bis nur noch ein Baum übrig bleibt. Das ist dann der minimal aufspannende Baum. ! Anmerkung: Der Wald von Bäumen lässt sich besonders effizient mit einer so genannten Union-Find-Struktur darstellen (siehe nächster Abschnitt). Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-112 Beispiel zu Algorithmus von Kruskal (1) 7 2 3 3 3 2 1 1 6 5 5 6 Am Anfang besteht der Wald aus 6 Bäumen mit jeweils genau einem Knoten. 1 4 4 2 Kante k = (1,6) mit Gewicht 1 wird gewählt 7 2 3 3 3 2 1 1 6 5 5 6 2 Prof. Dr. O. Bittel, HTWG Konstanz 1 Die beiden Bäumen, die aus dem Knoten 1 bzw. 6 bestehen, werden zu einem Baum vereinigt. Der Wald besteht damit nun aus 5 Bäumen. 4 4 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-113 Beispiel zu Algorithmus von Kruskal (2) Kante k = (3,4) mit Gewicht 1 wird gewählt 7 2 3 3 3 2 1 1 6 5 5 6 1 Die beiden Bäumen, die aus dem Knoten 3 bzw. 4 bestehen, werden zu einem Baum vereinigt. Der Wald besteht damit nun aus 4 Bäumen. 4 4 2 Kante k = (2,6) mit Gewicht 2 wird gewählt 7 2 3 3 3 2 1 1 5 5 6 Prof. Dr. O. Bittel, HTWG Konstanz 6 2 1 Der Baum mit den Knoten {1, 6} und der Baum mit dem Knoten 2 werden zu einem Baum vereinigt. Der Wald besteht damit nun aus 3 Bäumen. 4 4 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-114 Beispiel zu Algorithmus von Kruskal (3) Kante k = (4,6) mit Gewicht 2 wird gewählt 7 2 3 3 3 2 1 1 6 5 5 6 1 Der Baum mit den Knoten {1,2,6} und der Baum mit den Knoten {3,4} werden zu einem Baum vereinigt. Der Wald besteht damit nun aus 2 Bäumen. 4 4 2 Kante k = (4,5) mit Gewicht 4 wird gewählt 7 2 3 3 3 2 1 1 5 5 6 Prof. Dr. O. Bittel, HTWG Konstanz 6 2 1 Der letzte Vereinigungsschritt führt zu einem Wald mit genau einem Baum. ⇒ Minimal aufspannender Baum. 4 4 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-115 Algorithmus von Kruskal Rückgabe: Minimal aufspannender Baum Eingabe: Graph G = (V,E). List<Edge> minimumSpanningTree(Graph G) { (1) (2) (3) (4) (5) (6) UnionFind forest = { {v} / v ∈ V}; PriorityQueue<Edge> edges(E); List<Edge> minSpanTree = ∅; while ( forest.size() != 1 && ! edges.isEmpty() ) { (v,w) = edges.delMin(); t1 = forest.find(v); t2 = forest.find(w); if (t1 != t2) { forest.union(t1,t2); minSpanTree.add(v,w); } } if (edges.isEmpty() && forest.size() != 1) return “es existiert kein aufspannender Baum”; else return minSpanTree; } Prof. Dr. O. Bittel, HTWG Konstanz forest ist eine Menge von Bäumen, die anfangs aus jeweils genau einem Knoten aus V bestehen. Der Datentyp UnionFind wird im nächsten Abschnitt erklärt. Alle Kanten werden mit ihren Gewichten in einer Prioritätsliste gespeichert, so dass effizient auf die Kante mit dem kleinsten Gewicht zugegriffen werden kann. Solange der Wald noch mehr als ein Baum enthält und es noch Kanten gibt. Wähle Kante mit kleinstem Gewicht, die 2 Bäume aus dem Wald verbindet. forest.find(v) liefert denjenigen Baum zurück, in dem v enthalten ist. Vereinige die beiden Bäume zu einem Baum. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-116 Analyse des Algorithmus von Kruskal ! Mit dem Datentyp UnionFind lässt sich eine Menge (hier: Wald) von disjunkten Mengen (hier Bäume) effizient verwalten mit den Operationen find und union (siehe nächster Abschnitt). Damit: - find in (3) und (4): O(log|V|) - union in (5): O(1) • Eine Prioritätsliste PriorityQueue läßt sich mit einer Heap-Struktur implementieren. Damit: - Aufbau einer PriorityQueue in Zeile (1): O(|E|) - delMin in Zeile (2): O(log|E|) ! Der minimal aufpannende Baum minSpanTree ist als einfache Kanten-Liste realisiert. Damit ist (6) in O(1) durchführbar. ! Insgesamt: Im schlechtesten Fall wird delMin (Zeile (2)) gefolgt von Zeile (3), (4) und evtl. (5) und (6) |E|-mal durchgeführt. T = O(|E|(log|E| + log |V|)) Also: T = O(|E| log|V|). Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-117 Teil III: Graphenalgorithmen ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume - - - - Problemstellung AlgorithmusvonPrim AlgorithmusvonKruskal Union-Find-Strukturen ! Flüsse in Netzwerken ! Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-118 Problemstellung ! Effiziente Verwaltung einer Partitionierung (disjunkte Zerlegung in Teilmengen) einer Grundmenge G = {0, 1, 2, 3, ...,n-1}: G = S0 ∪ S1 ∪ ... ∪ Sm-1, 5 4 0 3 1 2 9 7 Si ∩ Sj = ∅ für i ≠ j G = {0, 1, 2, 3, ..., 9} 8 = {3, 4, 5} ∪ {0} ∪ {1, 9} ∪ {2, 6, 7} ∪ {8} 6 {2, 7, 6} ! folgende Operationen sollen unterstützt werden: - S = find(e): liefert diejenige Menge S zurück, in der das Element e enthalten ist. - union(S1,S2) : vereinigt die beiden Mengen S1 und S2. ! Problem: Wie sollen Mengen benannt werden. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-119 Idee (1) ! Als Name einer Menge S wird irgendein festes Element (Repräsentant) gewählt. 5 4 0 3 1 2 9 7 G = {0, 1, 2, 3, ..., 9} 8 = {3, 4, 5} ∪ {0} ∪ {1, 9} ∪ {2, 6, 7} ∪ {8} =5∪0∪9∪6∪8 6 {2, 7, 6} = 6 Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-120 Idee (2) 5 4 0 1 2 9 7 3 8 find(7) liefert 6 zurück. 6 union(5, 9) ergibt folgende neue Partitionierung. 5 4 3 1 9 Prof. Dr. O. Bittel, HTWG Konstanz 2 0 7 8 6 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-121 Datenstruktur für Union-Find ! Verwalte Mengen als Bäume, wobei Bäume mit einem Vorgänger- oder besser Elternfeld p dargestellt werden. ! Der Name (Repräsentant) einer Menge ist die Wurzel. Beispiel ! Partitionierung: G = {0, 1, 2} ∪ {4, 6, 7, 9, 10, 11} ∪ {3} ∪ {5, 8} ! Bäume: 6 0 2 3 4 1 11 7 10 Namen (Repräsentant) der jeweiligen Menge 5 8 9 ! Elternfeld: e 0 1 p[e] -1 0 2 3 4 5 6 7 8 9 10 11 0 -1 6 -1 -1 4 5 4 6 6 p[e] gibt den Elternknoten (Vorgängerknoten) zu p an. Falls e eine Wurzel ist, dann ist p[e] = -1 (d.h. kein Elternknoten) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-122 Find-Algorithmus int find(int e) { if (p[e] == -1) // e ist Wurzel return e; else return find(p[e]); } Beispiel ! Partitionierung: G = {0, 1, 2} ∪ {4, 6, 7, 9, 10, 11} ∪ {3} ∪ {5, 8} ! Bäume: 6 0 2 4 1 3 11 7 5 12 8 find(9) liefert 6 zurück. 9 ! Elternfeld: e 0 1 p[e] -1 0 2 7 8 9 10 11 0 -1 6 -1 -1 4 5 4 Prof. Dr. O. Bittel, HTWG Konstanz 3 4 5 6 6 6 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-123 Union-Algorithmus - Idee ! Werden zwei Mengen vereinigt, dann wird der kleinere Baum als Kind der Wurzel des größeren Baums eingehängt. ! Beispiel: 0 2 4 1 11 7 5 3 6 10 e 8 0 1 2 3 4 5 6 7 8 9 10 11 p[e] -1 0 0 -1 6 -1 -1 4 5 4 6 6 9 union(0, 6) vereinigt die Menge {0, 1, 2} und die Menge {4, 6, 7, 9, 10, 11}: 6 0 2 4 1 7 Prof. Dr. O. Bittel, HTWG Konstanz 3 11 10 5 8 e 0 1 2 3 4 5 6 7 8 9 10 11 p[e] 6 0 0 -1 6 -1 -1 4 5 4 6 6 9 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-124 Union-By-Height ! Es wird der Baum mit kleinerer Höhe an die Wurzel des Baums mit größerer Höhe gehängt. ! Die Höhe h für ein Baum lässt sich bei p[e], wobei e die Wurzel ist, als negative Zahl abspeichern: p[e] = -1-h void unionByHeight (int s1, int s2) { s1 und s2 müssen Repräsentanten einer Menge sein. if (p[s1] >= 0 || p[s2] >= 0) return; if (s1 == s2) return; Falls s1 und s2 dieselbe Menge ist, dann mache nichts. if ( -p[s1] < -p[s2] ) // Höhe von s1 < Höhe von s2 p[s1] = s2; else { if ( -p[s1] == -p[s2] ) p[s1]--; // Höhe von s1 erhöht sich um 1 p[s2] = s1; } } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-125 Union-By-Size ! Es wird der Baum mit geringerer Anzahl Knoten an die Wurzel des Baums mit größerer Anzahl Knoten gehängt. ! Die Anzahl der Knoten n für ein Baum lässt sich bei p[e], wobei e die Wurzel ist, als negative Zahl abspeichern: p[e] = -1-n void unionBySize (int s1, int s2) { if (p[s1] >= 0 || p[s2] >= 0) return; if (s1 == s2) return; if (-p[s1] < -p[s2] ) { // Größe von s1 < Größe von s2 p[s2] = p[s2] - p[s1] + 1; // Größe von s2 wächst um Größe von s1 p[s1] = s2; } else { p[s1] = p[s1] - p[s2] + 1; // Größe von s1 wächst um Größe von s2 p[s2] = s1; } } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-126 Beispiel für unionByHeight (1) 2 4 1 11 7 5 3 6 0 10 e 8 p[e] -2 0 0 -1 6 -2 -4 4 5 4 6 13 9 0 1 2 3 4 5 6 7 8 9 10 11 12 13 6 7 11 Wurzeln 12 Höheninformation unionByHeight(3, 5) 6 0 2 4 1 7 5 11 9 10 3 e 8 0 1 2 3 4 5 6 7 8 9 10 11 12 13 p[e] -2 0 0 5 6 -2 -4 4 5 4 6 6 7 11 13 12 Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-127 Beispiel für unionByHeight (2) 6 0 2 5 4 1 11 7 10 e 3 8 0 1 2 3 4 5 6 7 8 9 10 11 12 13 p[e] -2 0 0 5 6 -2 -4 4 5 4 6 6 7 11 13 9 12 unionByHeight(0, 5) 0 2 6 1 e 5 3 4 8 7 11 9 10 0 1 2 3 4 5 6 7 8 9 10 11 12 13 p[e] -3 0 0 5 6 0 -4 4 5 4 6 6 7 11 13 12 Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-128 Beispiel für unionByHeight (3) 0 6 2 1 e 5 3 4 8 11 7 10 0 1 2 3 4 5 6 7 8 9 10 11 12 13 p[e] -3 0 0 5 6 0 -4 4 5 4 6 6 7 11 13 9 12 unionByHeight(0, 6) 6 e 0 2 4 1 5 3 Prof. Dr. O. Bittel, HTWG Konstanz 7 11 9 10 0 1 2 3 4 5 6 7 8 9 10 11 12 13 p[e] 6 0 0 5 6 0 -4 4 5 4 6 6 7 11 13 8 12 Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-129 Analyse von Union-Find-Strukturen ! Für Union-Find-Strukturen mit n-Elementen ergeben sich folgende Laufzeiten: - Union: T(n) = O(1) - Find: T(n) = O(log n). Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-130 Generierung von Labyrinthen mit einer Union-Find-Struktur (1) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-131 Generierung von Labyrinthen mit einer Union-Find-Struktur (2) ! Idee: Union-Find-Struktur um Erreichbarkeit von Zellen in einem Labyrinth zu speichern: zwei Zellen des Labyrinths sind genau dann in der gleichen Menge, falls sie gegenseitig erreichbar sind. ! Labyrinth ! Union-Find-Struktur { {1, 2}, {3}, {4}, {5, 7, 8, 9, 10, 14, 15}, {6}, {11, 12, 16}, {13}, {17, 18, 19, 23}, {21}, {22}, {24}, {25} } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-132 Generierung von Labyrinthen mit einer Union-Find-Struktur (3) ! Algorithmus (1) : Initialisiere Labyrinth mit n*n Einzelzellen: Union-Find-Struktur = { {1}, {2}, {3}, {4}, {5}, ... {25} } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-133 Generierung von Labyrinthen mit einer Union-Find-Struktur (4) ! Algorihmus (2): while (Anzahl Mengen in Union-Find-Struktur != 1) { wähle eine Wand w (keine Außenwand) zufällig aus; beseitige Wand w, falls dadurch die beiden Nachbarzellen erreichbar werden; } 14 19 { {1, 2}, {3}, {4}, {5, 7, 8, 9, 10, 14, 15}, {6}, {11, 12, 16}, {13}, {17, 18, 19, 23}, {21}, {22}, {24}, {25} } Prof. Dr. O. Bittel, HTWG Konstanz { {1, 2}, {3}, {4}, {5, 7, 8, 9, 10, 14, 15, 17, 18, 19, 23}, {6}, {11, 12, 16}, {13}, {21}, {22}, {24}, {25} } Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-134 Teil III: Graphenalgorithmen ! ! ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume Flüsse in Netzwerken Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-135 Problemstellung (1) Netzwerk: ! Ein Netzwerk ist ein gewichteter und gerichteter Graph mit zwei speziellen Knoten: - Quelle (engl. source): Knoten ohne eingehende Kanten - Senke (engl. sink): Knoten ohne ausgehende Kanten. ! Die Gewichte c(v,w) werden auch als Kapazitäten bezeichnet. Beispiel: 3 a 3 1 q 2 c b 2 s 4 2 d 3 Netzwerk mit Quelle q, Senke s und Kapazitäten. Anwendungen: ! Straßenverkehrsnetz: Kapazitäten geben maximal mögliche Verkehrsdichte je Straße an. ! Kanalisationsysytem: Kapazitäten geben maximale Wassermenge an, die je Zeiteinheit durch ein Rohr fließen kann. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-136 Problemstellung (2) ! Ein Fluß f in einem Netzwerk G ordnet jeder Kante (v,w) eine Zahl f(v,w) mit folgenden Eigenschaften zu: (1) Kapazitätsbeschränkung: 0 ≤ f(v,w) ≤ c(v,w); (2) für jeden Knoten (außer Quelle und Senke) gilt die Erhaltungseigenschaft: die Summe der eingehenden Flüsse ist gleich der Summe der ausgehenden Flüsse. ! Folgerung: Die Summe der Flüsse, die die der Quelle verlassen, muss gleich der Summe der Flüsse sein, die an der Senke ankommen. ! Der Wert eines Flusses ist gleich der Summe der Flüsse, die die Quelle verlassen. ! Ziel: bestimme den maximalen Fluss eines Netzwerkes. Genauer: bestimme einen Fluss mit maximalem Wert. ! Anwendungen: - Welchen Verkehrsfluss verkraftet eine Stadt, für die ein Straßenverkehrsnetz gegeben ist. - Welche Wassermenge lässt sich durch eine Kanalisation höchstens abtransportieren. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-137 Beispiel für Netzwerk mit maximalem Fluss 3/3 q a 2/3 0/1 2/2 c Prof. Dr. O. Bittel, HTWG Konstanz b 2/2 s 1/4 2/2 d 3/3 Fluss / Kapazität Maximaler Fluss hat den Wert 5. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-138 Idee des Algorithmus Starte mit Null-Fluss, d.h. f(v,w) = 0 für alle Kanten (v,w); do { (1) // Erweiterungsweg suchen: Suche einen Weg p von q nach s, bei dem jede Kante um einen Fluss Δf vergrößert werden kann; (2) // Flusserweiterung: vergrößere für jede Kante (v,w) im Weg p den Fluss f(v,w) um Δf; } while (Fluss konnte erweitert werden); 0/3 q a 0/3 0/1 0/2 c Prof. Dr. O. Bittel, HTWG Konstanz b s 0/4 0/2 Null-Fluss. 0/2 d 0/3 q, a, b, s ist ein möglicher Erweiterungsweg: jede Kante kann um den Fluss Δf = 2 vergrößert werden. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-139 Problem ! Die Wahl eines Erweiterungsweges kann in eine Sackgasse führen (suboptimale Lösung). ! Beispiel 0/3 q 0/3 a 0/1 0/2 3/3 q c s d 0/2 a c 0/2 0/4 0/3 0/1 0/2 b b 0/3 0/2 s 3/4 0/2 d Erweiterungsweg, bei dem jede Kante um Δf = 3 vergrößert werden kann. Nach Flusserweiterung: Fluss hat nun den Wert 3. 3/3 ! Es gibt nun keinen weiteren Erweiterungsweg, obwohl es eine Lösung mit einem Flusswert von 5 gibt (siehe S. 136) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-140 Residualgraphen ! Lösung des Problems: Man erlaubt nach der Vergrößerung auch wieder eine Verkleinerung von Kantenflüssen. ! Verwalte außer Graph G mit den aktuellen Flüssen zusätzlich einen sogenannten Residualgraphen Gr (residual = als Rest zurückbleibend). ! Im Residualgraph wird gespeichert, um welchen Wert der Fluss jeder Kante noch verändert werden darf (Residualfluss): (1) Hat eine Kante (v,w) den Fluss f(v,w) > 0, dann darf der Fluss verkleinert werden. Im Residualgraphen Gr wird die Kante (w,v) mit dem Residualfluss f(v,w) abgespeichert. Beachte: Kante in Gr verläuft in umgekehrter Richtung. (Flusserhöhung in umgekehrter Richtung verkleinert den Fluss.) (2) Hat eine Kante (v,w) den Fluss f(v,w) < c(v,w), dann darf der Fluss vergrößert werden. Im Residualgraphen Gr wird die Kante (v,w) mit dem Residualfluss c(v,w) - f(v,w) abgespeichert. Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-141 Beispiel Residualgraph für Nullfluss 0/3 q a 0/3 0/1 0/2 b 3 0/2 s 0/4 c d 0/2 Graph G mit Nullfluss. a q 0/3 3 1 2 b 2 s 4 c d 2 Residualgraph Gr mit Residualflüssen 3 Residualgraph nach Flusserweiterung q, a, d, s wird als Erweiterungsweg gewählt. Fluss lässt sich um Δf = 3 vergrößern. 3/3 q a 0/1 0/2 0/3 b 0/2 s 3/4 c d 3/3 0/2 Graph G mit Flusswert 3. Prof. Dr. O. Bittel, HTWG Konstanz 3 q a c Fluss von q nach a darf um bis zu 3 verringert werden. b 2 3 1 2 3 s 1 d 2 Residualgraph Gr mit Residualflüssen 3 Fluss von a nach d darf um bis zu 1 erhöht und bis zu 3 verringert werden. Fluss von d nach s darf um bis zu 3 verringert werden. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-142 Algorithmus zur Berechnung des maximalen Flusses Initialisiere Graph G mit Null-Fluss, d.h. f(v,w) = 0 für alle Kanten (v,w); Initialisiere Residualgraph Gr; do { (1) // Erweiterungsweg suchen: Suche im Residualgraphen Gr einen Weg p von q nach s; Δf = Minimum aller Residual-Flüsse im Weg p; (2) // Fluss vergrößern: vergrößere für jede Kante (v,w) im Weg p den Fluss f(v,w) um Δf (beachte dabei, dass umgekehrte Residualflüsse den Fluss verkleinern); führe entsprechende Änderungen im Residualgraphen durch; } while (Fluss konnte vergrößert werden); 2 Ansätze, um einen Erweiterungsweg von q nach s im Residualgraphen zu suchen: ! suche den Weg mit dem größten Δf (wie bei kürzeste Wege in Distanzgraphen; siehe Algorithmus von Dijkstra) ! suche Weg mit kleinster Kantenzahl (wie kürzeste Wege in ungewichteten Graphen durch erweiterte Breitensuche) Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-143 Beispiel (1) Starte mit Nullfluss 0/3 q a 0/3 0/1 0/2 c b 0/2 s 0/4 0/2 d Graph G mit Nullfluss Prof. Dr. O. Bittel, HTWG Konstanz 3 0/3 q a 1 2 c 3 b 2 s 4 d 3 2 Residualgraph Gr mit Residualflüssen Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-144 Beispiel (2) Suche Weg mit größten Δf im Residualgraphen: 3 a 3 q b 1 2 2 q, a, d, s ist Weg mit größtem Δf. s 4 Δf = Minimum der Residual-Flüsse im Weg = 3. 3 c d 2 Residualgraph Gr Flusserweiterung um Δf: a 3/3 q 0/3 0/1 0/2 c b 0/2 s 3/4 0/2 d 3/3 Graph G mit Flusswert 3 Prof. Dr. O. Bittel, HTWG Konstanz 3 q a b 2 1 2 3 c 1 s 3 d 3 2 Residualgraph Gr Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-145 Beispiel (3) Suche Weg mit größten Δf im Residualgraphen: a 3 3 b 2 q 1 2 3 1 c s d q, c, d, a, b, s ist Weg mit größtem Δf. Δf = Minimum der Residual-Flüsse im Weg = 2. 3 2 Residualgraph Gr Flusserweiterung um Δf: a 3/3 q 2/3 0/1 2/2 c b 2/2 3 2/2 s 1/4 d 2 Beachte: Fluss wurde verkleinert! 3/3 Graph G mit Flusswert 5 q a b 2 1 2 1 3 c 1 s d 3 2 Residualgraph Gr Ende: Es gibt im Residualgraphen keine Wege mehr von q nach s. Maximaler Fluss gefunden! Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-146 Analyse und Bemerkungen ! Analyse ergibt: T = O(|E|2 log|V|) (Beweis siehe [Turau 2004]). ! Bemerkungen: - Der hier beschriebene Algorithmus geht zurück auf Ford und Fulkerson (1956). Analyse von Karp und Edmonds (1972). - Man beachte, dass bei einem dichten Graphen (d.h. |E| = O(|V|2) die Laufzeit anwächst auf T = O(|V|4log|V| ) - Es gibt inzwischen wesentlich schnellere Algorithmen: Preflow-Push-Algorithmus von Goldberg, 1985: T = O(|V|3). Mittels spezieller Datenstrukturen erreichte Goldberg 1988 sogar: T = O(|E| |V| log(|V|2/|E|). Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-147 Teil III: Graphenalgorithmen ! ! ! ! ! ! ! ! ! Anwendungen Definitionen Datenstrukturen für Graphen Elementare Algorithmen Topologisches Sortieren Kürzeste Wege Minimal aufspannende Bäume Flüsse in Netzwerken Zusammenhangskomponenten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-148 Zusammenhangskomponenten ! Ein ungerichteter Graph heißt zusammenhängend (engl. connected), falls es für jeden Knoten einen Weg zu jedem anderen Knoten gibt. ! Eine Zusammenhangskomponente eines ungerichteten Graphen G ist ein maximal zusammenhängender Teilgraph. ! Ein Knoten heißt Artikulationspunkt (Artikulation med. = Gelenk; engl. articulation point), wenn sein Wegfall die Anzahl der Zusammenhangskomponenten erhöht. ! Ein Graph heißt zweifach zusammenhängend (engl. biconnected), falls er keinen Artikulationspunkt besitzt. ! Wichtige Problemstellung: Bestimme in einem ungerichteten Graphen alle Artikulationspunkte. B A D C G ! Ein ungerichteter Graph G mit den Artikulationspunkten C und D. H Prof. Dr. O. Bittel, HTWG Konstanz F E ! G \ {C} zerfällt in 3 Zusammenhangskomponenten ! G \ {D} zerfällt in 2 Zusammenhangskomponenten. Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-149 Tiefensuchbaum mit Rückwärtskanten (TSB) ! Stellt man die Tiefensuche graphisch dar, ergibt sich der Tiefensuchbaum. ! Der Tiefensuchbaum besteht aus Vorwärtskanten. ! Zusätzlich wird der Tiefensuchbaum ergänzt um Rückwartskanten Rückwartskanten sind Kanten, die einen Knoten mit einem bereits früher besuchten Knoten verbinden jedoch nicht den unmittelbar davor besuchten Knoten. ! Abkürzung TSB für Tiefensuchbaum mit Rückwärtskanten. A B A B C C D D F G E G E F ! TSB, der mit A beginnt ! Vorwärtskanten und Rückwärtskanten Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-150 Vorfahre und Rückwärtsweg ! Knoten v ist Vorfahre von w, falls es im TSB einen Weg von v nach w gibt, der nur aus Vorwärtskanten besteht. ! Ein Rückwärtsweg ist ein Weg in einem TSB mit einer beliebig langen Folge von Vorwärtskanten und dann genau einer Rückwärtskante. A B C D E F Prof. Dr. O. Bittel, HTWG Konstanz Beispiele: ! A, B, C sind Vorfahren von D und von G G ! E – F – D ist ein Rückwärtsweg ! B – C – D – A ist ein Rückwärtsweg ! E – F – D – A ist kein Rückwärtsweg Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-151 Bestimmung von Artikulationspunken (W) Die Wurzel ist ein Artikulationspunkt, wenn sie mehr als ein Kind hat. (NW) Ein Knoten v ≠ Wurzel ist ein Artikulationspunkt, falls v im TSB ein Kind hat, von dem es keinen Rückwärtsweg zu einem Vorfahren von v gibt. B C A B A B G A C D C F D G E E F G E F ! TSB, der mit Knoten C beginnt ! TSB, der mit Knoten A beginnt ! C ist Artikulationspunkt wegen (W) ! C und D sind Artikulationspunkte wegen Regel (NW) ! D ist Artikulationspunkt wegen (NW) Prof. Dr. O. Bittel, HTWG Konstanz D Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-152 Zusätzliche Nummerierungen im TSB ! TSNr[v]: gibt für jeden Knoten v den Besuchszeitpunkt bei der Tiefensuche an. ! MinNr[v]: die kleinste TSNr eines Knoten w, der von v über einen Rückwärtsweg erreicht werden kann. Falls es von v keine Rückwärtswege gibt, ist MinNr[v] = TSNr[v]. A 1,1 B A B 2,1 C 3,1 C D D 4,1 F G 7,7 E 5,4 G E F 6,4 TSB mit TSNr und MinNr ! Regel (NW) lässt sich damit wie folgt umsetzen: Knoten v (außer Wurzel) ist genau dann ein Artikulationspunkt, wenn v ein Kind w hat mit MinNr[w] ≥ TSNr[v] . Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-153 Berechnung von MinNr ! MinNr[v] = Minimum von (M1) TSNr[v], (M2) kleinster Wert von TSNr[w] für alle Rückwärtskanten (v,w) (M3) kleinster Wert von MinNr[w] für alle Vorwärtskanten (v,w) B A 1,1 A B 2,1 C 3,1 C D G F E D 4,1 E 5,4 F 6,4 Prof. Dr. O. Bittel, HTWG Konstanz G 7,7 TSB mit TSNr und MinNr Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-154 Algorithmus zur Bestimmung von Artikulationspunkten boolean[ ] visited; int counter; int[ ] TSNr[n]; int[ ] MinNr; int[ ] p; public void findArtPoint(Graph g) { visited mit false initialisieren; counter = 1; Wähle beliebigen Knoten v; findArtPoint(g, v); } Globale Variablen: ! counter dient zum Hochzählen der Besuchszeitpunkte. ! TSNr und MinNr wie soeben beschrieben. ! p[v] speichert den im TSB zu v gehörenden Elternknoten ab. ! findArtPoint(g) gibt alle Artikulationspunkte des ungerichteten Graphen g aus. private void findArtPoint(Graph g, Vertex v) { ! findArtPoint(g,v) startet eine rekursive visited[v] = true; Tiefensuche bei Knoten v. TSNr[v] = counter++; ! Beachte: MinNr[v] = TSNr[v]; // (M1) Die Prüfung, ob die Wurzel ein for (jeden Nachbarn w von v ) Artikulationspunkt ist (Regel (W)), if (! visited[w]) { wurde einfachheitshalber weggelassen. p[w] = v; findArtPoint(g, w); if (MinNr[w] >= TSNR[v]) println(v + “ist ein Artikulations-Punkt”); MinNr[v] = Minimum(MinNr[v], MinNr[w]); // (M3) } else if (p[v] != w) // (v,w) ist Rückwärtskante MinNr[v] = Minimum(MinNr[v], TSNr[w]); // (M2) } Prof. Dr. O. Bittel, HTWG Konstanz Algorithmen und Datenstrukuren – Graphenalgorithmen SS 2017 3-155