Technische Universität München Technische Universität München Kapitel 10 Algorithmen auf Graphen Motivation: Graphen sind Datenstrukturen, die in der Praxis in sehr vielen Bereichen auftreten, u.a. Routenplanen, Kommunikation im Internet, Streckenplanung im Schienenverkehr, Planung von Leiterbahn-Entwürfen im Chipdesign, Planung von Materialflüssen in der Logistik und Produktion etc. Zur Erinnerung • Ein Graph ist ein Tupel G = (E, V) mit – V: Menge von Knoten (Vertices) – E: Menge von Kanten (Edges), (v, w) ∈ E, mit v, w ∈ V • Attributierter Graph: • Abbildung w: E → EA , EA Menge von Kanten-Gewichten • Abbildung a: V → EV , EV Menge von Knoten-Attributen 1 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 10.1 Breitensuche (Breadth First Search), BFS • Durchsuchen eines Graphen in der Breite Idee: Gegeben sei ein Graph G = (E, V) – Ausgehend von einem Startknoten s ∈ V werden schrittweise alle Knoten des Graphen aufgesucht. – Es wird für jeden Knoten v ∈ V der Abstand k zum Startknoten berechnet, also die Länge des Pfades von s zu v – es werden zunächst alle Knoten mit Abstand k, danach alle Knoten mit Abstand k + 1 etc. aufgesucht. Beispiel: Technische Universität München Technische Universität München BFS: Beispiel: Breitensuchbaum Algorithmus zur Breitensuche BFS Basis: Graph G ist bereits durch Adjazenzlisten dargestellt BFS(G, s) Datenstrukturen des Algorithmus 1 for alle Knoten u ∈ V[G] - {s} • Der Bearbeitungszustand von Knoten 2 farbe[u] = WEISS 3 d[u] = ∞ wird über das Attribut „farbe“ gespeichert: 4 π[u] = NIL schwarz: bearbeitet; weiß: noch nicht aufgesucht; grau: aufgesucht, aber noch nicht abschließend bearbeitet • Für jeden Knoten v ∈ V: – farbe(v) enthält seine Farbe – π(v) ist der Vorgänger-Knoten – d(u) ist der Abstand zur Wurzel s – FIFO-Warteschlange Q mit grauen Knoten AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 2 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 3 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 5 6 7 8 9 10 11 12 13 14 15 16 17 18 farbe[s] = GRAU d[s] = 0 π[s] = NIL Q=Ø ENQUEUE(Q, s) while Q ≠ Ø u = DEQUEUE(Q) for alle v ∈ Adj[u] if farbe[v] == WEISS farbe[v] = GRAU d[v] = d[u] + 1 π[v] = u ENQUEUE(Q, v) farbe[u] = SCHWARZ 4 Technische Universität München Beispiel: Gegeben sei der Graph G BFS(G, s) 1 for alle Knoten u ∈ V[G] - {s} 2 farbe[u] = WEISS 3 d[u] = ∞ 4 π[u] = NIL 5 farbe[s] = GRAU 6 d[s] = 0 7 π[s] = NIL 8 Q=Ø 9 ENQUEUE(Q, s) 10 while Q ≠ Ø 11 u = DEQUEUE(Q) 12 for alle v ∈ Adj[u] 13 if farbe[v] == WEISS 14 farbe[v] = GRAU 15 d[v] = d[u] + 1 16 π[v] = u 17 ENQUEUE(Q, v) 18 farbe[u] = SCHWARZ Adjazenzmatrix von G AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Der Starknoten sei s • s wird Wurzel des Suchbaums Zeile 1-4: Initialisierung • alle v mit weiß markieren, noch nicht besucht • Abstand zu s noch unklar: auf unendlich • Vorgänger noch unbekannt: NIL Beispiel Fortsetzung Erinnerung: • schwarz: bearbeitet • weiß: noch nicht aufgesucht • grau: nicht zu Ende bearbeitet Zeile 5-9: Initialisiere s • markieren als grau, in Q • Abstand zu sich selbst: 0 Zeile 12-17: Bearbeite s • bearbeite alle direkten Nachfolger von s • und deren direkte NF (gehe 5 in die Breite!) Beispiel: Übergang von (a) zu (b) • schattierte Kanten (s,r), (s,w) zu Nachfolgern von s werden Kanten des Breitensuchbaums • Attribut im Knoten u: d[u] Abstand zur Wurzel s • Knoten w, r sind als nächstes zu bearbeiten: Speicherung in Q Übergang von (b) zu (c) • Knoten w bearbeiten; Kanten (w,f), (w,x) in Breitensuchbaum aufnehmen 6 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Laufzeitanalyse von BFS: • Nach der Initialisierung wird kein weiterer weißer Breitensuchbaum : Technische Universität München BFS(G, s) 1 for alle Knoten u ∈ V[G] - {s} 2 farbe[u] = WEISS 3 d[u] = ∞ 4 π[u] = NIL 5 farbe[s] = GRAU 6 d[s] = 0 7 π[s] = NIL 8 Q=Ø 9 ENQUEUE(Q, s) 10 while Q ≠ Ø 11 u = DEQUEUE(Q) 12 for alle v ∈ Adj[u] 13 if farbe[v] == WEISS 14 farbe[v] = GRAU 15 d[v] = d[u] + 1 16 π[v] = u 17 ENQUEUE(Q, v) 18 farbe[u] = SCHWARZ Knoten mehr aufgenommen, deshalb stellt der • Test in Zeile 13 sicher, dass jeder Knoten nur einmal in Q eingefügt wird: Einfügen erfordert O(1). • Jeder Knoten wird damit auch höchstens einmal aus Q wieder entnommen: Entnehmen erfordert O(1). Damit gilt: Laufzeit der Operationen auf Q: O(|V|). • Die Adjazenzliste eines Knotens v wird bei Entnahme des Knotens aus Q geprüft, also nur einmal durchlaufen • Laufzeit zur Prüfung aller Adjazenzlisten ist: O(|E|) C-Implementierung der Breitensuche BFS void BreadthFirstSearch(int origin, int destination) { Queue *q = new Queue(); StartQueue(q); Enqueue(q, origin); while(IsQueueEmpty(q) == 0) { int u = Dequeue(q); if(u == destination) { printf("Path found."); break; } //………continued Damit ergibt sich die Gesamtlaufzeit von BFS: O(|V| + |E|) AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 7 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 8 Technische Universität München Technische Universität München C-Implementierung der Breitensuche BFS C-Implementierung der Breitensuche BFS else else { { visited[u] = 1; printf("Queue full."); for(int v = 1; v <= 20; v++) { if(map[u][v] != 0) { if(visited[v] == 0) { visited[v] = 1; break; } } else { ShowPath(v); return; parents[v] = u; } } if(v != destination) { if(!IsQueueFull(q)) { Enqueue(q, v); } } } } } ShowPath(v); printf("\n"); } //………continued 9 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Technische Universität München 10.2 Tiefensuche (Depth First Search), DFS Idee: Gegeben sei ein Graph G = (E, V) • Startknoten s: eine Kante (s, u) wird ausgewählt und der Knoten u wird weiter bearbeitet; falls s weitere Nachfolger hat, werden diese ‚vorgemerkt‘ auf Stack (für Backtracking). • Für u analoges Vorgehen: Auswahl einer Kante (u,r) und bearbeiten von r, alternative Wege werden ‚vorgemerkt‘. • Falls r schon fertig bearbeitet ist, dann zurück zum letzten Verzweigungspunkt (Stack-pop) und mit dem Knoten weitermachen Beispiel 1 2 3 4 5 6 Startkonten sei s= 1 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 10 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 11 • Nutzung von zwei zusätzlichen Markierungen: Werte 1 ≤ x ≤ 2|V| – Zeitmarke, wann Knoten a entdeckt und grau gefärbt wurde, in d[a] – Zeitmarke, wann seine adjazenten Knoten bearbeitet sind und a schwarz wurden, f[a] • Eingabe: gerichteter oder ungerichteter Graph G DFS(G) 1 for alle Knoten a ∈ V[G] 2 farbe[a] = WEISS 3 π[a] = NIL 4 zeit = 0 5 for alle Knoten a ∈ V[G] 6 if farbe[a] == WEISS 7 DFS-VISIT(a) DFS-VISIT(a) 1 farbe[a] = GRAU // Ein weißer Knoten a wurde entdeckt. 2 zeit = zeit + 1 3 d[a] = zeit 4 for alle b ∈ Adj[a] // Verfolge die Kante (a, b). 5 if farbe[b] == WEISS 6 π[b] = a 7 DFS-VISIT(a) 8 farbe[a] = SCHWARZ // a wird geschwärzt und ist abgearbeitet. 9 f[a] = zeit = zeit + 1 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 12 Technische Universität München Technische Universität München Laufzeit von DFS: • Zeile 1-3, 5-7 (ohne VISIT): Θ(|V|) • |V| mal Prozedur VISIT, • In der Prozedur Visit: Zeile 4-7 |Adj(v)| mal durchlaufen. Beispiel: wegen gilt für die Laufzeit von VISIT : Θ(|E|) • Gesamtlaufzeit DFS: Θ(|V| + |E|), gleiche Laufzeit wie BFS DFS(G) 1 for alle Knoten a ∈ V[G] 2 farbe[a] = WEISS 3 π[a] = NIL 4 zeit = 0 5 for alle Knoten a ∈ V[G] 6 if farbe[a] == WEISS 7 DFS-VISIT(a) 13 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen DFS-VISIT(a) 1 farbe[a] = GRAU // Ein weißer Knoten a wurde entdeckt. 2 zeit = zeit + 1 3 d[a] = zeit 4 for alle b ∈ Adj[a] // Verfolge die Kante (a, b). 5 if farbe[b] == WEISS 6 π[b] = a 7 DFS-VISIT(a) 8 farbe[a] = SCHWARZ // a wird geschwärzt und ist abgearbeitet. 9 f[a] = zeit = zeit + 1 Technische Universität München Technische Universität München 10.3 Minimaler Spannbaum (spanning tree), MST • In der Praxis sehr häufig verwendete Verfahren, um kostengünstige, zusammenhängende Netzwerke zu erstellen: z.B. Telefonnetz, elektrische Schaltungen, bei Rechnernetzen: Protokoll, um (Endlos-)Schleifen und redundante Pfade in geswitchten LANs zu vermeiden, etc. Definition: Minimaler Spannbaum • Gegeben sei ein ungerichteter, zusammenhängender Graph G = (E, V) und eine Gewichtsfunktion w: E → ℝ. • Für jede Kante (u, v) ∈ E ist ein Kanten-Gewicht w(u, v) gegeben, das die Kosten für die Verbindung von u mit v beschreibt. • Ein Minimaler Spannbaum von G ist ein Graph MST = (T, V) mit • einer Menge von Kanten T ⊆ E, so dass gilt: • T enthält keine Zyklen und • verbindet alle Knoten aus V und • das Gesamtgewicht w(T), mit ist minimal. • MST ist ein Baum, er wird als Spannbaum von G bezeichnet. Beispiel: Ungerichteter Graph G und sein MST AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Frage: wie ist ein MST definiert und wie bestimmt man ihn? 14 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 15 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 16 Technische Universität München Technische Universität München Beispiel: Minimaler Spannbaum eines ungerichteten Graphen • Im Beispiel bilden die schattierten Kanten den minimalen Spannbaum. • Die Kanten sind mit ihren Gewichten attributiert. • Gesamtgewicht des Baums? Aufbau eines minimalen Spannbaums Zwei Standardalgorithmen zur Bestimmung eines MST: • Algorithmus von Prim und Algorithmus von Kruskal. • Beide Algorithmen arbeiten nach der Greedy-Strategie. • Beide Algorithmen benötigen eine Laufzeit: O(|E| log2 |V|), wenn als Datenstrukturen binäre Heaps verwendet werden. 10.3.1 Greedy-Strategie • Anwendbar in Algorithmen, die aus verschiedenen Möglichkeiten eine auswählen müssen. • Geedy (gierig) Ansatz: Auswahl der aktuell besten Möglichkeit. • Eine solche Strategie garantiert aber nicht, dass auch ein globales Optimum gefunden wird! Bem.: Minimaler Spannbaum ist nicht eindeutig. Alternativer Spannbaum mit gleichem minimalem Gesamtgewicht? 17 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 18 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Technische Universität München Generischer (allgemeiner) Algorithmus MST: • Hilfsdatenstruktur A, die eine Kantenmenge repräsentiert, die eine Teilmenge des minimalen Spannbaumes ist. • In jedem Schritt wird eine Kante (u, v) bestimmt, die der Menge A hinzugefügt werden kann, ohne die Eigenschaft von A zu verletzen. • Eine solche Kante nennen wir sichere Kante. Beispiel: Vorgehen gemäß der Greedy-Strategie GENERIC-MST(G, w) 1 A={} 2 while A bildet keinen Spannbaum 3 bestimme eine Kante (u, v), die sicher für A ist 4 A = A ∪ {(u, v)} 5 return A Zeile 3 des Algorithmus erfordert Regeln, um eine geeignete Kante zu bestimmen. Der Algorithmus von Kruskal legt hierfür eine bestimmte Regel fest. AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 19 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 20 Technische Universität München Technische Universität München 10.3.2 Algorithmus von Kruskal Vorab-Bemerkungen Gegeben sei ein Graph G = (E,V). • G besteht aus n Zusammenhangskomponenten Gi=(Ei,Vi), i ∈ {1, .., n}, wobei gilt: E = E1 ∪ E2 ∪ … ∪ En , V = V1 ∪ V2 ∪ … ∪ Vn und jeder Graph Gi ist zusammenhängend • falls G ein Baum ist, dann ist n=1. Für den Kruskal-Algorithmus gilt: die Hilfsdatenstruktur A ist eine Menge von Zusammenhangskomponenten Beispiel: Graph mit 4 Zusammenhangskomponenten 21 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Algorithmus von Kruskal • Bestimmung der zu A hinzuzufügenden sicheren Kante: • Wähle von allen Kanten, die zwei Bäume des Waldes verbinden, eine Kante (u, v) mit dem kleinsten Gewicht aus. Idee: (1) Sortiere die Kanten nach ihren Gewichten. Aufwand? (2) Test für eine Kante (u, v): • Verbindet die Kante zwei Bäumen und macht daraus einen • oder erzeugt sie einen Kreis im Graph? Kreise sind nicht erwünscht! Beispiel: Technische Universität München Technische Universität München Algorithmus von Kruskal (Forts.) Aus dem zuvor Gesagten folgt: Der Algorithmus benötigt Datenstrukturen, 1. mit denen für jede Kante (u, v) ∈ E effizient entschieden werden kann, ob u und v in derselben Zusammenhangskomponente von G liegen, und 2. die die Zusammenhangskomponenten effizient verschmelzen. Lösung: Union-Find-Datenstruktur für disjunkte, dynamische Mengen: Find: Lösung für Aufgabe (1.) von oben Union: Lösung für Aufgabe (2.) AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 22 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 23 Union-Find-Datenstruktur • Gegeben sei eine Menge U von Elementen. Verwaltet wird eine Familie {S1, S2, …, Sk} von disjunkten Teilmengen von U. • jede Teilmenge Si wird identifiziert durch ein Element aus Si, das ist ein Repräsentant von Si. Folgende Operationen werden benötigt: • MAKE-SET(x): 1-elementige Menge erzeugt Teilmenge {x} mit Repräsentant x. • FIND-SET(x): liefert den Repräsentant derjenigen Menge, die x enthält. • UNION(x, y): Vereinigen der Teilmengen, die x bzw. y enthalten. AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 24 Technische Universität München Technische Universität München Realisierung einer Union-Find-Datenstruktur z.B. mit Verketteten Listen: • Jede Teilmenge Si wird als eine verkettete Liste Si der Elemente in Si repräsentiert. • Der Repräsentant von Si ist das erste Element von Si. • Für jedes Listenelement: verwalten eines Verweises auf den Repräsentanten rep(x) der Liste. Beispiel: Realisierung der Operationen mittels der verketteten Liste MAKE-SET(x) • MAKE-SET(x): erzeugen einer 1-elementigen Liste, Laufzeit O(1). • FIND-SET(x): Ausgabe des Repräsentanten rep(x), Laufzeit O(1). 1 2 3 rep[x] = x next[x] = NIL size[x] = 1 FIND-SET(x) 1 return rep[x] • UNION(x, y): Sx enthalte x und Sy enthalte y. (1) Bestimme die kürzere der beiden Listen Sx, Sy. (2) Hänge kürzere Liste an längere Liste an. (3) ersetze Repräsentanten für die Elemente der kürzeren Liste. Laufzeit proportional zur Länge der kürzeren Liste. 25 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 26 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Technische Universität München C-Implementierung einer Union-Find-Datenstruktur mit verketteten Listen C-Implementierung einer Union-Find-Datenstruktur mit verketteten Listen // the union_find.c file #include <stdlib.h> // the union_find.h file #include "union_find.h" #ifndef _UNION_FIND_H_ #define _UNION_FIND_H_ forest_node* MakeSet(void* value) { forest_node* node = malloc(sizeof(forest_node)); node->value = value; node->parent = NULL; node->rank = 0; return node; } typedef struct forest_node_t { void* value; struct forest_node_t* parent; int rank; } forest_node; forest_node* MakeSet(void* value); void Union(forest_node* node1, forest_node* node2); forest_node* Find(forest_node* node); AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen void Union(forest_node* node1, forest_node* node2) { if (node1->rank > node2->rank) { node2->parent = node1; } else if (node2->rank > node1->rank) { node1->parent = node2; } else { /* they are equal */ node2->parent = node1; node1->rank++; } } //…………… continued 27 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 28 Technische Universität München Technische Universität München Put it all together: Algorithmus von Kruskal • Benutzung einer Union-Find-Datenstruktur A, um die Zusammenhangskomponenten (hier Bäume) von G zu verwalten. • Test, ob eine Kante (u, v) zwei Zusammenhangskomponenten verbindet, durch Vergleich: FIND-SET(u) == FIND-SET(v)? • Verschmelzen von Zusammenhangskomponenten durch UNION. C-Implementierung einer Union-Find-Datenstruktur mit verketteten Listen // the union_find.c file forest_node* Find(forest_node* node) { forest_node* temp; /* Find the root */ forest_node* root = node; while (root->parent != NULL) { root = root->parent; } /* Update the parent pointers */ while (node->parent != NULL) { temp = node->parent; node->parent = root; node = temp; } return root; } MST-KRUSKAL(G, w) 1 A={} 2 for alle Knoten v ∈ V[G] 3 MAKE-SET(v) 4 sortiere die Kanten E in nichtfallender Reihenfolge nach dem Gewicht w 5 for alle Kanten (u, v) ∈ E 6 if FIND-SET(u) ≠ FIND-SET(v) 7 A = A ∪ {(u, v)} 8 UNION(u, v) 9 return A 29 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Technische Universität München Beispiel: Start mit n Teilbäumen, mit |V| = n, also jeder Knoten wird als ein eigener Teilbaum betrachtet. n-mal, wird die Operation MAKE-SET aufgerufen (Zeilen 2-3) Zeile 4: Sortieren der Kanten E= {(h,g), (g,f), (i,c), (a,b),(c,f), … } MST-KRUSKAL(G, w) Zeilen 5-8: bearbeite Kante (h,g) 1 2 3 4 5 6 7 8 9 A={} for alle Knoten v ∈ V[G] MAKE-SET(v) sortiere die Kanten E in nichtfallender Reihenfolge nach dem Gewicht w for alle Kanten (u, v) ∈ E if FIND-SET(u) ≠ FIND-SET(v) A = A ∪ {(u, v)} UNION(u, v) return A AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 30 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 31 Beispiel: Forts. MST-KRUSKAL(G, w) 1 A={} 2 for alle Knoten v ∈ V[G] 3 MAKE-SET(v) 4 sortiere die Kanten E in nichtfallender Reihenfolge nach dem Gewicht w 5 for alle Kanten (u, v) ∈ E 6 if FIND-SET(u) ≠ FIND-SET(v) 7 A = A ∪ {(u, v)} 8 UNION(u, v) 9 return A AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 32 Technische Universität München Technische Universität München Laufzeit des Kruskal Algorithmus: • Zeile 1: Initialisierung der Menge A: O(1) • Zeile 2-3: |V| mal MAKE-SET Operation • Zeile 4: Sortieren der Kanten: O(|E| log2|E|). • Es gibt höchstens |E| FIND-SET und |V| – 1 UNION Operationen. pro Kante: Herausfinden, ob Kreis geschlossen wird (FIND-SET) Verbinden der Teilbäume (UNION) Gesamtlaufzeit: O(|V| * UNION + |E| * Find + |E| * log2|E|) , • Implementierung mit Heaps: FIND: O(log (|E|)), UNION: O(log2(|V|)), also: O(|V| * log2(|V|) + |E| * log2 (|E|) + |E| * log2|E|) Man kann zeigen: TKruskal = O(|E| log2|V|). Beispiel (Forts.) Bem. Der Algorithmus von Prim arbeitet auf einem Baum, statt auf Wäldern; Nutzung von Prioritäten-Warteschlange 33 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Technische Universität München C-Implementierung des Algorithmus von Kruskal C-Implementierung des Algorithmus von Kruskal #include <stdio.h> #include <stdlib.h> #define MAX_EDGE 10000 #define MAX_VERTEX 100000 void Kruskal(int n, int m) { int i, k; for ( i = 0, k = 0 ; i < m, k < n - 1 ; i++ ) { if ( Find(edge[i].v1) != Find(edge[i].v2) ) { mst[k] = edge[i]; Union(edge[i].v1, edge[i].v2); k++; } } struct Edge { int v1, v2; double weight; }; struct Set { int parent, rank; }; } void Union(int a, int b) { int aRoot, bRoot; aRoot = Find(a); bRoot = Find(b); if ( set[aRoot].rank < set[bRoot].rank ) set[aRoot].parent = bRoot; else if ( set[aRoot].rank > set[bRoot].rank ) set[bRoot].parent = aRoot; else if ( aRoot != bRoot ) { set[bRoot].parent = aRoot; set[aRoot].rank++; } } typedef struct Edge Edge; typedef struct Set Set; Edge edge[MAX_EDGE], mst[MAX_EDGE]; Set set[MAX_VERTEX]; void Kruskal(int, int); void Union(int, int); int Find(int); void MakeSet(int); int compare(void const*, void const*); AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 34 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 35 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 36 Technische Universität München Technische Universität München C-Implementierung des Algorithmus von Kruskal C-Implementierung des Algorithmus von Kruskal int Find(int a) { if ( set[a].parent == a ) return a; else { set[a].parent = Find(set[a].parent); return set[a].parent; } } int main() { int n, m; int i; //scanf("%d %d", &n, &m); for ( i = 0 ; i < m ; i++ ) scanf("%d %d %lf", &edge[i].v1, &edge[i].v2, &edge[i].weight); MakeSet(n); qsort(edge, m, sizeof(Edge), compare); Kruskal(n, m); for ( i = 0 ; i < n - 1 ; i++ ) printf("%d %d %lf\n", mst[i].v1, mst[i].v2, mst[i].weight); return 0; void MakeSet(int n) { int i; for ( i = 0 ; i < n ; i++ ) { set[i].parent = i; set[i].rank = 0; } } } int compare(const void *a, const void *b) { if ( (*(Edge*)a).weight < (*(Edge*)b).weight ) return -1; else if ( (*(Edge*)a).weight == (*(Edge*)b).weight ) return 0; else return 1; } 37 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 38 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Technische Universität München 10.4 Kürzeste Pfade 10.4.1 Grundlagen • Gegeben sei ein gewichteter, gerichteter Graph G = (E, V) mit der Gewichtsfunktion w: E → ℝ. • Das Gewicht eines Pfades p= ( v0, v1, …, vk) ist die Summe der Gewichte aller seiner Kanten, also: • Das Gewicht des kürzesten Pfades von u nach v ist definiert durch: Beispiele für Gewichte: Entfernungen, Kosten, Zeit, Ressourcen, … AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 39 Anwendungsbeispiel: Kürzester Pfad Routenplaner: Straßenkarte von Deutschland – Knoten sind Großstädte – Kanten sind Autobahnverbindungen 300 – Kantengewichte stellen die Fahrstrecken dar HH 150 B 250 H 200 200 350 mögliche Fragestellungen K – der kürzeste Weg von München (M) 200 F 300 nach Hamburg (HH) ? 150 – Was sind die kürzesten Verbindungen KA von München zu allen anderen 50 S deutschen Großstädten ? 250 – Was sind die kürzesten Verbindungen zwischen allen deutschen Großstädten ? AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 200 450 D 200 300 N 150 M 40 Technische Universität München Technische Universität München Beispiel: Kürzester Pfad (Forts.) kürzester Pfad von Startknoten S zum Zielknoten Z: HH Pfad P* mit minimalem Gewicht w(P*) 200 150 300 w(PM,B) = 150 + 200 + 450 = 800 H w(PM,B) = 150 + 300 + 200 = 650 w(PM,B) = 250 + 150 + 350 + 250 = 1.000 200 K 200 300 P*M, B = (M, N, D, B) B 250 200 350 450 D F 200 150 KA 50 300 N S 150 250 M 41 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Bekannte Algorithmen zur Berechnung kürzester Pfade: • Kürzeste Pfade in einem Graphen mit gegebenem Startknoten s: • Dijkstra-Algorithmus (nur positive Kantengewichte): Beschreibung in 10.4.2. Laufzeit O(|E| * log2 |V|). • Bellman-Ford-Algorithmus (auch negative Gewichte): Algorithmus bestimmt immer kleiner werdende Schätzwerte für das Gewicht des kürzesten Pfades von s zu allen anderen Knoten v des Graphen, bis kürzester Pfad ohne negative Zyklen erreicht. Laufzeit O(|V| * |E|). • Kürzeste Pfade in einem Graphen zwischen allen Knotenpaaren • Floyd-Warshall-Algorithmus (All Pairs Shortest Path Problem) Basis: Prinzip der dynamischen Programmierung. Zeit: Θ(|V|3) Technische Universität München Satz: Teilpfade von kürzesten Pfaden sind auch kürzeste Pfade. Beweis: • Sei P = (vS, ... vi, ... vj, ... vz ) ein kürzester Pfad von Knoten vS zum Knoten vz. • Sei Pij = (vi, vi+1, ..., vj) der Teilpfad in P von vi nach vj • Es gilt w(P) = w(PSi) + w(Pij) + w(Pjz) Ann.: P*ij sei eine Abkürzung von vi nach vj, d.h. w(P*ij) < w(Pij), dann gilt: w(P) = w(PSi) + w(Pij) + w(Pjz) > w(PSi) + w(P*ij) + w(Pjz) = w(P‘) PSi Pij PjZ Widerspruch zur AnvS vZ vi vj nahme, denn dann wäre P kein kürzester Pfad, sondern P‘ wäre kürzer. AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 43 Pij* 42 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München 10.4.2 Dijkstra-Algorithmus Gegeben Graph G=(E,V) und Startknoten s. Bestimme kürzesten Pfad von s zu allen Knoten v ∈ V Einsatzgebiete u.a. • Routenplaner: Graph repräsentiert das Straßennetz, • Im Internet als Routing-Algorithmus (Wegewahl) im OSPF- und OLSR-Protokoll (für mobile drahtlose LANs, mobile Ad-hoc Netze) eingesetzt. Beispiel: Routing von Daten im Internet AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 44 Technische Universität München Technische Universität München Basis des Dijkstra-Algorithmus: • Gerichteter, gewichteter Graph mit nicht-negativen Kantengewichten. • Hilfsdatenstruktur S: Menge von Knoten, deren Gewichte der kürzesten Pfade von s aus bereits abschließend bestimmt wurden. • Hilfsdatenstruktur Q: Min-Prioritätenwarteschlange für Knoten Dijkstra arbeitet nach Greedy-Strategie und mit der Methode der Relaxation: Relaxation: • für jeden Knoten v ∈ V wird ein Attribut d[v] verwaltet, das ist die Schätzung des kürzesten Pfades von s zu v (obere Schranke für Gewicht des kürzesten Pfades). 45 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Relaxieren einer Kante (u, v): • Testen, ob der bisher gefundene kürzeste Pfad nach v noch verbessert werden kann, indem über den Knoten u gegangen wird. Relaxationsschritt: • für jeden Knoten v ∈ V wird ein Attribut d[v] verwaltet, d[v] ist die INITIALIZE-SINGLE-SOURCE(G, s) Schätzung des kürzesten Pfades von s zu v 1 for alle Knoten v ∈ V[G] 2 d[v] = ∞ • Eine solche Schätzung wird initialisiert: 3 π[v] = NIL 4 d[s] = 0 Nach der Initialisierung gilt: • Der Vorgängerknoten π[v] = NIL für alle v ∈ V, • d[s] = 0 und • d[v] = ∞ für alle v ∈ V – {s} 46 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Technische Universität München Pseudocode für einen Relaxationsschritt auf der Kante (u,v): Beispiel: Relaxieren einer Kante (u, v) mit dem Gewicht w(u, v) = 2. In (a) verringert sich die Schätzung, in (b) nicht. RELAX(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 d[v] = d[u] + w(u, v) 3 π[v] = u Beispiel Bem.: der Dijkstra-Algorithmus relaxiert jede Kante genau einmal. AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 47 • In jedem Knoten ist seine Schätzung des kürzesten Pfades eingetragen. • (a) vor der Relaxation gilt: d[v] > d[u] + w(u, v), d.h. es verringert sich der Wert von d[v]. • (b) vor der Relaxation gilt: d[v] ≤ d[u] + w(u, v), so dass sich der Wert von d[v] nicht verändert. AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 48 Technische Universität München Technische Universität München Dijkstra-Algorithmus: • Eingabe: gerichteter Graph und Gewichtsfunktion w • Vom Startknoten s werden die kürzest möglichen Wege weiterverfolgt. • Dazu wird in jedem Schritt der Knoten u ∈ V – S ausgewählt, der die kleinste Schätzung des kürzesten Pfades besitzt, • u wird S hinzugefügt und alle Kanten v, die von u abgehen, werden relaxiert. DIJKSTRA(G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 S=Ø 3 Q = V[G] 4 while Q ≠ Ø 5 u = EXTRACT-MIN(Q) 6 S = S ∪ {u} 7 for alle Knoten v ∈ Adj[u] 8 RELAX(u, v, w) Beispiel: Startknoten s • die Schätzungen der kürzesten Pfade sind in Knoten eingetragen. • Schwarze Knoten gehören zur Menge S. • Weiße Knoten werden in der MinPrioritätenwarteschlange Q verwaltet. RELAX(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 d[v] = d[u] + w(u, v) 3 π[v] = u INITIALIZE-SINGLE-SOURCE(G, s) 1 for alle Knoten v ∈ V[G] 2 d[v] = ∞ 3 π[v] = NIL 4 d[s] = 0 INITIALIZE-SINGLE-SOURCE(G, s) 1 for alle Knoten v ∈ V[G] 2 d[v] = ∞ 3 π[v] = NIL 4 d[s] = 0 RELAX(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 d[v] = d[u] + w(u, v) 3 π[v] = u 49 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen DIJKSTRA(G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 S=Ø 3 Q = V[G] 4 while Q ≠ Ø 5 u = EXTRACT-MIN(Q) 6 S = S ∪ {u} 7 for alle Knoten v ∈ Adj[u] 8 RELAX(u, v, w) Technische Universität München Beispiel: Forts. DIJKSTRA(G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 S=Ø 3 Q = V[G] 4 while Q ≠ Ø 5 u = EXTRACT-MIN(Q) 6 S = S ∪ {u} 7 for alle Knoten v ∈ Adj[u] 8 RELAX(u, v, w) AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 50 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München RELAX(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 d[v] = d[u] + w(u, v) 3 π[v] = u Beispiel: Forts. 51 DIJKSTRA(G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 S=Ø 3 Q = V[G] 4 while Q ≠ Ø 5 u = EXTRACT-MIN(Q) 6 S = S ∪ {u} 7 for alle Knoten v ∈ Adj[u] 8 RELAX(u, v, w) AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen RELAX(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 d[v] = d[u] + w(u, v) 3 π[v] = u 52 Technische Universität München Beispiel: Forts. DIJKSTRA(G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 S=Ø 3 Q = V[G] 4 while Q ≠ Ø 5 u = EXTRACT-MIN(Q) 6 S = S ∪ {u} 7 for alle Knoten v ∈ Adj[u] 8 RELAX(u, v, w) Technische Universität München Nach der Terminierung des Dijkstra- Algorithmus: Für alle v ∈ V gilt: • d[v]: minimaler Abstand von v zum Startknoten s Gewicht des kürzesten Pfades • π[v]: Zeiger auf den Vorgänger • Kürzester Pfad von v zu s: π[v] → π[π[v]] ... s RELAX(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 d[v] = d[u] + w(u, v) 3 π[v] = u Beispiel: Kürzester Pfad von x zu s 53 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Laufzeitanalyse des Dijkstra-Algorithmus • Operationen auf der Warteschlange Q: • Insert (Zeile 3), Extract-Min (Zeile 5) • Decrease-Key (Zeile 8) Technische Universität München DIJKSTRA(G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 S=Ø 3 Q = V[G] 4 while Q ≠ Ø 5 u = EXTRACT-MIN(Q) 6 S = S ∪ {u} 7 for alle Knoten v ∈ Adj[u] 8 RELAX(u, v, w) • Insert und Extract-Min werden für jeden Knoten einmal aufgerufen: bei |V| Knoten also |V| Oinsert + |V| OExtract-Min • Jeder Knoten u wird einmal zu S hinzugefügt, so dass • jede Kante aus Adj[u] in der for-Schleife (Zeile 7-8) genau einmal untersucht wird. • Es gibt insgesamt |E| Kanten, d.h. entsprechend viele Iterationen der for-Schleife sind erforderlich, also höchstens |E| Decrease-Key Operationen. AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 54 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Eine mögliche Implementierung und deren Laufzeit Die Knoten des Graphen seien von 1 bis |V| nummeriert. • d[v] sei an der v-ten Position eines Feldes A gespeichert, also A[v] = d[v] • dann benötigt jede Insert und Decrease-Key Operation O(1). • Extract-Min erfordert O(|V|), da das Feld durchsucht werden muss, also Gesamtlaufzeit Dijkstra-Algorithmus: O(|V|2 + |E|) = O(|V|2) Beispiel: 55 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 56 Technische Universität München Technische Universität München Laufzeitverbesserungen durch geschickte Implementierung: • mit Min-Heap statt Prio-Warteschlange, • für Graphen mit wenigen Kanten sehr gut geeignet: • Extract-Min (|V| mal), und Decrease-Key (|E| mal) : O(log2 |V|). • Gesamtlaufzeit: O((|V| + |E|) * log2 |V|). Vereinfacht zu: O(|E| * log2 |V|), falls alle Knoten von Startknoten erreichbar. C-Implementierung des Algorithmus von Dijkstra #include <stdlib.h> #include "bool.h" #include "wgraph.h" #define MAXINT 100007 int parent[MAXV]; /* discovery relation */ dijkstra(graph *g, int start) { int i,j; bool intree[MAXV]; int distance[MAXV]; int v; int w; int weight; int dist; /* counters */ /* is the vertex in the tree yet? */ /* distance vertex is from start */ /* current vertex to process */ /* candidate next vertex */ /* edge weight */ /* best current distance from start */ for (i=1; i<=g->nvertices; i++) { intree[i] = FALSE; distance[i] = MAXINT; parent[i] = -1; } distance[start] = 0; v = start; 57 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 58 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen Technische Universität München Technische Universität München C-Implementierung des Algorithmus von Dijkstra C-Implementierung des Algorithmus von Dijkstra while (intree[v] == FALSE) { intree[v] = TRUE; for (i=0; i<g->degree[v]; i++) { w = g->edges[v][i].v; weight = g->edges[v][i].weight; if (distance[w] > (distance[v]+weight)) { distance[w] = distance[v]+weight; parent[w] = v; } } main() { graph g; int i; read_graph(&g,FALSE); dijkstra(&g,1); for (i=1; i<=g.nvertices; i++) find_path(1,i,parent); printf("\n"); v = 1; dist = MAXINT; for (i=1; i<=g->nvertices; i++) if ((intree[i] == FALSE) && (dist > distance[i])) { dist = distance[i]; v = i; } } } } AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 59 AuD, WS11/12, C. Eckert &Th. Stibor, Kapitel 10 Algorithmen auf Graphen 60