Kapitel 1 Probleme, Komplexität, Berechnungsmodelle In der Vorlesung soll erlernt werden, Algorithmen - das sind wohldefinierte Verfahren zur Lösung von Problemen - zu entwerfen und zu analysieren. In der Vorlesung soll Effizienz von Algorithmen definiert und ihre Bestimmung erlernt werden, wobei sowohl der zeitliche Aufwand als auch der benötigte Speicher betrachtet werden. Es soll gezeigt werden, wie Effizienzbetrachtungen durch Analyse bereits beim Entwurf von Algorithmen angestellt werden. Dazu werden verschiedene Techniken vorgestellt. 1.1 Vier Beispiele für Algorithmen zum Sortieren Im Folgenden werden vier Algorithmen zum Sortieren vorgestellt. Gegeben ist eine endliche Folge S = a1 , . . . , an von Zahlen oder anderen Objekten, die man paarweise vergleichen kann. Berechne die Elemente von S aufsteigend sortiert, d.h. eine Permutation i1 , . . . , in von 1, . . . , n für die gilt ai1 ≤ ai2 ≤ . . . ≤ ain . Algorithmus A1 1. Erzeuge systematisch alle Permutationen i1 , . . . , in von 1, . . . , n. 2. Prüfe für jede Permutation, ob ai1 ≤ . . . ≤ ain erfüllt ist. (Dafür werden bis zu n − 1 Vergleiche benötigt.) 3. Falls die Bedingung erfüllt ist, halte und gib die Folge aus. Algorithmus A2 1. Durchlaufe die Folge S und finde ihr Minimum. 1 2. Gebe das Minimum aus. 3. Wiederhole die Schritte auf dem Rest der Folge, bis die Folge ganz abgearbeitet ist. Dieser Algorithmus ist unter dem Namen Sortieren durch Auswahl bekannt. Algorithmus A3 1. Falls die Folge S nur aus einem Element besteht, gib dieses aus (Verankerung). 2. Sonst teile S in S1 = a1 , . . . , ab n c und S2 = ab n c+1 , . . . , an . 2 2 3. Sortiere S1 und S2 rekursiv. 4. Mische die sortierten Teilfolgen zu einer sortierten Gesamtfolge. Dieser Algorithmus ist unter dem Namen Mergesort bekannt. Mergesort wurde erstmals 1945 von J. v. Neumann erwähnt. Er basiert auf dem Entwurfsprinzip divide and conquer. Algorithmus A4 1. Falls S nur ein Element enthält, gib dieses aus (Verankerung). 2. Sonst wähle zufällig ein j ∈ {1, . . . , } 3. durchlaufe S und teile es in S1 (für Elemente < aj ), S2 (für Elemente = aj ) und S3 (für Elemente > aj ) auf. 4. Sortiere S1 und S3 rekursiv und gibt S1 S2 S3 als Lösung aus. Dieser Algorithmus ist unter dem Namen Quicksort bekannt. Quicksort wurde 1962 von Hoare vorgestellt. Er basiert ebenfalls auf dem Divide-and-ConquerPrinzip. 1.1.1 Vergleich der Effizienz Wie vergleichen sich diese Algorithmen hinsichtlich ihrer Effizienz? Ist sie aus der verbalen Beschreibung des Algorithmus feststellbar? Als Komplexitätsmaß betrachten wir die Anzahl der Vergleiche zwischen Elementen als Funktion von n, also der Anzahl der zu sortierenden Elemente. Algorithmus A1 Im besten Fall, wenn die erste Permutation bereits die richtige ist, benötigt der Algorithmus n−1 Vergleiche. Dieser Fall ist allerdings sehr unwahrscheinlich. Im schlechtesten Fall ist die letzte gewählte Permutation die richtige. Es werden also höchstens n! · (n − 1) Vergleiche benötigt. Ungenau lässt sich für den mittleren Fall sagen, dass 12 n! · (n − 1) Vergleiche ausgeführt werden. 2 Algorithmus A2 In jedem Durchlauf entfernt der Algorithmus das Minimum der verbleibenden Folge S. Dazu sind je n − 1 Vergleiche nötig. Der Aufwand ist also n − 1 + n − 2 +... + 1 = | {z } | {z } 1. Durchl. 2. Durchl. n(n − 1) = O(n2 ). 2 Der Algorithmus hat also eine quadratische Laufzeit Bei diesem Algorithmus werden die meisten Vergleiche zwischen zwei Elementen häufig wiederholt und bereits gewonnene Informationen nicht genutzt. Algorithmus A3 T (n) sei die maximale Anzahl der Vergleiche als Funktion von n. Wir nehmen der Einfachheit halber an, dass n eine Potenz von 2 ist. T (1) = 0 und n + n T (n) ≤ 2 · T |{z} | {z2 } Vergleiche beim Mischen rek. Aufruf für n = 2k , k ≥ 1. Mischen Bildlich kann man sich zwei sortierte Listen vorstellen, die je einen Zeiger auf ihr aktuelles Element haben. Die beiden aktuellen Elemente werden miteinander verglichen, von denen das kleinere ausgewählt wird. Bei jedem Vergleich rutscht einer der Zeiger weiter zum nächst größeren Element. Das Mischen ist beendet, wenn einer der Zeiger das Ende seiner Liste erreicht hat. Im besten Fall, wenn alle Elemente einer Liste kleiner sind als das kleinste Element der anderen Liste, sind das also n2 Vergleiche, im schlechtesten Fall n Vergleiche. Abschätzen der Laufzeit Von der rekursiven Gleichung oben können wir die Laufzeit nicht ablesen. Wir suchen eine geschlossene Form. n +n T (n) ≤ 2 · T 2 n n n ≤2· 2·T + +n=4·T + 2n 4 2 4 .. . n ≤ 2k · T +k·n 2k ≤ 2log n · T (1) +n · log n | {z } =0 3 Da wir bei für die Laufzeitbetrachtung annehmen, dass n eine Potenz von 2 ist, ist der letzte (und k-te) rekursive Aufruf T (1), wobei für k gilt n = 1 ⇒ k = log n 2k Algorithmus A4 Die Anzahl der Vergleiche ist vom Zufall abhängig, ist also eine Zufallsvariable. Zufall ist beim Entwurf von Algorithmen häufig ein geeignetes Prinzip. Es erschwert allerdings die Analyse. Für den trivialen Fall gilt T (0) = T (1) = 0. Falls aj das k-t kleinste Element der Liste S war, dann gilt T (n) ≤ T (k − 1) + T (n − k) +n − 1. | {z } | {z } Teilfolge S1 Teilfolge S3 Im ungünstigsten Fall ist k = 1 oder k = n (aj ist das kleinste oder größte Element der Liste) und es ergibt sich folgende Kette von rekursiven Aufrufen. T (n) = T (n − 1) + n − 1 = T (n − 2) + n − 2 + n − 1 .. . = T (1) + 1 + 2 + · · · + n − 1 1 1 = n2 − n 2 2 Im ungünstigsten Fall hat Quicksort also eine Laufzeit von O(n2 ). Dieser Fall ist allerdings sehr unwahrscheinlich. Wir betrachten daher auch die mittlere Laufzeit, also den Erwartungswert der Zufallsvariablen. 4 Laufzeit von Quicksort im Mittel. Wir wollen die erwartete Effizienz von Quicksort ermitteln. Wir nehmen an, die Wahrscheinlichkeit, dass das gewählte Pivot-Element aj das k-t kleinste Element der Folge ist, ist gleich für alle k = 1, . . . , n und damit ist gleich n1 . Die obige Beschreibung der Laufzeit für ein bestimmtes k können wir also erweitern und erhalten die folgende Gleichung für den mittleren Fall: n 1X (T (k − 1) + T (n − k)) T (n) = (n − 1) + n k=1 Bis auf die Reihenfolge, in der sie auftreten, sind die von den Ausdrücken T (k−1) und T (n − k) erzeugten Summanden gleich, deshalb lässt sich die Summe kürzer schreiben: = (n − 1) + n−1 2X T (k) n k=0 Wir multiplizieren auf beiden Seiten mit n und ersetzen n durch n − 1: nT (n) = n(n − 1) + 2 n−1 X T (k) k=0 (n − 1)T (n − 1) = (n − 1)(n − 2) + 2 n−2 X T (k) k=0 Um T (n) und T (n − 1) miteinander in Verbindung zu bringen, ziehen wir die oberen beiden Zeilen voneinander ab und erhalten: nT (n) − (n − 1)T (n − 1) = 2(n − 1) + 2T (n − 1) nT (n) − (n + 1)T (n − 1) = 2(n − 1) n+1 n−1 T (n) = T (n − 1) + 2( ) n n Da lim 2 n−1 n = 2 · lim n→∞ n→∞ n−1 n = 2, wird dieser Teil gestrichen und wir erhalten eine (hinreichend genaue) Ungleichung: T (n) ≤ n+1 T (n − 1) n Wir entwickeln die Formel ein weiteres mal, multiplizieren dann aus und erweitern beim Ergebnis die letzte 2 um den Faktor n+1 n+1 : n + 1 n T (n − 2) + 2 + 2 n n−1 n+1 n+1 n+1 T (n − 2) + 2 +2 = n−1 n n+1 T (n) ≤ 5 Wir setzen noch einmal ein: = n + 1n − 1 n+1 n+1 T (n − 3) + 2 + 2 +2 n−1 n−2 n n+1 n+1 n+1 n+1 n+1 = T (n − 3) + 2 +2 +2 n−2 n−1 n n+1 Wir können jetzt erahnen, wie die Folge aussieht. Um sie korrekt herzuleiten, müsste an dieser Stelle ein Induktionsbeweis geführt werden, aber uns genügt das intuitive“ Ergebnis: ” T (k) = n+1 n+1 n+1 n+1 n+1 T (n − k) + 2 +2 +2 + ... + 2 n−k+1 n−k+2 n−k+3 n−k+4 2n − k + 1 Setze k = n: T (n) = 1 1 n+1 1 T (0) + 2(n + 1) + + ... + 1 2 3 n+1 Zur Abschätzung dieser Formel benötigen wir die harmonischen Zahlen. Sie sind n P 1 wie folgt definiert: Hn = k. k=1 Wir betrachten die Graphen der Funktionen f (x) = 1 x und g(x) = 1 x+1 4 f(x) g(x) Hn 3.5 3 2.5 2 1.5 1 0.5 0 0 0.5 1 1.5 2 2.5 3 3.5 Die harmonischen Zahl Hn kann geometrisch interpretiert werden als die Größe der Fläche, die sich aus den Rechtecken mit den Flächen der Größe k1 · 1 für 6 4 alle k = {1, . . . , n} zusammensetzt. Der Graph von f begrenzt diese Fläche offenbar von oben, der Graph von g von unten. Also können wir Hn mit Hilfe von Integralen abschätzen. Da wir f nicht von 0 integrieren können, beginnen wir bei 1 und addieren noch 1 für das erste Rechteck hinzu. Z n Hn ≤ Z1 n Hn ≥ 0 1 dx + 1 = 1 + ln n − ln 1 = 1 + ln n x 1 dx = ln(n + 1) x+1 Die Abschätzung ist recht genau: Der Unterschied zwischen den Schranken ist höchstens 1 (genauer: die Euler-(Mascheroni-)Konstante, also ' 0, 5772156649). Es gilt somit: Hn = Θ(ln n) bzw. sogar Hn = ln n + Θ(1) Nun ist es uns möglich, die erwartete Laufzeit von Quicksort abzuschätzen: T (n) ≤ 2(n + 1)(Hn+1 − 1) ≤ 2(n + 1) ln n + 1 = 2n · ln n + Θ(ln n) Umgerechnet in den Zweierlogarithmus: T (n) ≤ 2n log n + Θ(log n) ' 1,38n · log n + Θ(log n) log e Zusammengefasst haben wir folgende Laufzeiten für die Algorithmen Mergesort (A4) und Quicksort (A4) ermittelt: Mergesort: n · log n+ lineare Terme Quicksort: 1,38n · log n+ logarithmische Terme Ein erster Vergleich zeigt, dass Quicksort etwa um den Faktor 1,38 langsamer ist. Wir werden gleich darauf zurück kommen, warum dies in der Praxis etwas anders aussieht. Vergleich der 4 Algorithmen in der Praxis. Einige Zahlen sollen zeigen, dass der Unterschied der Algorithmen bei realer Rechenzeit trotz immer schneller werdender Prozessoren noch erheblich ist und ein Nachdenken über effiziente Algorithmen sich durchaus lohnt. 7 Die Anzahl der durchgeführten Vergleiche bei einer Eingabe der Länge n: n 10 20 50 n · log n 33 86 282 1 2 2n − 45 190 1225 n 2 n! 3,6 · 106 2,4 · 1018 3,0 · 1064 Größe der Probleme, die eine Maschine mit 109 Vergleichen pro Sekunde mittels der Algorithmen lösen kann: 1 Sekunde 1 Stunde n · log n 4 · 107 1 · 1011 1 2 2n − n2 4,0 · 104 2,6 · 106 n! 13 16 Dasselbe für eine 10-mal schnellere Maschine: 1 Sekunde 1 Stunde n · log n 3,5 · 108 9,1 · 1011 − n2 1,0 · 105 8,4 · 106 1 2 2n n! 14 17 Man sieht: Bei schneller werdendem Rechner wächst die Größe der berechenbaren Probleme bei schlechten Algorithmen entsprechend langsam – bei dem Algorithmus, der alle Permutationen eines Arrays durchgeht, um den sortierten zu finden, kann hier mit einem 10-mal schnelleren Rechner gerade mal ein um ein Feld längeres Array berechnet werden. Vergleich von Quicksort und Mergesort. Wenn man, wie hier, nur die Anzahl der Vergleiche als Maß nimmt, ist Mergesort zwar schneller, in der Realität wird aber Quicksort effizienter sein, weil es weniger Daten im Speicher lesen und schreiben muss. Bei Mergesort müssen die Daten in zwei getrennten Arrays abgelegt werden, Quicksort kann immer im selben Array operieren. Bei einer großen Anzahl von Vergleichen (bei einem handelsüblichen PC bei ca. 4000000) sieht man deshalb auch einen deutlichen Anstieg des Aufwands bei Mergesort. Dies liegt daran, dass irgendwann der Hauptspeicher voll ist und der Hintergrundspeicher (Swap) benutzt werden muss. Es gibt einen eigenen Zweig der Forschung, der Algorithmen entwickelt, die den Zugriff auf den Hintergrundspeicher vermeiden oder minimieren, weil es durchaus Probleme gibt, die derart viel Platz brauchen, dass dies stark ins Gewicht fällt. Beispiele finden sich etwa in der Meteorologie, wo Programme mit Millionen von Einzeldaten operieren. 1.2 Berechnungsmodelle Berechnungsmodelle sind mathematische Modelle für Rechner, die verwendet werden, um präzise Aussagen über Berechnungen treffen zu können. Begriffe wie etwa Algorithmus“, Laufzeit“ oder Speicher“ werden mit ihnen formal ” ” ” definiert. Das bekannteste Beispiel ist wohl die 1936 von Alan Turing entwickelte 8 Turingmaschine, die in dieser Vorlesung aber nicht eigens wiederholt wird. Definition 1.2.1 (Registermaschine (Random Access Machine, RAM)). Eine Registermaschine besteht aus einem unendlichen, aus den Zellen R0 , R1 , ... bestehenden Speicher. Diese können jeweils eine beliebig große ganze Zahl enthalten. Die Unbegrenztheit von Speicher und Inhalt einer Zelle stellen somit jeweils eine Abstraktion von realen Computern dar. Ein Programm ist eine endliche Folge von Befehlen. Ein Beispiel-Befehlssatz könnte etwa wie folgt aussehen. Ein in klammern gesetztes Register (Ri ) steht hier für den Wert der Speicherzelle Rj , wobei j der Wert von Ri ist. A := B op C A := B goto L GGZ B, L GLZ B, L GZ B, L HALT wobei A : Ri oder (Ri ), i ∈ N0 B, C : Ri oder (Ri ), i ∈ N0 oder Konstante k ∈ N0 op ∈ {+, −, ∗, /} wobei L eine Zeile des Programms ist gehe nach L, wenn B > 0 gehe nach L, wenn B < 0 gehe nach L, wenn B = 0 Beende Abarbeitung des Programms Jedem Befehl kann man so eine Semantik zuordnen. Auch dies kann man formalisieren – den Zustand einer RAM kann man beschreiben, indem man mit einer Funktion c : N → Z angibt, welcher Wert in jeder Speicherzelle steht. Wenn man dazu eine eine Menge von Funktionen definiert, die für jeden Zustand und jeden Befehl den Folgezustand angeben, hätte man eine vollständige operationelle Semantik. Definition 1.2.2 (Berechnung einer Funktion auf einer RAM). Eine Registermaschine R berechnet eine Funktion f : Z∗ → Z∗ bedeutet: Falls in den ersten m Speicherzellen eine Folge a0 , ..., am steht, rechnet“ R (läuft, bis es zu ” einem HALT-Befehl kommt) und hat dann f (a0 , . . . , am ) = (b0 , . . . , bn ) in die ersten Speicherzellen geschrieben. 1.2.1 Laufzeit und Speicherbedarf einer RAM Wir wollen nun die Laufzeit und den Speicherbedarf eine RAM formal definieren. Dafür gibt es verschiedene Kriterien, von denen hier zwei vorgestellt werden. Das Einheitskostenmaß (EKM): Man nimmt an, dass erstens die Ausführung von jedem Befehl indifferent eine Zeiteinheit kostet, und dass zweitens ein Register unabhängig von seinem Inhalt eine Speichereinheit belegt. Dieses Maß ist Proportional zu den Kosten auf einem normalen Rechner, aber nur für beschränkt große Operanden: In der Realität wird eine Addition von zwei 1000-stelligen Zahlen mehr kosten als die von 2 oder 3-stelligen, weil eine 1000-stellige Zahl auf einem tatsächlichen Computer nicht von einem einzigen Register/ einer einzelnen Speicherstelle dargestellt wird. 9 Das logarithmische Kostenmaß: Beim logarithmischen Kostenmaß geht die Größe der Zahlen, mit denen man operiert, in die Berechnung der Kosten mit ein. Außerdem unterscheidet man, auf welches Register man zugreift. Wenn n eineP Zahl ist, sei L(n) die Länge der Binärdarstellung von n. Ein Befehl kostet dann k L(k), wobei k als Wert alle involvierten Adressen und die Werte aller Operanden annimmt. 10 Beim Logarithmischen Kostenmaß wird, im Gegensatz zum EKM, die Stelligkeit der Werte berücksichtigt und mit in die Laufzeit eingerechnet. Beispiel: R1 := R2 ∗ (R3), wobei R2 den Wert 5, R3 den Wert 10 und R10 den Wert 1000 hat. Sei L die Länge der Binärdarstellung, so ergibt sich die Laufzeit im Logarithmischen Kostenmaß: L(5) + L(10) + L(1000) = 3 + 4 + 10 = 17. Es werden also 17 Zeiteinheiten benötigt. Allgemein kostet ein Register mit dem Inhalt k L(k) Platzeinheiten. Wir betrachten jetzt Laufzeit und Speicherplatz einer Registermaschine bei einer Eingabe x ∈ Z∗ . Hierbei steht die Eingabefolge von ganzen Zahlen in den ersten Registern. Definition 1.2.3. Die Laufzeit einer RAM bei Eingabe x ∈ Z∗ , t(x), ist die Summe der Zeitkosten aller ausgeführten Befehle bis die Maschine hält. Den Speicherplatz bei der Eingabe von x, s(x), erhält man, indem man in einem Schritt die Summe der Kosten für alle benutzten Register berechnet und dann das Maximum über alle Schritte nimmt. Üblicherweise wird nicht mit t und s für jede einzelne Eingabe gerechnet, sondern jeder Eingabe x wird als Funktion die Größe der Eingabe g(x) = n, n ∈ N zugeordnet. Beispiel: Beim Sortieren von n Zahlen wird als Größe der Eingabe g(x) die Anzahl der Zahlen in x genommen. Das wurde bereits bei den bisherigen Beispielen für Sortieralgorithmen berücksichtigt. Alternativ kann man die Summe der Längen der Binärdarstellungen aller Eingabezahlen als Größe der Eingabe betrachten. Komplexität Komplexität ist der Überbegriff für die unterschiedlichen Komplexitätsmaße wie Laufzeit, Speicher u.a. Wir unterscheiden 3 Fälle: 1. Im schlechtesten Fall: Um die Komplexität im schlechtesten Fall zu bestimmen, betrachten wir alle Eingaben der Größe n und nehmen über alle diese Eingaben das Maximum. T (n) = max t(n) g(x)=n S(n) = max s(n) g(x)=n Die Komplexität der Sortieralgorithmen entspricht der Anzahl der nötigen Vergleiche. Die für die Algorithmen Mergesort, Sortieren durch Auswahl und mittels Permutationen bereits angegebenen Schranken gelten auch im schlechtesten Fall. 2. Im Mittel: Hier gilt, daß T (n) der Erwartungswert von t(x) ist, wobei x eine Eingabe der Länge n ist. Dazu ist eine Wahrscheinlichkeitsverteilung auf 11 der Menge der Eingaben notwendig. Bei der Analyse vom deterministischen Quicksort-Algorithmus nehmen wir an, dass jede Permutation der Eingabefolge gleichwahrscheinlich ist. 3. Erwartete Laufzeit bei Zufallsalgorithmen: Zufallsalgorithmen werden auch probabilistische oder randomisierte Algorithmen genannt. Ein solcher Algorithmus trifft Entscheidungen, die vom Zufall abhängen. Er benutzt dazu einen Zufallsgenerator. Die Registermaschine RAM benutzt den Operator RAN D(n), der eine gleichverteilte Zufallszahl zwischen 1 und n liefert. Die erwartete Laufzeit für die Eingabe x wäre dann der Erwartungswert der Laufzeit, gebildet bezüglich der Zufallsentscheidung. Wir betrachten den Sortieralgorithmus Quicksort bezüglich der mittleren und der erwarteten Laufzeit. 1. Quicksort ist deterministisch, wenn das Pivot-Element deterministisch gewählt wird, z.B. immer das erste Element der Folge. Die mittlere Laufzeit, d.h., die über alle Eingabefolgen gemittelte Laufzeit, beträgt Θ(n log n). 2. Beim randomisierten Quicksort ist die erwartete Laufzeit für jede Eingabe Θ(n log n). Man erhält also diese Laufzeit, unabhängig davon, wie “schlecht“ die Eingabefolge aussieht. Für gleiche Eingaben kann es unterschiedliche Laufzeiten geben. 1.2.2 Turingmaschine Die Turingmaschine stellt ein weiteres Berechnungsmodell dar. Die Laufzeit ist gleich der Anzahl der Schritte, der Speicherplatz gleich der Anzahl der benutzten Zellen. Man muss hier nicht über EKM oder LKM nachdenken, da jeder Schritt (die gleiche) konstante Zeit braucht, weil in jedem Schritt der Kopf um eine Stelle nach rechts oder links bewegt werden kann und jede Speicherzelle den gleichen konstanten Platz braucht. Satz 1.2.4. Zu einem Algorithmus der Laufzeit T (n)im Logarithmischen Kostenmaß (LKM) auf einer Registermaschine existiert eine äquivalente Turingmaschine der Laufzeit O(T (n)5 ). Beispiel. Ein Algorithmus mit der Laufzeit O(n2 ) im LKM auf einer RAM könnte auf einer Turingmaschine mit der Laufzeit O(n10 ) implementiert werden. Die Laufzeit steigt zumindest nicht exponentiell. Man sagt dazu auch, dass die Laufzeit auf einer Registermaschine im LKM und auf der Turingmaschine sind polynomiell verwandt (sie stehen in polynomieller Beziehung). Insbesondere gilt: 12 Ein Problem, dass in polynomieller Zeit (d.h. Zeit O(nk ) für festes n ∈ N)) auf RAM im LKM lösbar ist, ist auch auf einer Turingmaschine in polynomieller Zeit lösbar. Probleme, die auf einer Turingmaschine in polynomieller Zeit lösbar sind, gehören zur Klasse P. Darstellung der Algorithmen 1. Man verwendet eine Art höherer Programmiersprache mit if-, whileu.a. Anweisungen sowie Rekursionen. 2. Oft werden Algorithmen auch umgangssprachlich beschrieben. 3. Man verwendet eine Mischung aus höherer Programmiersprache und Umgangssprache, den so genannten “Pseudocode“. Von Vorteil ist, daß es weniger Arbeit macht, wenn das Problem nicht exakt in eine Programmiersprache umgesetzt wird. Weiterhin wird die Lesbarkeit erhöht. Platzbedarf und Laufzeit lassen sich auch für so angegebene Algorithmen definieren oder zumindest diskutieren. Meist ist die Komplexität auch ohne eine Übersetzung in RAM-Code analysierbar in Form von O oder Θ. Typische Funktionen Es handelt sich um Funktionen, die häufig bei der Komplexitätsanalyse auftreten. Sie sind aufsteigend geordnet. log log n log n √ n n n log n n2 n3 n4 2n n! 2 2n “logarithmisch“ “linear“ polynomiell “quadratisch“ “kubisch“ “biquadratisch“ exponentiell Fakultät Anmerkung 1: exponentiell n n2 2 ≤ n! ≤ nn = 2n log n Anmerkung 2: log n wächst schwächer als jedes nα , α = 0, das gilt auch für α = 21 . 13 Kapitel 2 Sortieren, Suchen 2.1 Bemerkungen zum Sortieren Der Vergleich der bisher besprochenen Sortieralgorithmen bezüglich Zahl der Vergleiche und RAM-Laufzeit ergibt: Maximum Auswahl Mergesort rand. Quicksort Anzahl der Vergliche ∼ 12 n2 ∼ n log n ∼ 1,38 · n log n RAM-Laufzeit (EKM) 2,5 · n2 12 · n log n 9 · n log n Die höheren Werte bei der RAM-Laufzeit ergeben sich aus dem Auflösen der Rekursionen. Dazu wird ein Laufzeitstack angelegt. Dieser zusätzlich benötigte Platz ist die Größe des Laufzeitstacks und damit gleich der Tiefe der Rekursionen. Definition 2.1.1 (Allgemeine Sortieralgorithmen). Allgemeine Sortieralgorithmen sind solche, die nur auf Vergleichen zwischen Elementen der Eingabefolgen beruhen. Beispiele sind die bisher besprochenen Sortieralgorithmen. Als Beispiel für einen nicht algorithmischen Sortieralgorithmus betrachten wir eine Folge bestehend aus 1000 Bit. Hier wird natürlich nicht wie bisher beschrieben sortiert. Stattdessen wird die Folge durchlaufen, die Anzahl der Nullen und Einsen gezählt und die entsprechenden Anzahlen ausgegeben. Elemente werden nicht verglichen. Die Laufzeit ist O(n). Der Vorteil der allgemeinen Sortieralgorithmen liegt darin, dass man sie für alle Daten aus jedem beliebigen, linear geordneten Universum (Datenbereich) (U, ≤) nutzen kann. Darstellung durch einen Vergleichsbaum Allgemeine Sortieralgorithmen sind darstellbar durch einen Vergleichsbaum. 14 ai < aj al< ak ar < at ... Der Vergleichsbaum endet bei dem Blatt,das der Permutation der Eingabefolge entspricht. Die richtige Permutation stellt der Vergleichsbaum durch Folge der Abfragen fest. Beispiel für Mergesort für n=3 a1 | a2 a3 Zunächst werden Teilfolgen gebildet. 1. Vergleich: Wenn ja, geht a2 a3 a2 ≤ a3 als gemischte Teilfolge zurück. ja nein a1 ≤ a2 ja a1 ≤ a3 nein ja a1 ≤ a3 1 2 3 ja 2 1 3 nein a1 ≤ a2 1 3 2 ja nein 3 1 2 2 3 1 15 nein 3 2 1 Die inneren Knoten im Baum entsprechen den Vergleichen, die ein Algorithmus durchführt. Ein Blatt entspricht einer Permutation der Eingabefolge. Für n Elemente erhält man somit n! Blätter. Definition 2.1.2 (Untere Schranke). Eine untere Schranke entspricht einer Aussage der Form: Jeder Algorithmus braucht mindestens T (n) Zeit (oder S(n) Platz), um das Problem X für eine Eingabe der Größe n zu lösen. Beispiel: Sei das Problem X das Finden des Maximum in einer Eingabe der Länge n. Um die untere Schranke zu bestimmen stellt sich die Frage: Wie viele Vergleiche braucht jeder Algorithmus, um das Maximum zu bestimmen? Beim Standardalgorithmus benötigt man n − 1 Vergleiche. (Das aktuelle Maximum wird festgehalten und immer mit dem nächsten Element verglichen.) (n − 1) ist auch die untere Schranke, das es keinen Algorithmus gibt, der das schneller erledigt. Folgende Überlegung gibt uns eine intuitive Begründung dafür: Bei einem Sportwettkampf werden Zweikämpfe ausgetragen (vielleicht Tennis), um den Sieger zu ermitteln. Jemand, der nie ein Spiel verloren hat, erwartet dann auch der Gesamtsieger zu sein. Alle anderen müssen also jeweils mindestens ein Spiel verloren haben. Es müssen also (n − 1) Spiele stattfinden. Eine vergleichsbasierte Maximumsuche braucht also mindestens (n − 1) Vergleiche. 16 2.2 Allgemeine (vergleichsbasierte) Sortierverfahren Vergleichsbaum: Der Aufbau des Verbleichsbaum ist für jeden Algorithmus und jede Eingabelänge n gleich. Jede Permutation der Eingabe, muss zu einem anderen Blatt führen, sofern alle Elemente verschieden sind. Dies führt zu einer Laufzeit ≥ n! Blätter. Die Anzahl der Vergleiche ist im schlechtsten Fall gleich der Höhe des Vergleichsbaums. Also ist die Anzahl der Vergleiche ≥ log(Anzahl der Blätter) ≥ log (n!) Die Abschätzen von n! ergibt Grob“ : ” n! = 1 · 2 · 3 · . . . · n ≤ nn Anderseits ist: n! = 1 · 2 · 3 · . . . · n 2 j n k + 1 · ... · n ≥ + 1 · ... · n ≥ {z } | 2 ≥ n2 =⇒ n n2 2 n 2 n2 ≤ n! ≤ nn Es folgt n n n n n 2 n = (log (n) − 1) = log (n) − ≤ log (n!) ≤ log (n) ≤ n log (n) log 2 2 2 2 =⇒ log (n!) = Θ (n log (n)) Eine bessere Abschätzung für n! ist mit Hilfe der Stirlingschen Formel möglich. Stirlingschen Formel wurde 1730 von James Stirling (1692-1770) entwickelt und dient zur Berechnung von Näherungswerten großer Fakultäten. n n √ Θ n! = 2πn · · e( 12n ) wobei 0 < Θ < 1 e log (n!) = n log (n) − n log (e) + 17 1 1 log (n) + log (2π) + O (1) 2 2 = n log (n) − O (n) (O (1) ist eine Funktion, die durch eine Konstante beschränkt ist.) Daraus folgt: Jeder vergleichsbasierte Sortieralgorithmus benötigt zum Sortieren von n Elementen Ω (n log (n)) Vergleiche. Genauer: Der Algorithmus benötigt im schlechtesten Fall mindestens n log (n) − O (n) Vergleiche. Daher ist z.B. Mergesort im schlechtsten Fall asymptotisch (d.h. bis auf eine multiplikative Konstante) optimal für vergleichsbasierte Sortieralgorithmen. Daraus kann man schließen, dass man nicht schneller Sortieren kann als mit n log (n) Vergleichen. 2.3 Sortieren in linearer Zeit Sortieren in linearer Zeit ist ausschließlich mit Sortierverfahren möglich, die nicht nur auf Vergleichen beruhen und des weiteren nur für spezielle Universen (Menge der Eingabedaten). Sortieren durch Zählen (im endlichen Universum) Voraussetzung: Universum ist endlich U = {a1 , a2 , . . . , ak } 1. Stelle k Zähler zur Verfügung 2. Durchlaufe die Eingabefolge Zähle Anzahl der a1 en, a2 en, . . . , ak en 3. Dann i = 1, . . . , k Gib so viele ai s aus wie gezählt wurden. Laufzeit: θ (n) im EKM Speicher: θ (k) mit k ist Größe des Universums U 2.3.1 Bucket Sort Bucket Sort ist eine Verallgemeinerung des Sortierens in linearer Zeit. Die Idee des Algorithmus besteht darin, das zu sortierende Intervall in gleichgroße Teilintervalle aufzuteilen, in so genannte Buckets“ und in dies dann die Elemente ” einzusortieren. Pseudocode für Bucket Sort Gegeben: ein beliebiges Universum und eine Abbildung f : U → {1, . . . , r}. Die Abbildung muss mit der Ordnungsrelation kompatibel sein, d.h. f (x) ≤ f (y) ⇒ x ≤ y (∀x, y ∈ U) 1. Stelle r Buckets“ (Listen) zur Verfügung. ” 18 2. Durchlaufe die Liste und berechen f (x) für alle Elemente x und werfe“ x in bucket Nummer f (x). ” Anschließend werden die Buckets“ intern mit Hilfe eines geeigneten Verfahren ” sortiert und die Listen aus den buckets“ 1, 2, . . . , r hintereinander gehängt. ” 2.3.2 Radixsort Das Radixsort Sortierverfahren basiert auf dem Bucket Sort Algorithmus. Man P verwendet Radixsort für Wörter über einem endlichen Alphabet = a1 , q2 , . . . , ak Bsp.: Theoretisch Radixsort beginnt minimieren. M ON DIE M IT bräuchte man pro Schritt 26 Buckets, insgesamt also 263 . die Buchstaben von hinten zu sortieren, um die Laufzeit zu DIE F RE SAM letzter DON F RI SAM SON Buchstabe −→ SAM DIE M IT zweiter M ON DON SON M IT Buchstabe −→ DIE DON F RE Erster M ON DON SON F RE Buchstabe −→ M IT M ON SAM SON Pseudocode für Radixsort Eingabe: n Wörter w1 , . . . , wn der gleichen Länge l 1. FOR i = l, . . . , 1 DO Bucketsort bezüglich des i-ten Zeichen der Wörter 2. THEN konkateniere die Buckets in richtiger Reihenfolge. Dies kann verallgemeinert werden auf Wörter unterschiedlicher Länge. Korrektheit: Zeige, dass die Worte nach dem i-ten Durchlauf bzgl. der i letzten Zeichen lexikografisch sortiert sind. Beweis durch Induktion über i. Laufzeit: Für l Durchläufe mit je Θ (n) Laufzeit, ergibt sich eine Gesamtlaufzeit von Θ (l · n) = Θ (N ), wobei N = die Gesamtanzahl der Zeichen in der Eingabe, l die Länge der Worte und n die Anzahl der Worte ist. Man kann auch Zahlen mit diesem Algorithmus sortieren, dabei wird eine Darstellung der Zahlen bzgl. einer Basis gewählt und diese Zahlen werden dann wie Worte sortiert. Dafür ergibt sich eine Laufzeit von insgesamt Θ (N ), wobei N die Gesamtlänge aller Darstellungen ist. 19 2.4 Das Auswahlproblem (selection, order statistics) Gegeben: Folge S der Länge n mit S ⊂ U und (U, ≤) sei ein linear geordnetes Universum. Finde: k-t-kleinstes Element von S (d.h. das Element, das an k-ter Stelle steht, wenn S aufsteigend sortiert ist). k = 1, dann M inimum → n − 1 V ergleiche k = n, dann M aximum Frage: Wie findet man das k = b n2 c Element, also genau den Median“? ” Antwort: Folge sortieren und k-te Stelle ausgeben Θ (n · log (n)) z.B. Mergesort. 2.4.1 Randomisierter Algorithmus (randomized selection) SELECT(k,S) 1. Falls |S| = 1 return a, mit S = {a} 2. Sonst wähle zufällig (gleichverteilt) ein a ∈ S 3. Teile S auf in S1 (Elemente < a) S2 (Elemente = a) S3 (Elemente > a) 4. Falls |S1 | < k ≤ |n − S3 | return a 5. Sonst falls |S1 | ≥ k : return SELECT(k, S1 ) 6. Sonst return SELECT(k − (n − |S3 |), S3 ) Analyse: Die erwartete Laufzeit (im EKM der RAM) ist T (1) = b, wobei b konstant ist. Annahme: Das ausgewählte Element sei das i-t kleinste, falls i < k. Wir durchsuchen rekursiv eine Folge der Größe n − 1 falls i > k. Rekursiver Aufruf der Größe i − 1. 20 Damit folgt die Rekursion für T (n): Pk−1 Pn T (n) = n1 [ i=1 T (n − i) + i=k+1 T (n − 1) + c · n Was wird summiert? 1. Teil der Summe: T (n − 1) + T (n − 2) + . . . + T (n − k + 1) 2. Teil der Summe: T (n − 1) + T (n − 2) + . . . + T (k) 3. Teil der Summe: Eine Konstante c für das Aufteilen Annahme: n ≤ n2 Falls das helle“ Stück durch das dunkle“ ersetzt wird, wird die Summe asym” ” ptotisch größer, da T (n) monoton wächst. Dies ist für den anderen Fall asymmetrisch. Also gilt: P n−1 T (n) ≤ n2 · i=b n c T (i) + c · n 2 Behauptung: T (n) = O(n) Beweis durch Induktion über n. 21 Abschätzung für die Rekursion von SELECT Wir wollen nun die Behauptung beweisen, dass die Laufzeit von SELECT linear ist, also dass T (n) = O(n) gilt. Wir nehmen erst einmal an, dass eine Konstante d existiert, so dass gilt T (n) ≤ d·n. Nun benutzen wir die Induktion um zu zeigen, dass für ein geeignet gewähltes d dies auch der Fall ist. Induktionsanfang Für n = 1 erkennt man aus Zeile 1 von SELECT, dass T (1) = b und die Behauptung gilt, wenn wir d ≥ b wählen. Induktionsschritt Wir nehmen an, dass die Behauptung für alle i ≤ n − 1 gilt. Mit dieser Voraussetzung können wir zeigen, dass es auch für n gilt. T (n) ≤ 2 n n−1 X T (i) + cn i=bn/2c Setzen wir die Induktionsvoraussetzung ein, können wir T (i) durch d · i ersetzen und gleich noch das d, welches ja eine Konstante ist, vor die Summe ziehen. T (n) ≤ 2d n n−1 X i + cn i=bn/2c Als nächstes Schätzen wir die Summe, mit dem Satz von Gauss ab. Dabei müssen wir aber die Laufindexe der Summe beachten und den Anteil welches bn/2c zu viel einbringt wieder abziehen. n−1 X i= i=bn/2c n(n − 1) bn/2c(bn/2c − 1) − 2 2 Nun schätzen wir bn/2c ab. bn/2c ≥ n −1 2 und somit ist n−1 X i≤ i=bn/2c = n(n − 1) (n/2 − 1)(n/2 − 2) − 2 2 3 2 1 1 1 3 1 n + n(− + + ) − 1 = n2 + n − 1 8 2 4 2 8 4 Dieses Ergebnis setzen wir in die Ungleichung für T (n) ein. 2 3 2 1 T (n) ≤ d n + − 1 + cn n 8 4 22 die Subtraktion von 1 können wir auch weglassen; die Ungleichung stimmt dann noch immer 2 3 2 1 T (n) ≤ d n + n + cn n 8 4 Ausklammern und Zusammenfassen der Konstanten ergibt: 3 1 nd + d + cn 4 2 d 3 d+c n+ = 4 2 T (n) ≤ Was uns in der Gleichung noch stört ist der Term d2 . Wir lösen das Problem mit folgenden Kniff. Die 0,75d ersetzen wir durch 0,8d und ziehen die zu vielen 0,05 im zweiten Summanden wieder ab. Wir erhalten also: 1 − 0,05n T (n) = (0,8d + c)n + d 2 Nun kann man erkennen das der Term d wird und für diese n ergibt sich: 1 2 − 0,05n für n ≥ 10 kleiner gleich 0 T (n) ≤ (0,8d + c)n Wenn wir die Ungleichung 0,8d + c ≤ d wir letztendlich folgende Ungleichung: nach d ≤ 5c auflösen, erhalten T (n) ≤ dn Wenn wir d so wählen, das d das Maximum von b und 5c ist, ist gezeigt, dass die Laufzeit linear ist: T (n) = O(n) Ein Problem besteht aber noch. Wir haben während des Beweises folgende Einschränkung gemacht n ≥ 10, daher müssen wir noch den Induktionsanfang geeignet umschreiben für n < 10. Wir führen eine Konstante α ein, welche die Laufzeit für die Probleme konstanter Größe (n < 10) beschränkt: T (n) ≤ α für n < 10 Daraus folgt das d ≥ α sein muss, damit unser Beweis immer noch funktioniert und für d ergibt sich auch d = max(α, 5c). Halten wir also folgenden Satz fest. Satz 2.4.1. Die erwartete Laufzeit des Algorithmus SELECT für eine Folge S der Länge n ist O(n). Leider haben wir so nur die erwartete Laufzeit bestimmt. Da der Algorithmus vom Zufall abhängt, ist die Laufzeit im schlechtesten Fall größer als O(n). 23 Die Begründung hierfür liegt in der Wahl des Pivot-Elementes. Wählen wir das Pivot-Element am Rand der Folge benötigt der SELECT-Algorithmus mehr Zeit, als wenn wir das Pivot-Element genau in der Mitte gewählt hätten. Da unser Pivot-Element nun zufällig gewählt wird, kann es halt am Rand oder auch in der Mitte liegen, wir haben keinen Einfluss darauf. Zum Glück sagt die Statistik, dass es sehr selten ist, dass wir immer Pivot-Elemente bekommen, welche am Rand der Folge liegen. 2.4.2 Deterministischer SELECT-Algorithmus Betrachten wir nun einen deterministischen Algorithmus, der uns auch im schlechtesten Fall eine lineare Laufzeit garantiert. Der Algorithmus beschränkt sich auf das finden des Median, daher des Elementes welches an der k = bn/2c-ten Stelle einer Folge S steht. Wenn unser Beweis gelingt, hat man einen Grundstock gelegt, um zu zeigen, dass die lineare Laufzeit auch für ein beliebiges Element der Folge S gilt. Dazu verändern wir nun erstmal die 2. Zeile von SELECT: • falls |S| < 60 sortiere S und gib das k-te Element der sortierten Folge aus • anderenfalls – teile S in bn/5c Teilfolgen der Länge 5 (o.B.d.A. setzen wir voraus, dass n eine Potenz von 5 ist, wenn nicht müssten wir noch eine Teilfolge mit 1-4 Elementen zusätzlich aufstellen und beachten) – sortiere die Teilfolgen und bestimme durch rekursiven Aufruf den Median aller Mediane a dieser Teilfolgen Machen wir uns dieses einmal bildlich klar: Wir stellen die Elemente der sortierten 5-elementigen Teilfolegen in Spalten dar, und zeichnen die “≤”-Beziehung als Pfeil, s. Abbildung 2.1. Zusätzlich sind die Spalten nach deren Medianen sortiert, und die entsprechenden Pfeile sind im Bild eingezeichnet. Wir stellen für die Elemente, über die wir per Pfeil Bescheid wissen, zwei Mengen auf. Menge A enthält alle Elemente von denen es einen gerichteten Pfad zu dem Median a gibt. Das heißt, alle Elemente in A sind ≤ a. Analog dazu definieren wir die Menge B als Menge der Elemente zu denen es einen gerichteten Pfad von a gibt, damit sind alle Elemente in B ≥ a. Somit steht fest, dass in der Teilfolge S1 (zur Erinnerung, in Schritt 3 von SELECT wird die Folge S geteilt und S1 enthält alle Elemente < eines Pivot-Elementes) keine Elemente aus B auftauchen können. Wir können daher die Anzahl der Elemente von S1 als n− Anzahl der Elemente in B abschätzen. B enthält jeweils 3 Elemente aus bn/10c Spalten (es gibt bn/5c Spalten und die Hälfte davon tragen zu B bei). Damit haben wir: |S1 | ≤ n − 3bn/10c mit bn/10c ≥ n/10 − 1: n −1 10 = 0, 7n + 3 ≤n−3 24 Abbildung 2.1: Grafische Darstellung der Mediansuche Dieser Ausdruck ist ≤ 0, 75n wenn 0, 05n ≥ 3 und somit wenn n ≥ 60. Analog kann man das ganze für die Folge S3 und die Menge A machen. Es ergibt sich |S3 | ≤ b 43 nc. Dabei darf abgerundet werden, da wir mit ganzen Zahlen arbeiten. So lässt sich, für den zweiten Schritt des modifizierten SELECT-Algorithmus, T (n) wie folgt darstellen. T (n) ≤ cn n 3 T (n) ≤ T b c + T b nc + dn 5 4 für n ≤ 60 und eine Konstante c für n > 60 und eine Konstante d Dabei steht T b n5 c für die Suche des Median der Mediane a T b 43 nc für die rekursive Suche in S1 oder S3 dn für das Aufteilen in S1 , S2 , S3 und für das Sortieren der Teilfolgen Nun müssen wir begründen warum für n > 60 T (n) = O(n) ist. Allgemein kann man sagen wenn die Summe der Faktoren aller Rekursiven aufrufe kleiner 1 ist gilt O(n). Schreiben wir es aber nochmal formal als Lemma auf: Lemma 2.4.2. Falls für eine Funktion T (n) gilt: 1. T (n) ≤ cn für alle n ≤ n0 , für ein festes n0 ∈ N 25 2. T (n) ≤ T (bα · nc) + T (bβ · nc) + an für n ≥ n0 mit α, β(Konstanten) ≥ 0 und α + β < 1 dann ist T (n) = O(n) In unseren Fall haben wir 15 + 34 < 1 und somit laut dem Lemma gilt für den modifizierten SELECT-Algorithmus: T (n) = O(n) Bleibt als letzter Punkt das Lemma zu beweisen. Hierzu benutzen wir wieder die Induktion mit folgendem Ansatz bzw. folgender Induktionsvoraussetzung. Wir nehmen an es gilt T (n) ≤ bn und wählen b geeignet, so dass der Beweis funktioniert. Induktionsanfang: Für ein n ≤ n0 gilt T (n) ≤ cn ≤ bn falls b ≥ c. Induktionsschritt: Angenommen die Behauptung gilt für alle i ≤ n−1, zeigen wir dann für n: T (n) ≤ T (bα · nc) + T (bβ · nc) + dn ≤ bbα · nc + bbβ · nc +dn | {z } nach I.V. Die Ungleichung gilt immer noch wenn wir nicht abrunden. ≤ (b(α + β) + d)n ≤ bn T (n) ≤ bn gilt falls b(α + β) + d ≤ b d ≤ b(1 − (α + β)) und somit d b≥ 1 − (α + β) d Der Induktionsbeweis funktioniert also, wenn wir b mit b = max c, 1−(α−β) bestimmen. Halten wir zum Schluss dieses Abschnittes folgenden Satz fest: Satz 2.4.3. Die deterministische Variante von SELECT bestimmt den Median einer Folge S der Länge n in Laufzeit O(n) auch im schlechtesten Fall. 26 2.5 Suchen Eine Menge S will nach einem Element durchsucht werden. Die Menge S ist statisch und |S| = n. S ist Teilmenge eines Universums auf dem eine lineare Ordnung definiert ist und soll so gespeichert werden, dass sie schnell nach einem Element a ∈ U durchsucht werden kann. In der Praxis ist a ein Schlüssel für einen Datensatz, z.B. Telefonbuch. Es wird nach Namen gesucht und andere Daten wie die Nummer hängen daran. Die Menge S ist also eine Menge von Schlüsseln. Falls a ein Element von S ist, soll ein Zeiger auf den Datensatz zurückgegeben werden. Eine Effiziente Datenstruktur für ein solches Problem kann ein sortiertes Feld (Array) sein. Hashtabellen und Binärbäume brauchen zuviel Speicher für diese statischen Strukturen. Für dynamische Mengen sind sie dagegen geeignet. 2.5.1 Binäre Suche Ein sortiertes Feld kann in O(log n) durchsucht werden, z.B. mit Binärsuche. Der Algorithmus 1 gibt den Pseudocode der binären Suche an. Algorithm 1: binsearch(l,r,a) if l ≤ r then k ← b l+r 2 c if S[k] == a then return k else if S[k] > a then return binsearch(l, k − 1, a) else return binsearch(k + 1, r, a) else not Found Es wird immer nach spätestens log(n) Schritten abgebrochen, da der Suchraum bei jedem Schritt halbiert wird. Im Telefonbuch wird allerdings nicht nach dieser Vorschrift gesucht, zum Beispiel die Suche nach Prof. Alt. Vielmehr wird überlegt wo zu erwarten ist, dass der Name stehen wird und dann mit der binären Suche fortgesetzt. Diese Suche heißt Interpolationssuche. 2.5.2 Interpolationssuche Wir nehmen folgende Voraussetzungen für das Universum U und die Elemente aus S an: 27 1. Universum U ist ein linear geordnetes Interval, o.B.d.A. können wir annnehmen dass U = [0, 1]. 2. Elemente in S sind unabhängig und gleichverteilt aus U Bemerkung. Jedes beliebige Intervall lässt sich auf das Intervall [0, 1] und umgekehrt abbilden. Aus der Beschränkung auf ein Intervall folgt, dass es ein kleinstes und ein größtes Element in U gibt. Die Suchvorschrift (Algorithmus 2) ist analog zur binären Suche mit dem einzigen Unterschied, dass das Pivot“-Element auf eine andere Art gewählt wird. ” Als Pivot-Element nehmen wir das k-te Element der Folge S, wobei k wie folgt berechnet wird: a − S[l − 1] · (r − l + 1) , k =l−1+ S[r − 1] − S[l − 1] wobei S[0] = 0 and S[n + 1] = 1. Wir rechnen also aus, an welcher Stelle bei idealer Gleichverteilung, wir unser a vermuten würden. Algorithm 2: interpolsearch(l, r, a) if l ≤ r then l−1+(a−S[l−1]) k ← (S[r−1]−S[l−1])·r−l+1 if S[k] == a then return k else if S[k] > a then return interpolsearch(l, k − 1, a) else return interpolsearch(k + 1, r, a) else not Found Laufzeit: • Im Schlechtesten Fall Θ(n) • Im Mittel braucht der Algorithmus O(log log n) Zeit, ist also wesentlich besser als die binäre Suche. Das Mittel ist dabei der Erwartungswert über alle möglichen Auswahlen von S. Der Beweis ist zu kompliziert! Stattdessen analysieren wir die quadratische Binärsuche. 2.5.3 Quadratischen Binärsuche Die quadratische Binärsuche ist eine modifizierte Form der Interpolationssuche. Wir bestimmen die erwartete Position von a durch Interpolation und nennen 28 sie k. √ Falls a = S[k] ist, sind wir fertig. Falls a < S[k] ist, schauen wir uns S[k − m + 1] an, wobei m = r − l + 1. Der Wert von m entspricht dem zu durchsuchenden Bereich. √ Falls das immer noch größer als a ist, vergleichen wir weiter mit S[k−2·( m+1) usw. Erreichen wir den Anfang der Liste, ist a nicht enthalten. √ Falls ein i gefunden wird für √den S[k − i · ( m + 1) + 1] ≤ a, prüfen wir noch auf Gleichheit. Falls S[k − i · ( m + 1)√+ 1] = a sind wir fertig, sonst√durchsuchen wir rekursiv den Bereich S[k − i · ( m + 1) + 1] . . . S[k − (i − 1) · ( m + 1) + 1]. Analog gilt das gleiche wenn a > S[k]. Mit anderen Worten, der Algorithmus reduziert √ den Bereich der Länge m, in dem das a vermutet wird, auf ein Bereich mit m Elementen. Mit diesem reduzierten Bereich ruft sich der Algorithmus wieder selbst auf. Analyse des Algorithmus: Wir wollen zunächst untersuchen √ wieviele Sprünge macht der Algorithmus um das richtige Stück der Länge m zu finden. Wie √ Wahrscheinlich ist es also, dass a um i· n von der erwarteten Position abweicht? Diese Abschätzung können wir mit der Tschebyscheff-Ungleichung1 machen. Der Erwartungswert der Anzahl der benötigten Sprünge sei C. Pi sei die Wahrscheinlichkeit, dass mindestens i Sprünge notwendig sind. Dann ist offenbar C= ∞ X i· i≥1 = ∞ X (Pi − Pi+1 ) | {z } Wahrscheinlichkeit für genau i Sprünge Pi i≥1 P1 und P2 sind die Wahrscheinlichkeiten, dass mindestens 1 bzw. 2 Sprünge benötigt werden. Für beide Wahrscheinlichkeiten gilt P1 ≤ 1 und P2 ≤ 1. Das dient uns als Anfang um C auszurechnen. Falls der Algorithmus mindestens i Sprünge benötigt, dann weicht √ die Position von a von der erwarteten Position von a um mindestens (i − 2) · m ab. Also √ |rg(a) − k| ≥ (i − 2) · m wobei k ist der erwartete Rang, und rg(a) bezeichnet die tatsächliche Position von a in der betrachteten Folge, d.h., die Anzahl der S[j], die ≤ a sind. √ Also ist Pi ≤ P (|rg(a) − k| ≥ (i − 2) · m). Pi ist also kleiner oder gleich der Wahrscheinlichkeit, dass sich der rg(a) von √ seinem Erwartungswert um mindestens (i − 2) · m unterscheidet. Die Wahrscheinlichkeit, dass der Wert einer Zufallsvariablen sich um mehr als einen gegebenen Wert von dem Erwartungswert dieser Zufallsvariablen unterscheidet, lässt sich mit der Tschebyscheff Ungleichung abschätzen. P (|X − µ| ≥ t) ≤ σ2 , t2 1 http://de.wikipedia.org/wiki/Tschebyschow-Ungleichung 29 wobei X die Zuffals Variable, µ den Erwartungswert und σ 2 die Varianz bezeichnen. Standardabweichung und Varianz: Die Standardabweichung wird im allgemeinen mit σ bezeichnet und ist definiert als q 2 σ = E[(X − E(X)) ]. Die Varianz wird mit σ 2 bezeichnet und ist definiert als 2 σ 2 = E[(X − E(X)) ]. In unserem Fall ist X(a) die Anzahl der S[j]’s≤ a, also der Rang. Wenn S[j] unabhängig und gleichverteilt aus dem Interval [0, 1] ist, dann ist die Wahrscheinlichkeit, dass eines davon kleiner als a ist, a. Wenn wir die Teilfolge S[l] . . . S[r] betrachten, (also sind die Werte der S[j] aus dem Interval [S[l − 1], S[r + 1]]) dann ist diese Wahrscheinlichkeit P = a − S[l − 1] . S[r + 1] − S[l − 1] Die Wahrscheinlichkeit, dass q der S[j]’s ≤ a sind, ist m · P q · (1 − P )m−q , wobei q • m q die Anzahl der möglichen Auswahlen ist, • P q die Wahrscheinlichkeit, dass q der S[j] ≤ a sind und • (1 − P )m−q die Wahrscheinlichkeit, dass m − q der S[j] > a sind. Der Erwartungswert der Binomialverteilung ist E(X) = µ m X m = · P q · (1 − P )m−q q q=0 = P · m. Damit ergibt sich für die Varianz σ 2 2 σ 2 = E (X − E(X)) 2 m q m−q =E · P · (1 − P ) −P ·m q = P · (1 − P ) · m. 30 Jetzt haben wir alle Teile zusammen um Pi , für i ≥ 3, mit der Tschebyscheff Ungleichung abzuschätzen. σ2 t2 P · (1 − P ) · m √ = ((i − 2) · m)2 P (1 − P ) = (i − 2)2 Pi ≤ Wir kennen das P nicht, aber wir können den Ausdruck nach oben abschätzen. Der Term P (1 − P ) hat sein Maximum bei 0, 5 und ist somit immer kleiner 1 oder gleich 0, 252 Also setzen wir dafür 41 ein wodurch sich Pi mit Pi ≤ 4(i−2) 2 abschätzen lässt. Am Anfang haben wir bereits bestimmt wie sich der Erwartungswert der Anzahl der benötigten Sprünge C berechnen lässt. Da Pi jetzt bekannt ist, können wir in die Gleichung einsetzen. X C= Pi i≥1 P1 und P2 können wir jeweils mit 1 abschätzen ≤2+ 1X 1 4 (i − 2)2 i≥3 Variablensubstitution j = i − 2 ∞ =2+ Die Summe hat den Wert P∞ 1 j=1 j 2 = 1X 1 4 j=1 j 2 π2 6 π2 24 = 2, 41 . . . =2+ Sei T (n) die erwartete Laufzeit des Algorithmus für ein Feld der Länge n. Dann gilt: T (1) = b b ist eine Konstante √ T (n) ≤ T ( n) + d d ist die ausgerechnete Zeit für konstant viele Sprünge (2, 41 . . .) Die Funktion wächst langsamer als der Logarithmus. Die Lösung davon ist T (n) = O(log (log n)) 2 0, 5 · (1 − 0, 5) = 0, 25. Für alle anderen P ist das immer kleiner 0, 25. 31 Beweis. Wir nehmen an, dass T (n) ≤ α log log n, wobei α eine Konstante ist. • Induktionsanfang: Für n = 4 erhalten wir log log n = 1. Demzufolge wählen wir für α ≥ T (4). • Induktionsschritt: √ n +d √ ≤ α log log n + d √ ≤ α log log n | {z } T (n) ≤ T 1 2 nach I.V. log n = α(log log n − 1) + d ≤ α log log n falls α ≥ d Daraus folgt, dass für α = max {T (4), d} gilt die Behauptung. 32 Ein anderer Weg, die Laufzeit der quadratischen Binärsuche zu zeigen, ist das Auflösen der Rekursionsgleichung. Zur Vereinfachung nehmen wir ohne Bek schränkung der Allgemeinheit an, dass n = 22 ist, da man sonst nicht auf Gaußklammern verzichten könnte, die die Rechnung komplizierter machen würden. √ Nach jedem Schritt verkürzt sich das zu durchsuchende Intervall von n auf n Einträge. Wir müssen also folgende Gleichung lösen: √ T (n) = T ( n) + c für eine Konstante c q √ n +c+c =T 1 = T (n 4 ) + 2c 1 = T (n 8 ) + 3c 1 = T (n 2k ) + kc Als Rekursionsanker haben wir T (2) = b (wobei b konstant). Wir suchen also 1 das k, für das n 2k = 2 gilt. 1 n 2k = 2 1 log n = log2 = 1 2k log n = 2k log log n = k Setzen wir das in die Rekursionsgleichung ein erhalten wir: T (n) = T (2) + c ∗ log log n = Θ(log log n) Abschließend lassen sich die drei Suchalgorithmen in folgendem Satz zusammenfassen: Satz 2.5.1. Eine Menge S von n Elementen aus einem linear geordneten Universum (U, ≤) sei sortiert in einem Feld gespeichert. Dann gilt: 1. Binärsuche sucht nach einem gegebenen a ∈ U . Benötigt O(log n) Zeit. 2. Falls U = [0, 1] und die Elemente von S zufällig und gleichverteilt aus U gezogen sind, dann braucht die Interpolationssuche eine erwartete Zeit von O(log log n). 3. Unter der Voraussetzung von 2. braucht die quadratische Binärsuche erwartet O(log log n) Zeit. Alle drei Algorithmen arbeiten auf sortierten Feldern, diese haben jedoch einen Nachteil: das Einfügen oder Entfernen einzelner Elemente ist nicht effizient möglich und benötigt O(n) Zeit. Hat man eine, sich dynamisch verändernde, Menge von Elementen sollte man auf eine effizientere Datenstruktur, wie sie im nächsten Kapitel vorgestellt werden, zurückgreifen. 33 Kapitel 3 Datenstrukturen Eine Datenstruktur bezeichnet eine Art Daten abzuspeichern, so dass gewisse Operationen effizient durchführbar sind. Eine Datenstruktur ist die algorithmische Realisierung eines abstrakten Datentyps. Zum Beispiel ist im sortieren Feld das Suchen sehr effizient. Einfügen und Entfernen jedoch nicht, da man beim Einfügen ein neues Array erstellen muss und alle Elemente mitsamt dem Eingefügten hineinkopieren muss. Gleiches gilt beim Entfernen und kostet somit Θ(n). Alternativ hierzu könnte man gelöschte Felder markieren ohne ein neues Array zu erstellen. Dies ist aber auch nicht zu empfehlen, da auf Dauer größere unbesetzte Bereiche entstehen. 3.1 Wörterbücher (Dictionaries) Abstrakter Datentyp: eine Menge S ∈ U , U : Universum (meistens linear geordnet) Operationen: • SUCH(a,S) mit a ∈ U liefert 0 falls a ∈ / S und 1 falls a ∈ S. Bemerkung. Die Rückgabe ist zwar für den ADT sinnvoll, in der Praxis (zum Beispiel in einem Telefonbuch) werden üblicherweise anstelle der 1 ein Verweis auf a sowie zusätzliche Informationen dazu ausgegeben. In der Regel ist a ein Schlüssel in einem größeren Datensatz. • EINF(a,S) S := S ∪ {a} • STREICHE(a,S) S := S \ {a} Bemerkung. Bei EINF und STREICHE bleibt S nach Definition von ∪ und \ unverändert, wenn a schon in S enthalten ist, beziehungsweise gar nicht enthalten war. Nun benötigen wir eine effiziente Datenstruktur für den abstrakten Datentyp. Als erste Idee könnte man das sortierte Feld haben. 34 3.1.1 sortiertes Feld Die Suchen-Funktion benötigt O(log n) Zeit, Einfügen und Streichen aber Θ(n). Die beiden letzteren Operationen sind nicht effizient (siehe vorheriges Kapitel), daher kommt das sortierte Feld nicht in Frage. 3.1.2 Hashing Wir haben eine Hashfunktion h : U → N. Für jedes a ∈ U berechnet h(x) die Stelle, an die a gespeichert wird. Da U im Allgemeinen nicht begrenzt ist, der Speicher jedoch schon, ist h in der Regel nicht injektiv, bildet also verschiedene a auf die selbe Stelle h(a) ab. Daher sollte man h besser folgendermaßen beschreiben: h : U → [1, m], wobei m die Größe des verfügbaren Feldes angibt. Bei einem nicht injektiven h kann es aber zu Konflikten kommen, wenn a 6= b aber h(a) = h(b). Daher speichert man für jeden Hashwert eine Liste von Elementen, die den gleichen Hashwert haben, siehe Abbildung 3.1. 1 i m a b Abbildung 3.1: Hashing Beispiel für h(a) = h(b) = i Wenn die Hashfunktion die Eingaben gut verteilt, also jedes Ergebnis zwischen 1 und m die gleiche Wahrscheinlichkeit hat, und das m groß genug gewählt wurde, üblicherweise Θ(|S|), dann sind die Operationen des Wörterbuchproblems erwartet in O(1) möglich, also in Konstanter Zeit“. ” Bemerkung. Hier ist nicht mal eine lineare Ordnung erforderlich. In der Praxis wird Hashing oft erfolgreich verwendet, motiviert durch die binäre Suche gibt es aber noch weitere interessante Datenstrukturen, diese werden im Folgenden vorgestellt. 3.1.3 Binärer Baum Ein binärer Baum speichert die Elemente von S in seinen inneren Knoten. Für jeden inneren Knoten v gilt: 1. Elemente im linken Teilbaum sind kleiner, als das Element von v 2. Elemente im rechten Teilbaum sind größer, als das Element von v Zur Veranschaulichung siehe Abbildung 3.2. Die Blätter stehen für erfolgloses Suchen. Man landet also in einem Blatt, wenn ein Element nicht in dem Baum gespeichert ist. 35 a v <a >a Abbildung 3.2: Ein binärer Baum mit zwei angedeuteten Teilbäumen Beispiel. Wir wollen die Zahlen 5, 4, 6 ,3 in einen leeren Baum speichern, dieser Entwickelt sich dann wie in der Abbildung 3.3 zu sehen 5 4 6 3 5 5 4 5 4 5 6 4 6 3 Abbildung 3.3: Einfügen der Zahlenfolge 5, 4, 6, 3 in einen leeren Baum Das Streichen ist etwas komplizierter. Dabei sucht man den zu streichenden Knoten und ersetzt ihn durch das Maximum aus dem linken Teilbaum. Da das Maximum aus dem linken Teilbaum ebenfalls Kinder haben kann, die allerdings nur kleiner sein können, zieht man dessen Teilbaum an die Stelle des Maximums. In einem binären Baum geht das Suchen, Einfügen und Streichen in O(h) Zeit, wobei h die Höhe des Baumes bezeichnet, da man den Baum jeweils maximal bis zu einem Blatt durchlaufen muss. Im günstigsten Fall ist der Baum b balanciert“, dann gilt: h = Θ(log n) (Ab” bildung 3.5). Im schlechtesten Fall hat jeder Knoten nur ein Kind, dann gilt: h = Θ(n) (Abbildung 3.4). Im schlechtesten Fall wird der Baum somit zu einer verketteten Liste und garantiert keine logarithmische Laufzeit mehr. Wie kann man nun einen Baum so definieren, dass der schlechteste Fall immer noch eine effiziente Laufzeit für die Operationen auf dem Baum garantiert? Zuerst wollen wir dazu untersuchen, wie groß die mittlere Höhe eines zufälligen Binärbaums ist. Hierfür gibt es 2 Ansätze. Behauptung 3.1.1. In einen ursprünglich leeren Baum werden n Elemente aus U eingefügt, wobei jeder Permutation der aufsteigenden Ordnung gleich 36 Abbildung 3.4: Ein Binärbaum im ungünstigsten Fall Abbildung 3.5: Ein Binärbaum im optimalen Fall wahrscheinlich ist. Dann gilt: Die erwartete Höhe des entstehenden Baums ist O(log n). Beweis. Beweis zu komplex, siehe Cormen - Introduction to Algorithms p.254 Stattdessen untersuchen wir, wie viel n Einfügeoperationen im Mittel kosten. Wir gehen davon aus, das jedes Element mit der Wahrscheinlichkeit n1 auftritt. Damit ist die Wahrscheinlichkeit, dass das i-t kleinste Element ai an erster Stelle eingefügt wird n1 . Damit werden in den linken Teilbaum i−1 Elemente eingefügt und in den rechten Teilbaum n − i Elemente, s. Abbilding 3.6. Diese Teilbäume werden wiederum zufällig aufgebaut. Um die Rekursionsgleichung aufzustellen müssen wir nun den Erwartungswert T (n) der Einfügezeit für n Elemente über alle Auswahlmöglichen des ersten Elements ai ausrechnen. Für jedes i setzen sich die Gesamtkosten wie folgt zusammen: T (i − 1) + T (n − i) + O(n), wobei T (i − 1) und T (n − i) für die erwarteten Kosten für den Aufbau beider Teilbäume stehen, und der lineare Term dadurch zustande kommt, dass man die restlichen n − 1 Elemente mit ai vergleichen muss um den Binärbaum gemäß der Invariante aufzubauen. Nun 37 ai zufällige Bäume n-i i-1 Elemente Elemente Abbildung 3.6: Die Wahrscheinlichkeit des ai Elements beträgt n1 , dass es an erster Stelle eingefügt wird. Die beiden Teilbäume sind beides zufällige Bäume mitteln wir über alle i und erhalten folgende Rekursionsgleichung: n 1X [T (i − 1) + T (n − i) + O(n − 1)] n i=1 ( n ) 1 X = [T (i − 1) + T (n − i)] + nO(n − 1) n i=1 T (n) = n = 1X [T (i − 1) + T (n − i)] + O(n − 1) n i=1 Der Rekursionsanker liegt bei T (1) = c wobei c eine Konstante ist. Die Rekursionsgleichung ist identisch mit der Rekursionsgleichung der mittleren Laufzeit des Quicksort-Algorithmus. Es gilt also folgender Satz: Satz 3.1.2. Fügt man in einen ursprünglich leeren binären Suchbaum n Elemente ein, wobei die Reihenfolge jeder Permutation der aufsteigenden Ordnung gleich wahrscheinlich ist, so erfordert dies im Mittel Θ(n log n) Zeit. Folgerung: Die mittlere Tiefe eines Elements des Suchbaums ( Abstand zu der Wurzel ) ist Θ(log n). Entsprechend benötigt man im Mittel für das Suchen bzw. Streichen eines Elements Θ(log n) Zeit. Dies geht in der Praxis meistens gut, aber wie kann man nun den schlechtesten Fall ebenfalls auf Θ(log n) drücken? 3.1.4 AVL-Baum (Adelson-Velski/Landis - 1962) AVL-Bäume wurden von Georgi Adelson-Velski und Jewgeni Landis 1962 entwickelt. Ziel war es einen binären Suchbaum zu erstellen, der möglichst ausgeglichen ist um eine Laufzeit von Θ(log n) für die Operationen SUCH, EINF und STREICHE zu garantieren. 38 Definition 3.1.3 (AVL-Baum). AVL-Bäume sind binäre Suchbäume, wobei für jeden inneren Knoten v gilt: Die Höhe der beiden Unterbäume von v unterscheidet sich um höchstens 1. Man schreibt zur Vereinfachung in jeden Knoten die Differenz der Höhen des linken Teilbaums zum rechten Teilbaum auf. Ist sie Betragsmäßig kleiner oder gleich 1, so ist der Baum ein AVL-Baum. Siehe dazu Abbildungen 3.7 und 3.8. +1 0 0 0 0 Abbildung 3.7: Beides sind AVL-Bäume, da jeder Knoten die Invariante eines AVL-Baums erfüllt. +2 0 0 0 Abbildung 3.8: Das ist kein AVL-Baum, da bei der Wurzel die Höhe des linken Teilbaums um 2 geringer ist, als die Höhe des rechten Teilbaums. Satz 3.1.4. Die Höhe eines AVL-Baums mit n inneren Knoten ist Θ(log n) Beweis. Sei nh die minimale Anzahl der inneren Knoten eines AVL-Baums der Höhe h. Damit ein AVL-Baum möglichst unausgeglichen ist, muss der Baum in jedem inneren Knoten soweit unausgeglichen sein, wie es die Invariante zulässt. Das heißt der linke Teilbaum eines inneren Knotens ist immer um 1 größer als der rechte Teilbaum eines Knotens. Siehe dazu Abbildung 3.9. Daraus ergibt sich folgende Rekursionsgleichung: nh = nh−1 + nh−2 + 1 Durch Induktion lässt sich nh von unten durch die Fibinacci-Zahlen abschätzen. Induktionsbehauptung : nh ≥ f ibo(h − 1) 39 +1 h-2 h-1 Abbildung 3.9: Der linke Teilbaum hat die Höhe h-1, der rechte Teilbaum die Höhe h-2. Wird dies in jedem Knoten rekursiv fortgesetzt hat man einen AVLBaum der größtmöglichsten Höhe für n Knoten. Induktionsanfang : √ n1 = 1 = f ibo(0) = 1 √ n2 = 2 > f ibo(1) = 1 Induktionsschritt : 3 ≤ h → h + 1 nh = nh−1 + nh−2 + 1 ≥ f ibo(h − 2) + f ibo(h − 3) + 1 ≥ f ibo(h − 2) + f ibo(h − 3) √ = f ibo(h − 1) √ Da sich die Fibonacci-Zahlen von unten durch den Goldenen Schnitt (Φ = abschätzen lassen gilt weiterhin: nh ≥ Φh−2 log nh ≥ (h − 2) log Φ ⇒ h = O(log nh ) = O(log n) Also folgt, ein AVL-Baum mit n Knoten hat die Höhe O(log n). 40 5+1 2 ) Die AVL-Eigenschaft soll bei Einfügungen und Streichungen erhalten bleiben. Dafür gibt es zwei mögliche Operationen: 0 -2 Rotation y -1 x 0 x y T3 T1 T1 T2 T2 T3 Abbildung 3.10: Rotation nach rechts (analog links) 0 -2 Doppelrotation z +1 x ±1 T1 T4 y T2 y 0/-1 +1/0 x z T1 T2 T3 T4 T3 Abbildung 3.11: Doppelrotation links-rechts (analog rechts-links) Falls die Unterbäume AVL-Eigenschaft haben, ist der Baum nach den entprechenden Operationen auch wieder ein AVL-Baum. Damit gestaltet sich Einfügen bzw. Streichen wie beim einfachen binären Suchbaum, falls notwendig müssen zusätzlich Rotationen oder Doppelrotationen durchgeführt werden, und zwar entlang des Pfades zum eingefügten bzw. gestrichenen Element (nur da kann eine Veränderung der AVL-Eigenschaft auftreten). Alle Wörterbuchoperationen benötigen also O(log n) Zeit auch im schlechtesten Fall (Rotation bzw. Doppelrotation besteht nur aus dem Umhängen von Zeigern, was konstante Zeit kostet). 3.1.5 (a,b)-Bäume (a,b)-Bäume sind eine Datenstruktur für das Wörterbuchproblem und im allgemeinen keine binären Suchbäume. a, b ∈ N Definition 3.1.5. (a,b)-Baum Sei ρ(v) = Anzahl der Kinder eines Knoten v. Seien a, b ∈ N, a ≥ 2, b ≥ 2a − 1 41 Ein Baum B heißt (a,b)-Baum genau dann wenn gilt: • Alle Blätter haben die gleiche Tiefe, • für alle inneren Knoten v gilt: ρ(v) ≤ b, • für alle inneren Knoten v (außer der Wurzel) gilt: ρ(v) ≥ a, • für die Wurzel w gilt: ρ(w) ≥ 2. Der kleinstmögliche (a,b)-Baum ist ein (2,3)-Baum. Höhe vom (a,b)-Baum: Nun schätzen wir die Höhe eines (a,b)-Baums ab. Sei n die Anzahl der Blätter eines (a,b)-Baums der Höhe h. Ein Baum der Höhe h hat die meisten Blätter, wenn jeder Knoten b Kinder hat. Die wenigsten Blätter hat er, wenn die Wurzel zwei und jeder andere Knoten a Kinder hat. Also gilt: 2ah−1 ≤ n ≤ bh (h − 1) log a ≤ log n ≤ h log b Somit hat ein (a,b)-Baum mit n Blättern die Höhe h = Θ(log n) Das bedeutet für einen Baum mit einer Million Blättern und b etwa gleich 100 eine Höhe von nur drei bis vier. Wir betrachten die Abspeicherung einer Menge von Daten S ⊂ U (U , ≤ (linear geordnet)) mit S = x1 , ..., xn und x1 < x2 < ... < xn von einem (a,b)-Baum mit n Blättern. Daraus ergeben sich folgende Eigenschaften: 1. Die Elemente stehen in den Blättern und zwar aufsteigend geordnet von links nach rechts. 2. In jedem inneren Knoten v mit k = ρ(v) − 1 sind die Elemente y1 bis yk aufsteigend geordnet. 3. In den inneren Knoten verweisen Zeiger auf Teilbäume, wobei yi das größte Element in den Blättern im i-ten Teilbaum des Knotens v ist, für i = 1 bis k. (siehe Abbildung 3.12) Es folgt ein Beispiel, bei dem die Wochentage in einem (2,3)-Baum abgelegt sind. Dann werden zwei weitere Tage eingefügt. Der A-Tag und der E-Tag. (siehe Abbildung 3.13 bis 3.17) Wörterbuchoperationen: Wir betrachten nun die Wörterbuchoperationen zum Suchen, Einfügen und Streichen. 42 y1 y2 v y1 ... yk y2 >yk yk Abbildung 3.12: (a,b)-Baum Do Mo Di Di Fr Mi Do Fr Sa Mi Mo Sa So Abbildung 3.13: Bsp.: Wochentage im (2,3)-Baum aufsteigend geordnet Do Fr At Di At Di Fr Mi Do Fr Mi Sa Mo Sa So Abbildung 3.14: Als einfacher Fall der Einfügung wird der A-Tag eingefügt. Do Mo At Di At Di Et Do Et Fr Mi Fr Mi Mo Sa Sa So Abbildung 3.15: Als nächstes Beispiel wird ein E-Tag eingefügt. 43 Do Fr Mo Et At Di At Di Do Et Mi Fr Sa Mi Mo Sa So Abbildung 3.16: Der Knoten mit (Et Fr Mi) wird aufgespalten und Fr rückt nach oben. Fr Do At Di At Di Mo Et Do Et Mi Fr Mi Sa Mo Sa So Abbildung 3.17: Die Wurzel wird aufgespalten und aus Fr wird eine neue Wurzel erzeugt. Algorithmus zum Suchen: SUCH(x) 1. Sei y1 ...yk die Beschriftung der Wurzel, finde i mit yi−1 < x ≤ yi bzw. i = 1, falls x ≤ y1 oder i = k + 1, falls x > yk . 2. Suche rekursiv im i-ten Teilbaum. 3. Falls beim Blatt angelangt: Ist die Beschriftung = x, dann wurde x gefunden. Ist die Beschriftung 6= x, dann ist x nicht in der Menge S enthalten. Algorithmus zum Einfügen: EINF(x) 1. SUCH(x) liefert ein Blatt w. 2. Falls w das Element x enthält, dann sind wir fertig. Sonst schaffe ein neues Blatt, und hänge es links von w am Vaterknoten v von w an und füge x an entsprechender Stelle in v ein. Sind wir bei dem rechtesten Blatt angelangt, dann müssen wir noch mal vergleichen um herauszufinden, ob wir rechts oder links einfügen sollen. 44 3. Falls immer noch ρ(v) ≤ b ist, dann sind wir fertig. Sonst (ρ(v) = b + 1): Spalte v in zwei Knoten v 0 , v 00 mit . . . zm zm+1 . . . v T1 y1 . . . yk 2 und . . . zm v' Tk+1 b+1 Ts 2 Kindern. ys+1 zm+1 . . . y1 . . . ys T1 b+1 ys+2 . . . yk Ts+1 Ts+2 v'' Tk+1 Es kann auch sein, dass zm+1 der linkeste oder zm der rechteste Eintrag im Vaterknoten ist. 4. Wende Schritt 3 auf den Vater von v an... usw. ggf. bis zur Wurzel. 5. Falls die Wurzel gespalten werden muss, schaffe eine neue Wurzel mit zwei Kindern, die die beiden Teile der alten Wurzel als Kinder hat. Dabei müssen immer Schlüssel in den inneren Knoten aktualisiert werden. Der Algorithmus benötigt O(log n) Zeit, da von der Wurzel zum Blatt und eventuell wieder zurückgegangen werden muss. Spalten kostet konstante Zeit, da nur die Zeiger umgehängt werden. 45 Algorithmus zum Streichen: STREICH(x) 1. Um ein Element x zu streichen, wird zunächst das Blatt w gesucht, in dem sich x befinden müsste. Falls das Blatt w x nicht enthält ist der Algorithmus fertig. Ansonsten, sei v der Vater von w. 2. Ist x nicht das rechte äußere Blatt von v, wird der Schlüssel auf x in v gelöscht und das Blatt w gelöscht, s. Abb.3.18(a). 3. Ansonsten wird w und der Schlüssel auf das benachbarte Element y entfernt. Um den Schlüssel von y wieder einzufügen, wird nach oben den Weg folgend der Knoten gesucht, der x als Schlüssel enthält. Dieses x wird durch den Schlüssel y ersetzt, s. Abb. 3.18(b). .... x .... .... y .... ... v ... y x z ... y x w z y ... z y v ... y z ... z z (a) y ... v v ... z x w z y (b) Abbildung 3.18: Striechen: (a) x ist nicht das größte Kind von v; (b) x ist im rechten äußeren Blatt von v gespeichert, dabei muss der Schlüssel weiter auf dem Pfad zur Wurzel aktualisiert werden. In dem Fall das v noch genug (> a) Kinder hat, ist das Streichen fertig. 4. Ansonsten, d.h., v hat a−1 Kinder, wird ein Kind von einem benachbarten Knoten y adoptiert oder die beiden Knoten vereinigt: (a) y hat a Kinder. Es werden y und v zu einem Knoten mit 2a−1 Kinder verschmolzen, s. Abb. 3.19. Anschließend wird rekursiv überprüft, ob der Vaterknoten ausreichend Kinder enthält. (b) y hat noch genug (> a) Kinder. Um den Kindermangel bei v auszugleichen, adoptiert v ein Kind von y, s. Abb. 3.20. Zeitkosten: Alle Operationen finden entlang des Suchpfades statt, jede Operation benötigt konstante Zeit, insgesamt kann in O(log n) Zeit gestrichen werden. Satz 3.1.6. Die Operationen des Wörterbuchproblems (EINF, STREICH, SUCH) können in einem (a, b)-Baum mit n Blättern in O(log n) Zeit ausgeführt werden. 46 Abbildung 3.19: Knoten y und v werden zu einem Knoten verschmolzen. Abbildung 3.20: Knoten v adoptiert ein Kind von dem Nachbarknoten y. 47 Andere Operationen, bspw. Minimum bzw. Maximum finden können in konstanter Zeit O(1) stattfinden, wenn zwei Zeiger auf das linke äußere und rechte äußere Blatt mitgeführt werden. Das sortierte Ausgeben kann in linearer Zeit O(n) durch Inorder-Traversierung der Bäume stattfinden, da die Elemente schon sortiert im Baum stehen. Anmerkungen: B-Bäume sind (a, b)-Bäume mit der Belegung b = 2a − 1. Die einfachste Form von (a, b)-Bäumen sind (2, 3)-Bäume, sie sind eine Alternative zu Binärbäumen. Als Datenstruktur finden (a, b)-Bäume Anwendung bei Hintergrundspeicherintensiven Anwendungen. Sie sind besonders gut dafür geeignet, da sie den Zugriff auf den Hintergrundspeicher, der tausend Mal mehr Zeit kostet als Operationen in der CPU oder auf dem Hauptspeicher, minimieren. Der Hintergrundspeicher ist in Seiten organisiert, diese sind mehrere hundert Kilobyte groß. Die (a, b)-Bäume sind so konstruiert dass ein Knoten inkl. der Schlüssel auf eine Seite passt. Jeder Schritt von einem Knoten zu einem Kind entspricht einem Hintergrundspeicherzugriff. a, b sind gewöhnlich mehrere Hundert groß. In der Praxis ist die Tiefe der Bäume zwei, drei oder vier. Mit AV L- und (a, b)-Bäumen kann in logarithmischer Zeit Wörterbuchprobleme gelöst werden. 3.1.6 Andere Datenstrukturen für das Wörterbuchproblem Gewichtsbalancierte Bäume (BB[α]-Bäume) BB[α]-Bäume sind binäre Suchbäume. Für den innerer Knoten v wird der linke Teilbaum Tl , der rechte Teilbaum Tr genannt. Der Teilbaum der v als Wurzel hat, wird T genannt, s. Abb. 3.21. Abbildung 3.21: Beispiel: BB[α]-Baum Die Balance eines inneren Knotens v ist definiert als Anzahl Blätter in Tl durch die Anzahl Blätter in T : 48 balance(v) = Anz. Blätter Tl Anz. Blätter T Der binäre Suchbaum ist dann ein BB[α]-Baum, wenn die Balance für alle inneren Knoten v im Interval [α, 1 − α] liegt, wobei α ∈ (0, 21 ]. BB[α]-Bäume haben logarithmische Tiefe. Die aus den AVL-Bäumen bekannten Operationen Rotation und Doppelrotation stellen bei Einfüge-√oder Streichoperationen die Gewichtsbalancierung wieder her, falls α ∈ [ 41 , 1− 22 ]. Damit haben die Wörterbuchoperationen logarithmische Laufzeit. Bruderbäume Bruderbäume sind nicht unbedingt Binärbäume. Innere Knoten haben ein oder zwei Kinder. Jeder Knoten mit einem Kind hat einen Bruder mit zwei Kindern. Alle zwei Ebenen mindestens verdoppelt sich die Anzahl der Knoten. Somit hat jeder Knoten zwei Enkel, und die Tiefe liegt in O(log n). Ansonsten sind sie wie binäre Suchbäume, in den Blättern stehen die Daten, in den inneren Knoten stehen die Schlüssel. Die Operationen: sind denen der (a, b)-Bäume ähnlich. Wir betrachten zum Beispiel die Streichen-Operation: STREICHEN: v sei der Knoten, der ein Kind verloren hat. 1. Falls der Vater von v zwei Kinder hat, und der Bruder hat zwei Kinder, dann wird das Kind gestrichen. Es sind keine weiteren Operationen notwendig. 2. Vater hat ein Kind, Onkel hat vier Enkel: 49 In diesem Fall adoptiert v ein Kind vom Cousin. 3. (a) Vater hat ein Kind, Onkel hat drei Enkel, linker Cousin hat ein Kind, rechter Cousin hat zwei Kinder: v übernimmt das Kind des Cousins, dieser wird entfernt. Onkel und Vater v werden danach zusammengefasst. Mit dem markierten Knoten (dem Großvater) wird rekursiv weiter gemacht. Nun ist der markierte Knoten derjenige, der ein Kind verloren hat. (b) Vater hat ein Kind, Onkel hat 3 Enkel, der linke Cousin hat zwei Kinder, der rechte Cousin hat ein Kind v adoptiert linkes Kind vom linken Cousin, da nun beide Cousins je ein Kind haben, werden sie verschmolzen. Nun muss der Onkel rekursiv betrachtet werden, da er ein Kind weniger hat. 50 3.1.7 Rot-Schwarz Bäume Rot-Schwarz Bäume sind binäre Suchbäume, deren Knoten entweder “rot” oder “schwarz” gefärbt sind, d.h. sie werden unterschiedlich gekennzeichnet. Wie bei anderen Binärbäumen befinden sich die Daten in den Blättern. Dazu haben Rot-Schwarz Bäume folgende Eigenschaften: • jedes Blatt ist schwarz, • rote Knoten haben zwei schwarze Kinder, • jeder Weg von der Wurzel bis zu einem Blatt hat die gleiche Anzahl schwarzer Knoten. Daraus folgt, dass Rot-Schwarz Bäume logarithmische Höhe haben. Damit RotSchwarz Bäume beim Einfügen eines Elementes diese Eigenschaften bewahren, werden dabei wenn erforderlich Rotationen durchgeführt (so wie bei AVL-Bäumen). Beispiel. Ein Rot-Schwarz Baum kann beispielhaft folgendermaßen aussehen: Bemerkung. Wenn man rote Knoten mit ihren schwarzen Vätern verschmilzt (siehe Kreise auf der Abbildung), wird ein Rot-Schwarz Baum zu einem (2,4)Baum. Dies ist ausserdem auch der Fall bei Bruderbäumen : wenn man Vaterknoten mit ihren Einzelkindern verschmilzt, bekommt man einen AVL-Baum. 3.1.8 Wörterbuchproblem für Wörter bzw. Strings Gegeben ist ein endliches Alphabet Σ, wobei |Σ| = k. Das Universum ist die Menge der Wörter in Σ∗ . 51 Das Problem ist das gleiche wie beim normalen Wörterbuchproblem, bezogen auf dieses Universum. Man möchte also Elemente suchen, einfügen und streichen können. Anwendungen Dieses Problem ist für folgende Anwendungen relevant: • Für Suchmaschinen im Internet, da man nach bestimmten Strings in Internetseiten sucht. • In der Bioinformatik, da bei der Genomanalyse Teilstrings bei der Suche nach einem “größten gemeinsamen Superstring” manipuliert werden. • Für Datenkompression, weil es in einer Sprache etwa 60000 Wörter gibt, mit jeweils im Mittel 5 Zeichen, also insgesamt 300000 Zeichen. Aber log(300000) ' 18, also 3 Bytes, aber man benutzt im Mittel 5 Bytes. Es bleiben also 2 Bytes zum Komprimieren übrig. Datenstruktur Die benutzte Datenstruktur für das Problem ist der “Trie” (vom englischen retrieval), auch “digitaler Suchbaum” genannt. Es ist ein Suchbaum, in dem jeder Knoten einen Buchstaben enthält. Jedes seiner Kinder enthält den nächsten Buchstaben, der in einem der Wörter des Alphabets vorkommen. So wird ein Wort also durch einen Weg von der Wurzel zu einem Knoten dargestellt. Man muss also Knoten, die das Ende eines Wortes darstellen, speziell markieren. Bemerkenswert ist dabei, dass die Wurzel des Baumes keine Information enthält, da die Wörter ja nicht alle mit dem gleichen Buchstabe anfangen. Andernfalls müsste man einen Baum pro Anfangsbuchstabe haben. Beispiel. Sei das Alphabet Σ = {der, die, das, einer, eine, eines}. Erstellen wir den dazugehörenden Trie: Laufzeit der Operationen Suchen, Einfügen und Streichen eines Wortes W der Länge |W | dauert O(|W |). |W | ist nämlich die Länge des Weges, der vom ersten Buchstabe bis zum letzten Buchstabe führt, da in jedem Knoten nur ein Buchstabe gespeichert wird. Verbesserung Man kann dies etwas verbessern, indem man Folgen von Knoten, die nur ein Kind haben, einfach in einem Knoten zusammenschmilzt, wie beim folgenden Beispiel: Knotenorganisation In der Regel besteht jeder Knoten aus einem Feld oder einer verketteten Liste von Verweisen auf die Kind-Knoten. Um schnell feststellen zu können, ob ein Knoten ein Kind hat, das einen bestimmten Buchstaben enthält, kann man für jeden Knoten einen Bit-array der Größe des Alphabets speichern. 52 d a s e e u i r e i n e n s Abbildung 3.22: Beispiel : trie eine s r Abbildung 3.23: Verbesserung zum trie 3.2 Das Vereinige-Finde-Problem (auch “Union-Find” oder “Disjoint Sets”) Abstrakter Datentyp Der abstrakte Datentyp, der dieses Problem darstellt, ist eine feste endliche Menge S (o.B.d.A. ist S = {1,...,n}). Sei S eine Partition von dieser Menge S, mit S = {S1 , ..., Sk } Sk (d.h. i=1 Si = S und ∀i, j mit i 6= j: Si ∩ Sj = ∅). Jedes Si wird durch einen Repräsentanten - einem seiner Elemente - dargestellt. Operationen Bezüglich dieser Menge sollen folgende Operationen möglich sein: • VEREINIGE(Si , Sj ) mit Si , Sj zwei Repräsentanten. Diese Operation verschmilzt zwei Mengen der Partition S zu einer einzigen. Die Partition wird also sozusagen “vergröbert”. Formal kann man es folgenderweise darstellen : S := S \ {Si , Sj } ∪ {Si ∪ Sj }). • FINDE(a) mit a ∈ S. 53 Diese Operation liefert den Repräsentanten von der Menge Si aus S, die a enthält. Anwendungen Dieses Problem hat folgende Anwendungen : • Sei G = (V, E) ein ungerichteter Graph. Die Zusammenhangskomponente von G zu finden, ist ein Vereinige-Finde Problem : Sei S die Menge der Knoten und S zunächst eine Menge von n einelementiger Mengen. Man durchläuft die Menge E der Kanten ; für jede Kante : e = (u, v). Falls Si =FINDE(n)6= FINDE(v)=Sj , dann VEREINIGE(Si ,Sj ). Zum Schluß enthält S die Zusammenhangskomponente von G. • Bei der Suche nach minimal spannende Bäume (siehe später im Skript). • Bei der Bildverarbeitung, wenn man “Segmentierung” machen möchte, also das Einteilen des Bildes in mehrere ähnliche Zonen (der Farbe nach). • In der Sprache Fortran gibt es den Befehl EQUIVALENCE(x,y), der zwei Variablen x und y gleichstellt. Beim Kompilieren gibt es dann ein solches Vereinige-Finde Problem, wenn mehrere EQUIVALENCE-Befehle im Code vorkommen. (z.B. EQUIVALENCE(x,y) und EQUIVALENCE(y,z) ). Datenstruktur Die verwendete Datenstruktur für das Problem ist ein “Wald”, also eine Menge von Bäumen. Für jede Menge Si steht ein Baum, dessen Knoten die Elemente von Si enthalten. Verweise sind Kind-Vater-Verweise (und nicht Vater-Kind-Verweise wie z.B. bei Binärbäumen). Dabei ist der Repräsentant der Menge Si , die Wurzel des Baumes. 9 (1) 3 2 6 8 7 1 4 5 Abbildung 3.24: Beispiel : Wald Laufzeit von Vereinige-Finde Operationen Analysieren wir kurz die Laufzeit dieser Operationen : • VEREINIGE(Si ,Sj ): Diese Operation macht die Wurzel eines der beiden Bäume zum Kind der anderen Wurzel. Das dauert nur O(1) Zeit, weil es nur eine Zeigermodifikation ist. 54 Beispiel. siehe Pfeil (1) auf Abbildung 3.24. • FINDE(a): Diese Operation erfolgt folgenderweise : Man erhält zunächst einen Verweis auf die Stelle des Waldes, wo sich a befindet. Von dort folgt man den Vaterverweisen bis zur Wurzel. Der Verweis auf die Wurzel wird dann geliefert, da diese den Repräsentanten enthält. Die Laufzeit dieser Operation ist also O(h) wobei h die Höhe des Baumes ist, der a enthält, da wir den ganzen Baum von unten nach oben durchlaufen. Im schlechtesten Fall ist das Θ(n), wenn der Wald einen einzigen Baum enthält, und dieser in Form einer Liste ist (2). (1) (2) Abbildung 3.25: Alternative (1) wird angestrebt Dies kann aber verbessert werden, indem bei der VEREINIGE-Operation der Baum mit geringerer Höhe an die Wurzel des anderen gehängt wird (Wenn beide Höhen gleich sind ist es egal). Dazu braucht man ein zusätzliches Feld “Höhe”. Wenn i ein Repräsentant ist, ist Höhe[i] die Höhe des entsprechenden Baumes. Behauptung 3.2.1. Höhenbalancierung Falls die Startsituation so ist, dass jedes Element der Partition nur ein Element entält ( Si = {i}, i=1,...,n), dann kann eine Folge von VEREINIGE-FINDE Operationen ausgeführt werden, so dass die Höhe eines entstandenen Baumes mit k Knoten nie größer ist als dlog(k)e. Satz 3.2.2. Mit Höhenausgleich gilt also: • VEREINIGE-Operationen erfolgen in O(1) Zeit (dazu ist die Höheninformation der Wurzel mit einem Feld in konstanter Zeit aktualisierbar) • FINDE-Operationen erfolgen in O(log(n)) Zeit. 55 Beweis: (durch Induktion über k) Induktionsanfang: k = 1 Da es sich um einen einzelnen Knoten handelt, ist die Höhe h = 0. Dann gilt für die Formel 2h ≤ k: 20 = 1 X Induktionsvoraussetzung: Sei die Behauptung wahr für alle Bäume mit ≤ k − 1 Knoten. Induktionsschluss: Zeige, dass die Behauptung für Bäume mit k Knoten gilt: Sei T ein Baum mit k Knoten. Dann betrachtet man die VEREINIGE-Operation bei der T entstanden ist. T ist aus zwei Bäumen T1 und T2 entstanden: T1 hat k1 Elemente und Höhe h1 und T2 hat k2 Elemente und Höhe h2 . Für die Höhe h von T gilt: h = max(h1 , h2 + 1). Außerdem gilt für die Anzahl der Elemente von T: k = k1 + k2 und h1 ≥ h2 . Jetzt betrachten wir die folgenden 2 Fälle: 1. Fall: h = h1 k |{z} = k1 + k2 ≥ k1 ≥ 2h1 = 2h |{z} s.o. I.V. 2. Fall: h = h2 + 1 Dann muss h1 = h2 gelten. (Sonst müsste h2 < h1 gelten und damit wäre h nicht die Gesamthöhe.) k |{z} = k1 + k2 ≥ 2h1 + 2h2 = 2 ∗ 2h2 = 2h2 +1 = 2h |{z} s.o. I.V. Aufgrund des Höhenausgleichs hat die VEREINIGE-Operation O(1) (also konstante) und die FINDE-Operation O(h) = O(log(n)) Laufzeit. (Die Höheninformation der Wurzel ist in konstanter Zeit aktualisierbar.) Die Laufzeit der FINDE-Operation ist noch nicht zufriedenstellend, daher versuchen wir diese noch zu verbessern. Im folgenden versuchen wir dies durch Pfadkompression zu erreichen. 56 3.2.1 Pfadkompression: Bei der FINDE-Operation werden alle Knoten auf dem Pfad bis zur Wurzel auf” gesammelt“ und direkt an die Wurzel angehangen. Dies verkürzt dann spätere FINDE-Operationen. Frage: Wie stark werden diese verkürzt? Wenn man experimentell viele FINDE-Operationen mit diesem Prinzip durchführt, dann lässt das Ergebnis vermuten, dass dies in konstanter Zeit möglich ist. Dies stimmt aber nicht ganz. Man braucht O(f (n)) Zeit, wobei f eine Funktion ist, die sehr langsam wächst (weniger als log(log(n))). Analyse der Pfadkompression: Zunächst definieren wir zwei Funktionen F, G : N → N mit den Eigenschaften: F (0) = 1 F (i) = 2F (i−1) ∀i ≥ 1 G(n) = min {k|F (k) ≥ n} Von den beiden Funktionen wächst eine sehr schnell und eine sehr langsam.: i F (i) G(n) n 0 1 1 0 1 2 2 1 2 4 3, 4 2 3 16 5, .., 16 3 4 65536 17, .., 65536 4 5 265536 (etwa 22000 Stellen) 65537, .., 265536 5 ... ... ... ... F wächst also sehr schnell. Man kann sich F als Stapel von zweien“ vorstellen: ” (2(..))))) (2(2 F (i) = 2| (2 {z } i 2en G wächst extrem schwach, aber strebt nach ∞ für n → ∞. G wird auch als log ∗ bezeichnet. log ∗ entspricht der Anzahl der Anwendungen von log auf n bis man einen Wert ≤ 1 erhält. Im Mittel benötigen alle FINDE-Operationen log ∗ Zeit. Wir betrachten nun folgende Situation: Sei Si = {i} für alle i = 1, .., n. Nun betrachten wir eine Folge σ von m FINDE- und höchstens n−1 VEREINIGEOperationen. Wir definieren den Rang eines Knotens v in der Datenstruktur. Hierfür streichen wir die FINDE-Operationen aus σ und schauen uns nur die VEREINIGEOperationen an. σ 0 ist die Folge von VEREINIGE-Operationen. 57 Rang(v) = Höhe des Baumes Tv mit der Wurzel v nachdem σ 0 ausgeführt wurde. (Die Höhe kann durch Höhenbalancierung höchstens log(n) betragen.) Es gilt für alle Knoten v: a) Der Baum Tv hat mindestens 2Rang(v) Knoten. (Dies wurde bereits im Lemma gezeigt.) b) Es gibt höchstens 2nr Knoten mit Rang r ∈ N. (Dies folgt direkt aus 1.) c) Alle Ränge sind ≤ log(n). (Dies folgt aus dem Höhenausgleich und 2.) d) Falls bei der Ausführung von σ (besteht aus VEREINIGE- und FINDEOperationen) irgendwann w Nachkomme von v (↔ w ist Element des Unterbaumes mit Wurzel v) ist, dann ist der Rang(w) < Rang(v). (Denn: falls w Nachkomme von v bei Ausführung von σ ist, dann ist er das auch bei Ausführung von σ 0 . ⇒ Rang(w) < Rang(v) Im nächsten Schritt teilen wir die Ränge in Gruppen auf.: r → Gruppe G(r) 0, 1 → Gruppe 0 2 → Gruppe 1 3, 4 → Gruppe 2 5, .., 16 → Gruppe 3 Wir betrachten die Folge σ: Jede VEREINIGE-Operation kostet O(1) Zeit. Die Kosten der FINDE-Operationen verteilen sich auf die Knoten der Bäume und die FINDE-Operationen selbst (→ accounting, übersetzt“: Buchhalter-Analyse“): ” ” Die Kosten für FINDE(i) sind proportional zur Länge des Weges von i zur Wurzel. D.h. für jeden Knoten v ergeben sich konstante Kosten und diese werden angerechnet 58 1. der FINDE-Operation, falls v die Wurzel oder der Vater von v in einer anderen Ranggruppe als v ist. 2. dem Knoten v sonst (d.h. G(Rang(V ater(v))) = G(Rang(v))). Mit Fall 1 gilt, dass keine FINDE-Operation mit mehr als O(G(n)) Kosten belastet wird. Bergründung: Die Ränge sind auf dem Weg von i zur Wurzel aufsteigend (aus d) folgt, dass die Folge der Ränge streng monoton wächst) und die Ranggruppen ändern sich höchstens G(n) mal (sogar nur G(log(n)), aber G(n) und G(log(n)) unterscheiden sich nur um 1). Mit Fall 2 gilt: v ist selbst nicht die Wurzel und wird nach oben bewegt. v wird Kind eines Knotens mit Rang größer dem Rang seines bisherigen Vaters (nach d)). ⇒ Falls g := G(RAN G(v)) > 0 gilt, dann kann v höchstens F (g) − F (g − 1) mal belastet werden. | {z } Anzahl der Ränge in Gruppe g Gesamtkosten dieser Art: N (g) :=Anzahl der Knoten in der Ranggruppe g FP (g) n 1 1 n N (g) ≤ = 2F (g−1)+1 ∗ (1 + + + ...) ≤ r 2 r=F (g−1)+1 |{z} | 2 {z 4 } = n F (g) ≤2 nach b) Jeder Knoten wird höchsten F (g) n 2F (g−1) −F (g − 1) | {z } mal belastet. kann weggelassen werden Also sind die Kosten für jede Ranggruppe G insgesamt ≤ F n(g) ∗ F (g) = n. Insgesamt sind die Kosten für jede FINDE-Operation für Fall 1 O(G(n)) und für Fall 2 O(n ∗ G(n)). Zusammenfassend ergibt sich der folgende Satz: Ausgehend von Si = {i} für i = 1, .., n werde eine Folge σ von m FINDE- und beliebig vielen VEREINIGE-Operartionen mit Höhenausgleich und Pfadkompression ausgeführt. Dann ist die Laufzeit insgesamt O(m ∗ log ∗ (n) + n ∗ log ∗ (n)). (Die Kosten der VEREINIGE-Operationen sind konstant und fallen hierbei nicht ins Gewicht.) 59 O(m ∗ log ∗ (n) + n ∗ log ∗ (n)) ist die amortisierte Laufzeit“, d.h. die einzelnen ” Operationen können mehr kosten, aber die Gesamtfolge ist günstig pro Operation. Diese Laufzeit ist nah an einer konstanten Laufzeit. Allerdings geht es noch besser! Satz: (Ohne Beweis) Sei wie oben Si = {i} für i = 1, .., n. Es werde eine Folge σ von m FINDE- und n VEREINIGE-Operartionen mit Höhenausgleich und Pfadkompression ausgeführt, die O(m wobei ∗ α(m, n)) Zeit brauchen, ) > log(n) , wobei A die Ackermann-Funktion α(m, n) = min z ≥ 1, A(z, 4 ∗ m n sei. Es handelt sich hier ebenfalls um eine amortisierte Laufzeit. Ackermann-Funktion: A : N X N → N definiert durch: A(0, 0) = 0 A(i, 0) = 1 ∀i ∈ N A(0, x) = 2 ∗ x ∀x ∈ N A(i + 1, x) = A(i, A(i + 1, x − 1)) Schauen wir uns num mal einige Werte der Ackermann-Funktion an. i\x 0 1 2 3 4 5 0 0 2 4 6 8 10 1 1 2 4 8 16 32 1 2 4 16 65536 26 5536 2 3 1 2 4 65536 A(1, x) = A(0, A(1, x − 1)) = 2 ∗ A(1, x − 1) A(2, x) = A(1, A(2, x − 1)) = 2A(2,x−1) α ist die inverse Ackermann-Funktion“ und wächst gering, strebt aber nach ∞ ” für n → ∞. 60 3.3 Optimale binäre Suchbäume Problem 3.3.1. Sei S eine Menge von Schlüsseln aus einem endlichen, linear geordneten Universum U , S = {a1 , a2 , ..., an } ⊆ U und |S| = n ∈ N. Wir wollen S in einem binären Suchbaum abspeichern, um Anfragen der Form a ∈ S? für beliebige Elemente a ∈ U zu beantworten. Der binäre Suchbaum enthalte in den inneren Knoten die Schlüssel aus S. Entsprechend einem binären Suchbaum gelte, dass alle Elemente im linken Teilbaum eines inneren Knoten kleiner oder gleich dem Schlüssel im inneren Knoten sind. Die Blätter sollen die Intervalle zwischen den Schlüsseln in S enthalten, von links nach rechts aufsteigend sortiert: ( , a1 ), (a1 , a2 ), . . . , (an−1 , an ),(an , ). Die Blätter können unterschiedliche Höhen haben. Die Zugriffswahrscheinlichkeiten seien: 1. pi := Wahrscheinlichkeit, dass bei einer Suchanfrage nach ai ∈ S gefragt wird. 2. qi := Wahrscheinlichkeit, dass bei einer Suchanfrage nach a ∈ / S gefragt wird, mit ai < a < ai+1 . 3. q0 := Wahrscheinlichkeit, dass nach a < a1 gefragt wird. 4. qn := Wahrscheinlichkeit, dass nach a > an gefragt wird. wobei delt. Pn i=1 pi + qi = 1, da es sich um eine Wahrscheinlichkeitsverteilung han- Frage 3.3.2. Wie sieht der optimale binäre Suchbaum für S aus? Definition 3.3.3. Ein binärer Suchbaum ist optimal, wenn die erwartete Anzahl an Vergleichen für eine Suchanfrage minimal ist. Wir minimieren folgende Zielfunkunktion: P = n X pi · (Tiefe(i) + 1) + i=1 | n X qj · (Tiefe0 (j)) j=0 {z } 1. | {z 2. } wobei Tiefe(i) die Tiefe des Knotens ai sei und Tiefe0 (j) die Tiefe des Blattes (aj , aj+1 ) für 0 < j < n und Tiefe0 (0) die Tiefe des Blattes ( , a1 ) bzw. Tiefe0 (n) die Tiefe des Blattes (an , ). Also ist 1. Summe des Produktes aus der Anfragewahrscheinlichkeit eines Schlüssels ai ∈ S und der Länge dessen Suchpfades + 1 (für die Wurzel). 2. Summe über die Produkte aus allen möglichen Anfragen von Schlüsseln a 6∈ S und der Länge deren Suchpfade. 61 3.3.1 Beispiel Wir betrachten folgendes Beispiel. Sei U = {1, 2, 3, 4, 5, 6, 7} und die Menge der Schlüssel S = {2, 5, 6}. Nach Schlüsseln aus S wird mit den Wahrscheinlichkeiten p1 = 0, 3, p2 = 0, 2 und p3 = 0, 1 gefragt. Die Intervalle zwischen den Schlüsseln in S seien gleich wahrscheinlich, d.h. q0 = q1 = q2 = q3 = 0, 1. Dann sind z.B. die Suchbäume in Abbildung 3.26 möglich. a2 p2 = 0,2 a3 p3 = 0,1 p1 = 0,3 a1 (_,a1) 0,1 (a1,a2) 0,1 (a2,a3) 0,1 (a3,_) 0,1 (a) Baum T1 mit PT1 = 1, 8 p1 = 0,3 a1 a3 p3 = 0,1 p2 = 0,2 (a3,_) a2 p1 = 0,3 0,1 (a2,a3) a1 0,1 (a1,a2) (_,a1) 0,1 0,1 (_,a1) 0,1 p2 = 0,2 a2 a3 p3 = 0,1 (a1,a2) 0,1 (a2,a3) 0,1 (b) Baum T2 mit PT2 = 2, 3 (a3,_) 0,1 (c) Baum T3 mit PT3 = 1, 9 Abbildung 3.26: Beispiele für binäre Suchbäume Die Zielfunktion für die Suchbäume T1 , T2 , T3 in Abbildung 3.26 beträgt: PT1 PT2 PT3 = (0, 1 + 0, 1 + 0, 3) + (0, 1 + 0, 1 + 0, 1) + ((0, 1 + 0, 1 + 0, 3) + (0, 1 + 0, 1 + 0, 1) + 0, 2) = (0, 5 + 0, 3) + ((0, 5 + 0, 3) + 0, 2) = 1, 8. = (0, 1 + 0, 1 + 0, 3) + (0, 5 + 0, 1 + 0, 2) + (0, 8 + 0, 1 + 0, 1) = 0, 5 + 0, 8 + 1, 0 = 2, 3. = (0, 1 + 0, 1 + 0, 1) + (0, 1 + 0, 3 + 0, 2) + (0, 1 + 0, 6 + 0, 3) = 0, 3 + 0, 6 + 1, 0 = 1, 9. In diesem Beispiel ist also der Suchbaum T1 besser als die Bäume T2 und T3 . 62 3.3.2 Algorithmus Gesucht ist ein Algorithmus, der für eine gegebene Schlüsselmenge S aus einem Universum U einen optimalen binären Suchbaum konstruiert. Bemerkung. Die Methode des Durchtestens aller möglichen Suchbäume (brute force) ist zwar möglich aber ineffizient, da es 2n 1 · ≈ 4n n+1 n zu untersuchende Bäume gibt. Für große n ist diese Methode daher praktisch nicht umsetzbar. Wir stellen zunächst einige Vorüberlegungen an. Dazu benutzen wir folgende Bezeichnungen, welche in Abbildung 3.27 schematisch dargestellt sind. • Sei S 0 eine Teilmenge von S: S 0 ⊆ S, S 0 = {ai , . . . , aj }. • Ti,j sei der optimale Suchbaum für S 0 . • Die Wurzel von Ti,j sei am , m ∈ {i, . . . , j}. • Die inneren Knoten von Ti,j sind {ai , . . . , aj }. • Die Blätter von Ti,j haben die Gestalt (ai−1 , ai ), (ai , ai+1 ), . . . , (aj−1 , aj ), (aj , aj+1 ) • Mit PTi,j bezeichnen wir die Zielfunktion eingeschränkt auf den Teilbaum Ti,j . i • Die Zugriffswahrscheinlichkeiten im Teilbaum Ti,j sind pei = wpi,j und qei = qi , wobei w die Wahrscheinlichkeit dafür sei, dass nach einem Element i,j wi,j a ∈ [ai , aj ] gesucht wird. Behauptung 3.3.4. Die erwartete Anzahl an Vergleichen für eine Suchanfrage über einem Teilbaum Ti,j ist im Mittel: PTi,j = 1 + wi,m−1 · PTl + wm+1,j · PTr . wi,j Beweisskizze. Herleitung über: PTi,j = j X pek · (Tiefe(k) + 1) + k=i := m−1 X {z } I | := {z m−1 X } II . . . + pf m · (Tiefe(m) + 1) + k=i II qek · (Tiefe0 (k)) k=i−1 | I j X j X ... k=m+1 0 . . . + qf m · (Tiefe (m)) + k=i−1 j X k=m+1 63 ... wi,j am wi,m-1 T wm+1,j Ti,j Tl Tr 1 n pi pm i pj m-1 m+1 j Abbildung 3.27: Schematische Darstellung der Ausgangssituation. Lemma 3.3.5. Der binäre Suchbaum Ti,j sei optimal für die Menge {ai , . . . , aj } und sei am die Wurzel von Ti,j . Ferner sei Tl linker Teilbaum von am und Tr rechter Teilbaum von am . Dann ist Tl bzw. Tr optimaler binärer Suchbaum für die Menge {ai , . . . , am−1 } bzw. {am+1 , . . . , aj }. Beweis. Angenommen es gibt Tl0 mit PTl0 < PTl ⇔ 0 PTi,j wi,m−1 · PTl0 + wm+1,j · PTr wi,j wi,m−1 · PTl + wm+1,j · PTr < 1+ wi,j = 1+ = PTi,j . Dies steht jedoch im Widerspruch dazu, dass Ti,j optimal ist. Seien folgende Variablen für den Algorithmus vereinbart: ri,j ci,j wi,j := := := Index der Wurzel für Ti,j Kosten von Ti,j = wi,j · PTi,j Wahrscheinlichkeit, dass a ∈ [ai , aj ] (wie bisher). Behauptung 3.3.6. Es gilt ci,j wi,j = wi,j · PTi,j = wi,j + ci,m−1 + cm+1,j = wi,m−1 + pm + wm+1,j . 64 Basierend auf den vorangegangenen Lösungen, können wir nun einen Algorithmus zum Berechnen des optimalen binären Suchbaums angeben. Algorithm 3: [Bellman, 1957] Iterative Suche nach dem optimalen Suchbaum T . 1: for i = 0, . . . , n do 2: wi+1,i = qi 3: ci+1,i = 0 4: end for 5: for k = 0, . . . , n − 1 do 6: for i = 1, . . . , n − k do 7: j =i+k 8: Bestimme m mit i ≤ m ≤ j, so dass ci,m−1 + cm+1,j minimal ist. 9: ri,j = m 10: wi,j = wi,m−1 + wm+1,j + pm 11: ci,j = ci,m−1 + cm+1,j + wi,j 12: end for 13: end for Der Algorithmus benutzt das Konzept der dynamischen Programmierung. Bei dynamischer Programmierung wird die Lösung eines Problems durch Zusammenfügen der Lösungen von Teilproblemen erreicht. Die Lösungen der Teilprobleme sind dabei voneinander abhängig. Insbesondere beruht die Lösung eines Teilproblems auf den Lösungen von kleineren Teilproblemen. Bei dynamischer Programmierungen werden diese kleineren Teilprobleme nur einmal gelöst, und ihre Lösung für mehrere größere, sie enthaltene Teilprobleme weiterverwendet. Eine ausführlichere Beschreibung von dynamischer Programmierung findet sich z.B. im Kapitel 16 des Buches “Introduction to Algorithms” von Cormen et.al. In unserem Fall sind die Teilprobleme gegeben durch die Teilgraphen Ti,j , 1 ≤ i ≤ j ≤ n. Der Algorithmus berechnet nacheinander die optimalen Lösungen für die Teilgraphen Ti,j für k = |i − j| = 0, . . . , n − 1. Für k = 0 berechnet er die optimalen Lösungen für die Teilgraphen Ti,i , i = 1, . . . , n, welche nur aus einem Knoten, ai , bestehen. Für k > 0 berechnet er die optimale Lösung für Ti,j aufbauend auf den bereits berechneten kleineren Lösungen. Im letzten Schritt wird so der optimale binäre Suchbaum T1,n = T berechnet. 3.3.3 Beispiel Wir veranschaulichen die Arbeitsweise des Algorithmus an folgendem Beispiel. Gegeben sei die Menge S = {a1 , a2 , a3 , a4 }. Nach den Schlüsseln in S wird mit den Wahrscheinlichkeiten p1 = p2 = 0, 1, p3 = 0, 2 und p4 = 0, 2 gefragt. Die von den Schlüsseln gebildeten Intervalle seien auch hier gleich wahrscheinlich, d.h. q0 = q1 = q2 = q3 = q4 = 0, 1. Die vom Algorithmus iterativ berechneten Ergebnisse für die Teilprobleme lassen sich in folgender Tabelle 3.1 abspeichern und darstellen. In der ersten Blockzeile der Tabelle stehen die während der Initialisierung berechneten Werte und in den folgenden Blockzeilen die Werte für wachsende k von 0 bis n − 1. Den resultierenden optimalen Suchbaum, dargestellt in Abbildung 3.28, liest 65 i Init 0 1 w2,1 = 0, 1 c2,1 = 0 r1,1 = 1 w1,1 = 0, 2 c1,1 = 0, 2 w1,0 = 0 c1,0 = 0 k=0 k=1 2 w3,2 = 0, 1 c3,2 = 0 r2,2 = 2 w2,2 = 0, 3 c2,2 = 0, 3 r1,2 = 2 w1,2 = 0, 4 c1,2 = 0, 6 k=2 3 w4,3 = 0, 1 c4,3 = 0 r3,3 = 3 w3,3 = 0, 4 c3,3 = 0, 4 r2,3 = 3 w2,3 = 0, 6 c2,3 = 0, 9 r1,3 = 2 w1,3 = 0, 7 c1,3 = 1, 3 k=3 4 w5,4 = 0, 1 c5,4 = 0 r4,4 = 4 w4,4 = 0, 4 c4,4 = 0, 4 r3,4 = 3 w3,4 = 0, 7 c3,4 = 1, 1 r2,4 = 3 w2,4 = 0, 9 c2,4 = 1, 6 r1,4 = 3 w1,4 = 2 c1,4 = 1 Tabelle 3.1: Tabelle zur Speicherung der Berechnungen des Algorithmus man, wie in der Tabelle angedeutet, heraus. Der Index der Wurzel ist der Index mw = r1,n im untersten Block. Die Indezes des linken und rechten Kindes der Wurzel sind ml = r1,mw −1 und mr = rmw +1,n usw. 3.3.4 Analyse Speicherkomplexität Der Algorithmus benötigt zum Speichern der Zwischenergebnisse eine Matrix M ∈ M (n + 1 × n, R). Der Speicherbedarf liegt daher bei S(n) = n · (n + 1) = n2 + n ∈ Θ(n2 ). Laufzeitkomplexität T (n) = c1 · n | {z } + Zeile (1) bis (4) = c1 · n + k=0 i=1 n−1 X n−k X k=0 i=1 = c1 · n + n−1 X n−k X n−1 X z }| { c2 + (j − i)c3 + |{z} | {z } Zeile (7) = c1 · n + c5 · (c2 + c4 ) + k · c3 | {z } c5 ((n − k)c5 + (n − k)k · c3 ) k + c3 · k=0 ∈ Zeile (8) k=0 n−1 X O n + n2 + n3 =k n−1 X k=0 = O(n3 ) 66 k2 c4 |{z} Zeile (9) bis (11) i=0 k=0 k=1 i=2 i=1 i=3 i=4 a3 r4,4 = 4 w4,4 = 0,4 c4,4 = 0,4 r1,1 = 1 w1,1 = 0,2 c1,1 = 0,2 r1,2 = 2 w1,2 = 0,4 r1,2 = 0,6 a2 a4 a1 k=2 r1,4 = 3 w1,4 = 2 c1,4 = 1 k=3 Abbildung 3.28: Resultierender Optimaler Binärer Suchbaum. 3.3.5 Optimierungen Es wurde gezeigt, dass der Iterationsalgorithmus nach Bellman kubische Laufzeit hat. Der quadratische Anteil folgt direkt aus der Anwendung der dynamischen Programmierung, so dass hier kein Ansatz zur Verbesserung gefunden werden kann. Interessant ist vielmehr Zeile (8) des Algorithmus, die den Index der Wurzel am des optimalen Suchbaumes Ti,j berechnet. Es ist in diesem Schritt nicht notwendig, alle m mit i ≤ m ≤ j zu prüfen. Sondern man kann zeigen, dass die optimalen Wurzeln von Teilbäumen, die sich nur um einen Knoten unterscheiden, relativ nahe beieinander. Dies geschieht in folgendem Lemma. Lemma 3.3.7. ri,j−1 ≤ ri,j ≤ ri+1,j . Der Beweis von Lemma 3.3.7 kann dem Buch von Donald E. Knuth The Art of Functional Programming, Band 3: Sorting and Searching entnommen werden. Durch die Eingrenzung der Wurzelsuche auf das im Lemma beschriebene Intervall reduziert sich die Gesamtlaufzeit des Algorithmus auf T (n) ∈ O(n2 ). 67 Kapitel 4 Graphenalgorithmen 4.1 Definitionen Definition 4.1.1. Der Graph G = (V, E) ist über die beiden Mengen V und E definiert, wobei V die Menge der Knoten und E die Menge der Kanten in dem Graph ist. Definition 4.1.2. Ein gerichteter Graph G = (V, E) ist ein Graph von geordneten Paaren (u, v) mit u ∈ V und v ∈ V . Beispiele für Graphen sind: • Straßennetz (gerichtet) • Eisenbahn • Rechnernetz • WWW (gerichtet) • Projekt mit Teilprojekten (gerichtet) • soziales Netzwerk 4.1.1 Weg Definition 4.1.3. Ein Weg im Graphen G = (V, E) ist eine Folge von Knoten W = (v1 , ..., vn ), v1 , ..., vn ∈ V , wobei die Kante e = (vi , vi+1 ), i = 1, ..., n − 1, in der Menge E enthalten ist. Eigenschaft 4.1.4. Ein Weg W ist einfach, wenn jeder Knoten v ∈ W genau einmal besucht wurde. Eigenschaft 4.1.5. Ein Weg W beschreibt einen Kreis, wenn v1 = v n . Definition 4.1.6. Ein Graph G heißt azyklisch, wenn er keine Kreise enthält. 68 Definition 4.1.7. Der Durchmesser eines Graphen G ist das Maximum der Abständer alle Knotenpaare (vi , vj ) ∈ W . Definition 4.1.8. Ein Graph G ist zusammenhängend, wenn zwischen einem beliebigen Paar (vi , vj ), vi , vj ∈ V , ein Weg existiert. Ein gerichteter Graph ist stark zusammenhängend, wenn es für je zwei Knoten vi und vj einen Weg von vi nach vj gibt und einen Weg von vj nach vi . Definition 4.1.9. Eine Zusammenhangskomponente ist ein maximaler Teilgraph von G, der zusammenhängend und nicht erweiterbar ist. 4.1.2 Darstellungsformen Graphen können auf verschiedene Arten repräsentiert werden. Die gebräuchlichsten sind Adjazenzmatrizen, Adjazenlisten und Inzidenzmatrizen. Adjazenzmatrix Eine Adjazenzmatix <adjazent lat.; angrenzend, benachbart> ist eine n× n-Matrix, wobei n die Anzahl der Knoten in dem Graphen G = (V, E) ist. In der Matrix wird in der i-ten Zeile und in der j-ten Spalte eine 1 eingetragen, wenn e = (vi , vj ) ∈ E ist. Ist der Graph G ungerichtet, ist die Adjazenzmatrix symmetrisch. Adjazenzliste Eine Adjazenzliste ist eine Liste, die alle Knoten des Graphen G = (V, E) und zusätzlich zu jedem Knoten v ∈ V eine Liste mit seinen Nachbarn enthält. Inzidenzmatrix Eine Inzidenzmatrix ist eine n × m-Matrix, wobei n die Anzahl der Knoten und m die Anzahl der Kanten des Graphen G = (V, E) ist. Ist G ungerichtet, dann wird in der i-ten und j-ten Zeile der l-ten Spalte eine 1 eingetragen, wenn el = (vi , vj ) ∈ E. Ist G gerichtet dann enthält die Zeile zu vi eine −1 (Startknoten) und die Zeile zu vj eine 1 (Endknoten). 4.1.3 Traversierung von Graphen Zwei bekannte Varianten, wie Graphen durchlaufen werden können, sind Breitensuche und Tiefensuche. Breitensuche - BFS (engl; breath first search) In der Breitensuche werden zuerst alle Geschwisterknoten durchsucht und dann die Kinderknoten. Bemerkung. Breitensuche findet immer den kürzesten Weg 69 Tiefensuche - DFS (engl; depth first search) In der Tiefensuche werden zuerst rekursiv alle Kinderknoten durchsucht bevor die Geschwisterknoten durchsucht werden. Bemerkung. Enthält der Graph G = (V, E) einen unendlich langen Weg, ist nicht sichergestellt, dass Tiefensuche einen Weg von vi nach vj mit vi , vj ∈ V findet. Tiefensuche kann eingesetzt werden um den Weg aus einem Labyrinth zu finden, oder um Zusammenhangskomponenten in einem Graphen zu bestimmen. 4.1.4 Topologisches Sortieren Definition 4.1.10. Topologisches Sortieren ordnet die Knoten eines gerichteten, azyklischen Graphen G = (V, E) so an, dass der Knoten u vor dem Knoten v erscheint, wenn G die Kante (u, v) enthält. Beispiel. Teilaufgaben eines Projektes, die hintereinander abgearbeitet werden müssen, Etappen eines Wettkampfes die nacheinander absolviert werden müssen oder Veranstaltungen eines Studienfaches die aufeinander aufbauen. Eine topologische Sortierung des Graphen in 4.1 ist a, f, b, c, g, d, e. Abbildung 4.1: Graph für die topologische Suche 4.1.5 Teilgraph Seien G0 = (V 0 , E 0 ) und G = (V, E) zwei Graphen. Ist V 0 ⊆ V und E 0 ⊆ E dann ist G0 ein Teilgraph von G. G0 ist ein induzierter Teilgraph von G, wenn G0 alle Kanten zwischen den Knoten 0 aus V 0 enthält, die in G vorhanden sind, formal: E 0 = E ∩ V2 . 4.2 starke Zusammenhangskomponenten Definition 4.2.1. Ein induzierter Teilgraph G0 von G, heißt starke Zusammenhangskomponente falls G0 stark zusammenhängend ist und es keinen größeren Teilgraphen gibt, der G0 enthält und stark zusammenhängend ist. 70 (a) (b) (c) Abbildung 4.2: (a) Ausgangsgraph. (b) Teilgraph von (a). (c) Induzierter Teilgraph mit der gleichen Knotenmenge wie in (b). Problem 4.2.2. Gegeben ist ein gerichteter Graph G = (V, E). Gesucht sind nun die starken Zusammenhangskomponenten von G. Algorithm 4: Finde Zusammenhangskomponenten 1. Führe DFS(G) aus, wähle den Startknoten zufällig und nummeriere Knoten in DFS-Reihenfolge 2. Konstruiere GT = (V, E T ) mit E T = {(u, v)|(v, u) ∈ E} // Graph mit umgedrehten Kanten 3. Führe DFS(GT ) aus, wähle den Startknoten mir der höchsten Nummer und nummeriere Knoten in DFS-Reihenfolge // wie in Schritt 1 Eine Veranschaulichung ist in 4.3 und in 4.4 gegeben. Abbildung 4.3: Graph nach Schritt 1 Behauptung 4.2.3. u, v sind in gleicher Zusammenhangskomponente⇔ u, v in gleichem Baum von DFS(GT ). Beweis. Zuerst beweisen wir ⇒. Es ist ein Weg von u nach v und ein Weg von v nach u in G enthalten, dann ist auch ein Weg von u nach v und ein Weg von v nach u in GT enthalten. O.B.d.A. wird DFS(u, GT ) vor DFS( v, GT ) aufgerufen. 71 Abbildung 4.4: Graph nach Schritt 3 Da v vor dem Aufruf von DFS(u,GT ) noch nicht besucht wurde und es einen Weg von u nach v in GT gibt, wird v innerhalb dieses Aufrufs gefunden und gehört somit zu dem Unterbaum von u. Also, ist v in dem gleichen DFS-Baum wie u. Als nächstes Beweisen wir ⇐. Dazu werden wir zeigen, dass alle Knoten der Zusammenhangskomponente im gleichen Baum wie die Wurzel der Zusammenhangskomponente sind. Angenommen x sei die Wurzel von v. Daraus folgt, dass ein Weg von x nach v in GT enthalten ist, das heißt, es gibt einen Weg von v nach x in G. Da x die Wurzel von v ist, ist die Nummer von x größer als die Nummer von v (nach dem Schritt 1). Daraus folgt dass DFS( x, G) nach DFS( v, G) beendet wird. Dies kann nur geschehen wenn x und v verschachtelt (4.5) oder disjunkt (4.6) sind. Abbildung 4.5: verschachtelter Aufruf Abbildung 4.6: disjunkter Aufruf Jetzt müssen wir noch ausschließen, dass x und v disjunkt sind. Angenommen DFS(v,G) wird vor DFS(x,G) aufgerufen. Da der Weg v nach x in G enthalten ist, müsste der Aufruf von DFS(x,G) aus dem Aufruf von DFS(v,G) heraus geschehen (siehe Abbildung 4.7) und damit auch die Nummer von v größer als die Nummer von x sein. Dies ist ein Widerspruch! Also, wurde DFS( v, G) innerhalb von DFS( x,G) aufgerufen. Das bedeutet, es gibt einen Weg von x nach v in G. Damit liegen x und v in der gleichen starken Zusammenhangskomponente. 72 Abbildung 4.7: verschachtelter Aufruf (2) 4.3 Minimal spannende Bäume Gegeben sei ein ungerichteter, zusammenhängender Graph G = (V, E) und eine Kostenfunktion c : E → R. Gesucht ist ein spannender Baum mit minimalen Kosten. Ein Beispiel für dieses Problem ist die Elektrizitätsversorgung. Knoten sind dann die Orte, die Kosten sind die Kosten um Leitungen zu verlegen oder Abstände zwischen den Orten. Lemma 4.3.1. Der Graph G = (V, E) sei ein Baum. e = (u, v) ∈ / T , u, v ∈ V ⇒ im Graph G0 = (V, T ∪ {e}) existiert genau ein Kreis. Beweis. Für den Beweis zeigen wir zuerst, dass e einen Kreis schließt und anschließend, dass es nur einen Kreis geben kann. 1. G sei ein Baum wie oben gefordert. Dann existiert ein Weg von u nach v mit Kanten aus T . ⇒ e schließt den Kreis. 2. Es existiert nur ein Kreis: Angenommen es gäbe zwei Kreise in G. Wir betrachten die Punkte, an denen die Kreise zum ersten Mal auseinander gehen und zum ersten Mal zusammen kommen. Es existierte also schon zuvor ein Kreis zwischen diesen Knoten in G (siehe Abbildung 4.8) . Widerspruch! Abbildung 4.8: zwei Kreise Lemma 4.3.2. Gegeben G = (V, E) und ein spannender Wald (V1 , T1 ), ..., (Vk , Tk ), k S / V1 mit minimalen T = Ti (Wie in Abbildung 4.9). Sei e = (u, v)u ∈ V1 , v ∈ i=1 Kosten. Dann existiert ein spannender Baum von G, der T ∪ {e} enthält und der minimale Kosten unter Bäumen, die T enthalten, hat. Beweis. Angenommen S 0 sei ein spannender Baum, S 0 = (V, T 0 ), T ⊂ T 0 , e ∈ / T 0, S 0 minimal. Nach Lemma 4.3.1 hat der Graph (V, T 0 ∪ {e}) genau einen Kreis. 73 Abbildung 4.9: spannender Wald Es existiert eine Kante e0 = (u0 , v 0 ) mit u0 ∈ V1 , v 0 ∈ / V1 (Siehe Abbildung 4.10). c(e) ≤ c(e0 ) nach Definition von e. ⇒ S = (V, T 0 ∪ {e}\{e0 }) ist zusammenhängend, kreisfrei und Kosten von S ≤ Kosten von S 0 . Abbildung 4.10: Wald mit zusätzlichen Knoten Wie findet man nun einen minimalen spannenden Baum? Idee Fange mit Wald {V1 }, {V2 }, ..., {Vn } an. Füge nacheinander Kanten mit minimalen Kosten ein, die Vi mit Vj verbinden, i 6= j. Dabei werden Kanten ignoriert, die Knoten im gleichen Baum verbinden. Als Datenstruktur wird UNION-FIND genutzt. Algorithm 5: Algorithmus von Kruskal Initialisierung: T := ∅, Q := alle Knoten, V S := {V1 }, ..., {Vn } while |V S| > 1 do e = (v, w) ist die Kante mit minimalen Kosten aus Q entferne e aus Q V = F IN D(v) W = F IN D(w) IF(V 6= W ) THEN U N ION (V, W ) T := T ∪ {e} Wegen Lemma 4.3.2 enthält T nun Kanten eines minimal spannenden Baums von G. Laufzeit Zeile 1 Prioritätsschlange hat Laufzeit O(m log(m)) mit m = |E| und V S hat die Laufzeit O(n) mit n = |V |. 74 Zeilen 3 und 4 Das Finden des Minimums dauert O(m log(m)). Zeilen 5 und 6 Es gibt O(m) UNION-FIND-Operationen. Diese haben zusammen eine Laufzeit von O(m log∗ (m)) oder genauer O(mα(2m, n)). Zeilen 8 und 9 Die Vereinigung ist bekannter Maßen in konstanter Zeit möglich, deshalb ist Laufzeit O(1). Gesamt Da die Zeilen 3 und 4 am meisten Zeit benötigen, ergibt sich eine Gesamtlaufzeit von O(m log(m)). 75 Der Kruskal-Algorithmus ist ein Beispiel für die “greedy”-Strategie, weil er immer nur die lokal minimal kostende Kante zwischen zwei Knoten der Menge hinzufügt. Die “greedy”-Strategie ist eine Entwurfsstrategie für Algorithmen, bei der immer das lokal beste bearbeitet(hinzugefügt) wird. 4.4 Wegeprobleme in Graphen Situation: Gegeben sei ein gerichteter Graph G = (V, E), dessen Kanten mit nichtnegativen reellen Zahlen R≥0 markiert sind, welche sich aus der Kostenfunktion c : E → R≥0 ergeben. Die Kanten können auf zwei Arten dargestellt werden: 1. Listendarstellung: Für jeden Knoten gibt es eine Liste der adjazenten Knoten mit jeweils auch Kosten der entsprechenden Kante. 2. Matrixdarstellung: Die Listen sind zu einer n × n-Matrix mit Einträgen ∈ R≥0 ∪ {∞} (“∞”, wenn keine Kante zwischen i und j existiert, also (i, j) ∈ / E) Problem 4.4.1. Wir betrachten nun folgende Probleme: a) geg: Knoten u, v finde: kürzesten Weg von u nach v, d.h. die Summe aller Kanten ist minimal (Beispiel: Routenplanung) b) SSSP - “single source shortest paths” geg: Knoten s finde: Länge aller kürzesten Wege von s nach allen anderen Knoten c) APSP - “all pairs shortest paths” geg: u, v ∈ V finde: für alle Paare u, v den kürzesten Weg von u nach v Anmerkung: Ein Algorithmus der eins dieser Probleme löst, löst automatisch die vorhergehenden Probleme. Allerdings ist kein schnellerer Algorithmus für a) bekannt, als b) zu lösen. 4.4.1 SSSP & Dijkstras Algorithmus Idee: Es wird eine Menge S aufrecht erhalten, welche jene Knoten enthält, zu denen bereits der kürzeste Weg (ausgehend von s) gefunden wurde. Initialisiert wird die Menge mit S = {s}. Wir definieren dazu ein Feld D, welches mit D [v] die Länge des kürzesten Weges von s nach v angibt, der nur Zwischenknoten ∈ S verwendet. 76 Algorithmus von Dijkstra: 1 2 3 4 5 6 7 S := {s} D [s] := 0; für alle Knoten v 6= s: D [v] := C (s, v); while V \ S 6= ∅ do wähle den Knoten w ∈ V \ S mit minimalem D [w]; S := S ∪ {w}; for each u ∈ V \ S, u adjazent zu w do D [u] := min (D [u] , D [w] + C (w, u)) Algorithm 6: Dijkstra i 0 1 2 3 D [v1 ] 0 0 0 0 D [v2 ] 2 2 2 2 D [v3 ] ∞ 5 5 5 D [v4 ] ∞ ∞ 9 9 D [v5 ] 10 9 9 9 S {1, 2} {1, 2, 3} {1, 2, 3, 4} {1, 2, 3, 4, 5} Korrektheit von Dijkstra Behauptung 4.4.2. Zu jeder Zeit gilt, dass zu jedem Knoten v in S D[v] gleich der Länge des kürzesten Weges von s nach v ist. Beweis. Induktion über die Anzahl k der Iterationen der Schleife (3)-(6) Induktionsanfang: Sei d(v) = Länge des kürzesten Weges von s nach v,k = 0. Es ist S = {s} und D[s] = 0 Induktionsschritt: k → k + 1 Sei w der Knoten, der in der (k + 1)-ten Iteration zu S kommt. Angenommen, die Behauptung sei falsch für w, also D[w] > d[w] (denn D[w] ist die Länge eines Weges von s nach w). Betrachte den kürzesten Weg π von s nach w. (u, v) sei die Kante auf π, die als erste aus Sk herausführt, wobei Sk = S nach k Iterationen. Dann ist nach Induktionsvoraussetzung d[u] = D[u] d(v) = d(u) + c(u, v), wobei c(v,u) die Kosten der Kanten von u nach v sind 77 d(v) = d(u) + c(u, v) = D[u] + c(u, v) ≤ D[v], denn beim Einfügen von u in S wurde D[v] mit D[u] + c(u, v) verglichen und auf das Minimum von beiden gesetzt(Zeile 6). Also ist d(v) ≤ D[v] ⇒ d(v) = D[v]. Andererseits ist D[w] ≥ d(w) ≥ d(v) = D[v]. Dies ist ein Widerspruch zu Zeile 4 des DijkstraAlgorithmus, denn dort wäre v ausgewählt worden. Datenstrukturen und Laufzeit Für Knoten V \S brauchen wir eine Prioritätswarteschlange. Hierfür bietet sich ein “Heap” an. Die Initialisierung in Zeile 2 ist in O(n) Zeit möglich. Das “Minimum streichen” in Zeile 4 braucht jeweils O(log n) Zeit, den “Wert vermindern” in Zeile 6 braucht jeweils O(log n) Zeit, 6 wird m-mal ausgeführt , wobei m = Anzahl der Kanten. Also kommt man insgesamt auf O( n log n + m log n) = O((m + n) log n). | {z } | {z } Zeilen2,4 Zeile6 Satz 4.4.3. Der Algorithmus von Dijkstra berechnet die Längen aller kürzesten Wege eines gerichteten, markierten Graphen mit n Knoten und m Kanten in O((m + n) log n) Zeit. Also höchstens O(n2 log n) 4.4.2 Kürzeste Wege zwischen allen Knotenpaaren Man könnte n-mal Dijkstra ausführen. Dadurch würde sich eine Laufzeit von O((nm + n2 ) log n) = O(n3 log n) ergeben. Algorithmus von Floyd-Warshall (k) Die Knotenmenge sei o.B.d.A V = {1, ..., n}. Wir definieren Pij = Menge aller (k) Wege von i nach j mit Zwischenknoten ∈ {1, ..., k}, dij = Länge des kürzesten (k) Weges in Pij , wobei 1 ≤ i, j ≤ n 0 ≤ k ≤ n Dann gilt für i 6= j: c(i, j) if (i, j) ∈ E; (k) dij = ∞ sonst. 78 Dabei war ja die Idee, dass wir unser k Schritt für Schritt erhöhen bis wir bei n angekommen sind, denn dann haben wir das Problem gelöst. Dies ist im Grunde unser Algorithmus. Wir müssen diesen nur noch in drei Schleifen einbetten. Die Äußerste Schleife geht über k für k = 1 . . . n die 2. über i für i = 1 . . . n und die innerste über j für j = 1 . . . n. Der folgende Algorithmus ist nur ein Beispiel für die vielfältigen Anwendungen, auf welche wir jedoch später eingehen werden. (k) (k) Und dij ist die Länge des kürzesten Weges in Pij . for i = 1, . . . , n do for j = 1,. . . , n do c(i, j): falls (i, j) ∈ E (0) 3: dij = ∞: sonst 4: end for 5: end for 6: for k = 1, . . . , n do 7: for i = 1, . . . , n do 8: for j = 1, . . . , n do (k) (k−1) (k−1) (k−1) 9: dij = min(dij , dik + dkj ) 10: end for 11: end for 12: end for Algorithm 7: [Floyd-Warshall] Bestimmung aller kürzesten Wege im Graphen G = (V, E). Warum funktioniert der Algorithmus? (k) Erklärung: dij ist die Länge eines kürzesten Weges von i nach j mit Zwischenknoten ∈ 1 . . . k (Zwischenknoten sind alle Knoten außer die Anfangs- und Endknoten). 1: 2: (k) (k−1) 1. Fall : es gibt nur einen Weg, der k nicht benutzt =⇒ dij = dij 2. Fall : k kommt nicht mehrfach vor, daher ist die Länge des Stücks (k−1) (k−1) von i nach k = dik bzw. von k nach j = dkj (Siehe Abbildung 4.11) Abbildung 4.11: Weg von i nach j über k 79 4.4.3 Laufzeit von Floyd-Warshall Θ(n2 ) für die Schleifen (1 . . . 3) und Θ(n3 ) für die Schleifen (6 . . . 12) ⇒ Θ(n3 ) also insgesamt. Dijkstra hatte eine Laufzeit von : O(n(n + m)log(n)) wobei die Anzahl der Kanten maximal n2 werden kann. Bei dichten Graphen wäre die Laufzeit somit Θ(n3 log(n)) ⇒ bei dünnbesetzten Graphen (wenn Anzahl der n2 Kanten ≤ nlog(n) ist es effizienter n mal Dijkstras Algorithmus durchzuführen. Floyd-Warshall basiert auf der Idee des Dynamischen Programmierens. Man löst erst alle kleinere Teilprobleme um mit Hilfe derer Lösungen irgendwann am Ende das Gesamtproblem lösen zu können. Wie schon angemerkt ist der Algorithmus von Floyd und Warshall vielseitig einsetzbar. Er ist nicht nur effizient beim Finden aller kürzesten Wege in einem Graphen, sondern er liefert bei leichter Modifizierung der Operanden min und + in Zeile 7 durch ∨ und ∧ ersetzen folgt: wahr: falls(i, j) ∈ E cij = . falsch: sonst Also würde hier Floyd-Warshall gleich noch die Antwort auf die Frage geben, ob überhaupt ein Weg zwischen i und j existiert. Eine weitere Möglichkeit wäre die Ersetzung durch min und max welche dann die Kante mit maximalen Gewicht berechnen würde. Es gibt also unzählige Möglichkeiten den Floyd-Warshall Algorithmus sinnvoll zu modifizieren und nicht zuletzt ist auch der Algorithmus von Kleene eine mögliche Abwandlung. Beispiel. endlicher Automat mit Floyd-Warshall: (k−1) (k−1) (k−1) (k−1) dkij = dij ∪ dik (dkk )k dkj 4.5 Flussprobleme und Matching in Graphen Gegeben ist ein gerichteter Graph G = (V, E) (oft auch Netz oder Netzwerk) sowie spezielle Knoten Quelle (source) s ∈ V und Senke (target) t ∈ V genannt. Außerdem ist eine Kapazitätsfunktion c : E → R≥0 gegeben. Die Kapazität gibt an, wieviel Materialeinheiten (z.B. Wasser) maximal von s nach t geschickt werden können. Ziel ist es möglichst viel Materiealeinheiten zu schicken und somit einen maximalen Fluss für das Netzwerk von s nach t zu erzielen (Siehe Flussbeispiele in Abbildung 4.12 und 4.14) .Das Flussproblem ist eines der zentralen algorithmischen Probleme. 4.5.1 Anwendungsbeispiele: • Röhrensyteme für Flüssigkeiten • Elektrisches Netz • Verkehrsnetz • Informationen in Kommunikationsnetzen • ... 80 Abbildung 4.12: Beispielgraph für das Flussproblem Definition 4.5.1 (Fluss). gegeben sei ein Netz (G, c, s, t) mit G = (V, E) Ein Fluss ist eine Funktion f : V × V → R mit: 1. ) f (u, v) ≤ c(u, v) ∀u, v ∈ V 2. ) f (u, v) = −f (u, v) ∀u, v ∈ V (*) P 3. ) v∈V f (u, v) = 0 ∀u, v ∈ V \{s, t} Abbildung 4.13: Zulauf=Ablauf (*) Bedeutet: die Summe der zulaufenden Flüsse ist gleich der Summe der ablaufenden Flüsse (Siehe Abbildung 4.13). Der Wert des Flusses f : |f | = P f (s, v). Somit ist der maximale Fluss ein Fluss mit maximalem Wert. v∈V Bemerkung. In der Realität hat man eine Menge von Quellen und Senken. Diese lassen lassen sich aber auf das Problem mit einer Quelle und Senke zurückführen. Dies geschieht, indem man die vielen Quellen durch eine Superquelle ersetzt (Siehe Abbildung 4.15). Von dieser Superquelle fließt zu jeder Quelle eine Kante mit unendlichem Gewicht (analog Supersenke). Definition 4.5.2. Wenn X und Y Mengen von Knoten sind, dann ist der Fluss von X nach Y gleich der Summe aus P der Summe von allen x ∈ X und y ∈ Y P aller Flüsse von X nach Y f (X, Y ) = x∈X y∈Y f (x, y) (Abbildung 4.16). 81 Abbildung 4.14: Fluss mit Wert 10 [nur positive Flüsse] Lemma 4.5.3. Es gilt: 1. f (X, X) = 0 ∀X ∈ V 2. f (X, Y ) = −f (Y, X) ∀X, Y ∈ V 3. f (X ∪ Y, Z) = f (X, Z) + f (Y, Z) ∀X, Y, Z ∈ V wobei X ∩ Y = ∅ 4. f (X, Y ∪ Z) = f (X, Y ) + f (X, Z) ∀X, Y, Z ∈ V wobei X ∩ Y = ∅ Definition 4.5.4. Ein augmentierender Weg ist ein Weg im Netz, wo die Kapazitäten noch nicht voll ausgenutzt sind (Beispiel in Abbildung 4.17). Der Fluss von s → t kann noch erhöht werden. Bemerkung. Idee: Man beginnt mit Fluss = 0 entlang jeder Kante und erhöht die augmentierenden Wege um den größtmöglichen Wert. Diese Schleife wird solange durchgeführt bis kein besserer augmentierender Weg mehr gefunden wird. Wir werden zeigen, solange der Fluss nicht maximal ist, findet man immer einen größeren augmentierenden Weg. 4.5.2 Ford-Fulkerson-Methode Bemerkung. Um den maximalen Fluss bestimmen zu können, muss man erst den augmentierenden Weg für den Fluss finden. Ein maximaler Fluss ist ein Weg G von s nach t wo für jedes Knotennachbarnpaar e gilt, dass f (e) < C(e) Für die Ford-Fulkerson-Methode initialisiert man mit f = 0 folgende Schleife: 1. Finde einen augmentierenden Weg π! 82 Abbildung 4.15: Rückführung Superquelle und Supersenke Abbildung 4.16: Mengen X und Y 2. Erhöhe Fluss entlang π, um den größtmöglichen Wert, bis kein neuer augmentierender Weg mehr gefunden wird! Um den besten augmentierenden Weg zu finden konstruiert man das Restnetz bei zu einem gegebenen Fluss. Definition 4.5.5 (Restnetz). : Gf bei gegebenen Fluss f [Hilfstruktur] im Graphen. Falls man in G eine Kante mit K = b hat, dann gibt es in Gf folgende 2 Kanten: 1. eine Kante von n → V mit K = b − a 2. eine Kante von V → n mit K = a Daraus folgt dann, dass der Fluss f (u, v) um bis zu b−a Einheiten erhöht werden kann und f (v, u) um bis zu a Einheiten erniedrigt werden kann. Desweiteren folgt daraus, dass die Kanten mit K = 0 wegfallen. Der augmentierende Weg hat nun eine Restkapazität > 0 (Siehe Abbildung 4.18). Definition 4.5.6. In einem Einführenden Weg p von s nach t in Gf ist die Kapazität: Cf (p) = min{Cf (e) | e Kante in Gf }. 83 Abbildung 4.17: augmentierender Weg Abbildung 4.18: Restkapazitäten 84 4.5.3 Elementarer Ford-Fulkerson-Algorithmus Sei G = (V, E) ein Netz mit den Kapazitäten c : E → R≥0 und s, t zwei Knoten von G. 1. initialisiere f mit 0 2. solange ein augmentierender Weg P von s nach t in Gf existiert 3. für jede Kante e auf P erhöhe den Fluss um cf (P ) Algorithm 8: Ford-Fulkerson-Algorithmus (FFA) Um in der zweiten Zeile des Algorithmus einen augmentierenden Weg zu finden benötigt man zwei Schritte: 2a. Konstruktion bzw. Aktualisierung des Restnetzes Gf 2b. Finden eines augmentierenden Weges Dazu kann man als Datenstruktur den gerichteten Graphen G0 = (V, E 0 ) mit den Kanten E 0 = {(u, v) | (u, v) oder (v, u) ∈ E} benutzen. Jedes Restnetzwerk Gf ist ein Teilgraph von G0 . Ein Beispiel für das Vorgehen des Ford-Fulkerson Algorithmus zeigt Abbildung 4.19. Analyse der Laufzeit: Schritt 1: O(|E|) Schritt 2a: O(|E|) pro Durchlauf Schritt 2b: O(|E|) pro Durchlauf, wenn z.B. Tiefen- oder Breitensuche benutzt wird. Frage 4.5.7. Wie viele Durchläufe gibt es? Nehmen wir an, dass die Kapaztitäten ganze Zahlen sind. Dann erhöht jeder Durchlauf den Fluss um mindestens eins, also gibt es bis zu |f ∗ | Durchläufe, wobei f ∗ der maximale Fluss ist. Also ist die Laufzeit insgesamt O(|f ∗ | · |E|). Das gleiche Argument funktioniert, wenn die Kapazitäten rationale Zahlen sind, indem man zunächst mit dem kleinsten gemeinsamen Nenner multipliziert, den Algorithmus laufen lässt und dann wieder durch den Nenner teilt. Die Laufzeit O(|f ∗ | · |E|) ist nicht befriedigend, da f ∗ eventuell exponentiell in der Größe der Eingabe ist. Außerdem gilt die Laufzeitanalyse nur für ganze Zahlen, und es existiert ein Beispiel, wo tatsächlich |f ∗ | Durchläufe ausgeführt werden. Ein solches Beispiel zeigt Abbildung 4.20. Bei diesem Beispiel werden die augementierenden Wege mit Tiefensuche gefunden. In jedem Schritt wird der Fluss um 1 erhöht und die in (b) und (c) dargestellten Schritte werden |f ∗ | = 2000 mal wiederholt bis der maximale Fluss erreicht ist. Wenn bei demselben Beispiel die augementierenden Weg mit Breitensuche bestimmt werden, dann werden nur zwei Durchläufe benötigt, wie in Abbildungen 4.21 dargestellt. 85 (a) Graph G (b) augmentierender Weg mit Kapazität 7 (c) Restnetz nach Schritt 1, augmentierender Weg mit Kapazität 4 (d) Restnetz nach Schritt 2, augmentierender Weg mit Kapazität 5 (e) Restnetz nach Schritt 3, augmentierender Weg mit Kapazität 4 (f) Restnetz nach Schritt 4, augmentierender Weg mit Kapazität 3 (g) Restnetz nach Schritt 5, kein augmentierender Weg (h) Maximaler Fluss und minimaler Schnitt, beide mit Wert 23 Abbildung 4.19: Beispiel für den Ford-Fulkerson-Algorithmus. Augmentierende Wege und der minimale Schnitt sind in grün eingezeichnet, Kanten im Restnetz deren Kapazität sich geändert hat sind geschrichelt. 86 (a) Netz (b) Schritt 1 (c) Schritt 2 Abbildung 4.20: Bestimmen des augementierenden Weges mit Tiefensuche. (a) Netz (b) Schritt 1 (c) Schritt 2 Abbildung 4.21: Bestimmen des augementierenden Weges mit Breitensuche. 4.5.4 Edmonds-Karp-Algorithmus Es ist ersichtlich aus den Bespielen, dass die Breitensuche Vorteile hat gegenüber der Tiefensuche. Ein Algorithmus der dies nutzt, ist der Algorithmus von Edmonds und Karp. 1. initialisiere f mit 0 2. solange ein augmentierender Weg P von s nach t in Gf existiert 2a. Konstruktion bzw. Aktualisierung des Restnetzes Gf 2b. Finden des augmentierenden Weges mit Breitensuche 3. für jede Kante e auf P erhöhe den Fluss um cf (p) Algorithm 9: Edmonds-Karp-Algorithmus Bei diesem Algorithmus wird der kürzeste augmentierende Weg bezüglich der Kantenzahl ausgewählt. Sei δf (u, v) der Abstand zwischen u und v im Restnetz, also die Anzahl der Kanten auf dem kürzesten Weg von u nach v. Dann gilt: Lemma 4.5.8. Beim Edmonds-Karp-Algorihtmus gilt für alle Knoten v ∈ V \{s, t}: Während des Ablaufs des Algorithmus ist δf (s, v) monoton wachsend. 87 Beweis. Angenommen δf (s, v) wächst nicht. Dann exisitiert ein Knoten v ∈ V , ein Fluss f und ein Fluss f 0 im nächsten Schritt des Algorithmus, so dass δf 0 (s, v) < δf (s, v). Sei v der Knoten mit dieser Eigenschaft, für den δf 0 (s, v) minimal ist. Also gilt für alle anderen Knoten u: δf 0 (s, u) < δf 0 (s, v) ⇒ δf (s, u) ≤ δf 0 (s, u) (∗). Sei P 0 der kürzeste Weg von s nach v in Gf 0 und u der letzte Knoten vor v auf P 0 . Dann gilt δf (s, u) ≤ δf 0 (s, u) (∗∗) nach (∗). Betrachten wir f (u, v), den Fluss von u nach v vor der Augmentierung. Fall a) f (u, v) < c(u, v). Dann ist (u, v) eine Kante in Gf und es gilt δf (s, v) ≤ δf (s, u) + 1 ≤ δf 0 (s, u) + 1 = δf 0 (s, v) Widerspruch zur Annahme. Fall b) f (u, v) = c(u, v). Dann ist (u, v) keine Kante in Gf , aber in Gf 0 . Daraus folgt, dass der augmentierende Weg P die Kante (v, u) enthalten haben muss und es gilt δf (s, v) = ≤ = < δf (s, u) − 1 δf 0 (s, u) − 1 nach (∗∗) δf 0 (s, v) − 2 δf 0 (s, v) Widerspruch zur Annahme. 88 Lemma 4.5.9. Der Algorithmus von Edmonds-Karp führt höchstens O(|V ||E|) Augmentierungen durch. Beweis. Eine Kante (u, v) heiße kritisch auf augmentierenden Weg p gdw. cf (u, v) | {z } = cf (p). Restkapazität Eine kritische Kante verschwindet bei einer Augmentierung aus dem Restnetz. Nun stellt sich die Frage, wie oft kann eine Kante (u, v) kritisch werden? Die Kante (u, v) kann in einem späteren Restnetz wieder auftreten, wenn sie irgendwann wieder Restkapazität > 0 erhält, d.h. (v, u) liegt auf einem augmentierenden Weg. Sei f ein Fluss bei dem (u, v) kritisch war ⇒ δf (s, v) = δf (s, u) + 1. u v s t f 0 sei der Fluss, bei dem die Kante (v, u) das nächste Mal wieder auf dem augmentierenden Weg liegt. Dann gilt δf 0 (s, u) = δf 0 (s, v) + 1 ≥ δf (s, v) + 1 (nach Lemma 2) = δf (s, u) + 2. v u s t Also gilt: zwischen zwei Malen, wo eine Kante (u, v) kritisch ist, erhöht sie ihren Abstand zu s im Restnetz um mindestens 2. Der Abstand kann überhaupt höchstens |V | − 2 sein und damit wird jede Kante höchstens |V 2|−2 = O(|V |) mal kritisch. Also gibt es insgesamt höchstens O(|V ||E|) Augmentierungen. Es folgt: Satz 4.5.10. Der Algorithmus von Edmonds/Karp hat eine Laufzeit von O(|V ||E|2 ). Bemerkung. Sei |V | = n. Der Algorithmus von Edmonds/Karp hat eine Laufzeit von O(n5 ). Diese wurde von Goldberg auf O(|V |2 |E|)[= O(n4 )] verbes|2 sert. Die besten“ bekannten Algorithmen laufen in O(|V ||E| log( |V |E| )) (Gold” berg/Tarjan 1986) und in O(|V ||E| + |V |2 log(|V |)) (Mehlhorn, Cheryan und Hagerup, 1997). 89 4.6 Bipartites Matching Definition 4.6.1 (Matching 1 ). Sei ein ungerichteter Graph G = (V, E) gegeben. Ein Matching ( Paarung“) ist eine Teilmenge M ⊂ E von unabhängigen“ ” ” Kanten, d.h. keine zwei Kanten haben einen gemeinsamen Endpunkt. M ist Matching, man kann hier sogar keine weiteren Kanten hinzufügen. Ein Matching M heißt maximal (engl. maximal matching), wenn keine echte Obermenge von M ein Matching ist. Wir wollen aber lieber größte Matchings (engl. maximum matching) betrachten, d.h. M mit |M | maximal. M ist immer noch maximales Matching, M ist größtes Matching Wir suchen im Folgendem nach größten Matchings in bipartiten Graphen. Definition 4.6.2 (bipartite Graphen). Ein ungerichteter Graph G = (V, E) ˙ 2 gibt, so dass alle Kanten zwiheißt bipartit gdw. es eine Partition V = V1 ∪V schen einem Knoten in V1 und einem in V2 verlaufen. (Es verlaufen also keine Kanten innerhalb von V1 und V2 .) |{z} V1 |{z} V2 M ist Matching 1 Eine mögliche Zweitlektüre zu bipartiten Matchings ist das Kapitel 1.1 in dem Buch Gra” phentheorie“ von R. Diestel, welches unter http://www.math.uni-hamburg.de/home/diestel/ books/graphentheorie/ online verfügbar ist. 90 Wie findet man nun das größte Matching in bipartiten Graphen G = (V, E)? Dieses Problem kann auf das maximale Fluss Problem zurück geführt werden: Aus G konstruieren wir einen neuen Graphen G0 = (V 0 , E 0 ), dabei ist V 0 = V ∪ {s, t} und E 0 = {(s, u)|u ∈ V1 } ∪ {(v, t)|v ∈ V2 } ∪ {(u, v)|u ∈ V1 , v ∈ V2 , {u, v} ∈ E}. Als Kapazitäten wählen wir c(e) = 1 für alle Kanten e ∈ E 0 . G0 G → |{z} V1 s |{z} V2 t |{z} V1 |{z} V2 Alle Kanten sind von links nach rechts gerichtet. Wir nehmen also unseren Ursprungsgraphen G und fügen die beiden Knoten s und t hinzu. Nun geben wir in den Graphen Kanten hinzu, die von s zu jedem Knoten in V1 und von jedem Knoten aus V2 nach t verlaufen. Alle ursprünglichen Kanten in G werden gerichtet von V1 nach V2 . Alle Kanten in G0 bekommen die Kapazität 1. Um nun das Problem des größten Matchings mit Flüssen zu lösen, benötigen wir noch folgendes Lemma 4.6.3. Für k ∈ N gilt: Es gibt ein Matching M in G mit |M | = k ⇔ Es gibt einen ganzzahligen Fluss f in G0 mit |f | = k Beweis. ⇒: t s Sei M ein Matching mit k Kanten. Wir schicken von s zu jedem Matchingknoten in V1 den Fluss 1. Von dort verläuft der Fluss weiter über die k Matchingkanten zu V2 und weiter zu t. Der Wert eines Flusses ergibt sich als die Summe der ausgehenden Flüsse aus s, also hier k. 91 ⇐: Sei f ein ganzzahliger Fluss mit |f | = k. Wähle M = {(u, v)|u ∈ V1 , v ∈ V2 , f (u, v) > 0 }. | {z } also f (u, v) = 1 Da die Kapazität jeder Kante 1 ist, muss für eine Kante in einem ganzzahligen Fluss mit f (u, v) > 0 gelten f (u, v) = 1. Weiter muss für jedes u ∈ V1 , das in einer Kante in M vorkommt, f (s, u) = 1 sein. 1 s 1 u einzige eingehende Kante Also muss jede andere von u ausgehende Kante Fluss 0 haben. Dies bedeutet, dass nur eine Kante in M inzident zu u ist. Analog für jedes v ∈ V2 . Also ist M ein Matching mit k Kanten. Es gilt allgemein für Flüsse in Netzen: Satz 4.6.4. Sei G = (V, E) ein gerichteter Graph, mit Kapazität c : E → R. Dann gilt: Falls die Kapazitätsfunktion nur ganzzahlige Werte hat, so hat der maximale Fluss f eine ganzzahligen Wert f . Außerdem existiert ein ganzzahliger maximaler Fluss. Beispiel 0,5 1 1 0,5 1 1 1 1 0,5 1 1 1 0,5 0 0 In grün - ganzzahliger Fluss, in rot - nicht ganzzhaliger Fluss und in schwarz sind die Kapazitäten eingezeichnet Beweis. Übung Korollar 4.6.5. Die Kardinalität eines größten Matchings in einem bipartiten Graphen G ist der Wert eines maximalen Flusses in G0 . Es folgt: Satz 4.6.6. Ein größtes Matching in einem bipartiten Graphen kann in O(|V ||E|) Zeit gefunden werden. Beweis. Man beachte das Korollar und die Laufzeit des Ford-Fulkerson Algorithmus, welche O(|f ∗ ||E|) ist. Für ein Matching M gilt |f ∗ | = |M | ≤ |V2 | und daher O(|f ∗ ||E|) = O(|V ||E|). 92 Der beste bekannte Algorithmus für bipartitep Graphen ist von Hopcroft/Karp aus dem Jahr 1973 mit einer Laufzeit von O( |V ||E|) ≈ O(n2,5 ). Eine Verallgemeinerung auf allgemeine Graphen kam von Micali/Vazirani in 1983 mit der gleichen Laufzeit. Die Frage nach einem größten Matching kann man ebenfalls in einem gewichteten Graphen stellen. Hier ist ein Matching gesucht mit größtem Gesamtgewicht, genannt größtes gewichtetes Matching. Dieses Problem ist in O(|E||V | log(|V |)) Zeit lösbar mit einem Algorithmus von Galil, Micali und Gabow aus dem Jahr 1986. Ein Anwendungsbeispiel für die Suche nach einem größten gewichteten Matching ist das Job-Assignment Problem. Dazu betrachten wir den bipartiten Graphen ˙ 2 , E), wobei die Menge V1 eine Gruppe von Personen und V2 eine G = (V1 ∪V Menge von Jobs darstellen. Jede Kante von einem Knoten vi ∈ V1 zu einem Knoten vj ∈ V2 beschreibt die Eignung der Person vi für den Job vj . Nun ist das Matching mit dem größten Gesamtgewicht gesucht. Dies entspricht einer Zuordnung der Personen zu den Jobs, so dass insgesamt die Personen am besten zu den Jobs passen. 93 4.7 Der Algorithmus von Dinic für maximalen Fluss Wir kennen bereits den Algorithmus von Ford Fulkerson zur Suche nach einem maximalen Fluss in einem Graphen. Wir lernen nun einen Algorithmus für maximalen Fluss kennen, der effizienter ist als der Algorithmus von Ford Fulkerson. Dies ist der Algorithmus von Dinic. Definition 4.7.1. Ein Fluss f in einem Netz G = (V, E), c heißt blockierend, wenn es auf jedem Weg von s nach t mindestens eine saturierte Kante gibt. Eine Kante e heißt saturiert, wenn ihre Kapazität voll ausgeschöpft ist (f (e) = c(e)), d.h. der Fluss kann nicht mehr nach oben verändert werden. Bemerkung. Nicht jeder blockierende Fluss ist maximal, aber jeder maximale Fluss ist blockierend. Basierend auf dem Restgraphen Gf definieren wir den Levelgraphen Lf : • Der Level l eines Knoten v sei level(v) := δf (s, v) (die Anzahl der Kanten auf dem kürzesten Weg von s nach v). • Der Levelgraph Lf ist ein Teilgraph von Gf : Die Knoten von Lf sind alle von s aus erreichbaren Knoten im Graphen Gf und die Kanten von Lf sind alle Kanten (u, v), so dass – (u, v) ∈ Gf – Level(u) = Level(v) − 1. Abbildung 4.22: Levelgraph Lf Lf enthält alle kürzesten augmetierenden Wege und ist in O(m) Zeit konstruierbar, wobei m = |E|, n = |V |, und die Breitensuche dazu angewandt werden kann. 94 Schema für Dinics Algorithmus: 1. beginne mit Fluss 0 wiederhole 2. finde einen blockierenden Fluss f 0 auf Lf 3. ersetze f := f + f 0 4. bis f liegt nicht mehr V (Lf ) (in der Knotenmenge von Lf ) Lemma 4.7.2. Dinics Algorithmus hält nach höchstens (n-1) Iterationen der Schleife (2. - 3.) an. Beweis. Wir betrachten eine Iteration. Nehmen wir an es sei vorher: Fluss f und Funktion level nachher: Fluss f 0 und Funktion level0 . Eine Kante (v, w) in Gf 0 ist entweder • eine Kante in Gf (wenn die Kante trotz Addition des blockierenden Flusses nicht ganz ausgeschöpft wird), Abbildung 4.7(a), oder • eine umgekehrte Kante in Lf (dann muss die Kante schon vor der Iteration eine umgekehrte Kante in Lf gewesen sein), Abbildung 4.7(b). (a) Kante in Gf (b) umgekehrte Kante in Lf (c) Länge des Weges im Levelgraph Lf 0 Für jede Kante (v, w), die in Gf 0 liegt, gilt daher level(w) ≤ level(v) + 1. Daraus folgt (siehe Abbildung 4.7(c)): level0 (t) ≥ level(t). Als nächstes zeigen wir, dass sogar level0 (t) > level(t) gilt. Nehmen wir an, es gilt Gleichheit: level(t0 ) = level(t). Dann muss es einen kürzesten Weg p von s nach t in Gf 0 geben, der auch kürzester Weg in Gf war. Jede Kante von p ist dann in Lf . Dies ist ein Widerspruch dazu, dass auf jedem Weg in Lf mindestens eine Kante saturiert wird (wg. den Zeilen 2. und 3.: wir nehmen einen blockierenden Fluss) Also kann Gleichheit nicht gelten, sondern es gilt level(t0 ) > level(t). 95 In jedem Iterationsschritt erhöht sich daher der Abstand von s zu t in Gf um mindestens 1. Der kürzeste Weg kann nicht länger als (n-1) sein, also sind maximal (n-1) Iterationsschritte durchführbar. Zwei Fragen sind jetzt noch offen: 1. Was kostet ein Iterationsschritt? 2. Wie findet man einen blockierenden Fluss? Wir betrachten dazu als nächstes Einheitsnetze. Definition 4.7.3 (Einheitsnetz). Ein Einheitsnetz G = (V, E), c ist ein Netz mit • ganzzahligen Kapazitäten und • jeder Knoten v ∈ V \{s, t} hat genau eine hineingehende Kante mit Kapazität 1 oder genau eine herausführende Kante mit Kapazität 1. Ein Beispiel für Einheitsnetze, das wir bereits gesehen haben, sind die Netze in der Zurückführung von grös̈ ten Matchings in bipartiten Graphen auf maximale Flüsse wie in Abbildung 4.23 dargestellt. Abbildung 4.23: Einheitsnetz Bemerkung. Wenn ein Knoten genau eine hineingehende Kante hat, so kann er mehrere herausführende Kanten besitzen (und andersherum). Lemma √ 4.7.4. Für ein Einheitsnetz hält Dinics Algorithmus nach höchstens (2 · d n − 2 e) Iterationen. Beweis. Wir betrachten eine Iteration. Sei f der momentane Fluss, und f ∗ der maximale Fluss, beide ganzzahlig. Dann ist f ∗ − f ein Fluss in Gf . Gf ist ein Einheitsnetz und f ∗ − f ist auf jeder Kante 0 oder 1. Wir teilen die Kanten, auf denen f ∗ − f = 1 ist, in eine Sammlung von Wegen von s nach t auf. Dies sind (|f ∗ | − |f |) Wege von s nach t sein (und evtl. einige Kreise). Diese Wege sind knotendisjunkt, deswegen gibt es einen solchen Weg 96 Abbildung 4.24: Restnetz Gf mit ≤ n−2 |f ∗ |−|f | + 1 Knoten. Dies ist ein augmentierender Weg. √ Nach √ ( n − 2) Iterationen gilt: Der kürzeste augmentierende Weg hat mindestens ( n − 2 + 1) Knoten. (Beweis von Lemma 6, der Zielknoten wird immer um eins grösser.) Also gilt: √ n−2+1 ⇔ |f ∗ | − |f | n−2 +1 |f ∗ | − |f | √ ≤ n−2 ≤ √ Nach höchstens n − 2 weiteren Iterationen hat man den maximalen Fluss, da sich der Fluss bei jeder Iteration um mindestens 1 erhöht. 4.7.1 Wie findet man einen blockierenden Fluss? Wir verwenden einen Greedy-Algorithmus (Tiefensuche), um einen blockierenden Fluss im Levelgraphen Lf zu finden. wiederhole: • finde einen Weg p von s nach t durch Tiefensuche • erhöhe den Fluss entlang p um soviel wie möglich • entferne saturierte Kanten bis: • kein Weg von s nach t mehr existiert Der resultierende Weg ist blockierend, da jeder Weg von s nach t mindestens eine saturierte Kante enthält. Laufzeit: eine Tiefensuche kostet O(m) Zeit. 97 Dinics Algorithmus: 1. INIT: • p := [s]; (Weg besteht aus einem Knoten) • v := s; • goto VOR; 2. VOR: • falls v keine ausgehende Kante hat: goto ZURÜCK • sonst: wähle eine Kante(v, w); – erweitere p um w; – v := w; – falls w 6= t: goto VOR; – sonst: goto AUGM; 3. AUGM: • 4 := mine∈P c(e) − f (e); • addiere Fluss 4 zu jeder Kante auf p; • entferne saturierte Kanten; • goto INIT; 4. ZURÜCK: • falls v = s: HALT; • sonst: sei (u, v) letzte Kante auf p; – schneide (u,v) ab; (v ist nicht mehr im Graphen) – v := u; – goto VOR; Bemerkung. Man arbeitet alle Wege von einem Knoten ab und geht dann erst den nächsten Weg. Laufzeitanalyse: • INIT, VOR, ZURÜCK: O(1) pro Iteration • AUGM: O(n) pro Iteration. (Bei jeder Ausführung von AUGM fällt mindestens eine saturierte Kante weg.) insgesamt für AUGM: O(m · n) insgesamt: O(m · n) Satz 4.7.5. Der Algorithmus von Dinic hat eine Laufzeit von O(n2 · m) bei einem Netz mit n Knoten und m Kanten. 98 Vergleich mit dem Edmonds-Karp-Algorithmus: Edmonds-Karp Dinic allgemein O(n m2 ) O(n2 m) dicht besetzter Graph O(n5 ) O(n4 ) dünn besetzter Graph O(n3 ) O(n3 ) Der Algorithmus zum Finden eines blockierenden Flusses bei Einheitsnetzen: AUGM braucht O(k) Zeit, wobei k die Länge des augmentierenden Weges ist, da k Kanten saturiert und entfernt werden. (Wenn alle Kapazität 1 haben, dann fallen alle weg) Insgesamt wird also O(m) Zeit für die Augmentierungen benötigt. Zusammen mit Lemma 7 gilt somit: Lemma 4.7.6. Auf Einheitsnetzen n Knoten und m Kanten braucht der Algo√ rithmus von Dinic O( n · m) Zeit. Korollar 4.7.7. Bipartites Matching ist mit dem Algorithmus von Dinic in √ O( n · m) Zeit machbar. √ Bemerkung. Für dichtbesetzte Graphen ist n · m = n2.5 . 99 Kapitel 5 String - Matching 5.1 Einleitung String - Matching (übersetzt in etwa “Zeichenkettenanpassung“ ) ist die Suche eines Musters (Pattern) in einem Text. Es findet beispielsweise Anwendung bei der Suche im World Wide Web. Dabei gibt man einen Begriff (Muster) in eine Suchmaschine ein und erhält dann zuvor indizierte Webseiten als Ergebnis. Andere Anwendungen für String-Matching sind z.B. Textsuche in einem Editor, Genomforschung zum Finden bestimmter DNS-Sequenzen, etc.. Gegeben ist: • ein endliches Alphabet Σ • ein Text T ∈ Σ∗ , wobei | T | = n gilt • ein Muster P ∈ Σ∗ , wobei | P | = m, m ≤ n gilt und die Felder: • T [1..n] ( enthält den Text ) • P [1..m] ( enthält das Muster ) Definition 5.1.1 (Präfix). w ist ein Anfangsstück ( Präfix ) von x, symbolisiert durch ( x @ w ), wenn ein u ∈ Σ∗ existiert für das gilt: x = wu mit w, x ∈ Σ∗ ( Konkatenation von Strings ). Definition 5.1.2 (Suffix). w ist ein Endstück ( Suffix ) von x, symbolisiert durch ( x A w ) , wenn ein u ∈ Σ∗ existiert für das gilt: x = uw mit w, x ∈ Σ∗ . 100 Gesucht ist das Vorkommen von P in T, also die Stellen s mit: T [s+i] = P [i] für i = 1, . . . , m. Wir definieren folgende Bezeichnungen: Pk := P [1..k] ( die ersten k Zeichen von P ), P0 := ε ( das leere Wort ). Gesucht sind die Stellen s im Text T bei denen das Muster P ein Suffix ist, und zwar von T1 bis Ts+m . 5.1.1 Naiver Algorithmus Ein naiver Algorithmus für String-Matching ist folgender. NAIV(T ,P ) n := Länge T ; m := Länge P ; for s := 1,. . . , n − m do 5: if P = T [s + 1 . . . s + m] then 6: gib s aus 7: end if 8: end for Algorithm 10: Naiver Algorithmus zum Finden eines Patterns P in einem Text T . Der Vergleich in Zeile 5 enthält die Schleife, welche das Muster mit dem Abschnitt s + 1 bis s + m aus dem Text T zeichenweise vergleicht. 1: 2: 3: 4: Laufzeit Die Laufzeit wird durch zwei Teile des Algorithmus bestimmt, der FOR-Schleife und dem Vergleich. Die FOR-Schleife wird n − m mal im average - und im worst case n - mal durchlaufen. Dies ist der Fall, wenn T = an ( also n - mal das selbe Zeichen ) und P = an−1 b ist. Der Vergleich, welcher überprüft ob das Pattern mit dem Abschnitt s + 1 bis s + m aus dem Text T übereinstimmt, muss m Vergleiche ausführen und hat daher eine Laufzeit von O(m). Da dieser Vergleich bei jedem Schleifendurchlauf gemacht wird, ergibt sich eine Gesamtlaufzeit von O(m(n−m)) beziehungsweise Θ(mn). 101 5.2 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: Algorithmus von Knuth - Morris - Pratt Berechne für P die Präfixfunktion π q := 0; for i := 1,. . . ,n do while ((q > 0) ∧ (P [q + 1] 6= T [i])) do q := π [q] end while if (P [q + 1] = T [i]) then q := q + 1; end if if (q = m) then Match an Stelle i − m; q := π [q] end if end for Algorithm 11: Algorithmus von Knuth - Morris - Pratt Der Algorithmus basiert auf folgender Idee: Ein ganz gewichtiger Nachteil des naiven Algorithmus besteht in seinem ineffizienten Verhalten nach einem Mismatch. Dabei wird das Muster um nur eine einzige Position relativ zum Text verschoben. Des Weiteren wird immer wieder zum ersten Zeichen des Musters gesprungen um einen erneuten Vergleich zu starten. Durch Einbindung einer Präfixanalyse soll nun nach einem Mismatch das Muster P um möglichst mehr als eine Stelle verschoben werden, bevor ein neues Matching gestartet wird. Diese Präfixfunktion, bildet den funktionalen Hauptteil des Knuth - Morris - Pratt Algorithmus und soll daher im Weiteren näher betrachtet werden. 5.2.1 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: Präfixfunktion P REF (P ) π [1] := 0 for q := 2,. . . ,m do while ((k > 0) ∧ (P [k + 1] 6= P [q])) do k := π [k] end while if (P [k + 1] = P [q]) then k := k + 1; end if π [q] := k end for Algorithm 12: Präfixfunktion Definition 5.2.1 (Präfixfunktion π). Die Funktion π : {1, . . . , m} → {1, . . . , m − 1} heißt Präfixfunktion und ist definiert als: Gesucht ist somit nach einem größtmöglichen k mit: π(q) := max{k|k < q, Pk A Pq }. 102 Weniger formal, k Stellen des Musters müssen mit dem Suffix der Länge k des Musters bis zur Stelle q übereinstimmen. Dies hat nunmehr keinen Bezug zum Text, sondern nur noch zum Muster P ! Beispiel für die Präfixfunktion . Die Präfixfunktion für das Muster P = ababaca ist q 1 2 3 4 5 6 7 Pq Pπ(q) a ab π(q) 0 0 aba a abab ab ababa aba ababac ababaca a 1 2 3 0 1 Das Vorgehen des Pref-Algorithmus für das Muster P = ababaca zeigt folgende Abbildung. Laufzeit der Präfixfunktion Die Präfixanalyse dient also der Bestimmung einer möglichst großen Anzahl von Zeichen um die das Muster nach einem Mismatch gegenüber dem Text verschoben werden kann. Dazu benötigen die Zeilen 7 und 8 konstante Zeit pro Iteration der FOR-Schleife, welche insgesamt m mal durchlaufen wird, also O(m). Die Zeilen 4 und 5 werden ebenfalls höchstens m - mal durchlaufen und benötigen daher auch nur O(m) Laufzeit. Somit ergibt sich die Gesamtlaufzeit der Präfixfunktion zu O(m), ist demnach also linear. Laufzeit von Knuth - Morris - Pratt Die Laufzeitanalyse des gesamten Algorithmus gestaltet sich analog zur Präfixfunktion. Zeilen 4 und 5 werden 103 q q=2 P P' q a b a b a c a a b a b a c a q=3 P P' k k+1 k k+1 q q=4 P P' q a b a b a c a a b a b a c a q=5 P P' k k+1 P P' a b a b a c a a b a b a c a q q=6 2. P P' k k+1 P P' a b a b a c a a b a b a c a a b a b a c a a b a b a c a k k+1 q q=6 3. a b a b a c a a b a b a c a k k+1 q q=6 1. a b a b a c a a b a b a c a q q=7 k k+1 P P' a b a b a c a a b a b a c a k k+1 Abbildung 5.1: Beispielablauf PREF für Zeichenkette ababaca höchstens n - mal wiederholt, also so oft wie es Zeichen im Text T gibt - daher ergibt sich eine Laufzeit von O(n). Die Zeilen 7 - 12 werden ebenfalls n - mal ausgeführt, ergo ergibt sich wieder O(n). Die Zeile 1 benötigt O(m) Zeit ( Anzahl der Zeichen des Musters P ), woraus sich für den Algorithmus von Knuth Morris - Pratt eine Gesamtlaufzeit von O(m + n) ergibt. Korrektheit der Präfixfunktion Lemma 5.2.2. Sei π ∗ (q) := {q, π(q), . . . , π i (q)}, wobei π i = π(π(. . . π(q) . . . )) ist. Dann ist π ∗ (q) = {k|Pk A Pq fürq = 1, . . . , m} Für q = 1, . . . , m gilt also, dass P [1, . . . , k] Suffix von P [1, . . . , q] ist. Beweis. ⊂ “ : ” Für alle i ∈ π ∗ (q) gilt, i = π l (q). Wir führen also den Induktionsbeweis über l und zeigen somit die Korrektheit der Funktion über alle l ∈ N in angegebener Richtung. Induktionsanfang mit l = 0: π 0 (q) = q und Pq ⊃ Pq . π 0 (q) = q : ist trivial erfüllt, denn aus i = q folgt, dass P [1, . . . , q] Induktionsschritt mit Übergang von l → l + 1: Pπl+1 (q) ⊃ Pπl (q) ⊃ Pq . P [1, . . . , π(i)] ist trivialerweise Suffix von P [1, . . . , (i)] und unter Brücksichtigung der Transitivität der Suffixrelation gilt das Lemma für die angegebene 104 Richtung als bewiesen. ⊃“ : ” Angenommen es existiert ein l ∈ k, wobei P [1, . . . , k] Suffix von P [1, . . . , q] ist. Ausgeschlossen werden muss dabei aber l ∈ π ∗ (q), denn q ist als letztes Zeichen des Musters in beiden Strings vorhanden. Sei nun l0 die kleinste Zahl in π ∗ (q), mit der Eigenschaft l0 > l. Dann ist P [1, ..., l] Suffix von P [1, ..., q] und P [1, ..., l0 ] Suffix von P [1, ..., q]. Daraus aber folgt, dass P [1, ..., l] ist Suffix von P [1, ..., l0 ] und l ist maximal. Und schließlich der Widerspruch zur Annahme π(l0 ) = l und → l ∈ π ∗ (q). 105 Lemma 5.2.3. Falls π(q) > 0, dann ist π(q) − 1 ∈ π ∗ (q − 1). Beweis. Pπ(q) A Pq ⇒ Pπ(q)−1 A Pq−1 Lemma ?? ⇒ π(q) − 1 ∈ π ∗ (q − 1). Definition 5.2.4. Eq−1 ⊂ π ∗ (q − 1) sei definiert durch Eq−1 = {k ∈ π ∗ (q − 1)|P [k + 1] = P [q]}. Dann gilt k ∈ Eq−1 ⇒ Pk A Pq−1 und Pk+1 A Pq . Korollar 5.2.5. π(q) = falls Eq−1 = ∅ sonst. 0 1 + max Eq−1 Beweis. Sei im Folgenden r = π(q). Es folgt Pr A Pq ⇒ P [r] = P [q] falls r ≥ 1 und Pr−1 A Pq−1 . Also ist r = 1 + max{k ∈ π ∗ (q − 1) | P [k + 1] = P [q]} für r ≥ 1. Für r = 0 muß Eq−1 = ∅ sein (es gibt keinen Präfix). Korrektheit von PREFIX Am Anfang jeder Iteration der for-Schleife (nach Zeile 3) ist k = π[q − 1]. Das folgt aus den Zeilen 1, 2 und 7. Die While-Schleife durchsucht π ∗ [q − 1], bis ein Element von π ∗ gefunden wird mit P [k + 1] = P [q], also k = max Eq−1 . Daraus folgt nach Korollar: π[q] = k + 1. Also sind Zeilen 6 und 7 korrekt. Falls kein Element gefunden wird, folgt k = 0. Dies ist nach Korollar 5.2.5 ebenfalls korrekt. Die Korrektheit von Knuth-Morris-Pratt wird analog zur Korrektheit von PREFIX gezeigt. Bemerkung. Es gibt zwei klassische String-Matching Algorithmen. Den Algorithmus von Knuth-Morris-Pratt und den Algorithmus von Boyer-Moore. In der Praxis kommen häufig andere Anfragen vor: der zu durchsuchende Text ändert sich nicht und kann vorverarbeitet werden. Es werden Anfragen gestellt, wo ein bestimmtes (variables) Muster im Text zu finden ist. 5.3 Suffixbäume Motivation Suffixbäume sind eine Datenstruktur für String-Matching in einem festen Text. Der zu durchsuchende Text S der Länge n wird zu einer Datenstruktur vorverarbeitet, so dass Anfragen mit einem Muster P der Länge m, nämlich die Frage, wo das Muster in S vorkommt, effizient beantwortet werden können. In Anwendungen ist häufig m n. Wichtige Aspekte dabei sind • Vorverarbeitungszeit 106 • Platzbedarf • Anfragezeit. Naive Version des Suffixbaumes benötigt Θ(n2 ) Zeit für Anlegen und hat Θ(n2 ) Platzbedarf. Für ein Buch mit ca. 106 Zeichen bedeutet dies einen Platzbedarf von ca. 1012 Zeichen ≈ 1 Terabyte. Im Folgenden wird dieser naive Zugang vorgestellt. Es gibt jedoch auch Algorithmen mit O(n) Zeitbedarf für die Vorverarbeitung und O(n) Platzbedarf. Formale Definition Ein Suffixbaum T für ein Wort S der Länge n: a) Baum mit Wurzel und genau n Blättern, nummeriert von 1 bis n. b) Jeder innere Knoten, außer der Wurzel, hat ≥ 2 Kinder. c) Jede Kante im Baum ist markiert mit einem nicht-leeren Teilwort von S. d) Keine zwei Kanten, die aus einem Knoten kommen, haben Markierungen mit gleichen Anfangszeichen. e) Für ein Blatt bi mit Nummer i gilt: die Markierung des Wegen zu bi von der Wurzel (Konkatenation) ist S[i . . . n]. bxac a c xa 6 Beispiel c 5 bxac 2 3 c bxac 4 1 Suffixbaum für den String S = xabxac. Nummerierung 6 5 4 3 2 1 Matching P Teilwort von S ⇐⇒ P Präfix eines Suffix von S. D.h. die Suche nach P im Suffixbaum T ist erfolgreich in dem Sinne, dass man P als Beschriftung entlang eines Weges findet, wobei es innerhalb der Beschriftung einer Kante aufhören darf. Suchergebnisse für das Beispiel S = xabxac: P bxac bxa xa abx Position in S 3 3 1,4 2 107 Suffix c ac xac bxac abxac xabxac Bemerkung. Ein Suffix kann Präfix eines anderen Suffix sein. Beispiel: xabxab. Dann ist kein Suffixbaum möglich. Abhilfe: Hänge ein Sonderzeichen $ an das Ende von S an und konstruiere dann den Suffixbaum. 5.3.1 Algorithmus zum Suchen eines Musters 1: Beginnend bei der Wurzel nimm die Kante $e$, deren Markierung $w_e$ mit P [1] beginnt. 2: Falls diese nicht existiert, gibt es kein Match. 3: Andernfalls: 4: Falls we @ P , also P = we α, dann durchsuche rekursiv den Teilbaum, dessen Wurzel Endpunkt v von e ist mit String α. 5: Falls P @ we , gib alle Nummern von Blättern des Teilbaums mit der Wurzel v aus. 6: Sonst: es gibt kein Match. Laufzeit des Algorithmus Alle Zeichen des Musters P mit den Zeichen der Kantenbeschriftungen zu vergleichen dauert O(m) Zeit. Im Wesentlichen sind dies Schritte 1 − 4 des Algorithmus. Der Schritt 5 des Algorithmus kostet O(Größe des Teilbaums) = O(k) Zeit, wobei k die Anzahl der Blätter, also die Anzahl der Vorkommen von P in S ist. Satz 5.3.1. String-Matching von einem Muster P in einem String S, mit den Längen |P | = m und |S| = n, ist, wenn ein Suffixbaum T von S vorliegt, in O(m + k) Zeit möglich, wobei k die Anzahl der Vorkommen von P in S ist. 108 5.3.2 Konstruktion der Suffixbäume Beipiel: xabxac$ (siehe Abbildung 5.2) Man beginnt mit der Konstruktion eines Suffixbaumes für gesamten String und schreibt eine 1 am Blatt, weil der Suffix xabxac$ an der Stelle 1 des Strings beginnt. So geht man Zeichen für Zeichen den String durch. Kommt jedoch ein Suffix vor, dessen Anfangssymbol(e) bereits im Suffixbaum vorkommt, so zerlegt man die bereits existierende Kante in zwei Kanten (Wörter) genau an der Stelle, wo die Gleichheit zwischen bereits existierendem und neu einzufügundem Suffix aufhört. xabxac$ abxac$ 1 $ abxac $ bx ac $ $ 7 xa a $ bx ac 1 2 4 6 c$ 3 5 bxac$ bx ac $ a xa xa a bx ac$ bxac$ $ abxac bx ac xa bx ac $ xa a 6 $ b x ac$ xabxac$ abxac$ bxac$ xac$ ac$ c$ $ 3 5 c$ bxac$ 2 4 c$ $ c$ 1 c$ bx ac 3 bxac$ b x ac$ 4 bxac$ 3 5 c$ c$ 2 3 c$ ac$ bxac$ 2 4 1 2 4 c$ 1 2 1 4 xabxac$ abxac$ bxac$ xac$ ac$ c$ 1 c$ bxac$ 3 c$ 2 1 xabxac$ abxac$ bxac$ xac$ ac$ xa bx ac 2 xa xabxac$ abxac$ bxac$ xac$ $ $ ab x ac $ xa bx ac 1 bxac$ xa bx ac $ xabxac$ abxac$ bxac$ ab x ac xabxac$ 3 5 Abbildung 5.2: Konstruktion des Suffixbaumes am Beispiel von xabxac$ 109 Naiver Algorithmus zu Konstruktion von Suffixbäumen. Konstruiere Folge T1 , T2 ,..., Tn von Bäumen, wobei Ti alle Suffizies S[1..n], S[2..n],..., S[i..n] enthält. D.h., der Baum T1 hat nur eine Kante und enthält nur einen Suffix S[1..n] = S (Abbildung 5.3), T2 enthält Suffizies S[1..n], S[2..n] usw. T1 : 1 Abbildung 5.3: Erster Schritt bei der Aufbau eines zunächst leeren Suffixbaumes Ti+1 wird aus Ti wie folgt konstruiert: 1: Durchlaufe Ti mit dem Suffix Si+1 (= S[i + 1..n]) wie beim Matching Algorithmus bis man "stecken bleibt". Also (Abbildung 5.4): Si+1 = αβ, wobei α ist ein maximaler Präfix, der als Markierung eines Knotens u (d.h. Beschriftung des Weges von Wurzel bis u) plus Präfix α0 , der Markirung γ einer von u ausgehender Kante e = (u, v) auftritt. 2: Wir schaffen ein neues Blatt w mit dem Index i + 1 3: Falls α0 = ε (leeres Wort) (Abbildung 5.5): schaffe eine neue Kante e0 = (u, v) und beschrifte sie mit β Sonst (Abbildung 5.6): sei γ = α0 β, wobei α0 6= ε, δ 6= ε schaffe neuen Knoten x spalte Kante e in e1 = (u, x) und e2 = (x, v) schaffe neue Kante e0 = (x, w) beschrifte e1 mit α0 , e2 mit δ, e0 mit β Wurzel α α' γ u α' γ v Abbildung 5.4: α0 ist Präfix eines existierenden Suffixes im Baum 110 Wurzel α α'=ε (leeres Wort) u γ β w v i+1 Abbildung 5.5: α0 = ε (Leeres Wort) Wurzel α γ=α'δ, α'≠ε, δ≠ε u γ β w i+1 α' δ x v Abbildung 5.6: γ = α0 δ, wobei α0 6= ε, δ 6= ε 111 Laufzeit Für jeden Suffix Si (|Si | = n−i+1) wird eine Suche durchgeführt, die O(n−i+1)Zeit kostet. Schritte 2 und 3 benötigen konstante Zeit, also O(1). ! n X Insgesamt : O n − i + 1 = O n2 i=1 {z | Pn j=1 j= } 2 n(n+1) ≈ n2 2 Speicherbedarf Die Größe des entstehenden Baumes (Speicherbedarf), einschließlich der Beschriftungen der Kanten ist O(n2 ). Best case ist nur dann zu erwarten wenn alle Zeichen gleich sind (S = an ), so liegt der Speicherbedarf bei O(n) und der Baum sieht wie auf der Abbildung 5.7 aus: Abbildung 5.7: Suffixbaum mit dem Speicherbedarf O(n) Sonst aber kommt es an Θ(n2 ) heran, was für große Texte (Buch, DNA-Analyze) nicht akzeptabel ist. In der Praxis wird eine andere Konstruktion/Modifikation des Suffixbaumes verwendet, die in linearer Zeit konstruiert werden kann und linearer Speicherplatz benötigt. Dabei handelt es sich um das Algortihmus von Ukkonen (AU). Wir werden den Algorithmus nicht näher betrachten, da er sehr kompliziert ist. 5.3.3 Anwendungen von Suffixbäumen 1 Stringmatching Konstuktion des Suffixbaumes mit AU mit anschließender Suche nach P liefert alternativen O(n + m)-Algorithmus. 1a Finden einer Menge von Strings {P1 , .., Pl } in einem Text S Zu finden sind alle Vorkommen von Mustern im Text S. Das ist machbar in Vorverarbeitungszeit O(n) mit AU. Laufzeit für eigentPk liche Suche beträgt O(m + k), wobei m = i=1 |Pi |, k ist Anzahl aller Vorkommen. 2 Datenbank von Texten S1 , ..., Sl Zu finden sind alle Vorkommen eines Musters P in Texten S1 , ..., Sl . 112 Dafür werden die Texte zusammengestellt getrennt durch spezielle Trennzeichen $i die weder im Text noch im Muster vorkommen: S1 $1 S2 $2 ..$l−1 Sl . Ohne dieser Trennzeichen wäre es möglich ein Muster so zu finden, dass sein Präfix in Sj und sein Suffix in Sj+1 liegt. Dafür bauen wir einen Suffixbaum auf. Vorverarbeitungszeit beträgt O(n), Pl n = i=1 |Sl |, Suchzeit ist O(m + k), wobei m = |P | und k ist Anzahl aller Vorkommen. 3 Suche nach dem längsten gemeinsamen Teilwort Gegeben sind Strings S1 und S2 . Zu finden ist das längste gemeinsame Teilwort. Idee: - konstruiere einen Suffixbaum wie in o.a. Anwendung 2. für S1 und S2 - markiere jeden inneren Knoten v mit 1, falls sein Unterbaum einen Suffix von S1 enthält und mit 2 falls einen Suffix von S2 . D. h. in v endet sich ein Teilwort von S1 (oder S2 ) - finde tiefsten Knoten, der mit 1 und 2 markiert ist, wobei die Tiefe = Länge der Beschriftungen von der Wurzel bis dorthin. Mit AU lässt sich das Problem in O (|S1 | + |S2 |) Zeit lösen. Ungestritten gibt es viele weitere Anwendungen. Einige der wichtigen davon finden sich in der Bioinformatik. 5.3.4 Anwendungen in der Bioinformatik Definition 5.3.2 (DNA-Moleküle (deutsch: DNS - Desoxyribonukleinsäure)). Moleküle, die die Erbinformation eines Organismus enthalten und sind Doppelketten aus folgenden Bausteinen (Molekülen) {A, T, C, G} (Adenin, Thymin, Cytosin, Guanim). Einzelne Bausteine heißen Nukleotide. Ein Molekül besteht aus ≈ 109 − 1010 Nukleotiden. Dabei gibt es zwei Zuordnungen: A ↔ T und C ↔ G. Definition 5.3.3 (Genom). Gesamtkette der DNA-Moleküle, die gesamte Erbinformation enthält (mehr dazu kann man z.B. bei http://de.wikipedia. org/wiki/Genom nachlesen). Definition 5.3.4 (Protein). Ein String über einem 20-elementigen Alphabet (die Zeichen des Alphabets sind die Aminosäuren). Die Länge eines solchen Strings kann mehrere Hunderte sein. (z.B.: bei Bakterien: 500-1500, bei Menschen (Säugetieren) ≈ 100.000). Definition 5.3.5 (Genetischer Code). Codierung eines Aminosäurenbausteins im Protein durch jeweils ein Tripel von aufeinanderfolgenden Nukleotiden. Beispiel (Genetischer Code). TTT - Phenylamin, GTT - Valin Da es um die Tripeln handelt, ist es wichtig bei der Decodierung eines DNAAbschnittes an der richtigen Stelle anzufangen (man weiß nicht genau, am Anfang des DNA-Stücks der Tripel ’sauber’ oder innendrin getrennt wurde). 113 Definition 5.3.6 (Sequenzierung). Bestimmung der Folge von Nukleotiden eines DNA-Moleküls. Den ganzen Genom erhält man durch das Überlappen einzelner Stücke (Abbildung 5.8). Bisher (Stand: 2004) ist es technisch möglich Stücke der Länge 300-500 zu identifizieren. Dabei soll man bedenken in welche Richtung die Einzelstücke bei dem Überlappen gerichtet werden sollen. Abbildung 5.8: Rekonstruktion eines Genoms durch das Überlappen der DNAFragmente Es gibt also folgendes Problem (theoretisch) zu lösen: gegeben sind viele Teilworte, gesucht ist das kleinste gemeinsame Oberwort. Dieses Problem ist NP-schwer und ist ein gutes Beipiel für die o.a. Anwendung 1.a (Finden einer Menge von Mustern in einem Text) 5.3.3. Dabei ist der Text ein bereits sequinziertes DNA-Stück. Jedes neue Fragment wird gegen den Text getestet, ob nicht bereits vorhanden. Ist es nicht der Fall, so sucht man nach einer Überlappung mit dem Ende des Textes. 114 Kapitel 6 NP-Vollständigkeit Bisherige Probleme waren alle mit Algorithmen polynomieller Laufzeit lösbar, d.h. O(nk ) für eine Konstante k. Dies galt im EKM, aber es gilt auch im LKM. In diesem Kapitel werden wir nur das LKM zu Grunde legen. Wenn ein Algorithmus polynomiell im EKM ist, ist er auch polynomiell im LKM. Zur Erinnerung: Wenn ein Problem in polynomieller Zeit auf einer Registermaschine im LKM lösbar ist, ist es auch in polynomieller Zeit auf einer TuringMaschine lösbar. Für viele Probleme ist nicht bekannt, wie das Problem in polynomieller Zeit lösbar ist. Um dies zu erläutern, erst einmal zwei Beispiele: Beispiele 1. PARTITION Gegeben: a1 , ..., an ∈ Z. Frage: Gibt es eine Teilmenge I ∈ {i, ..., n}, so dass P i∈I ai = P j ∈I / aj ? z.B.: 2 5 3 7 8 1 Naiver Algorithmus: Test aller Teilmengen, das sind 2n Stück. Dies ist der beste bekannte Algorithmus. Laufzeit: O(2n ) → exponentiell! Dies ist ein Entscheidungsproblem, d.h. die Antwort ist ja“ oder nein“. ” ” 2. Travelling Salesperson Problem (TSP) zu deutsch: Problem des Handelsreisenden Gegeben: Vollständiger ungerichteter Graph G = (V, E) und die Gewichtsfunktion c : E → N. Problem: Finde die kürzeste Rundreise in G, d.h. einen Kreis, auf dem alle Knoten liegen. Dabei darf jeder Knoten nur ein Mal besucht werden und die Summe der Kantengewichte muss minimal sein. 115 Dies ist ein Optimierungsproblem, d.h. ein Problem, in dessen Fragestellung ein Superlativ vorkommt. Formal: Man hat eine Zielfunktion, die maximiert oder - wie in diesem Fall - minimiert werden soll. Das kann man leicht in ein Entscheidungsproblem umwandeln: Gegeben: G = (V, E) , c : E → N , Zahl k ∈ N. Frage: Existiert eine Rundreise der Länge ≤ k ? Man kann nicht immer aus einem Entscheidungsproblem ein Optimierungsproblem machen, jedoch kann man jedes Optimierungproblem auf ein Entscheidungsproblem zurückführen. TSP hat die gleichen Eigenschaften wie PARTITION. Die Laufzeit des naiven Algorithmus wäre exponentiell (alle Permutationen der Knoten). In einem konkreten Beispiel, in dem der naive TSP-Algorithmus auf alle Orte in Deutschland angewandt wurde, ergab sich folgendes: • Die Gesamtstrecke betrug 66.000 km. • Die Rechenzeit für einen Computer würde ca. 22,6 Jahre betragen. Das Entscheidungsproblem ist effizient lösbar, wenn dies auch für das Optimierungsproblem gilt. Umgekehrt im Allgemeinen auch. Für beide Probleme ist kein Algorithmus polynomieller Laufzeit bekannt. Allerdings ist es für das Entscheidungsproblem leicht, nachzuprüfen, ob eine gegebene Lösung“ wirk” lich eine ist, d.h. es gibt einen kurzen effizient verifizierbaren Beweis, dass eine Lösung existiert. 6.1 Die Komplexitätsklassen P und NP Wir betrachten ausschließlich Entscheidungsprobleme. Diese können als formale Sprachen aufgefasst werden. L = Menge der (Codierungen der) Eingaben, bei denen die Antwort positiv ist. Definition 6.1.1. Die Menge aller Sprachen L, für die es einen Entscheidungsalgorithmus polynomieller Laufzeit gibt, heißt P. Beispiele: • {w|w ist die Binärdarstellung einer durch 3 teilbaren Zahl} • {w|w ist die Binärdarstellung einer Primzahl} Die Menge aller Sprachen (= Entscheidungsprobleme) L, die in polynomieller Zeit verifizierbar sind, heißt NP, d.h. 116 Es gibt einen Algorithmus A polynomieller Laufzeit und ∃ k ∈ N, sodass L = {w | ∃ Wort x mit |x| ≤ |w|k und A(w, x) = 1}. A(w, x) = 1 bedeutet: A angewandt auf die Eingabe (w, x) liefert positive Antwort. z.B. bei PARTITION: w ist die Instanz des Problems: bin(a1)#...#bin(an ) über dem Alphabet Σ = {0, 1, #}. x = bin(ai ) ist die (Codierung einer) Teilmenge. x heißt auch Zeuge dafür, dass w ∈ L. ” ” a prüft nach, ob Σ = {0, 1, #}. ⇒ PARTITION ist in NP ! Beobachtung 6.1.2. P ⊆ N P 6.2 Polynomzeit-Reduktionen Beispiel. SUBSET-SUM Gegeben: a1 , ..., an ∈ Z, b ∈ Z. Frage: Existiert eine Teilmenge I ⊂ {1, ..., n} mit P i∈I ai = b ? Beobachtung 6.2.1. Existiert ein Algorithmus in polynomieller Laufzeit für SUBSETSUM, so existiert auch einer für PARTITION. Denn: Sei a1 , ..., an eine Eingabe für PARTITION. a1 , ..., an hat eine positive Antwort Pn ⇔ a1 , ..., an ,b hat eine positive Antwort bei SUBSET-SUM, wobei b = 12 i=1 ai . Jede Eingabe w für PARTITION ist leicht zu übersetzen in eine Eingabe w0 für SUBSET-SUM, so dass w eine positive Antwort liefert gdw. w0 eine positive Antwort liefert. Schreibweise: PARTITION ≤P SUBSET-SUM. Genauer: Definition 6.2.2. Seien L1 , L2 ⊂ Σ∗ zwei Sprachen. L1 heißt in polynomieller Zeit reduzierbar auf L2 (geschrieben: L1 ≤P L2 ) gdw. ∃ eine in polynomieller Zeit berechenbare Funktion f : Σ∗ → Σ∗ mit w ∈ L1 gdw. f (w) ∈ L2 für alle w ∈ Σ∗ . 117 Beobachtung 6.2.3. P ⊂ N P . Für Sprachen/Probleme in P spielt der Zeuge, so zu sagen, keine Rolle, denn für Sprachen aus P können wir in polynomieller Laufzeit entscheiden ob ein Wort in der Sprache ist oder nicht. Frage: Ist P = N P ? Das ist ein grosses offenes Problem. P“ kommt von polynomiell“. ” ” NP“ kommt von nichtdeterministisch“ polynomiell. ” ” N P sind die Sprachen, die von einer nichtdeterministischen Turingmaschine in polynomieller Zeit akzeptiert werden. Das ist eine alternative, ursprüngliche, äquivalente Definition. Zur Erinnerung: eine nichtdeterministische Turingmaschine hat in jedem Schritt mehrere Möglichkeiten für ein Symbol was auf das Band geschrieben wird, für den Folgezustand und Bewegungsrichtung. Ein Wort wird von einer nichtdeterministischen Turingmaschine akzeptiert, wenn es eine Ausführungsfolge gibt die zu einem akzeptierenden Zustand führt. Erinnern wir uns an die Definition von der polynomzeit-Reduktion: Der Ausdruck L1 ≤p L2 bedeutet, dass wir ein Wort w mit Hilfe einer in polynomieller Zeit berechenbaren Funktion f so transformieren können, dass w ∈ L1 ⇔ f (w) ∈ L2 , siehe Abbildung 6.1. Daraus folgt die nächste Beobachtung. f w ist in L2 ? f(w) ja/nein Abbildung 6.1: Dieser ganze Kasten entscheidet, ob w ∈ L1 . Beobachtung 6.2.4. Falls L1 ≤p L2 und L2 ∈ P , dann ist auch L1 ∈ P . Falls L2 in Zeit p(n) entscheidbar ist, p ist ein Polynom, und f in Zeit q(n) berechenbar ist, dann hat f (w) die Länge ≤ q(|w|), denn pro Schritt kann höchstens 1 Zeichen produziert werden. Damit ist die Laufzeit der gesamten Konstruktion ≤ q(|w|) + p(q(|w|)) also polynomiell. | {z } | {z } f (w) berechnen Test, ob f (w)∈L2 Definition 6.2.5. Problem (Sprache) L heißt N P -schwer (N P -hard) genau 0 0 dann, wenn L ≤p L für alle L ∈ N P . Definition 6.2.6. Problem (Sprache) L heißt N P -vollständig genau dann, wenn es in N P liegt und N P -schwer ist. Beobachtung 6.2.7. Wenn ein Problem L N P -vollständig ist, dann L ∈ P ⇔ P = NP . Das folg aus der Beobachtung 6.2.4. Wie zeigt man N P -Schwere von Problemen? 118 Beobachtung 6.2.8. Falls L1 N P -schwer ist und L1 ≤p L2 , dann ist auch L2 N P -schwer. 0 0 0 Für alle L ∈ N P gilt L ≤p L1 ∧ L1 ≤p L2 ⇒ L ≤p L2 also, L2 ist N P -schwer, denn ≤p ist transitiv. Die Eigenschaft aus der letzten Beobachtung kann man benutzen, um N P Schwere von Problemen zu zeigen, wenn man bereits andere N P -schwere Probleme kennt. Für mindestens eines ist also ein direkter Beweis erforderlich. 6.3 Circuit Satisfiability Definition 6.3.1. Ein Schaltkreis ist ein gerichteter azyklischer Graph mit folgenden Arten von Knoten: oder, und, nicht, Eingangsknoten, Konstanten (0 oder 1), Verzweigung, Ausgangsknoten, s. Abbildung 6.2. Der Ausgangsknoten kommt in einem Schaltkreis nur ein mal vor. oder V Ingrad 2 Ausgrad 1 und Ÿ ÿ 2 1 1 1 Konstanten 0 Ingrad 0 Ausgrad 1 nicht Verzweigung 1 X 0 1 Ausgang A V 0 1 Eingang 1 2 1 0 Abbildung 6.2: Arten von Knoten. Ein Schaltkreis S realisiert eine Boolesche Funktion fs : B n → B, wobei B = {0, 1}, und n ist die Anzahl der Eingänge von S. Betrachten wir als Beispiel den Schaltkreis S aus der Abbildung 6.3, dieser realisiert die Boolesche Funktion XOR. 119 X Y Ÿ Ÿ ⁄ ⁄ V A Abbildung 6.3: Schaltkreis S realisiert Boolesche Funktion x̄y ∨ xȳ = x XOR y. Definition 6.3.2. Schaltkreis S heißt erfülbar genau dann, wenn fs nicht konstant 0 ist, d.h., es gibt eine Belegung der Eingabe, so dass der Ausgang auf 1 gesetzt wird. Im Beispiel: mit den Belegungen der Eingabe x = 1 und y = 0 kommt 1 aus. Problem 6.3.3. CSAT (circuit satisfiability) gegeben: ein Schaltkreis S Frage: Ist S erfüllbar? Satz 6.3.4 (Cook/Levin). CSAT ist N P -vollständig Beweis. Idee: a) CSAT ∈ N P , denn wähle als Zeuge x eine Belegung der Eingänge. Es ist in polynomieller Zeit (sogar in linearer Zeit) nachprüfbar, ob im Ausgang 1 erscheint. b) Nun wollen wir zeigen dass CSAT N P -schwer ist, d.h. jedes Problem in NP lässt sich in polynomieller Zeit auf das CSAT-Problem reduzieren, d.h. für alle L ∈ N P : L ≤p CSAT . Dafür für eine Eingabe w für L konstruieren wir in polynomieller Zeit einen Schaltkreis Sw , der erfüllbar ist ⇔ w ∈ L. w ∈ L heißt, es existiert ein Polynomiellzeit-Algorithmus A, und eine Zahl k ∈ N , so dass k L = {w | ∃x, |x| ≤ |w| ∧ A (w, x) = 1} Sei MA eine deterministische Turingmaschine, die A berechnet, MA = (Q, Σ, δ, q0 ) . Zur Erinnerung, die Überführungsfunktion δ (c, q) = c̃, q̃, R̃ der Turingmaschine bestimmt, falls im Zustand q ein c gelesen wird, den Folgezustand q̃, 120 das Zeichen c̃ mit dem c überschrieben wird und die Bewegungsrichtung R̃ ∈ {R, L, 0} des Schreib- und Lesekopfes. Wir können annehmen, dass das Ergebnis 0 oder 1 in der Zelle 1 des Bandes steht. Idee: Der Schaltkreis Sw ist eine feste Verdrahtung der Rechnung von MA bei Eingabe w, die Eingänge entsprechen der Kodierung des Zeugen x. Man braucht als Baustein B ein Schaltkreis der die Verdrahtung der kompletten Überganstabele δ der Turingmaschine MA realisiert. B hat als Eingänge die Binärkodierung eines Zeichens des Alphabets c, eine Kontrollleitung k und die Kodierung des Zustands q der Turingmaschine und als Ausgänge die Kodierung des Folgezeichens c0 , drei neuen Kontrollleitungen k 0 und die Kodierung des Nachfolgezustands q 0 hat, wie in der Abbildung 6.4 dargestellt wird. c k q B c' k' q' Abbildung 6.4: Baustein B kodiert die gesamte Übergangstabelle der Turingmaschine MA . Der Baustein B wird für jede Stelle des Bandes und für jeden Schritt von MA eingesetzt. Für jede Stelle i haben wir ein Kontrollbit ( 1, falls sich der Kopf an der Stelle i befindet ki = 0, sonst Die Belegung der Ausgänge wird wie folgt definiert: ( c falls k = 0 0 ci = c̃ sonst ( q̃ falls k = 1 qi0 = 0. . .0 falls k = 0 000 für k = 0 100 für k = 1 und R̃ = L ki0 = 010 für k = 1 und R̃ = 0 001 für k = 1 und R̃ = R Dabei nehmen wir an, dass eine Folge von Nullen keinen Zustand der Turingmaschine kodiert. So bleibt das Zeichen unverändert falls der Baustein an der i-ten stelle nicht aktiv war, und bekommt den Wert von dem neuen Zeichen entsprechend der Übergangstabelle, falls die Kontrollleitung den Wert 1 hatte. 121 Der Baustein B hat konstante Größe, denn die Überführungstabelle der Turingmaschine MA konstante Größe hat, die unabhängig von dem Eingabewort w ist. Nummerieren wir die Stellen des Bandes 1, 2, ..., N , wobei N = T (n), n = |w|, denn mehr Stellen des Bandes kann die Turingmaschine MA nicht benutzen. An jeder Stelle, für jeden Schritt nehmen wir einen Baustein B. Eine Schicht besteht aus N Bausteinen und simuliert einen Schritt der Turingmaschine MA . Die Ausgänge, die die Zeichen auf dem Band kodieren werden als Eingänge an die nächste Schicht direkt weitergeleitet. Das Kontrollbit für die i-te Stelle der nächsten Schicht setzt sich aus den Kontrollausgängen des i-ten Bausteins und der zwei Benachbarten, so dass die Leitung ki in der nächsten Schicht auf eins gesetzt wird genau dann wenn der Schreib- und Lesekopf der Turingmaschine an der i-ten Stelle des Bandes positioniert wird, s. Abbildung 6.5. Die Ausgänge die den Zustand der MA kodieren werden mit “oder” verknüpft (zur Erinnerung: der neue Zustand ist am Ausgang von genau einem Baustein kodiert, alle anderen Zustand-Ausgänge werden auf Null gesetzt) und der cnächsten verteilt. ci-1 ankalle k i Schicht qi c i+1 k i+1 q i+1 i-1 qBausteine i-1 i B c’i-1 B k’ i-1 q'i-1 c’i k’i B q'i c’i+1 k’i+1 q'i+1 V V V k i zum nächsten Zeitpunkt Abbildung 6.5: Verknüpfung der Ausgänge in einer Schicht. (der Beweis wird in der nächsten Vorlesung fortgesetzt.) 122 Erinnerung Ein Problem L ist NP-vollständig, genau dann wenn L in NP liegt und NPschwer ist. Ein Problem L ist NP-schwer, genau dann wenn ∀ L0 ∈ NP : L0 ≤ L, sprich alle bereits bekannten Probleme in NP lassen sich auf das neue Problem L reduzieren. D.h. ist das neue Problem L gelöst so sind damit auch alle anderen Probleme in NP gelöst. Problem CSAT Zur Erinnerung: Gegeben: Sei ein Schaltkreis S und eine Sprache L ∈ N P gegeben. Frage 6.3.5. Ist der Schaltkreis S erfüllbar? Beweis. (Fortsetzung) Wir haben ein Wort w = a1 . . . an gegeben und möchten Sw nun so konstruieren, dass unser Schaltkreis genau dann erfüllbar ist, wenn das Wort w in der Sprache L liegt, d.h. w ∈ L ⇔ Sw erfüllbar. Als verifizierenden Algorithmus A für unseren Zeugen w P wählen wir nun eine Turing-Maschine Ma als Kontrollmaschine mit Ma = (R, , δ, q0 ). Wir nehmen nun einen Baustein B, den wir so verdrahten, dass er genau unserer Überführungsfunktion δ der TM Ma entspricht. Dabei hat der Baustein B einen Aufbau wie auf Abbildung 6.16 dargestellt. Abbildung 6.6: Baustein B Dabei entspricht C k q Leitungen für die Kodierung der Zeichen Kontroll-Leitung Leitungen für die Kodierung des Zustandes Damit ergibt sich aus den Bausteinen der folgende Schaltkreis Sw , wie in Abbildung 6.7 dargestellt. (Vergleiche auch Abbildungen vom 22.01.07) 123 Abbildung 6.7: Schaltkreis Sw Jede Schicht entspricht dabei dem Zustand des Bandes der TM Ma . Jeder Baustein gibt den neuen Zustand an die nächste Schicht weiter, die Zeichen müssen lediglich durchgereicht werden. Die Maschine hat dabei eine Laufzeit von T (n) = N , wobei pro Schicht je O(N ) Platz benötigt wird. Damit ergibt sich eine Gesamtgröße von O(N 2 ) Die Behauptung w ∈ L ⇔ Sw erfüllbar ist somit erfüllt und der Beweis damit erbracht. SAT (satisfiability) Erfüllbarkeit Einleitung: Als nächstes widmen wir uns dem Problem SAT. Dabei geht es um die Frage, ob es zu einer aussagenlogischen Formel in KNF, also z.B. (x ∨ y) ∧ (x ∨ y) (6.1) eine Belegung der Variablen gibt, die diese Aussage erfüllt. Wie man im obrigen Beispiel sehen kann ist diese Aussage äquivalent zu der Aussage x ⇔ y, die genau dann erfüllt ist, wenn x und y den selben Wert annehmen. Diese Formel ist also erfüllbar. Eine Formel α gliedert sich dabei aus den folgenden Elementen: • Literalen, z.B. a, a • Klauseln, z.B. a ∨ b • KNF, z.B. Formel (6.1) Gegeben: Sei eine Formel α in KNF gegeben. Frage 6.3.6. Ist die Formel α mit einer Belegung x der Variablen erfüllbar? 124 Satz 6.3.7. SAT ist NP-vollständig Beweis. Um zu beweisen, dass SAT NP-vollständig ist, genügt es zu zeigen: 1. SAT liegt in NP, d.h. es gibt einen Zeugen und dieser kann in polynomieller Zeit verifiziert werden. 2. SAT ist NP-schwer, d.h. wir können ein bereits bekanntes Problem aus NP auf SAT reduzieren. Da wir nun wissen was zu tun ist, benötigen wir zunächst ein Problem aus NP welches wir bereits kennen und auf das wir auf SAT reduzieren können. Wir wählen hierfür das Problem CSAT. Für die Wahl des Zeugen ist es naheliegend, dass wir x, also die Belegung der Variablen nehmen. Weiter ist klar, dass wir für eine gegeben Belegung leicht in polynomieller Zeit prüfen können, ob diese die Formel αs erfüllt. Daraus ergibt sich, also: 1. Wähle als Zeugen x die Belegung der Variablen. Erfüllbarkeit in polyno√ mieller Zeit überprüfbar. 2. CSAT ≤p SAT noch zu zeigen Es gilt nun zu zeigen, dass die unter 2. aufgeführte Aussage zutreffend ist. Dies erreichen wir in dem wir eine Konstruktion angeben, die eine Instanz des alten Problems, in diesem Fall CSAT, in eine Instanz des neuen Problems, hier SAT, überführt. Dabei ist zu beachten, dass diese Konstruktion natürlich in polynomieller Zeit ausgeführt werden muss. Im konkreten Fall benötigen wir also eine Konstruktion, die einen Schaltkreis S aus CSAT genau dann erfüllend werden lässt, wenn unsere Formel αs erfüllt ist. Dazu überlegen wir uns zunächst, dass die für unsere Formel αs definierten booleschen Operationen leicht in hardwaretechnische Bausteine überführt werden können und umgekehrt. Um nun also einen gegeben Schaltkreis in eine Formel in KNF umzuwandeln, gehen wir wie folgt vor: Für jeden Knoten von S (außer Verzweigungen, Eingänge und Konstanten) definieren wir eine Klausel. Dabei helfen uns die Konstruktionsvorschriften, wie in Abbildung 6.8 dargestellt. Nun lässt sich jeder Baustein in eine Klausel umwandeln. Alle Klauseln zusammen werden mit ∧“ verknüft und in eine KNF zusammengefasst. Es sei ” angemerkt, dass die folgenden beiden Aussagen äquivalent sind: x≡y (x ∨ y) ∧ (x ∨ y) (6.2) (6.3) Weiter lässt sich feststellen, dass die Anzahl der Variablen kleiner gleich der Anzahl der Knoten in S ist. Außerdem ist die Länge der Formel linear zur Größe von S. 125 Abbildung 6.8: Konstruktionsvorschrift Zuletzt bleibt noch zu zeigen, dass die folgende Gleichung erfüllt ist: S erfüllbar ⇔ αs erfüllbar ⇒“: Betrachten wir zunächst eine erfüllende Belegung Ψ von S. Nun belegen ” wir jede Variable von αs mit dem Wert des entsprechenden Knotens. √ ⇒ Wir haben eine erfüllende Belegung für αs ⇐“: Betrachten wir nun eine erfüllende Belegung Ψ von αs . Nun belegen wir ” die Eingänge von S mit dem Wert der entsprechenden Variablen. √ ⇒ Wir haben eine erfüllende Belegung für S Beispiel: X Y Ÿ Ÿ ⁄ ⁄ V A Abbildung 6.9: Schaltkreis-Beispiel 126 Aus dem Beispiel in Abbildung 6.23 ergibt sich nun nach obriger Vorschrift die folgende Formel: (x3 ≡ x1 )∧(x4 ≡ x2 )∧(x5 ≡ x1 ∧x4 )∧(x6 ≡ x3 ∧x2 )∧(x7 ≡ x5 ∨x6 )∧(x8 ≡ x7 )∧(x8 ) Weitere NP-vollständige Probleme Problem : 3-SAT Einleitung: Verwandt mit dem Problem SAT ist das Problem 3-SAT. Auch hier haben wir eine Formel in KNF, diesmal jedoch in 3-KNF, d.h. jede Klausel hat kleiner gleich 3 Literale. Gegeben: Sei eine Formel β in 3-KNF gegeben. Frage 6.3.8. Ist β erfüllbar? Satz 6.3.9. 3-SAT ist NP-vollständig Beweis. Auch hier gehen wir wieder nach dem bekannten Schema vor: 1. Zu Zeigen: 3-SAT ∈ NP Dazu wählen wir als Zeugen x wieder die Belegung der Variablen. Offensichtlich können wir analog zu SAT auch hier in polynomieller Zeit verifizieren, dass der Zeuge x unsere Formel β erfüllt. 2. Es liegt nahe, dass wir als Problem, welches wir auf 3-SAT reduzieren, dass sehr ähnliche Problem SAT verwenden, d.h. wir müssen zeigen: SAT ≤p 3 − SAT Wir benötigen nun wieder eine Konstruktion, die unser Problem aus SAT zu einem Problem aus 3-SAT überführt. Dazu sei α eine Formel in KNF und wir konstruieren daraus die Formel β in 3-KNF, so dass α genau dann erfüllbar ist, wenn β erfüllbar ist. Hat nun eine Klausel aus α kleiner gleich 3 Literale müssen wir diese nicht verändern, sei aber C-Klausel aus α mit größer 3 Literalen, d.h. C := (x1 ∨ x2 ∨ · · · ∨ xk ) mit k > 3, ersetze C durch KNF der Form: (x1 ∨ x2 ∨ y1 ) ∧ (y1 ∨ x3 ∨ y2 ) ∧(y2 ∨ x4 ∨ y3 ) ∧ · · · ∧ (yk−3 ∨ xk−1 ∨ xk ) | {z } | {z } ↑ yj =1 yj =0 Wenn wir also nach obriger Konstruktion die xi entsprechend in die einzelnen 3-er Klauseln aufteilen und den Rest mit den Hilfsvariablen yj auffüllen und diese entsprechend setzen, erreichen wir es, dass alle Klauseln erfüllt sind, wenn mindestens ein xi eine Klausel erfüllend macht. Dies erreichen wir indem wir alle yj mit j = 1 . . . (i − 2) gleich 1“ setzen ” 127 und alle yj mit j = (i − 1) . . . (k − 3) gleich 0“ setzen. Dadurch werden ” alle Klauseln links vom xi durch die y1 . . . yi−2 Variablen zu 1“ ausge” wertet und alle Klauseln rechts vom xi durch die Negationen yi−1 . . . yk−3 ebenfalls zu 1“ ausgewertet. ” Wir machen weiter noch die Beobachtung, dass die Länge von β kleiner gleich der dreifachen Länge von α ist. Es bleibt nun noch formal zu zeigen, dass folgende Gleichung immer erfüllt ist: α erfüllbar ⇔ β erfüllbar ⇒“: Sei Ψ eine erfüllende Belegung von α für die Klausel C, so dass ” ein Index i existiert, so dass Ψ(xi ) = 1. Dafür müssen wir wie oben beschrieben die Hilfsvariablen yj entsprechend setzen, d.h. (a) Ψ(yj ) = 1 (b) Ψ(yj ) = 0 für j = 1, . . . , i − 2 für j = i − 1, . . . , k − 3 Auf diese Weise erreichen wir also, dass durch Vorschrift für Ψ alle linken, d.h. i − 2-ten Klauseln erfüllt sind, und durch Vorschrift √ für Ψ auch alle rechten, d.h. i − 1 . . . k − 3-ten Klauseln erfüllt sind. ⇐“: Sei Ψ nun eine erfüllende Belegung für β. Dafür betrachten wir die ” Gruppe von Klauseln, die einer Klausel C in α entspricht. Zeigen durch Widerspruch: Angenommen C ist nicht erfüllt, d.h. Ψ(xi ) = 0 für i = 1, . . . , k ⇒ ⇒ ⇒ ⇒ Ψ(y1 ) = 1 damit 1. Klausel erfüllt Ψ(y2 ) = 1 damit 2. Klausel erfüllt Ψ(yk−3 ) = 1 damit vorletzte Klausel erfüllt (y1 ∨ xk−1 ∨ xk ) ist nicht erfüllt ⇒ Widerspruch! Daraus folgt, wenn ein Index i ∈ {1 . . . k} existiert, so dass Ψ(xi ) = 1, also ein Literal xi den Ausdruck erfüllt, dann ist auch der gesamte Ausdruck Ψ(C) = 1, also erfüllt. Problem : CLIQUE Einleitung: Es folgt nun das Problem CLIQUE. Zur Erinnerung: Eine Clique ist eine Teilmenge von Knoten, die einen vollständigen Graphen erzeugen, d.h. jeder Knoten der Teilmenge ist mit jedem beliebigen anderen Knoten der Teilmenge verbunden. Eine k-Clique oder eine Clique der Größe k ist eine Clique aus k unterschiedlichen Knoten. Gegeben: Sei ein Graph G := (V, E) gegeben. Frage 6.3.10. Existiert in G eine Clique der Größe k? Satz 6.3.11. CLIQUE ist NP-vollständig 128 Beweis. Auch hier folgt der Beweis wieder in zwei Schritten: 1. Um zu zeigen, dass CLIQUE in NP liegt, benötigen wir wieder einen Zeugen und einen Algorithmus der den Zeugen in polynomieller Zeit verifiziert. Als Zeugen wählen wir die Teilmenge V 0 ⊂ V . Dabei ist per Definition |V 0 | = k. Offensichtlich lässt sich leicht in polynomieller Zeit feststellten ob V 0 eine Clique bildet. 2. Um zu zeigen, dass CLIQUE NP-schwer ist, reduzieren wir 3-SAT auf CLIQUE, d.h. 3-SAT ≤p CLIQUE Sei α eine Formel in 3-KNF mit k-Klauseln. Zur Erinnerung, eine 3-KNF besteht aus: Formel Klauseln Literalen α = C1 ∧ C2 ∧ · · · ∧ Ck Ci = xi1 ∨ xi2 ∨ xi3 xij Wir benötigen nun eine Konstruktion von 3-SAT auf CLIQUE. Dies erreichen wir in dem wir jede Klausel Ci als einen Cluster von Knoten ansehen. Dabei sind zwei beliebige Knoten genau dann miteinander verbunden - haben also eine Kante, wenn sie nicht aus dem selben Cluster kommen und sie keinen Widerspruch enthalten. Ein Widerspruch ist dann gegeben, wenn die Knoten dieselbe Variable bezeichnen und einer von beiden negiert ist. Also ist xi = xi fehlerhaft, weil er ein Widerspruch in sich darstellt. Formal ergibt sich die Konstruktion also wie folgt: V = {xij | i = 1 . . . k, j = 1 . . . 3} (xik , xjl ) ∈ E ⇔ i 6= j ∧ xik 6= xjl (6.4) (6.5) Bleibt zuletzt noch zu Zeigen das folgende Gleichung immer erfüllt ist: α erfüllbar ⇔ ∃ Clique der Größe k in G ⇒“: Sei ϕ eine erfüllende Belegung für α, dann ist in jeder Klausel min” destens ein Literal erfüllt. Die Knoten, die diesen Literalen entsprechen bilden dann eine k-Clique. ∀i i = 1 . . . k ∃j j = 1 . . . 3 : ϕ(xij ) = 1 V 0 = {xij | i = 1 . . . k ∧ ϕ(xij ) = 1} √ Daraus folgt V 0 bildet eine k-Clique (6.6) (6.7) ⇐“: Sei V 0 nun eine k-Clique in G. Wenn wir nun eine Belegung ϕ ” wählen, dann entsprechen die Knoten wieder den Literalen, die die einzelnen Klauseln erfüllen. In jeder Klausel gibt es demnach ein Literal x, welches mit ϕ(x) = 1 ausgewertet wird. Daraus folgt, dass √ die Belegung ϕ die Formel α erfüllt. 129 Beispiel: Sei die folgende Formel in 3-KNF gegeben: (x1 ∨ v2 ∨ x3 ) ∧ (x1 ∨ x2 ∨ x3 ) Daraus ergibt sich der Graph aus Abbildung 6.10. Abbildung 6.10: Graph 130 u u v z w y v z x w y x Abbildung 6.11: Reduktion: CLIQUE zu VERTEX-COVER. links: Clique V 0 = {u, v, x, y}. rechts:der Graph Ḡ mit VC V \ V 0 = {w, z} Definition 6.3.12 (Vertex Cover (VC)). Gegeben: Ein ungerichteter Graph G = (V, E), k ∈ N. Frage: Existiert eine Knotenüberdeckung der Grösse k? D.h. ∃V 0 ⊂ V , |V 0 | = k, ∀(u, v) ∈ E: u ∈ V 0 oder v ∈ V 0 (oder beides). Satz 6.3.13. VC ist NP-vollständig. Beweis. 1) Wir zeigen zunächst, dass VC in NP liegt. Gegeben sei hierzu ein Graph G = (V, E) und ein k ∈ N. Ein Zeuge ist V 0 ⊂ V . Der Verifikationsalgorithmus bestätigt, ob |V 0 | = k und überprüft, ob für jeder Kante(u, v) ∈ E gilt, dass u ∈ V 0 oder v ∈ V 0 . Dies kann offenbar in O(|E|) durchgeführt werden. 2) Nun zeigen wir, dass VC NP-schwer ist, indem wir CLIQUE darauf reduzieren, d.h. CLIQUE≤p VC (Zur Erinnerung: CLIQUE Gegeben: Ein ungerichteter Graph G = (V, E) Frage: Existiert ein vollständiger Teilgraph (Clique) der Grösse k?) In der Reduktion konstruieren wir in polynomieller Zeit aus einer Eingabe (G, k) für CLIQUE eine Eingabe (G0 , k 0 ) für VC, sodass gilt: G enthält eine k-Clique ⇔ G0 hat einen VC der Grösse k 0 . Wir wählen als G0 das Komplement Ḡ von G und k 0 = n − k mit n = |V |. (Erinnerung: Ḡ = (V, Ē) mit Ē = {(u, v)|(u, v) ∈ / E}). Dies ist in polynomieller Zeit möglich. Es bleibt nun also noch zu zeigen: G enthält eine k-Clique ⇔ Ḡ hat einen VC der Grösse n−k. Wir betrachten hierzu V 0 ⊂ V , eine k-Clique von G. Per Definition heißt das: ∀u, v ∈ V 0 : (u, v) ∈ E und |V 0 | = k. Da Ē das Komplement von E ist, bedeutet dies nichts anderes als: ∀u, v ∈ V 0 : (u, v) ∈ / Ē und |V 0 | = k . Das heißt, entweder u oder v liegen nicht 0 in V , ∀(u, v) ∈ Ē : u ∈ V \V 0 oder v ∈ V \V 0 (oder Beides) und |V \V 0 | = n−k. Es wird also jede Kante aus Ē von V \ V 0 bedeckt, was genau der Definition eines VC entspricht: V \ V 0 ist n − k-VC in Ḡ. Definition 6.3.14 (SUBSET-SUM). Gegeben: Eine Menge S = {a1 , . . . , ar } ⊂ N und eine Zahl b ∈ N. P Frage: Existiert eine Teilmenge S 0 ⊂ S mit a∈S a = b? Satz 6.3.15. SUBSET-SUM ist NP-vollständig. Beweis. 1) Dass SUBSET-SUM ∈ NP ist, wurde bereits gezeigt. 2) Nun zeigen wir, dass SUBSET-SUM NP-schwer ist, durch VERTEX-COVER≤p SUBSETSUM. Die Reduktion funktioniert wie folgt: Wir konstruieren in polynomieller Zeit aus einer Eingabe (G, k) für VC eine Eingabe a1 , . . . , an , b ⊂ N für 131 v1 v2 e3 e4 e5 e2 v0 e0 e1 v4 v3 Abbildung 6.12: Der Graph V ertex − Cover mit Vertex-Cover {v0 , v1 , v2 } x0 x1 x2 x3 x4 y0 y1 y2 y3 y4 y5 b 1 1 1 1 1 0 0 0 0 0 0 k e5 0 1 0 0 1 1 0 0 0 0 0 2 e4 1 1 0 0 0 0 1 0 0 0 0 2 e3 1 0 1 0 0 0 0 1 0 0 0 2 e2 0 0 1 1 0 0 0 0 1 0 0 2 e1 1 0 0 1 0 0 0 0 0 1 0 2 e0 1 0 0 0 1 0 0 0 0 0 1 2 v0 v1 v2 v3 v4 Tabelle 6.1: Die fertige Matrix zum Graphen V ertex − Cover. SUBSET-SUM, sodass gilt: G hat einen VC der Grösse k ⇔ ∃I ⊂ {1, . . . , n} P mit i∈I ai = b. Man betrachte hierzu die Inzidenzmatrix von G: Sei ( V = {v0 , . . . , vn−1 } und 0 falls vj ∈ ei E = {e0 , . . . , em−1 }, M = (bij ) i=m−1,...,0 mit bij = j=0,...,n−1 1 sonst M wird zuerst um eine Einheitsmatrix Em mit Dimension m nach unten erweitert. Dann wird eine zusätzliche linke Spalte hinzugefügt, deren oberen n Einträge Einsen enthalten und die unteren m Nullen. Zuletzt wird die unterste Zeile angehängt, die k als ersten Eintrag und an den übrigen Stellen Zweien enthält. Alle Zeilen der Matrix werden nun als 4äre Zahlen interpretiert, wobei k beliebige Werte aus N annehmen darf. Nun identifizieren wir die SUBSET-SUM Eingabe S = {a0 , . . . , ar } mit {x0 , . . . , xn−1 , y0 , . . . , ym−1 }, wobei Pm−1 xi = 4m + j=0 bij ∗ 4j y j = 4j Pm−1 und b = k ∗ 4m + j=0 2 ∗ 4j Dies ist in polynomieller Zeit möglich. Wir zeigen nun die Behauptung für die so konstruierte Eingabe: ⇒: Sei V 0 ⊂ V mit V 0 = {vi1 , . . . , vik } ein VC von G. Wählt man nun ein S 0 ⊂ S mit S 0 = {xi1 , . . . , xik } ∪ {yi genau ein Knoten ei ist in V 0 }, so ist nach 132 Konstruktion P a∈S 0 a = b. ⇐: Sei umgekehrt S 0 = {xi1 , . . . , xik } ∪ {yj1 , . . . , yjl } sodass ist V 0 = {vi1 , . . . , vik } ein VC von G. P a∈S 0 a = b. Dann Definition 6.3.16 (PARTITION). Gegeben: EinePMenge R =P{a1 , . . . , ar } ⊂ ˙ 2 mit a∈R a = a∈R a? N. Frage: Existiert eine Zerlegung R = R1 ∪R 1 2 Satz 6.3.17. PARTITION ist NP-vollständig. Beweis. 1) Dass PARTITION ∈ NP ist, wurde schon gezeigt. 2) Nun zeigen wir noch, dass PARTITION auch NP-schwer ist durch SUBSET-SUM≤p PARTITION. Um die Reduktion durchzuführen,konstruieren wir also polynomieller Zeit aus einer Eingabe (S, b) R für PARTITION, sodass Pfür SUBSET-SUM eine EingabeP P ˙ 2 mit a∈R a = a∈R a. gilt: ∃S 0 ⊂ S mit a∈S 0 a = b ⇔ ∃R = R1 ∪R 1 2 Wir wählen hierzu R = {a0 , . . . , ar , x, y} = S ∪ {x, y} wobei x = 2P ∗ A − b und y = A + b A = a∈S 0 a Dies ist in polynomieller Zeit möglich. Wir beobachten nun Folgendes: x und y können nicht in der gleichen Menge der P Zerlegung von R liegen, denn es ist x + y = 2 ∗ A − b + A − b = 3 ∗ A, aber a∈R a = A + x + y = 4 ∗ A, jede Zerlegungsmenge muss also die Grösse 2 ∗ A haben. Demnach müssen R1 und R2 folgende Form haben: R1 = S 0 ∪ {x} und R2 = S \ S 0 ∪ {y} für ein S 0 ⊂ S. Wir zeigen jetzt die Behauptung für die so konstruierte Eingabe: P P 0 0 ⇒: Für S ⊂ S mit 0 a = b wähle R1 = S ∪ {x}. Dann ist a∈S a∈R1 a = P a∈S 0 a + 2 ∗ A − b = 2 ∗ A. ⇐: Aus der Beobachtung folgt, dass R1 und R2 von genau dieser Form sein müssen, d.h. S 0 ist Lösung für SUBSET-SUM. Definition 6.3.18 (3-FÄRBBARKEIT). Gegeben: Ein ungerichteter Graph G = (V, E). Frage: Ist G 3-färbbar? Definition 6.3.19 (k-Färbung). Sei G = (V, E) ungerichteter Graph. Eine Funktion c : V → {1, . . . , k} heißt k-Färbung von G, wenn gilt: ∀(u, v) ∈ E : c(u) 6= c(v). Satz 6.3.20. 3-FÄRBBARKEIT ist NP-vollständig. Beweis. Wir zeigen zunächst, dass 3-FÄRBBARKEIT in NP liegt. Ein Zeuge ist eine Färbung c mit |c| ∈ O(n). Ob c eine gültige Färbung ist lässt sich einfach in O(|E|) Zeit überprüfen. Nun zeigen wir noch, dass 3-FÄRBBARKEIT NPschwer ist durch 3-SAT≤p 3-FÄRBBARKEIT. Als Reduktion konstruieren wir in polynomieller Zeit aus einer Eingabe Φ für 3-SAT eine Eingabe G = (V, E) für 3-FÄRBBARKEIT, sodass gilt: G ist 3-färbbar ⇔ ∃ erfüllende Belegung φ vpn Φ. Hierbei ist Φ = c1 ∧ . . . ∧ cm mit Klauseln ci = xi1 ∨ xi2 ∨ xi3 und Literalen xij ∈ {v1 , . . . , vm , v¯1 , . . . , v¯m }. Eine Belegung φ ist eine Abbildung φ : {v1 , . . . , vm } → 0, 1. 133 False True T F Base B v¯3 v1 v3 v¯1 v2 v¯2 Abbildung 6.13: 1. Schritt der Reduktion für 3-Färbung Wir konstruiere nun G in zwei Schritten: 1) Sei G = (V, E) ein Graph mit V = {v1 , . . . , vm , v¯1 , . . . , v¯m , T, F, B} Man assoziiert also die Knoten des Graphen mit den Variablen und ihren Negationen. T, F und B sind spezielle Knoten, die mit True, False und Base bezeichnet werden. Zuerst verbinden wir jedes Paar von Knoten vi und v¯i mit einer Kante und jeden von ihnen mit Base. Ebenso verbinden wir True, False und Base zu einem Dreieck. Dieser Graph hat einige hilfreiche Eigenschaften: (i) In jeder 3-Färbung von G müssen vi und v¯i verschiedene Farben haben, und beide müssen sich von B unterscheiden. (ii) In jeder 3-Färbung von G müssen die Knoten T,F und B die 3 verschiedenen Farben annehmen, insofern können wir die Farben als True, False und Base-Farbe auffassen. Insbesondere bekommen die Knoten vi und v¯i die Farben True oder False. Wir haben jetzt einen Graph G, in dem jede 3-Färbung eine gültige Belegung der Variablen in 3-SAT definiert. 2)Nun müssen wir G so erweitern, dass nur erfüllende Belegungen (d.h. solche die als Antwort true haben) von Φ zu einer 3-Färbung führen. Sei ci = xi1 ∨xi2 ∨xi3 eine beliebige Klausel von Φ. ci ist erfüllt, wenn mindestens eins der Literale mit true belegt ist. (Es muss also gelten: ¬(φ(xi1 ) = φ(xi2 ) = φ(xi3 ) = f alse) Wir benötigen also einen Teilgraphen, den wir so in G einbauen, dass jede 3-Färbung von G zu einer wahren Belegung von mindestens einem der Knoten vi1 , vi2 , vi3 führt. Der in Abbildung 6.14 gezeigte Teilgraph wird in G an den fünf markierten Knoten eingefügt, indem T, F, vi1 , vi2 und vi3 mit denselbigen im schon bestehenden Graphen G identifiziert werden. Dies wird füer alle Klauseln aus Φ durchgeführt. Diesen Graphen nennen wir G’. Dies ist in polynomieller Zeit möglich. Nach obiger Konstruktion gilt also: G0 ist 3-färbbar ⇔ ∃ erfüllende Belegung φ von Φ. Satz 6.3.21 (VIERFARBEN-THEOREM). Alle planaren Graphen lassen sich mit 4 Farben färben. Bemerkung. Planaren Graphen lassen sich in der Ebene zeichnen, ohne dass sich ihre Kanten kreuzen. Für k ≥ 4 ist das k-Färbproblem für planare Graphen trivial. 134 vi2 F T vi1 vi3 Abbildung 6.14: 2.Schritt: Dieser Teilgraph wird an den bereits bestehenden Graphen angehanngt. Der obere Knoten kann nicht gefärbt werden, wenn alle 3 Literale False gefärbt werden. 135 VERTEX-COVER Gegeben Graph G und Zahl k. Frage: Gibt es eine Auswahl von k Knoten, so dass jede Kante in G überdeckt ist? Die Menge der Knoten die Graph G überdecken sei Ve = ve1 ...e vk ⊂ V . Kanten deren Endknoten beide in VC sind, nennen wir zweifach überdeckt“, alle die nur einen Knoten in VC haben einfach über” ” deckt“. Problem DIR-HC Satz 6.3.22. DIR-HC ist NP-vollständig. Beweis. 1. Zu Zeigen: DIR-HC ∈ NP (siehe Hausaufgabe für ungerichteten HAM-CIRCLE). 2. VERTEX-COVER ≤p DIR-HC. Ziel: Konstruiere einen gerichteten Graphen Γ (anhand der Eingabeparameter (G, k)), so dass G ein k-VertexCover hat ⇔ Γ ein HC. Konstruktion des Graphen 1. Konstruktion des Basisblock Abbildung 6.15: Leerer Basisblock mit i-Ebenen Um den Graphen Γ zu konstruieren, fügt man in einen Basisblock (Abbildung 6.15) für jede Kante einen Baustein (Abbildung 6.16) mit vier Knoten ein. Dabei sind alle waagerechten Kanten von links nach rechts orientiert. Die senkrechten Kanten zeigen in beide Richtungen, zur Vereinfachung verwendet man im Bild 6.16 Doppelpfeile. 136 Abbildung 6.16: Baustein für Kante (vi , vj ) auf Ebene i und Ebene j Alle Bausteine werden untereinander verbunden. Dies geschieht auf jeder Ebene von links nach rechts. Der dadurch entstandene Graph Γ ist weiterhin kreisfrei. 2. Im nächsten Schritt fügen wir 2k Knoten si (Start), zi (Ziel) mit i = 1..k in den Graph ein. Die Startknoten verbinden wir mit jedem ersten Knoten einer Ebene, die letzten Knoten entsprechend mit jedem Ziel Knoten. Dabei einstehen widerum nur Wege von links nach rechts. In Abbildung 6.17 ist ein Beispiel für k = 2 gegeben, dort wurden allerdings für die Übersicht die Knoten der Bausteine auf den Ebenen gespart. Abbildung 6.17: Basisblock mit Start- und Zielknoten (Bausteine nicht vollständig) 3. In Schritt 3 verbindet man die si -Knoten und zi -Knoten. Dies erfolgt nach dem Schema zi → si+1 und zk → s1 . ⇒“: Sei (G, k) ∈ V C ⇒ Γ hat einen HC Ve sei VC Auf diesen Ebenen durch” laufe Block B k-mal nach folgendem Schema 1. Falls (vi , vj ) zweifach überdeckt ist, durchlaufe den Baustein direkt (Abbildung 6.19) ohne Umwege. 2. Falls (vi , vj ) einfach überdeckt a) (vi ∈ Ve ∧ vj ∈ / Ve ) Laufe Umweg über Ebene vj (Abbildung 6.21) b) (vi ∈ / Ve ∧ vj ∈ Ve ) Laufe Umweg über Ebene vi (Abbildung 6.20) Benutze alle Kanten aus Schritt 3, welche die Ziel- und Startknoten verbinden. Erweitere den Graph abschließend um alle Kanten die von den Startknoten zu den ersten Knoten einer Ebene verlaufen und von den 137 Abbildung 6.18: Block mit Kanten zwischen z und s Abbildung 6.19: Direkter Weg über die Knoten in Ebene i und j Abbildung 6.20: Umweg nach oben über Ebene i Abbildung 6.21: Umweg nach unten über Ebene j 138 letzten Knoten der jeweiligen Ebenen zu den Zielknoten (siehe auch Abbildung 6.22. Zeichne dabei die Kante si → Erster Knoten auf Ebene vi und Letzer Knoten auf Ebene vei → zi wobei vei ∈ Ve . Abbildung 6.22: Knoten des VC mit Bausteinen (überdeckten Kanten) verbinden Beispiel. Am folgenden Beispiel (Abbildung 6.23) sollen die vorangegangenen Schritte erläutert werden. Dabei gilt ve2 = v3 und ve1 = v4 . Weiterhin ist die Kante (v3 , v4 ) doppelt überdeckt und alle anderen Kanten einfach. Einen Hamiltonischen Weg durch den Graph Γ zeigt Abbildung 6.24. Abbildung 6.23: Beispielgraph mit k = 2 ⇐“: Wenn Γ ein HC hat ⇒ (G, k) ∈ V C ” Man kann in Bausteinen nicht über Ebenen springen (siehe Abbildung 6.25). Einzig vollständige“ Umwege sind erlaubt. ” Die Kanten zi → si+1 müssen in HC enthalten sein ⇒ wir müssen k-mal Block B anlaufen ⇒ Anlaufstellen definieren uns Ve ⇒ entpricht der Form der Beweisrichtung ⇒“, wobei Ve Vertex-Cover ⇒ (G, k) ∈ V C ” Satz 6.3.23. HC ist NP-vollständig. 1. HC ∈ NP (siehe Hausaufgabe). ~ → G. 2. DIR-HC ≤p HC, G 139 Abbildung 6.24: HC in Graph Γ für Beispielgraph in Abbildung 6.23 Abbildung 6.25: Möglichkeiten nicht erlaubter Umwege, analog Ebenenwechsel über zweiten Knoten eines Bausteins 140 ~ in Beweis. Konstruktion eines neuen Graphen. Dabei wird jeder Knoten v in G drei Knoten in G umgebaut. In den Startknoten“ v dieses Bausteins laufen alle ” eingehenden Kanten, dann folgt ein Zwischenknoten v̇. Aus dem Endknoten v̈ ~ heraus. gehen alle ausgehenden Kanten von v in G Sei G ein Graph mit einem hamiltonischen Kreis(HC) (v1 , v2 , ..., vn ) ⇒ G hat einen HC mit (v1 , v˙1 , v¨1 , v2 , v˙2 , v¨2 , ..., vn , v˙n , v¨n ) Sei G der Graph mit HC ⇒ Für alle i müssen die Kanten vi → v˙i → v¨i Teil des Kreises sein. Orientieren wir diesen Kreis so dass vi → v˙i → v¨i dann folgt nach ~ ist. dem Entfernen von vi und v˙i das es ein Kreis in G Satz 6.3.24. TSP ist NP-vollständig. TSP oder Traveling Sales Person behandelt die Frage ob auf einem gewichteten Graphen G = (V, E) mit einer Gewichtsfunktion w : E → R eine Tour zu finden ist, deren Gesamtgewicht ≤ k ist. 1. TSP ∈ NP (siehe Hausaufgabe). 2. HC ≤p TSP. Beweis. Gegeben sei ein Graph G aus welchem man Ġ(V̇ , Ė, w) und k konstruiert. Dazu definiert man V̇ = V , Ė = V̇ × V̇ (G ist der Kn , vollständiger Graph auf n-Knoten) und wählt k = n. Die Gewichte der Kanten werden wie folgt definiert. 1 wenn (i, j) ∈ E w(i, j) = . n + 1 sonst Wenn G ein HC hat dann hat die dadurch gegebene Tour das Gewicht n. Darausfolgt (Ġ, k) ∈ T SP . Hat G keinen HC dann benutzt jede Tour eine Kante mit dem Gewicht n + 1. Daher existiert keine Tour mit Gesamtgewicht ≤ k und es folgt (Ġ, k) ∈ / T SP . PSPACE Die Klasse PSPACE wird definiert als die Menge aller Sprachen L welche von einen Turingmaschine T M erkannt werden und dabei nur polynomiell viel Platz bezüglich der Eingabe brauchen. Dabei ist egal ob TM deterministisch oder nicht läuft. CO-NP Die Klasse CO-NP ist definiert als die Menge der Sprachen deren Komplement in NP liegt. Anmerkung NDET-PSPACE = DET-PSPACE nach dem Satz von Savitch (1970). PSPACE = CO-PSPACE nach dem Satz von Immermann, Szelepzeni (1987). 141 Kapitel 7 Approximationsalgorithmen Viele NP-schwere Probleme sind in der Praxis von großer Bedeutung. Um sie zu lösen gibt es die folgenden Möglichkeiten. Eine Möglichkeit ist es, die exakte Lösung mit einem Algorithmus exponentieller Laufzeit zu berechnen und diesen ggf. für Eingaben aus der Praxis durch Heuristiken zu verbessern. Dabei bezieht sich die Laufzeit auf den schlechtesten Fall. Das kann für den Normalfall bedeuten, dass das Problem eventuell sogar effizient lösbar ist. Die andere Möglichkeit besteht darin, Approximationsalgorithmen zu verwenden. Anmerkung: Optimierungsprobleme nennt man NP-Schwer, wenn auch das zugehörige Entscheidungsproblem NP-Schwer ist. Beispiel. Optimierungsproblem: Finde beim Travelling Salesperson Problem (T SP ) die kürzeste Rundreise. Zugehöriges Entscheidungsproblem auf einem Graph G: Gibt es eine Rundreise deren Länge kleiner als die Schranke k ist? Das Entscheidungsproblem ist dabei höchstens so schwer wie das Optimierungsproblem. Anmerkung: Es macht keinen Sinn, ein Optimierungsproblem NP-vollständig zu nennen. Beispiel. VERTEXCOVER (Überdeckende Knotenmenge) ist NP-vollständig. Zugehöriges Optimierungsproblem: Finde eine überdeckende Knotenmenge mit minimaler Elementanzahl. Algorithmus: Geg.: Knotenmenge U = ∅ , Graph G = (V, E) 1. Nimm beliebige Kante e = {u, v} und füge {u, v} zur Menge U hinzu. 2. Entferne {u, v} und alle zu u oder v inzidenten Kanten aus G. 142 3. Falls G noch Kanten hat GOTO 1. Der Algorithmus ist ein Greedy-Algorithmus der Laufzeit: O(m + n), n = |V |, m = |E| U =∅ e = {5, 6} U = {5, 6} e = {1, 2} U = {5, 6, 1, 2} Abbildung 7.1: Greedy Algorithmus für VERTEXCOVER U ist aber nicht minimal. Minimal ist die Knotenmenge {2, 5}. Es gilt: der obige Algorithmus liefert eine überdeckende Knotenmenge U , die höchstens doppelt so viele Elemente hat, wie die optimale Menge. Beweis. Die konstruierte Menge U ist eine überdeckende Knotenmenge, denn der Algorithmus entfernt nur Kanten, die zu den zu U hinzugefügten Knoten inzident sind. Am Schluss sind alle entfernt. Jede überdeckende Knotenmenge muss mindestens einen der beiden Knoten jeder Kante enthalten, die in Schritt 1 ausgewählt wird. Diese Kanten sind alle disjunkt. Sei p die Anzahl der ausgewählten Kanten, dann ist p ≤ |U 0 | für jede überdeckende Knotenmenge U 0 . Ausserdem ist |U | = 2p ≤ 2|U 0 | für jede überdeckende Knotenmenge U 0 . Daraus folgt 2p ≤ 2|Uoptimal | für die minimale überdeckende Knotenmenge Uoptimal . Voraussetzungen für die folgende Definition: Sei P ein Optimierungsproblem mit Kostenfunktion c: für eine Eingabe I ist eine Lösung foptimal (I) gesucht, so dass c(foptimal (I)) 143 optimal ist (also maximal oder minimal). Beispiel. Minimale überdeckende Knotenmenge: I = G = (V, E), foptimal (I) ist überdeckende Knotenmenge mit minimaler Anzahl von Knoten, also c(U ) = |U |. Definition 7.0.25. Ein Algorithmus A heißt α-approximativ für ein Optimierungsproblem gdw. er eine Lösung für fA liefert mit: • c(fA (I)) ≥ αc(foptimal (I)) im Falle eines Maximierungsproblems • c(fA (I)) ≤ αc(foptimal (I)) im Falle eines Minimierungsproblems Der oben vorgestellte Algorithmus ist also 2-approximativ. 7.1 Approximationsalgorithmen für das TSP Das TSP für n Knoten sei durch die n × n - Abstandsmatrix“D gegeben. ” Definition 7.1.1. D erfüllt die Dreiecksungleichung “heißt ” dij + dik ≥ dik für alle i, j, k ∈ {1, . . . , n} i→j→k≥i→k Abbildung 7.2: Dreiecksungleichung anschaulich in vielen Anwendungen kann die Dreiecksungleichung vorausgesetzt werden, insbesondere wenn die Abstandsfunktion auf einer Metrik beruht. Definition 7.1.2. Das Problem des Handelsreisenden mit Dreiecksungleichung wird als (4 − T SP ) definiert. Satz 7.1.3. 4 − T SP ist NP-vollständig Auch diese eingeschränkte Variante des TSP ist als Entscheidungsproblem NPvollständig. Als Optimierungsproblem ist es NP-schwer. 144 Beweis. Wir betrachten dazu die Reduktion von HAMILTON-KREIS auf TSP. Dabei sei G = (V, E) mit n = |V | eine Problemstellung von HAMILTONKREIS. Diese transformieren wir in die Matrix D = (dij )1≤i,j≤n mit dij = 1 2 falls (i, j) ∈ E, . sonst Dann gilt (dij )1≤i,j≤n erfüllt die Dreiecksungleichung, da dij + dik ≥ dik immer gilt. Daraus folgt | {z } |{z} ≥2 ≤2 es gibt einen HAMILTON-KREIS in G ⇐⇒ es gibt eine Rundreise der Länge ≤ n + 1 in Bezug auf D. Also ist HAMILTON-KREIS ≤p 4 − T SP . 7.1.1 Approximationsalgorithmus 1 Wir betrachten einen Multigraphen G = (V, E) (siehe Abbildung). Das heißt, dass zwischen zwei Knoten mehr als eine Kante möglich ist. Abbildung 7.3: Beispiel für einen Multigraphen G heißt Eulersch gdw. es einen geschlossenen Weg gibt, der jeden Knoten mindestens einmal und jede Kante genau einmal durchläuft. Satz 7.1.4 (Euler). G = (V, E) ist Eulersch gdw.: • G ist zusammenhängend, • alle Knoten in G haben geraden Grad. Gegeben: die Abstandsmatrix D = (dij )1≤i,j≤n . Ein Eulerscher aufspannender Graph (EAG) ist P ein Eulerscher Multigraph G = (V, E) mit V = 1, . . . , n, Kosten c(G) := i,j∈E dij . Lemma 7.1.5. Ist G = (V, E) ein EAG für D, dann lässt sich eine Rundreise τ von V mit c(τ ) ≤ c(G) in O(|E|) Zeit finden. Der problematische “Schritt ist die Konstruktion des EAG. Dafür konstruieren ” wir einen Minimal aufspannenden Baum und verdoppeln so lange die Kanten, bis ein EAG entstanden ist. 145