http://www.mpi-sb.mpg.de/~hannah/info5-ws00 IS UN R S WS 2000/2001 E R SIT S Schömer, Bast SA IV A Grundlagen zu Datenstrukturen und Algorithmen A VIE N Lösungsvorschläge für das 12. Übungsblatt Letzte Änderung am 31. Januar 2001 Aufgabe 1 Vorbemerkung. Wir interessieren uns für ungerichtete azyklische Graphen G = (V, E) mit maximaler Kantenzahl. Eine einfache Induktion (über |V |) zeigt, dass jeder Graph mit |E| ≥ |V | bereits zyklisch ist, oder äquivalent, G maximal azyklisch impliziert |E| = |V | − 1: Der Induktionsbeginn (|V | = 3) ist klar. Sei nun also G = (V, E) ein maximaler azyklischer Graph. Für den Induktionsschritt nutzt man aus, dass es ein v ∈ V mit deg(v) = 1 geben muss (“≤”, sonst wäre G nicht azyklisch, “≥” wegen Maximalität). Entfernen von v und inzidenter Kante e liefert G0 = (V − {v}, E − {e}), nach Induktionsvoraussetzung |E − {e}| = |V − {v}| − 1, also ist |E| = |V | − 1. Aus obigem folgt, dass |E| ≥ |V | schon impliziert, dass G zyklisch ist. Einer der Standardalgorithmen zum Finden von Zykeln (bfs, dfs, topSort. . . ) kann nun so modifiziert werden, dass erst auf |E| ≥ |V | geprüft wird, und dann der eigentliche Algorithmus nur bei |E| < |V | durchgeführt wird. In diesem Fall gilt für die Laufzeitschranke O(|E| + |V |) = O(|V |). Aufgabe 2 Der Graph sei als Adjazenzliste mit eingehenden Knoten gegeben. (Diese kann man leicht in O(|V | + |E|) aus einer Adjazenzliste mit ausgehenden Knoten erstellen.) Um aus den verschiedenen Quellen eine einzige zu machen, fügt man einen neuen Startknoten s in den Graphen ein und fügt eine Kante vom Gewicht 0 von s zu jeder der ursprünglichen Quellen hinzu. Analog wird eine Senke t sowie Kanten mit Gewicht 0 von den ursprünglichen Senken zu t hinzugefügt. Führe zunächst eine topologische Sortierung der Knoten durch. Sei min[v] (max[v]) die aktuelle Länge des kürzesten (längsten) Pfades von s nach v und pmin[v] (pmax[v]) der Vorgänger von v auf diesem Pfad. Setze zu Beginn min[v] = ∞ und max[v] = −1 für alle v ∈ V \ {s}, min[s] = max[s] = 0 sowie pmin[v] = pmax[v] =nil für alle v ∈ V . Tue dann für jeden Knoten v in topologisch aufsteigender Ordnung folgendes: for all e = (u, v) ∈ V : if (min[u] + d(u, v) < min[v]) min[v] = min[u] + d(u, v); pmin[v] = u if (max[u] + d(u, v) > max[v]) max[v] = max[u] + d(u, v); pmax[v] = u Dann entspricht nach Ende des Algorithmus min[v] (max[v]) der Länge des kürzesten (längsten) Pfades von s nach v, min[v] = ∞ (bzw. max[v] = −1) genau dann, wenn v nicht von s erreichbar ist. Also entspricht min[t] (max[t]) der Länge des kürzesten (längsten) Pfades von S nach T , den man mithilfe der pmin- bzw. pmax-Informationen zurückverfolgen kann. Laufzeit: Die topologische Sortierung geht in O(|V |+|E|), die Initialisierung in O(|V |), die Schleife in O(|V |+|E|) (für jede eingehende Kante eines Knotens hat man konstante Laufzeit, jeder Knoten wird bearbeitet). Also insgesamt O(|V | + |E|). Korrektheit: Zeige: Nach Ausführung des Algorithmus ist für alle v ∈ V min[v] (max[v]) gleich der Länge des kürzesten (längsten) Pfades von s nach v. Zeige dazu die Invariante: Nach Abarbeiten des i-ten Knotens, ist schon min[v] (max[v]) die Länge des kürzesten (längsten) Pfades von s nach v für alle v an den Positionen 1, ..., i. Induktion über die Position i in der topologischen Sortierung: i = 1: Der erste Knoten v in der Sortierung kann keine eingehenden Kanten haben, also wird weder min[v] noch max[v] verändert. Dieser Knoten ist entweder der künstlich erzeugte Knoten s oder ein isolierter Knoten. Ist er s, so bleiben min[v] und max[v] korrekterweise bei 0, ist er nicht s, bleibt min[v] bei ∞ und max[v] bei −1, was ebenso korrekt ist, denn es existiert ja kein Pfad von s zu diesem isolierten Knoten. i → i + 1: Gelte die Induktionsannahme für den i-ten Knoten in der topologischen Sortierung. Nun wird der (i + 1)−te Knoten v abgearbeitet. Dazu werden alle in diesen Knoten eingehenden Kanten betrachtet. Der Algorithmus setzt dann min[v] = min(u,v)∈E {min[u] + d(u, v)} (bzw. max[v] = max(u,v)∈E {max[u] + d(u, v)}. Nach Induktionsannahme ist jedes min[u] (max[u]) die Länge des kürzesten (längsten) Weges von s nach u, also entspricht min[v] (max[v]) nun der Länge des kürzesten (längsten) Weges von s nach v. Aufgabe 3 a) ((A · B) · C)ij = = = = = = n _ k=1 n _ k=1 n _ k=1 n _ l=1 n _ l=1 n _ (A · B)ik ∧ Ckj n _ l=1 n _ Ail ∧ Blk ! ∧ Ckj Ail ∧ Blk ∧ Ckj ! Ail ∧ Blk ∧ Ckj ! l=1 n _ k=1 Ail ∧ n _ Blk ∧ Ckj k=1 ! Ail ∧ (B · D)lj l=1 = (A · (B · C))ij . Dabei braucht man die Distributivität und die Assoziativität von ∨ und ∧. b) Behauptung: Für k ∈ N und 1 ≤ i, j ≤ n ist (Ak )ij = 1 genau dann, wenn Knoten j von Knoten i aus in ≤ k Schritten erreichbar ist. Beweis mit Induktion über k. k = 1: Das ist gerade die Definition der Adjazenzmatrix. k → k + 1: Es ist (Ak+1 )ij = (Ak · A)ij = n _ (Ak )il ∧ Alj = 1 l=1 genau dann, wenn ein 1 ≤ l ≤ n existiert mit (Ak )il = 1 und Alj = 1, also (nach Induktionsvoraussetzung) genau dann, wenn ein 1 ≤ l ≤ n existiert, so daß Knoten l von Knoten i aus in ≤ k Schritten erreichbar ist und Knoten j von Knoten l aus in ≤ 1 Schritten erreichbar ist. Das ist also genau dann der Fall, wenn Knoten j von Knoten i aus (auf dem Weg über Knoten l) durch ≤ k + 1 Schritte erreichbar ist. c) Ist Knoten j von Knoten i aus durch irgendeinen Pfad erreichbar, so kann man einen solchen Pfad mit ≤ n Schritten wählen. Hat dieser Pfad nämlich > n Schritte, so muß ein Zykel in dem Pfad sein, da n die Anzahl der Knoten im Graphen ist. Man kann dann diesen Zykel weglassen und erhält einen Pfad mit weniger Schritten. Das macht man solange, bis man einen Pfad mit ≤ n Schritten gefunden hat. Es ist also jeder Knoten j von einem Knoten i aus entweder nicht erreichbar, oder durch einen Pfad mit ≤ n Schritten erreichbar. Das bedeutet, daß An = An+k ist für alle k ≥ 0. Also ist lim Ak = An . k→∞ d) Nach Teil c) ist A∞ = An+k für alle k ≥ 0. Durch wiederholtes Quadrieren kann man A2 berechnen mit 2l−1 < n ≤ 2l , also l = dlog2 ne. l Man muß dabei l mal quadrieren. Für jede Quadrierung muß man n2 Elemente bestimmen. Zur Bestimmung von (A · B)ij braucht man n Vergleiche. Also ist die Laufzeit O(n3 log n). Man kann das wiederholte Quadrieren abbrechen, falls ein m existiert mit (Am )2 = Am , da dann schon der Grenzwert erreicht wurde. Für diesen Algorithmus muß man sich aber bei jeder Multiplikation ansehen, ob sich ein Element verändert oder nicht. Zweite Möglichkeit: Nach Teil b) ist (A∞ )ij = 1 genau dann, wenn Knoten j von Knoten i aus (auf irgendeinem Pfad) erreichbar ist. Man geht jetzt zeilenweise vor und bestimmt A∞ direkt. In der i-ten Zeile setzt man zuerst alle (A∞ )ij = 0. Dann macht man vom iten Knoten aus BFS. Wird dabei ein Knoten j erreicht (einschließlich j = i), so setzt man (A∞ )ij = 1. Die Laufzeit für eine Zeile ist somit O(|V | + |E|). Damit hat man insgesamt eine Laufzeit von O(|V |(|V | + |E|)). Mit |V | = n und |E| = O(n2 ) erhält man die Gesamtlaufzeit O(n3 ). Aufgabe 4 a) ((A · B) · C)ij = min{(A · B)ik + Ckj |k = 1, ..., n} = min{(min{Ait + Btk |t = 1, ..., n}) + Ckj |k = 1, ..., n} = = = = min{Ait + Btk + Ckj |t, k = 1, ..., n} min{Ait + (min{Btk + Ckj |k = 1, ..., n})|t = 1, ..., n} min{Ait + (B · C)tj |t = 1, ..., n} (A · B · C))ij b) formal: Beweis durch Induktion über k: k = 1: Enthält nach Definition der Distanzmatrix die kürzesten Wege der Länge ≤ 1. k = x + y: Induktionsvoraussetzung: Ax und Ay enthalten jeweils die richtigen Werte für kürzeste Wege der Länge ≤ x bzw. ≤ y Induktionsschritt: (Ax · Ay )ij := min{Axik + Aykj |k = 1, ..., n} Die Matrizenmultiplikationsvorschrift betrachtet die Wege von i nach j über k. Da es keine anderen Wege geben kann und nach IV Ax und Ay korrekt sind liefert die Minimumbildung der summierten Wegkosten den kürzesten Weg der maximalen Länge x + y = k. c) Da kürzeste Wege in G maximal alle Knoten einmal besuchen können (sonst könnte man einen Zyklus unter Wegverkürzung herausschneiden) ergibt sich eine obere Weglänge von n = Anzahl der Knoten. D.h. spätestens bei An kann sich keine Änderung mehr ergeben, weil längere Pfade nicht sinnvoll wären! d) find_shortestpaths(matrix A) { matrix B = A * A; if (A==B) return (B) else return (find_shortestpaths(B)); } Der Algorithmus verdoppelt immer die maximal zulässige Weglänge. Sollte sich bei der Matrizenmultiplikation nichts verändert haben, dann ist man fertig. Wenn sich etwas verändert hat, dann muss man die Routine nochmal mit der neuen Matrix aufrufen. Laufzeitanalyse: • Die Matrizenmultiplikation muss für jeden der Einträge (= n · n) die Länge der Wege über alle möglichen Knoten (= n) ausrechnen und dann das Minimum eintragen (O(n)). Daraus ergibt sich für diesen Punkt eine Gesamtlaufzeit von O(n3 ). • Aus Teil c) wissen wir das man höchstens An berechnen muss. Da sich die Weglänge immer verdoppelt müssen also log n Multiplikationen ausgeführt werden. • Damit ergibt sich die geforderte Laufzeit von O(n3 log n)!