151 6. Graphen • viele praktische (Optimierungs-)Probleme sind als graphentheoretische Probleme formulierbar • z.B. in Produktionsplanung, Personaleinsatzplanung, . . . 6.1 Grundlagen • gerichteter, ungerichteter und gewichteter Graph 152 Gerichteter Graph Definition: gerichteter Graph G = (V, E) mit: V = {v0, . . . , vn−1} Knotenmenge E ⊂V ×V Kantenmenge (im folgenden: m =| E |) • Graphen werden meist graphisch dargestellt Beispiel: G1 = ({v0, v1, v2, v3}, {(v0, v2), (v1, v2), (v2, v1), (v2, v3)}) v0 v1 v2 v3 153 Ungerichteter Graph Definition: ungerichteter Graph G = (V, E) mit: V = {v0, . . . , vn−1} Knotenmenge E ⊂ {{v, v 0} | v, v 0 ∈ V } Kantenmenge Beispiel: G2 = ({v0, v1, v2, v3}, {{v0, v2}, {v1, v2}, {v2, v3}}) v0 v1 v2 v3 • im folgenden werden gerichtete Graphen betrachtet, sofern nicht explizit anders hervorgehoben 154 Gewichteter Graph Definition: gewichteter Graph G = (V, E, β) mit: Knotenmenge V Kantenmenge E β : E−→IR (Kanten-)Gewichtsfunktion Beispiel: G3 = ({v0, v1, v2, v3}, {(v0, v2), (v1, v2), (v2, v1), (v2, v3)}, β) mit β(v0, v2) = 2, β(v1, v2) = 5, β(v2, v1) = 3, β(v2, v3) = 2 v0 2 v2 v1 5 3 2 v3 155 6.2 Datenstrukturen für Graphen 6.2.1 Adjazenzmatrix sei G = (V, E) ein Graph Definition: Adjazenzmatrix AG ist n × n-Matrix mit ( 1, falls (vi, vj ) ∈ E f ür 0 ≤ i, j < n (bzw. {vi, vj } ∈ E) ai,j = 0, sonst Beispiel: v0 v1 AG 1 G1 : • Platzbedarf: O(n2) v2 v3 0 0 = 0 0 0 0 1 0 1 1 0 0 0 0 1 0 156 6.2.2 Adjazenzlisten • Array (oder sonstige Kollektion) von Knoten • für jeden Knoten: Liste mit Verweisen auf Nachfolgeknoten Beispiel: 0 v0 G1 : v3 • Kantengewichte leicht ergänzbar • Platzbedarf: O(n + m) 2 3 v1 - v2 1 2 - 2 1 - 3 157 6.2.3 Distanzmatrix sei G = (V, E, β) ein gewichteter Graph Definition: Distanzmatrix DG ist n × n-Matrix mit ( β(vi, vj ), falls (vi, vj ) ∈ E f ür 0 ≤ i, j < n (bzw. {vi, vj } ∈ E) di,j = ∞, sonst Beispiel: v0 2 G3 : v2 • Platzbedarf: O(n2) v1 5 DG3 3 2 v3 ∞ ∞ 2 ∞ ∞ ∞ 5 ∞ = ∞ 3 ∞ 2 ∞ ∞ ∞ ∞ 158 6.3 Transitive Hülle • gegeben: G = (V, E) • gesucht: (reflexive,) transitive Hülle G∗ = (V, E ∗) mit: ? (v, v) ∈ E ∗ ∀v ∈ V ? (v, v 0) ∈ E ∗, (v 0, v 00) ∈ E ⇒ (v, v 00) ∈ E ∗ ∀v, v 0, v 00 ∈ V • sonst keine Kanten in E ∗ Beispiel: G1 = (V, E): v0 v1 v0 v1 v2 v3 v2 v3 G∗1 = (V, E ∗): 159 Transitive Hülle in Java • gegeben: Adjazenzmatrix AG von G = (V, E) public static void huelle(byte[][] A){ int n = A.length; for(int i=0; i<n; i++) A[i][i] = 1; for(int j=0; j<n; j++) for(int i=0; i<n; i++) if (A[i][j] == 1) for(int k=0; k<n; k++) if (A[j][k] == 1) A[i][k] = 1;} 2 ∗ • Aufwand: trth (n, m) ∈ O(n + | E | ·n)) W 160 6.4 Durchlaufen von Graphen 6.4.1 Tiefensuche • an jedem Knoten: ? zuerst die Nachfolger rekursiv betrachten ? dann die “Geschwister” Beispiel: G4: v0 v1 v4 v2 v3 v5 Durchlaufreihenfolge: v0, v2, v1, v4, v3, v5 s • Aufwand: tdf W (n, m) ∈ O(n + m) (bei Adjazenzlisten) 161 6.4.2 Breitensuche • alle Knoten mit Abstand i vom Ausgangsknoten werden vor denen mit Abstand i + 1 besucht (i = 0, 1, 2, . . .) • hierzu: Knotenqueue (FIFO) Beispiel: G4: v0 v1 v4 v2 v3 v5 Durchlaufreihenfolge: v0, v2, v1, v3, v4, v5 s • Aufwand: tbf W (n, m) ∈ O(n + m) (bei Adjazenzlisten) 162 6.5 Kürzeste Wege 6.5.1 Kürzeste Wege von einem Ausgangsknoten • sei G = (V, E, β) mit β : E−→IR+ Algorithmus von Dijkstra (grober Pseudocode) drei Knotenmengen: F (fertig), R (Rand), U (unerreicht) anfangs: F = {v0}; R = Nachbarn(v0); U =V −F −R bis F == V (*) bestimme v ∈ R mit d(v0, v) ≤ d(v0, v 0) ∀ v 0 ∈ R F = F ∪ {v} R = R ∪ (Nachbarn(v) − F ) − {v} U = U −R verschiedene Implementierungen von (*) möglich: 1) durchlaufe alle Knoten: → Aufwand O(n2) (Original) 2) mit Knoten-Heap: → Aufwand O(m · log n) 3) mit Knoten-Fibonacci-Heap (s. Ottmann/Widm.): Aufwand O(m + n · log n) 163 Beispiel: Algorithmus von Dijkstra v0 0 3 v2 2 6 3 v2 2 6 v1 5 2 3 v2 2 3 v4 3 4 5 1 v4 2 v3 7 3 4 6 v1 5 2 2 6 3 v2 2 5 1 v4 2 v3 2 v0 0 1 3 v2 2 v1 5 2 5 2 v3 2 v0 0 v4 6 v0 0 1 2 v1 5 2 6 v3 2 v0 0 v1 5 2 2 6 3 4 5 1 v4 2 v3 3 4 6 164 6.5.2 Alle Paare kürzester Wege Ansatz 1: n-mal Dijkstra-Algorithmus • Aufwand: O(n3) bzw. O(n · m · log n) (mit Heap) Ansatz 2: (+,min)-Multiplikationen geg.: Distanzmatrix D M = D; for(int i=0; i <= log2 n; i++) M = M ⊕ M; wobei ⊕: (+,min)-Matrixmultiplikation, d.h. min statt +, • Aufwand: O(n3 · log n) • verbesserbar auf: O(n2.81 · log n) mit Strassen-Verfahren + statt · 165 6.6 Minimaler Spannender Baum • gegeben: ungerichteter, gewichteter Graph G = (V, E, β) 0 0 0 0 • gesucht: Baum B = (V, E , β ) mit E ⊂ E, β = β |E 0 und P β(e) minimal e∈E 0 Algorithmus von Kruskal (Pseudocode) E0 = ∅ while B noch kein Baum do wähle e ∈ E − E 0 mit kleinstem β(e) und so dass B = (V, E 0 ∪ {e}) ‘‘kreisfrei’’ E 0 = E 0 ∪ {e} • Aufwand: O(m · log n) mit “union-find-Struktur” (s. z.B. Ottmann/Widmayer) 166 Beispiel: Algorithmus von Kruskal 6 v0 v1 v0 1 2 v4 2 3 2 6 v0 v3 v1 2 1 3 v2 2 6 v0 1 v4 2 3 v3 v1 3 1 2 2 2 v4 2 3 v2 v1 2 2 v2 6 2 v3 3 2 v4 2 3 v2 2 v3 3 167 Algorithmus von Jarnı́k/Prim/Dijkstra • analog zu Dijkstra-Algorithmus (s.o) • jedoch wird für jeden Randknoten der Abstand zum fertigen Baum vermerkt (nicht zum Anfangsknoten) Beispiel: 6 v0 v1 6 v0 1 2 v4 2 3 v2 2 6 v0 v0 1 v3 v1 v4 2 3 3 v2 2 2 6 v0 1 v4 2 3 2 v3 3 1 2 v3 v1 3 2 v3 2 2 1 1 v4 3 • Aufwand: O(m + n log n) (mit Fibonacci-Heap) v4 2 v2 2 2 v2 v1 3 3 2 2 6 2 2 2 v2 v1 3 2 2 2 6 2 v3 3 3 168 6.7 Maximaler Fluss • gegeben: gewichteter Graph G = (V, E, β) mit β : E−→IR+ Kantenkapazität q∈V Quelle s∈V Senke • gesucht: maximaler Fluss f : E−→IR+ von q nach s P P d.h. max ( f (q, v) − f (v 0, q)) mit: f :E−→IR+ (q,v)∈E (v 0 ,q)∈E * f (e) ≤ β(e) ∀e ∈ E * P (v 0 ,v)∈E 0 f (v , v) − P (v,v 00 )∈E Kapazitätsrestriktion f (v, v 00) = 0 ∀v ∈ V − {q, s} Flusserhaltung 169 Beispiel: Fluss 4/ 4 3/ 2 v0 v1 1/ 0 3/ 2 q 4/ 3 v2 Definition: 3/ 3 s 1/ 1 3/ 2 1/ 1 v3 5/ 4 • ein zunehmender Weg w von q nach s mit Kapazität k ist eine 0 • Kantenfolge ( v00 , v10 ), (v10 , v20 ), . . . , (vr−1 , vr0 ) mit |{z} |{z} =q =s 0 0 ? f (vi0 , vi+1 ) + k ≤ β(vi0 , vi+1 ), 0 ? f (vi+1 , vi0 ) − k ≥ 0, (für i = 0, . . . r − 1) 0 falls (vi0 , vi+1 )∈E 0 falls (vi+1 , vi0 ) ∈ E oder 170 Beispiel: Flusserhöhung 4/ 4 3/ 2 v0 1/ 0 3/ 2 q 4/ 3 v2 v1 s 1/ 1 3/ 2 1/ 1 v3 4/ 4 3/ 3 5/ 4 1/ 0 3/ 3 q 4/ 4 • Fluss f 0 nach Addition des zunehmenden Weges w: 0 0 f 0(vi0 , vi+1 ) := f (vi0 , vi+1 ) + k, 3/ 1 v0 v2 v1 3/ 3 s 1/ 1 3/ 3 1/ 1 v3 5/ 5 (für i = 0, . . . r − 1) 0 falls (vi0 , vi+1 )∈E 0 0 und f (vi0 , vi+1 ) + k ≤ β(vi0 , vi+1 ) 0 0 f 0(vi+1 , vi0 ) := f (vi+1 , vi0 ) − k, sonst 171 Algorithmus von Ford/Fulkerson für alle e ∈ E: f (e) = 0; while ∃ zunehmenden Weg w von q nach s do sei k die (max.) Kapazität von w erhöhe f entlang w um k • zur Implementierung: speichere f (e) für jede Kante e • bei naiver Wahl von w ist weder die Termination noch die Korrektheit garantiert Aufwand im schlechtesten Fall: • abhängig vom Vorgehen bei der Bestimmung des zunehmenden Weges 1) wenn w mit maximaler Kapazität: O(m · log βmax) 2) wenn w mit minimaler Kantenanzahl: O(n · m2) 172 Andere Algorithmen zur Bestimmung des maximalen Flusses • Algorithmus von Dinic: O(n2 · m) • Algorithmus von Karsanow/Tarjan: O(n3) • Algorithmus von Sleator/Tarjan: O(n · m · log n) • Details siehe Ottmann/Widmayer Bemerkungen: • maximaler Fluss entspricht “minimalem Schnitt” • ein Fluss f ist maximal, wenn es keinen zunehmenden Weg gibt