Algorithmen und Datenstrukturen Guido Moerkotte 1. Vorbemerkungen 2. Einleitung 3. Wachstumsordnungen 4. Sortieren 5. Datenstrukturen 6. Dynamisches Programmieren 7. Greedy-Algorithmen 8. Graphalgorithmen 9. Stringmatching 1 Vorbemerkung Literatur: Thomas H. Cormen, Charles E. Leiserson und R. L. Rivest: Introduction to Algorithms MIT Press 2 Vorbemerkungen Lernziele: 1. Für jedes Problem gibt es viele unterschiedlich gute Lösungen. 2. Spezifizieren/Quantifizieren von “gut”. 3. oft benötigte Datenstrukturen und Algorithmen 4. Probleme selber lösen, Lösungen beurteilen 3 1.0 Einleitung 4 Algorithmen Ein Algorithmus ist ein • Verfahren (ähnlich Kochrezept) zur Lösung eines Problems. Üblicherweise: • EINGABE −→ AUSGABE EINGABE durch Problemstellung beschrieben −→ durch Algorithmus beschrieben. AUSGABE durch Problemstellung beschrieben Ein Algorithmus ist kein Programm: • Abstraktion von der Darstellung 5 Datenstrukturen Datenstrukturen dienen der Darstellung • der Eingabedaten • der Hilfsdaten • der Ausgabedaten Am wichtigsten: Hilfsdaten, da auf ihnen gearbeitet wird. Die Darstellung beeinflußt erheblich die (Effizienz der) Arbeitsweise mit den Daten. Beispiel: • Telefonbuch nach Telefonnummern sortiert Suchen der Telefonnummer von Guido Moerkotte wird sehr ineffizient. 6 Problemstellung Sortieren input: Eine Folge ha1 , . . . , an i von Zahlen output: Eine Permutation a01 , . . . , a0n der Zahlen a1 , . . . , a n mit a0i ≤ a0i+1 für 0 < i < n. Wahl der Datenstruktur: • Array, Feld 7 Sortieren durch Einfügen Idee: Wie beim Kartenspielen: 1. Nimmt die erste Karte auf. 2. Dann füge jede weitere Karte von rechts in die bereits aufgenommenen Karten so ein, daß die Folge auf der Hand immer sortiert ist. Merke: Die Karten müssen im 2. Schritt verschoben werden, damit sie sich nicht verdecken. 8 Pseudo-Code Wir müssen Algorithmen hinschreiben können. Dazu benutzen wir Pseudo-Code. 1. Bcodiert einen Kommentar 2. ←codiert eine Zuweisung 3. [·] ist Array-Zugriff 4. Datenstrukturen haben Eigenschaften (Attribute). Diese werden über Funktionen abgerufen. (Bsp.: length(A) für ein Array A). Zusätzlich benutzen wir Kontrollkonstrukte wie if-then-else, while, for etc. 9 Sortieren durch Einfügen Algorithmus: INSERTION-SORT (A) 1 for j ← 2 to length[A] 2 do key ← A[j] 3 B Insert A[j] into sorted sequence A[1..j − 1]. 4 i←j−1 5 while i > 0 and A[i] > key 6 do A[i + 1] ← A[i] 7 i←i−1 8 A[i + 1] ← key 10 Sortieren durch Einfügen Beispielanwendung: 5 2 |2 5 4 6 1 3 |4 6 1 3 |6 1 3 |1 3 2 4 5 2 4 5 1 2 4 5 1 2 3 4 6 6 5 |3 6 11 Was jetzt? Wir haben: • Einen Algorithmus zum Sortieren einer Folge. Fragen: 1. Wie “gut” ist sortieren durch einfügen? 2. Gibt es “bessere” Algorithmen? 12 Analyse von Algorithmen Was heißt “gut”? Wie messen wir “gut”? • Unser Interesse: Laufzeit • Gemessen in: Schritten • Vorteil: Maschinenunabhängigkeit Wir gehen dabei von random access machines aus. 13 Random Access Machine (RAM) • Es wird ein Schritt nach dem anderen ausgeführt. • Schritt: Test, Zuweisung usw. • Jeder Schritt i bekommt ci • k-maliges Ausführen hat dann Kosten k ∗ ci • Gesamtkosten = Σ 14 Eine Beispielanalyse Laufzeit-/KomplexitätsAnalyse von INSERTION-SORT: 1. komplexe Formel herleiten 2. Darstellung vereinfachen 3. erlaubt einfacheren Vergleich verschiedener Algorithmen 15 Analyse von INSERTION-SORT Sei tj für j = 2, . . . , n die Anzahl der Ausführungen des Schleifentests in Anweisung 5 für ein gegebenes j. Wir definieren nun folgende Kostenkonstanten und stellen folgende Ausführungsanzahlen fest: INSERTION-SORT (A) 1 for j ← 2 to length[A] 2 do key ← A[j] 3 B Insert A[j] into the sorted B sequence A[1..j − 1]. 4 i←j−1 5 while i > 0 and A[i] > key 6 do A[i + 1] ← A[i] 7 i←i−1 8 A[i + 1] ← key Kosten c1 c2 Anzahl n n−1 0 c4 c5 c6 c7 c8 n−1 n−1 Pn j=2 tj Pn (tj − 1) Pj=2 n j=2 (tj − 1) n−1 16 Analyse von INSERTION-SORT Wir addieren die Produkte Kosten mal Anzahl und erhalten als Laufzeit T (n): T (n) = c1 n + c2 (n − 1) + c4 (n − 1) + n X tj + c5 ∗ j=2 (c6 + c7 ) ∗ c8 (n − 1) Was sagt uns diese Formel? Was machen wir mit tj ? 17 n X j=2 (tj − 1) + Analyse von Algorithmen Die Analyse von Algorithmen ist nicht einfach! Folgendes erschwert die Analyse: Wir beobachten beim Sortieren durch Einfügen: • Die Anzahl der Schritte ist abhängig von der Folge selbst. Bei gleicher Anzahl von Elementen benötigt der Algorithmus für unterschiedliche Folgen unterschiedlich viele Schritte. Diese Abhängigkeit sieht man deutlich in Schritt 5. Daher benötigen wir Möglichkeiten verschiedene Ergebnisse für Eingaben gleicher Größe zusammenzufassen. 18 Analyse von Algorithmen Zum Zusammenfassen hat man mehrere Möglichkeiten: best-case Es wird für jede Eingabegröße das Minimum der Anzahl der Schritte gezählt, die die Eingaben dieser Größe verursachen. worst-case Es wird für jede Eingabegröße das Maximum der Anzahl der Schritte gezählt, die die Eingaben dieser Größe verursachen. average-case Es wird die durschnittliche Anzahl der Schritte aller Eingaben einer Eingabegröße gezählt. Wir werden hauptsächlich worst-case-Analysen durchführen. 19 Analyse von INSERTION-SORT Den best-case erhält man, falls die Folge bereits sortiert ist. Dann ist tj = 1 für alle j = 2, . . . , n. Aus T (n) = c1 n + c2 (n − 1) + c4 (n − 1) + c5 ∗ n X (tj − 1) + c8 (n − 1) (c6 + c7 ) ∗ n X tj + j=2 j=2 wird dann T (n) = c1 n + c2 (n − 1) + c4 (n − 1) + c5 (n − 1) + c8 (n − 1) = (c1 + c2 + c4 + c5 + c8 )n − (c2 + c4 + c5 + c8 ) Wir erhalten also eine Funktion, die in n linear ist. 20 Den worst-case erhält man, falls die Folge der umgekehrten Sortierung entspricht. Dann ist tj = j für alle j = 2, . . . , n. Pn Pn n(n+1) n(n−1) − 1 und (j − 1) = erhalten wir Mit j=2 j = j=2 2 2 T (n) = c1 n + c2 (n − 1) + c4 (n − 1) + c5 ∗ c8 (n − 1) n X j=2 tj + (c6 + c7 ) ∗ n X j=2 (tj − 1) + wird dann T (n) = c1 n + c2 (n − 1) + c4 (n − 1) + n(n + 1) n(n − 1) − 1) + (c6 + c7 )( ) + c8 (n − 1) c5 ( 2 2 c5 − c 6 − c 7 c5 + c6 + c7 2 n + (c1 + c2 + c4 + + c8 )n − = 2 2 (c2 + c4 + c5 + c8 ) Also ist T (n) quadratisch in n. 21 Wachstumsordnung Da wir nicht an den Details einer Funktion an2 + bn + c für Konstanten a, b, c interessiert sind, wollen wir die Konstanten ignorieren. Da des weiteren n2 schneller wächst als n oder jede Konstante, vernachlässigen wir auch die kleineren Terme. Wir sind also nur an der Wachstumsordnung (-rate) n2 interessiert. Hierfür führen wir die Notation Θ(n2 ) (zunächst informell) ein. Wir sagen also beispielsweise für INSERTION-SORT, daß er eine worst-case Laufzeitkomplexität von Θ(n2 ) hat. In dieser Notation sehen wir schnell, daß ein Algorithmus mit Laufzeitkomplexität Θ(n3 ) schlechter ist als einer mit Θ(n2 ). 22 Wie entwirft man Algorithmen? Algorithmen zu entwerfen ist eine Kunst. Es gibt nur wenige Verfahren. Ein Verfahren (noch aus dem alten Rom): • divide et impera (teile und herrsche, divide and conquer) Vorgehen: Teile: Zerlege ursprüngliches Problem in Teilprobleme. Herrsche: Löse die Teilprobleme getrennt. Kombiniere: Füge die Teillösungen zusammen. 23 Teile und Herrsche: Beispiel Problemstellung: • Sortiere eine Folge Sortieren durch Mischen: Teile: Zerlege Folge in zwei Teilfolgen Herrsche: Sortiere Teilfolgen REKURSIV Kombiniere: Füge sortierte Teilfolgen durch Mischen zu einer sortierten Gesamtfolge zusammen Beachte: Folge der Länge 0 oder 1 ist bereits sortiert. 24 Sortieren durch Mischen MERGE-SORT( A,p,r) 1 if p < r 2 then q ← b(p + r)/2c 3 MERGE-SORT(A, p, q) 4 MERGE-SORT(A, q + 1, r) 5 MERGE(A, p, q, r) Dabei benutzen wir die Prozedur MERGE um die zwei Teilfolgen A[p . . . q] und A[q + 1 . . . r] von A zu mischen. Aufwand: Θ(n) mit n = r − p + 1. (s. Übung) Prinzipielles Vorgehen beim Mischen zweier Folgen: Nimm immer das kleines Element beider Folgen als nächstes Element der zu konstruierenden Gesamtfolge. 25 Sortieren durch Mischen sorted sequence 1 3 2 2 4 5 6 6 merge 2 4 6 5 1 2 4 5 1 6 merge merge 5 2 6 merge merge 2 3 4 3 2 merge 6 1 initial sequence 26 6 merge 3 2 6 Analyse von Teile-und-Herrsche-Algorithmen • Teile-und-Herrsche-Algorithmen sind oft rekursiv. • Daher kann man ihre Laufzeit auch am besten durch “rekursive” Gleichungen beschreiben. • Diese heißen rekurrente Gleichungen, oder kurz Rekurrenzen. • Mathematische Werkzeuge helfen beim Lösen. 27 Analyse von Teile-und-Herrsche-Algorithmen Analog der Vorgehensweise beim Entwurf werden die Rekurrenzen erstellt: • Gesamtaufwand sei T (n) für Problemgröße n • Für kleine Probleme (n < c) für eine Konstante c ist der Aufwand konstant. Dies notieren wir durch Θ(1). • Wir teilen das Problem in a Teile der Größe 1/b der Ursprungsgröße. • D(n) sei der Aufwand für das Teilen. • C(n) sei der Aufwand für das Kombinieren der Teillösungen. erhalten wir die Rekurrenz: Θ(1) T (n) = aT (n/b) + D(n) + C(n) 28 if n ≤ c sonst Analyse von MERGE-SORT Annahme: • n ist Zweierpotenz, d.h. n = 2k für geeignetes k. Das vereinfacht unsere Analyse, da dann die Teilfolgen genau die Größe n/2 haben. Bemerkung: • Dies beeinflußt nicht Wachstumsordnung. 29 Analyse von MERGE-SORT Die Teile: Teile: die Mitte der Folge kann in konstanter Zeit ermittelt werden. Daher: D(n) = Θ(1) Herrsche: Wir teilen in 2 Teile der Größe n/2. Dies ergibt den Anteil: 2T (n/2) Kombiniere: MERGE hat linearen Aufwand, d.h.: C(n) = Θ(n) 30 Vereinfachung: • Da Θ(n) + Θ(1) immer noch linearen Aufwand hat, können wir diese Summe durch Θ(n) ersetzen. Die Summe: Θ(1) T (n) = 2T (n/2) + Θ(n) 31 if n = 1 if n > 1 Die Lösung der Rekurrenz ist T (n) = Θ(n lg n) (lg ist Log. zur Basis 2) Folgerung: • Für große n ist – MERGE-SORT mit Θ(n lg n) besser als – INSERTION-SORT mit Θ(n2 ) worst-case-Verhalten. So sieht das aus: n n lg n n2 16 = 24 1024 = 210 1.048.576 = 220 64 10.240 20.971.520 256 1.048.576 1.099.511.627.776 (Letzter Eintrag: Telefonbucheinträge von Hamburg sortieren.) 32 Abgleich mit Lernzielen 1. Zwei Lösungen für Sortierproblem. 2. MERGE-SORT schneller als INSERTION-SORT 3. Laufzeit quantifizierbar in Θ(·). 4. Sortieren ist häufig vorkommendes Problem. Für dieses kennen wir jetzt zwei Algorithmen. 5. “Probleme selber lösen, Lösungen beurteilen:” Dafür fehlt noch die Übung. 33 Offene Fragen: 1. Was ist Θ(·) genau? 2. Wie löst man Rekurrenzen? Die Antworten kommen in Kürze. Was fehlt noch: • Mehr Beispiele und viel Übung. 34 Wachstumsordnung • Ein Algorithmus benötigt für jedes Problem eine bestimmte Anzahl von Schritten. • Die Anzahl der Schritte wird abgeschätzt. • Die abgeschätzte Anzahl der Schritte wird durch Funktionen beschrieben. • Wir interessieren uns nicht für Details. Bei einer Funktion an2 + bn + c interessieren uns nicht: – Konstanten (a, b, c) – Terme, die durch andere subsumiert werden (n durch n2 ) • Uns interessiert nur das asymptotische Verhalten. 35 Wozu das Ganze? • Zählen der Schritte, damit wissen wir wie schnell ein Algorithmus ist. • Zählen der Schritte, damit wir sagen können welcher Algorithmus besser ist. • Weg mit den Details, damit – wir maschinenunabhängig sind – damit die Sache einfacher wird: ∗ Zählen wird einfacher. ∗ Vergleichen wird einfacher. 36 Funktionen für die Analyse • Die Eingabegröße geben wir als natürliche Zahl n an. Beispiel: Länge der zu sortierenden Folge. • Die Anzahl der Schritte geben wir als natürliche Zahl T (n) an. • Also verwenden wir zur Beschreibung der Laufzeit eines Algorithmus Funktionen T : N → N. Anmerkungen: • Meistens führen wir eine worst-case-Analyse durch. (Da viel einfacher als mittlerer Fall.) • Wir benutzen aber auch Funktionen die reelle Zahlen auf reelle Zahlen abbilden. Man kann sich vorstellen, daß diese auf natürliche Zahlen eingeschränkt werden, und das Bild mittels d·e auf eine natürliche Zahl abgebildet wird. Dies machen wir jedoch nicht explizit. 37 Die Θ-Notation Def.: Sei g(n) eine Funktion. Wir definieren Θ(g(n)) als eine Menge von Funktionen: Θ(g(n)) := {f (n)|∃c1 , c2 , n0 > 0∀n ≥ n0 0 ≤ c1 g(n) ≤ f (n) ≤ c2 g(n)} Es gilt also f (n) ∈ Θ(g(n)), falls f (n) zwischen c1 g(n) und c2 g(n) eingeklemmt werden kann. Θ(g(n)) bildhaft: 38 Vereinbarung/Anmerkungen • Wir schreiben auch f (n) = Θ(g(n)) statt f (n) ∈ Θ(g(n)). • Falls f (n) = Θ(g(n)), so ist f (n) ab einem bestimmten n > n0 nie weiter als einen konstanten Faktor von g(n) entfernt. Falls dies gilt, so sagt man auch, daß g(n) eine asymptotisch enge Grenze für f (n) ist. • Für f (n) = Θ(g(n)) muß f (n) asymptotisch nicht negativ sein. Für jedes g(n), das nicht asymptotisch nicht negativ ist, ist Θ(g(n)) = ∅. Daher setzen wir voraus, daß alle g(n), die innerhalb von Θ(·) auftauchen asymptotisch nicht negativ sind. Das gleiche gilt auch für andere asymptotische Notationen, die wir noch einführen werden. 39 Macht Θ was es soll? Motiviert wurde Θ durch weglassen von Konstanten und Termen niedriger Ordnung. Daß dies durchaus richtig ist, demonstrieren wir an einem Beispiel. Wir zeigen: 1/2n2 − 3n = Θ(n2 ) Dazu benötigen wir Konstanten c1 , c2 , n0 mit c1 n2 ≤ 1/2n2 − 3n ≤ c2 n2 für alle n ≥ n0 . Dividieren durch n2 ergibt c1 ≤ 1/2 − 3/n ≤ c2 • Die rechte Gleichung gilt (bspw) für alle n ≥ 1, falls c2 ≥ 1/2. • Die linke Gleichung gilt (bspw) für alle n ≥ 7, falls c1 ≤ 1/14. Mit c1 = 1/14, c2 = 1/2 und n0 = 7 haben wir also bewiesen, daß 1/2n2 − 3n = Θ(n2 ). 40 Macht Θ was es soll? Wir zeigen jetzt: Annahme: ∃c2 , n0 , so daß für alle n ≥ n0 . Daraus folgt aber 6n3 6= Θ(n2 ) 6n3 ≤ c2 n2 n ≤ c2 /6 Das kann aber nicht sein, da n beliebig groß werden kann und c2 eine Konstante sein muß. Widerspruch. 41 Allgemeineres Beispiel Für f (n) = an2 + bn + c gilt f (n) = Θ(n2 ) Dazu kann man bspw. die Konstanten c1 = a/4 c2 = 7a/4 n0 benutzen. (s. Übung) p = 2 ∗ max(|b|/a, |c|/a) 42 Polynome Satz: Sei d X a i ni i=0 ein Polynom mit Konstanten ai , wobei ad > 0. Dann gilt d X ai ni = Θ(nd ) i=0 (Beweis s. Übung) Vereinbarung: Jede Konstante ist ein Polynoms n0 0-ten Grades. Wir schreiben jedoch statt Θ(n0 ) lieber Θ(1). Dabei identifizieren wir die Konstante 1 mit der Funktion, deren Funktionswerte konstant 1 sind. 43 Obere und untere Schranken Θ klemmte eine Funktion nach oben und unten bis auf eine Konstante ein. Manchmal ist das ein bischen viel. Daher führen wir noch Notationen ein für • obere Schranken • untere Schranken 44 Die O-Notation sprich: • groß Ohhh O • wird für asymptotische obere Schranken benutzt • das ist nützlich für einfache worst-case Abschätzungen Def.: O(g(n)) = {f (n) | ∃ c, n0 ∀ n ≥ n0 0 ≤ f (n) ≤ cg(n)} 45 Die O-Notation Anmerkungen: • wir schreiben auch wieder f (n) = O(g(n)) • es gilt Θ(g(n)) ⊆ O(g(n)) • Implikation: an2 + bn + c = O(n2 ) O(g(n)) bildhaft: 46 Beispiel Wir wollen zeigen: Für f (n) = an + b gilt f (n) = O(n2 ) Zu finden sind c, n0 mit 0 ≤ an + b ≤ cn2 ∀n ≥ n0 Für c = a + |b| gilt dies für alle n ≥ 1(=: n0 ), da an + b ≤ an + |b| ≤ an + |b|n ≤ (a + |b|)n ≤ (a + |b|)n2 ≤ cn2 47 Anwendung von O Der Algorithmus INSERTION-SORT hatte die Form 1 2 5 6 7 8 for j ←2 to length(A) do key ←A[j] while i > 0 and a[i] > key do A[i + 1] ←A[i] i ←i − 1 A[i + 1] ←key Wir beobachten: • Die beiden Schleifen werden jeweils maximal n mal durchlaufen (eigentlich viel weniger oft). • Die beiden Schleifen sind geschachtelt. 48 Dies impliziert sofort für die Laufzeit T (n): T (n) = O(n2 ) Manchmal ist man schon mit so einem Ergebnis zufrieden. 49 Anmerkung: Es ist technisch gesehen ein Fehler zu sagen, daß die Laufzeit von INSERTION-SORT O(n2 ) ist, da die Laufzeit noch von der konkreten Eingabefolge abhängt. Noch deutlicher: • Die Laufzeit von INSERTION-SORT ist keine Funktion von n!!! Wir werden jedoch über solche Details hinwegsehen. Wie: • Für die worst-case-Analyse kann das Maximum aller Laufzeiten für alle Eingabefolgen der Länge n den Wert T (n) annehmen. Damit sind wir aus dem Schneider: Wir haben eine Funktion in n. 50 Die Ω-Notation So wie O eine obere Schranke ist, ist Ω eine untere Schranke. Def. Ω(g(n)) = {f (n) | ∃ c, n0 ∀ n ≥ n0 0 ≤ cg(n) ≤ f (n)} Bildhaft: 51 Der erste wichtige Satz Theorem 2.1: Für je zwei Funktionen f (n) und g(n) gilt: f (n) = Θ(g(n)) ≺ f (n) = O(g(n)) ∧ f (n) = Ω(g(n)) (Beweis: s. Übung) Anwendung des Theorems: asymptotisch enge Schranken aus unteren und oberen Schranken gewinnen. Manchmal geht es jedoch nicht so gut: INSERTION-SORT: • best-case: T (n) = Ω(n) • worst-case: T (n) = O(n2 ) Verabredung: Wenn wir sagen die Laufzeit eines Algorithmus ist Ω(f (n)), meinen wir den best-case. Analog für O(f (n)). 52 Asymptotische Notationen in Gleichungen In der Einleitung haben wir bereits asymptotische Notationen in Gleichungen verwendet. Bsp.: • T (n) = Θ(n) + Θ(1) • T (n) = 2T (n/2) + Θ(n) • n = O(n2 ) Was ist die Semantik? 53 Asymptotische Notationen in Gleichungen Für einen Fall haben wir es schon definiert: • Falls auf der linken Seite der Gleichung eine Funktion steht, und auf rechten asymptotische Notation, so ist dies als Mengenzugehörigkeit zu interpretieren. Ansonsten interpretieren wir eine asymptotische Notation auf der rechten Seite einer Gleichung als eine anonyme Funktion f , die wir nicht bezeichnet haben, und die in der entsprechenden Klasse enthalten ist. Beispiel: 2n2 + 3n + 1 = 2n2 + Θ(n) interpretieren wir als 2n2 + 3n + 1 = 2n2 + f (n) für f (n) ∈ Θ(n). (Dabei ist f (n) implizit existenzquantifiziert.) 54 Asymptotische Notationen in Gleichungen Wichtig: • Jedes Vorkommen einer asymptotischen Notation bedeutet eine originäre Funktion, selbst wenn der Ausdruck gleich sein sollte. Extremes Beispiel: In n X O(i) i=1 bezeichnet O(i) eine Funktion. Damit ist dieser Ausdruck ungleich von folgendem Ausdruck: O(1) + O(2) + O(3) + . . . + O(n) 55 Asymptotische Notationen in Gleichungen Manchmal kommen asymptotische Notationen auf beiden Seiten einer Gleichung vor. Dann interpretieren wir dies wie folgt: • Für alle möglichen Funktionen, die wir für die asymptotischen Notationen auf der linken Seite einsetzen können, existieren Funktionen, die wir für die asymptotischen Notationen auf der rechten Seite einsetzen können, so daß die resultierende Gleichung gilt. 56 Beispiel: 2n2 + Θ(n) = Θ(n2 ) wird interpretiert als: Für alle f (n) ∈ Θ(n) existiert ein g(n) ∈ Θ(n2 ), so daß 2n2 + f (n) = g(n) gilt. 57 Gleichungsketten Beispiel: 2n2 + 3n + 1 = 2n2 + Θ(n) = Θ(n2 ) Dies resultiert in zwei Gleichungen. Die erste Gleichung besagt, daß es eine Funktion f (n) ∈ Θ(n) gibt mit 2n2 + 3n + 1 = 2n2 + f (n) Die zweite Gleichung besagt, daß es für jedes f (n) ∈ Θ(n) ein g(n) ∈ Θ(n2 ) gibt mit 2n2 + f (n) = g(n) Diese beiden Aussagen implizieren: Es gibt ein g(n) ∈ Θ(n2 ) mit 2n2 + 3n + 1 = g(n) Dies ist genau das, was wir intuitiv erwarten würden. 58 Die o-Notation Betrachte: O(n2 ) n = 2n2 O(n2 ) = Die zweite Gleichung ist eng, die erste ist es nicht. o erfaßt nun nur die nicht engen asymptotischen oberen Schranken: o(g(n)) = {f (n)|∀c > 0∃n0 > 0∀n ≥ n0 0 ≤ f (n) ≤ cg(n)} Man beachte, daß c gegen 0 gehen kann. f (n) = o(g(n)) impliziert also f (n) =0 n→∞ g(n) lim 59 Die ω-Notation Analog definieren wir die nicht engen unteren Schranken: ω(g(n)) = {f (n)|∀c > 0∃n0 > 0∀n > n0 0 ≤ cg(n) < f (n)} Beispiel: n2 /2 = ω(n) n2 /2 6= ω(n2 ) Die Definition von ω impliziert für f (n) = ω(g(n)): f (n) =∞ lim n→∞ g(n) 60 Eigenschaften der asymptotischen Notationen Viele Eigenschaften, die beispielsweise für die reellen Zahlen gelten, gelten auch für die asymptotischen Notationen: Reflexivität: f (n) = Θ(f (n)) f (n) = O(f (n)) f (n) = Ω(f (n)) Symmetrie: f (n) = Θ(g(n)) ≺ g(n) = Θ(f (n)) 61 Eigenschaften der asymptotischen Notationen Transitivität: f (n) = Θ(g(n)), g(n) = Θ(h(n)) f (n) = Θ(h(n)) f (n) = O(g(n)), g(n) = O(h(n)) f (n) = Ω(g(n)), g(n) = Ω(h(n)) f (n) = o(g(n)), g(n) = o(h(n)) f (n) = ω(g(n)), g(n) = ω(h(n)) f (n) = O(h(n)) f (n) = Ω(h(n)) f (n) = o(h(n)) f (n) = ω(h(n)) Antisymmetrie: f (n) = O(g(n)) f (n) = o(g(n)) ≺ g(n) = Ω(f (n)) ≺ g(n) = ω(f (n)) 62 Analogien zu den Reellen Zahlen Wegen dieser Eigenschaften, kann man folgende (hinkende, siehe unten) Analogie aufstellen: f (n) = O(g(n)) f (n) = Ω(g(n)) f (n) = Θ(g(n)) f (n) = o(g(n)) f (n) = ω(g(n)) ≈ a≤b ≈ a≥b ≈ a=b ≈ a<b ≈ a>b Eine Eigenschaft gilt jedoch nicht: Für je zwei reelle Zahlen a und b hat man a = b, a < b, oder a > b. Es kann aber sein, daß für zwei verschiedene Funktionen f (n) und g(n) weder f (n) = O(g(n)) noch f (n) = Ω(g(n)) gilt. Beispiel: n und n1+sin(n) . 63 Relationen, Graphen und Bäume Was brauchen wir noch bevor es richtig losgeht? Das nächste Kapitel ist über Sortieren. • Dazu brauchen wir Bäume. • Für Bäume brauchen wir Graphen. • Für Graphen brauchen wir Relationen. • Für Relationen brauchen wir Mengen. Letztere setze ich als bekannt voraus. 64 Relationen (Def.) Def.: Eine binäre Relation R zweier Mengen A und B ist eine Teilmenge des kartesischen Produkts A × B, also R ⊆ A × B. • Für (a, b) ∈ R schreiben wir auch aRb. • eine binäre Relation R über A ist eine Teilmenge von A × A. • Beispiele über den natürlichen Zahlen: =, ≤ <. Letzteres: < = {(a, b) | a, b ∈ N, a < b} Def.: Eine n-äre Relation über den Mengen A1 , . . . , An ist eine Teilmenge von A1 × . . . × A n . 65 Relationen (Eigenschaften) Sei R eine binäre Relation R ⊆ A × A. R heißt reflexiv, falls ∀a ∈ A aRa R heißt symmetrisch, falls aRb bRa R heißt transitiv, falls aRb, bRc aRc Ist R alles drei, so heißt R Äquivalenzrelation. Beispiele: • = ist eine Äquivalenzrelationen. • < und ≤ sind keine Äquivalenzrelationen. Jede Äquivalenzrelation partitioniert A in n ≥ 0 disjunkte Teilmengen (Äquivalenzklassen). 66 Gerichtete Graphen Def. Ein gerichteter Graph ist ein Paar (V, E), wobei V eine endliche Menge ist und E eine binäre Relation über V , also E ⊆ V × V . Die Elemente aus V heißen Knoten, die aus E Kanten. Beispiel: • V = {1, 2, 3, 4, 5, 6} • E = {(4, 1), (1, 2), (2, 2), (2, 4), (4, 5), (5, 4), (3, 6)} 67 1 2 3 4 5 6 (a) 1 2 3 4 5 6 1 2 3 6 (b) (c) 68 Ungerichtete Graphen Def. Ein ungerichteter Graph ist ein Paar (V, E), wobei V eine endliche Menge ist und E eine ungerichtete Menge von Paaren (u, v) mit u, v ∈ V und u 6= v ist. Die Elemente aus V heißen Knoten, die aus E Kanten. Beispiel: • V = {1, 2, 3, 4, 5, 6} • E = {(1, 2), (1, 5), (2, 5), (3, 6)} 69 Allgemeine Sprechweisen bei Graphen Viele Sprechweisen sind bei gerichteten und ungerichteten Graphen die gleichen, haben aber u.U. leicht unterschiedliche Semantik. Sei (u, v) ∈ E. Dann sagen wir: • Es führt eine Kante von u nach v. • Die Kante verläßt u. • Die Kante führt zu v. • Die Kante ist eine von u ausfallende Kante. • Die Kante ist eine in v einfallende Kante. • u ist benachbart zu v. (bei gerichteten Graphen ist dies nicht notwendig symmetrisch) • u und v sind durch eine Kante verbunden (bei ungerichteten Graphen). • Ist u = v, so heißt die Kante Schleife (bei gerichteten Graphen). 70 Grad Für gerichtete Graphen definieren wir: • Der In-Grad eines Knotens ist die Anzahl der einfallenden Kanten. • Der Aus-Grad eines Knotens ist die Anzahl der ausfallenden Kanten. • Der Grad eines Knotens ist die Summe aus In-Grad und Aus-Grad. Für ungerichtete Graphen definieren wir: • Der Grad eines Knotens ist die Menge aller Kanten, die mit dem Knoten verbunden sind. 71 Pfad Def.: Ein Pfad der Länge k von einen Knoten u zu einem Knoten u0 in einem Graphen G = (V, E) ist eine Folge mit folgenden Eigenschaften: hv0 , v1 , . . . , vk i 1. v0 = u 2. vk = u0 3. (vi−1 , vi ) ∈ E, für 1 ≤ i ≤ k Anmerkung: • Die Länge eines Pfades ist also die Anzahl der Kanten im Pfad. 72 Sprechweisen/Definitionen • Der Pfad enthält die Knoten vi . • u0 ist von u aus erreichbar. • u und u0 sind durch einen Pfad verbunden. • Ein Pfad heißt einfach, falls er keinen Knoten mehr als einmal enthält. • Ein Teilpfad ist eine Teilfolge von hv0 , v1 , . . . , vk i, also eine Folge hvi , vi+1 , . . . , vj i mit 0 ≤ i ≤ j ≤ k. • Ein Zyklus ist ein Pfad mit v0 = vk . • Ein einfacher Zyklus ist ein Zyklus, bei dem sonst keine zwei Knoten gleich sind. • Ein Graph mit Zyklus heißt zyklisch, einer ohne Zyklus heißt azyklisch. 73 Zusammenhang • Ein ungerichteter Graph heißt zusammenhängend, falls je zwei Knoten durch einen Pfad verbunden sind. • Die Zusammenhangskomponenten eines Graphen sind die Äquivalenzklassen unter der erreichbar-Relation. • Ein ungerichteter Graph ist genau dann zusammenhängend, wenn er nur eine Zusammenhangskomponente hat. • Ein gerichteter Graph heißt streng zusammenhängend, falls je zwei Knoten voneinander erreichbar sind. • Die strengen Zusammenhangskomponenten sind die Äquivalenzklassen der gegenseitig-erreichbar-Relation. 74 Graphisomorphie Def.: Zwei Graphen G = (V, E) und G0 = (V 0 , E 0 ) heißen isomorph, falls es eine Bijektion f : V → V 0 gibt, mit (u, v) ∈ E ≺ (f (u), f (v)) ∈ E 0 75 1 G 1 2 6 2 3 3 5 5 G’ u 4 v 4 w x w x y (a) u v (b) 76 y z Teilgraphen Def.: Ein Graph G0 = (V 0 , E 0 ) heißt Teilgraph (Unter-, Sub-) eines Graphen G = (V, E), falls 1. V 0 ⊆ V 2. E 0 ⊆ E Gegeben eine Menge V 0 ⊆ V , dann definiert man den von V 0 induzierten Teilgraphen von G = (V, E) als den Graphen G0 = (V 0 , E 0 ) mit E 0 = {(u, v) ∈ E|u, v ∈ V 0 } 77 1 2 3 4 5 6 (a) 1 2 3 4 5 6 1 2 3 6 (b) (c) 78 Richten und Unrichten Def.: Sei G = (V, E) ein ungerichteter Graph. Dann heißt G0 = (V, E 0 ) mit (u, v) ∈ E 0 ≺ (u, v) ∈ E die gerichtete Version von G. Es wird also jede ungerichtete Kante durch zwei gerichtete Kanten ersetzt. 79 Richten und Unrichten Def.: Sei G = (V, E) ein gerichteter Graph. Dann heißt G0 = (V, E 0 ) mit (u, v) ∈ E 0 ≺ (u, v) ∈ E ∧ u 6= v die ungerichtete Version von G. Man beachte, daß keine Kante “doppelt” vorkommt, da ja (u, v) und (v, u) in einem ungerichteten Graphen die gleiche Kante sind. In einem gerichteten Graphen G = (V, E) ist der Knoten u ein Nachbar von Knoten v, falls in der ungerichteten Version von G die Knoten u und v benachbart sind. 80 Spezielle Graphen (Sonderfälle) • Ein vollständiger Graph (Clique) ist ein ungerichteter Graph, in dem je zwei Knoten benachbart sind. • Ein bipartiter Graph ist ein ungerichteter Graph G = (V, E), in dem V in zwei disjunkte Teilmengen (Partitionen) V 0 und V 00 zerlegt werden kann mit (u, v) ∈ E (u ∈ V 0 ∧ v ∈ V 00 ) ∨ (u ∈ V 00 ∧ v ∈ V 0 ) 81 Spezielle Graphen (Sonderfälle) • azyklischer, ungerichteter Graph heißt auch Wald. • verbundener, azyklischer, ungerichteter Graph heißt (freier) Baum. • Multigraph ist analog zum ungerichteten Graphen definiert, nur daß er Mehrfachkanten und Schleifen enthalten kann. • Hypergraph ist analog zum ungerichteten Graphen definiert, nur daß eine Kante mehr als zwei Knoten beinhalten kann (also eine beliebige Menge). Eine solche Kante heißt dann Hyperkante. Bäume sind so wichtig, daß wir sie noch ausführlicher behandeln. 82 Freie Bäume Def.: Ein freier Baum ist ein verbundener, azyklischer, ungerichteter Graph. 83 (a) (c) (b) 84 Bäume Satz: Sei G = (V, E) ein ungerichteter Graph. Dann sind folgende Aussagen äquivalent: 1. G ist ein freier Baum. 2. Je zwei Knoten in G sind durch einen eindeutigen einfachen Pfad verbunden. 3. G ist zusammenhängend, aber falls nur eine Kante entfernt wird, so ist der resultierende Graph nicht mehr zusammenhängend. 4. G ist zusammenhängend und |E| = |V | − 1. 5. G ist azyklisch und |E| = |V | − 1. 6. G ist azyklisch, aber falls nur eine Kante zu E hinzugefügt wird, so ist der resultierende Graph zyklisch. 85 Beweis (1) =⇒ (2): Da ein Baum zusammenhängend ist, sind je zwei Kanten in G durch mindestens einen einfachen Pfad verbunden. Seien u und v durch zwei einfache Pfade p1 und p2 verbunden: Sei w der Knoten, nach dem sich p1 und p2 unterscheiden. Sei z der erste Knoten, an dem sich p1 und p2 wieder treffen. Seien p0 und p00 die Teilpfade von p1 bzw. p2 , die von w nach z führen. Dann haben p0 und p00 (nur) die Endpunkte gemeinsam. Daher ist p0 p00 ein Zyklus. 86 p’ x v w z y u p’’ 87 (2) =⇒ (3): Da je zwei Knoten durch einen einfachen Pfad verbunden sind, ist G verbunden. Da dieser Pfad eindeutig ist, führt das entfernen einer Kante auf diesem Pfad dazu, daß G unverbunden ist. (3) =⇒ (4): G ist verbunden. Also (Übung) |E| ≥ |V | − 1. Wir zeigen durch Induktion |E| ≤ |V | − 1. I.A.: Ein verbundener Graph mit n = 1 oder n = 2 Knoten hat n − 1 Kanten. I.S.: Annahme: G hat n ≥ 3 Knoten. Jeder Graph mit < n Knoten und (3) erfüllt |E| ≤ |V | − 1. Entfernen einer Kante läßt G in k ≥ 2 Komponenten zerfallen. Jede Komponente erfüllt (3) (sonst würde G nicht (3) erfüllen). I.H.: Anzahl der Knoten in allen Komponenten ist höchstens |V | − k ≤ |V | − 2. Plus die entfernte Kante ergibt maximal |V | − 1 Kanten. 88 (4) =⇒ (5): Sei G verbunden mit |E| = |V | − 1 Kanten. Zu zeigen: G azyklisch. Annahme: G enthält Zyklus v1 , . . . , vn . Sei Gk = (Vk , Ek ) der Teilgraph von G, der nur den Zyklus enthält. Dann gilt |Vk | = |Ek | = k. 89 Falls |V | > k, gibt es einen Knoten vk+1 ∈ V \ Vk , mit (vk+1 , vi ) ∈ E für ein vi ∈ Vk , da G verbunden ist. Definiere Gk+1 = (Vk+1 , Ek+1 ) Vk+1 = Vk ∪ {vk+1 } Ek+1 = Ek+1 ∪ {(k+1 , vi )} Es gilt: |Vk+1 | = |Ek+1 | = k1 . Falls k + 1 < |V |, machen wir weiter mit Gk+2 usw. Ergebnis: Teilgraph Gn = (Vn , En ) von G mit |Vn | = |En | = n. Widerspruch. 90 (5) =⇒ (6): Sei G azyklisch mit |E| = |V | − 1. Sei k die Anzahl der Verbundkomponenten von G. Jede Verbundkomponente ist ein freier Baum (nach Definition). Da (1) =⇒ (5) ist die Summe aller Kanten in allen Komponenten |V | − k. Also muß k = 1 gelten und G ist ein Baum. Da (1) =⇒ (2) sind je zwei Knoten durch einen einfachen Pfad verbunden. Hinzufügen einer Kante erzeugt also einen Zyklus. 91 (6) =⇒ (1): Sei G azyklisch, aber jedes Hinzufügen einer Kante erzeuge einen Zyklus. zu zeigen: G verbunden. Seien u und v zwei beliebige Knoten in G. Falls u und v nicht bereits benachbart sind, führt das Hinzufügen von (u, v) zu einem Zyklus. Also gab es bereits einen Pfad von u nach v. Da u und v beliebig gewählt waren ist G verbunden. 92 Wurzelbaum (rooted tree) Def.: Ein Wurzelbaum ist ein freier Baum in dem ein Knoten als Wurzel ausgezeichnet wird. Die Wurzel eines Wurzelbaums (oder kurz Baums) zeichnen wir nach oben. 93 7 3 depth 0 depth 1 4 10 height = 4 8 6 12 5 11 2 depth 2 depth 3 1 depth 4 9 (a) depth 0 7 depth 1 3 4 10 depth 2 12 8 11 2 depth 3 1 5 6 depth 4 9 (b) 94 Baum Betrachte einen Knoten x in einem Baum T mit Wurzel r. • Jeder Knoten y auf einem Pfad von r zu x heißt Vorgänger (ancestor) von x. • Falls y ein Vorgänger von x ist, so heißt x Nachfolger von y. • Jeder Knoten ist Vorgänger und Nachfolger von sich selbst. • Ist x 6= y, so sprechen wir von echten Vorgängern und Nachfolgern. • Der Teilbaum des Knotens x ist der von den Nachfolgern von x induzierte Teilbaum von T . 95 Baum • Falls die letzte Kante vom Pfad von r nach x (y, x) ist, so heißt y Vater-/Elternknoten (parent) von x und x heißt Kind-/Sohnknoten (child) von y. • Falls x und z den gleichen Vaterknoten haben, so heißen sie Geschwisterknoten (sibling). • Die Wurzel ist der einzige Knoten in einem Baum ohne Vaterknoten. • Knoten ohne Kindknoten heißen äußere Knoten oder Blattknoten. • Alle anderen Knoten heißen innere Knoten. 96 Baum • Die Anzahl der Sohnknoten eines Knotens x heißt Grad von x. • Die Länge des Pfades von r nach x heißt Tiefe (Ebene, Level) von x. • Die maximale Tiefe der Knoten in T heißt Höhe von T . 97 Baum Def.: Ein geordneter Baum ist ein Wurzelbaum, bei dem die Sohnknoten geordnet sind. Das heißt also, daß wir vom 1., 2., bis zum k-ten Sohn sprechen können. 98 Binärbaum Def.: Ein Binärbaum T ist über einer endlichen Menge N von Knoten wie folgt definiert: • N ist leer, oder • N enthält drei disjunkte Teilmengen: einen Wurzelknoten r, und zwei Binärbäume L und R, die auch linker und rechter Teilbaum von T genannt werden. Falls N leer ist, so heißt T auch leerer Baum. Wir bezeichnen ihn mit NIL. • Für einen Knoten x sprechen wir von rechtem und linkem Sohn für die Wurzeln nichtleerer linker und rechter Teilbäume. • In einem vollständiger Binärbaum hat jeder Knoten entweder 2 oder 0 Söhne. 99 3 2 4 7 1 3 5 2 7 6 (a) 1 4 5 3 6 2 (c) 7 5 1 4 6 (b) 100 Binärbaum Beachte: • Ein Binärbaum ist ein bischen mehr als ein geordneter Baum mit Gradbeschränkung 2 für jeden Knoten: – Selbst wenn nur ein Sohn vorhanden ist, so ist dieser entweder linker oder rechter Sohn. • Also können zwei verschiedene Binärbäume den gleichen geordneten Baum darstellen. 101 Positionsbäume Die Art der Positionierung, die bei Binärbäumen benutzt wurde, kann auf beliebige Anzahl von Sohnknoten verallgemeinert werden. Dies führt zu Positionsbäumen oder k-ären Bäumen. Nicht-Blattknoten in vollständige k-äre Bäumen haben genau k Nachfolger. 102 Positionsbäume Die Anzahl der inneren Knoten in einem vollständigen k-ären Baum der Höhe h bestimmt sich wie folgt: 1 + k + k2 + k3 + . . . + kh = h X i=0 h = 103 ki k −1 k−1 Vollständiger Binärbaum (h = 2) depth 0 depth 1 height = 3 depth 2 depth 3 104 Sortieren Eingabe: Eine Folge von Zahlen. ha1 , . . . , an i Ausgabe: Eine Permutation der Zahlen a1 , . . . , an mit für 0 < i < n. ha01 , . . . , a0n i a0i ≤ a0i+1 105 Die Struktur der Daten • Normalerweise sortiert man nicht nur reine Zahlen, sondern Datensätze (records, Tupel) nach einem Feld (Attribut). • Dieses Feld wird auch Schlüssel (key) genannt. • Da Datensätze groß sein können, werden Zeiger auf die Datensätze benutzt. • Das spart Kopierkosten! • Trotzdem formulieren wir die Algorithmen mit Zahlen. • Umformulierung/Umkodierung für konkretes Problem notwendig. • Ersichtlich: Algorithmus ist Abstraktion. 106 Übersicht Wir hatten bis jetzt: Sortier-Algorithmus worst-case in-place INSERTION-SORT Θ(n2 ) yes MERGE-SORT Θ(n lg n) NO Sortier-Algorithmus worst-case in-place HEAP-SORT Θ(n lg n) yes QUICK-SORT Θ(n2 ) yes Jetzt kommen: QUICK-SORT: avg-case von Θ(n lg n) und in praxi sehr schnell. 107 Übersicht Reine Sortieralgorithmen: • Alle oben genannten Sortierverfahren basieren auf Vergleichen. • Wir werden die Grenzen dieser Verfahren kennenlernen. Untere Schranke: Ω(n ln n). • Einige Sortierverfahren schlagen diese untere Schranke: – COUNTING-SORT Für n Zahlen 1, . . . , k: O(n + k). – RADIX-SORT Erweitern des Zahlenbereiches. – BUCKET-SORT Reelle Zahlen, wobei Wahrscheinlichkeitsverteilung bekannt sein muß. 108 Übersicht Dazu lernen wir noch Algorithmen kennen, die die i-t kleinste Zahl aus einer Sequenz von Zahlen heraussuchen. • Mit Sortieren: O(n lg n). • Besseres Verfahren: O(n). • Noch eins: O(n2 ) worst case, aber O(n) avg-case. • Und noch ein komplizierterer Algorithmus: O(n). 109 Heapsort Die wichtigsten Eigenschaften von Heapsort in Kürze: • worst-case: O(n lg n) • in-place • benutzte Datenstruktur: Heap Ein Heap wird in vielen Algorithmen benötigt, z.B.: • Prioritätsschlangen (priority queues) 110 (Binary) Heap Ein binärer Heap ist ein Array, daß als “vollständiger” binärer Baum angesehen werden 1 kann: 16 2 14 3 10 4 8 6 9 5 7 8 2 3 7 10 1 9 4 (a) 1 2 16 14 3 4 5 6 7 8 9 10 10 8 7 9 3 2 4 1 (b) 111 (Binary) Heap • Knoten ↔ Arrayelement • Baum ist vollständig gefüllt, bis zu einem Knoten auf der untersten Ebene. Die restlichen Knoten rechts davon fehlen. • Ein Array A, das einen Heap repräsentiert hat zwei Attribute: 1. length[A]: die Anzahl der Elemente von A. 2. heap-size[A]: die Anzahl der Elemente im Heap, der in A gespeichert wird. A ist Vorrat an Speicherplätzen. • Es gilt: heap-size[A] ≤ length[A] • Kein Element von A nach A[heap-size[A]] ist benutzt, obwohl natürlich potentiell alle Elemente A[1 . . . length[A]] benutzt werden können. 112 Heap-Indizes Wie numeriert man die Knoten im Baum, so daß diese Nummern dann gültige Arrayindizes ergeben? • Die Wurzel bekommt die 1, d.h.: A[1] speichert die Wurzel. • Index des linken Sohnes von Knoten i: LEFT(i) { return 2i; } • Index des rechten Sohnes von Knoten i: RIGHT(i) { return 2i + 1; } • Index des Vaters von i: PARENT(i) { return bi/2c; } Implementierung: Als Macros using bitshifts (besser: inline-Funktionen). 113 Heap-Eigenschaft Es gilt die Heap-Eigenschaft: Für jeden Knoten außer der Wurzel gilt A[PARENT(i)] ≥ A[i] In Worten: Der Wert eines Knotens (der in ihm gespeicherten Zahl) ist höchstens so hoch wie der Wert des Vaterknotens. 114 Heap-Höhe Def.: Die Höhe eines Knotens in einem Baum definieren wir als die Länge des längsten Pfades vom Knoten bis zu einem Blatt. Die Höhe eines Baumes ist die Höhe der Wurzel. Bem: Die Höhe eines Heaps für n Elemente ist Θ(lg n). (s. Übung) Die meisten Prozeduren auf einem Heap benötigen eine Laufzeit, die maximal proportional zur Höhe ist. Also: O(lg n). 115 Heap-Operationen Für das Sortieren interessant: • HEAPIFY mit O(lg n) erhält/restauriert die Heap-Eigenschaft • BUILD-HEAP mit O(n) baut einen Heap aus einem beliebigen Array • HEAPSORT mit O(n lg n) sortiert ein Array in-place Für Prioritätsschlangen: • EXTRACT-MAX mit O(lg n) gibt Maximum der im Heap gespeicherten Zahlen • INSERT mit O(lg n) fügt Element in Heap ein 116 HEAPIFY Ziel: • Erhalten der Heap-Eigenschaft. Diese war: • A[PARENT(i)] ≥ A[i] für alle Knoten ausser der Wurzel Idee: • Eingabe: ein i • Annahme: Left(i) und Right(i) sind Heaps • Aber: A[i] kann kleiner als Sohn sein. • Maßnahme: lasse A[i] nach unten sinken. • Dann: Teilbaum von i ist Heap 117 HEAPIFY HEAPIFY (A, i) 1 l ← LEFT (i) 2 r ← RIGHT (i) 3 if l ≤ heap-size[A] and A[l] > A[i] 4 then largest ← l 5 else largest ← i 6 if r ≤ heap-size[A] and A[r] > A[largest] 7 then largest ← r 8 if largest 6= i 9 then exchange A[i] ←→ A[largest] 10 HEAPIFY (A, largest) 118 1 16 i 2 4 4 14 8 2 6 5 9 7 9 8 10 1 (a) 119 3 10 7 3 1 16 2 14 i 8 2 3 10 4 4 6 9 5 7 9 8 10 1 (b) 120 7 3 1 16 2 3 10 14 4 8 8 2 6 9 5 7 9 4 10 1 (c) 121 7 3 HEAPIFY Beispiel (a) i = 2 verletzt Heap-Eigenschaft, da A[2] nicht größer als beide Söhne. (b) Heap-Eigenschaft für i = 2 wird durch vertauschen des Inhalts von Knoten 2 und 4 wiederhergestellt. Aber: Heap-Eigenschaft von i = 4 ist dadurch verletzt. Daher rekursiver Aufruf. (c) Vertauschen der Inhalte der Knoten 4 und 9 stellt die Heap-Eigenschaft für i = 4 wieder her. 122 HEAPIFY Analyse Laufzeit von HEAPIFY für Teilbaum der größe n bei Knoten i: • max A[i], A[LEFT(i)], A[RIGHT(i)] suchen: Θ(1) • rekursiver Aufruf auf einem Unterbaum von i: maximale Größe eines solchen Unterbaums: 2n/3 – unterste Ebene ganz voll: n/2 – unterste Ebene nur halb voll: 23 n Es ergibt sich also für die Laufzeit folgende Rekurrenz: T (n) ≤ T (2n/3) + Θ(1) Die Lösung ist T (n) = O(lg n) Oder in Termini der Höhe: T(h) = O(h) 123 Rekurrenzen Wie bekommt man die Lösung einer Rekurrenz? Methoden: 1. Substitutionsmethode 2. Iterationsmethode (Rekursionsbaum) 3. Master Theorem Bemerkungen: • Für obige Rekurrenz verwenden wir das Master Theorem. • Wenn anwendbar, Master Theorem am einfachsten. 124 Master Theorem Seien • a ≥ 1 und b > 1 Konstanten, • f (n) eine Funktion, • T (n) definiert für die natürlichen Zahlen durch T (n) = aT (n/b) + f (n) wobei n/b interpretiert wird als dn/be oder bn/bc. 125 Dann ergeben sich für T (n) folgende asymptotischen Grenzen: 1. Falls f (n) = O(nlogb a− ) für ein > 0, dann T (n) = Θ(nlogb a ) 2. Falls f (n) = Θ(nlogb a ), dann T (n) = Θ(nlogb a lg n) 3. Falls f (n) = Ω(nlogb a+ ) für ein > 0 und af (n/b) ≤ cf (n) für ein c < 1 und genügend große n, dann T (n) = Θ(f (n)) 126 Master Theorem Anwendung von 2 Für HEAPIFY: T (n) ≤ T (2n/3) + Θ(1) Fall 2 des Master Theorems: Falls f (n) = Θ(nlogb a ), dann T (n) = Θ(nlogb a lg n) Zu zeigen: Θ(1) = Θ(nlog2/3 (1) ) das ist richtig, da nlog2/3 (1) = n0 = 1 Also T (n) = O(nlog2/3 (1) lg n) = O(lg n) 127 BUILD-HEAP • BUILD-HEAP baut einen Heap aus einen Array • BUILD-HEAP verwendet HEAPIFY – von den Blättern aufwärts zur Wurzel • In A[(bn/2c + 1) . . . n] sind nur Blätter. • Blätter sind Bäume aus einem Knoten und diese sind bereits Heaps. • Anwendung von HEAPIFY auf A[1 . . . bn/2c] von rechts nach links. • Letzteres garantiert für i – die linken und rechten Teilbäume von i sind bereits Heaps bevor HEAPIFY auf i angewendet wird. 128 BUILD-HEAP BUILD-HEAP( A) 1 heap-size[A] ← length[A] 2 for i ← b length[A]/2c downto 1 3 do HEAPIFY(A, i) 129 A 4 1 3 2 16 9 10 14 8 7 1 4 2 3 1 3 4 8 14 8 2 i 9 10 5 6 7 16 9 10 7 (a) 130 1 4 i 8 14 3 1 3 4 5 6 7 2 16 9 10 9 8 2 10 7 (b) 131 1 4 2 3 1 3 4 5 14 8 2 9 8 16 10 7 (c) 132 i 6 7 9 10 1 4 i 2 3 1 10 4 5 14 8 2 9 8 6 16 9 10 7 (d) 133 7 3 1 4 i 2 3 16 10 4 5 7 14 8 2 9 8 6 9 10 1 (e) 134 7 3 1 16 2 3 14 10 4 5 8 8 2 9 4 6 7 9 10 1 (f) 135 7 3 BUILD-HEAP Analyse Eine einfache obere Schranke: • Jeder Aufruf von HEAPIFY kostet O(lg n) • Es gibt O(n) solche Aufrufe • Laufzeit: O(n lg n) Dieses ist zwar eine korrekte obere Schranke, jedoch keine enge Schranke. Also suchen wir eine bessere. Knackpunkt: • Laufzeit von HEAPIFY variiert mit der Höhe 136 BUILD-HEAP Analyse Eigenschaft eines Heaps: • In einem n-elementigen Heap gibt es höchstens dn/2h+1 e Knoten der Höhe h. (Übung) 137 BUILD-HEAP Analyse HEAPIFY braucht für Knoten der Höhe h O(h) Zeit. Es ergeben sich also folgende Kosten für BUILD-HEAP: blg nc X h=0 blg nc X h d h+1 eO(h) = O(n ) h 2 2 n h=0 138 Zwischenrechnung Es gilt (3.6): ∞ X kxk = k=0 mit x=1/2 erhalten wir ∞ X h=0 hxh = x (1 − x)2 1/2 =2 2 (1 − 1/2) 139 BUILD-HEAP Analyse Aus blg nc X h=0 ergibt sich also blg nc X h d h+1 eO(h) = O(n ) 2 2h n h=0 blg nc ∞ X h X h ) = O(n ) O(n h h 2 2 h=0 h=0 = O(n) Also: • BUILD-HEAP läuft in O(n) 140 HEAP-SORT 1. Wir bauen zunächst einen Heap. 2. Damit steht das größte Element an der Wurzel. 3. Tausche Wurzel (= A[1]) mit letztem Element in Heap (A[heap-size]). 4. Wiederherstellen der Heap-Eigenschaft. 141 HEAP-SORT HEAPSORT( A) 1 BUILD-HEAP( A) 2 for i ← length[A] downto 2 3 do exchange A[1]←→ A[i] 4 heap-size[A] ← heap-size[A]− 1 5 HEAPIFY( A,1) 142 16 14 8 2 4 10 7 9 1 (a) 143 3 14 8 10 4 2 1 9 7 16 i (b) 144 3 10 8 9 4 7 1 i 2 14 16 (c) 145 3 9 8 3 4 7 1 i 10 14 16 (d) 146 2 8 7 3 4 10 14 2 1 16 (e) 147 i 9 7 4 3 1 10 14 2 8 16 (f) 148 i 9 4 3 2 1 10 14 i 7 8 16 (g) 149 9 3 2 i 10 1 4 14 7 8 16 (h) 150 9 2 3 1 4 10 14 8 7 16 (i) 151 i 9 1 i 2 3 4 10 7 8 9 16 14 (j) A 1 2 3 4 7 8 (k) 152 9 10 14 16 HEAP-SORT Analyse HEAP-SORT benötigt O(n lg n) Zeit, da • Aufruf von BUILD-HEAP O(n) braucht • jeder der n − 1 Aufrufe von HEAPIFY O(lg n) braucht QUICK-SORT wird schlechteres worst-case Verhalten haben, ist aber in der Praxis meistens schneller als HEAP-SORT. 153 Ausflug: Prioritätsschlangen • Der Heap ist eine nützliche Datenstruktur. • Verwendet z.B. für Prioritätsschlangen. • Die Datenstruktur der Prioritätsschlangen braucht man z.B. für – Job Scheduling key = Priorität – Simulatoren key = Zeitstempel 154 Anmerkungen • Heap ist eine Datenstruktur • Prioritätsschlangen sind eine Datenstruktur • Wir bauen Prioritätsschlangen mit Heaps • Ergo: man kann Datenstrukturen mit anderen Datenstrukturen bauen. 155 Prioritätsschlange Eine Prioritätsschlange unterstützt folgende Operationen: • INSERT(S,x) fügt ein Element x in Menge S ein S ← S ∪ {x} • MAXIMUM(S) gibt das Maximum der Elemente von S zurück • EXTRACT-MAX(S) gibt das Maximum von S zurück und entfernt es aus S. Dual: MINIMUM(S) und EXTRACT-MIN(S). Implementierung mit Heaps. 156 HEAP-MAXIMUM HEAP-MAXIMUM(A) return A[1] Laufzeit: O(1) 157 HEAP-EXTRACT-MAX HEAP-EXTRACT-MAX(A) 1 if heap-size[A] < 1 2 then error “heap underflow” 3 max ← A[1] 4 A[1] ← A[heap-size[A]] 5 heap-size[A] ← heap-size[A] − 1 6 HEAPIFY(A,1) 7 return max Laufzeit: O(lg n) 158 HEAP-INSERT Idee: 1. Einfügen des neuen Elementes ans Ende des Arrays 2. Dann wie INSERTION-SORT (l 5-7) auf dem Pfad bis zur Wurzel richtigen Platz suchen 159 HEAP-INSERT HEAP-INSERT(A, key) 1 heap-size[A] ← heap-size[A] + 1 2 i ← heap-size[A] 3 while i > 1 and A[PARENT(i)] < key 4 do A[i] ← A[PARENT(i)] 5 i ← PARENT(i) 6 A[i] ← key Laufzeit: O(lg n) (Pfadlänge) 160 16 insert: 15 14 10 8 2 7 4 9 1 (a) 161 3 16 14 10 8 2 7 4 9 1 (b) 162 3 16 10 8 2 14 4 1 9 7 (c) 163 3 16 10 15 8 2 14 4 1 9 7 (d) 164 3 Prioritätsschlangen auf HEAP-Basis Zusammenfassend: • Jede Operation einer Prioritätsschlange kann in O(lg n) mit einem Heap realisiert werden. 165 Quicksort Eigenschaften von Quicksort: • divide-and-conquer-Algorithmus • worst-case: Θ(n2 ) • avg-case: Θ(n lg n) • versteckten Konstanten in Θ(n lg n) sind sehr klein • sortiert in-place Idee: • teile nach Elementgrößen (MERGE-SORT teilt Array nach Lage (Mitte).) 166 Quicksort Divide-and-conquer-Schritte um A[p . . . r] zu sortieren: Divide A[p . . . r] wird umgeordnet in zwei nichtleere Teilarrays A[p . . . q] und A[q + 1 . . . r], so daß jedes Element von A[p . . . q] kleiner ist als jedes Element von A[q + 1 . . . r]. q wird von der Umordnungsprozedur mitberechnet Conquer Die Teilarrays A[p . . . q] und A[q + 1 . . . r] werden rekursiv durch Quicksort sortiert. Combine Da in-place sortiert wird, ist in diesem Schritt keine Arbeit notwendig. 167 QUICKSORT QUICKSORT(A, p, r) 1 if p < r 2 then q ← PARTITION(A, p, r) 3 QUICKSORT(A, p, q) 4 QUICKSORT(A, q + 1, r) Ein Aufruf QUICKSORT(A,1,length[A]) sortiert das ganze Array A. 168 PARTITION Idee: • Suche ein Pivot-Element x • Alle Elemente kleiner als x kommen dann in A[p . . . q] und alle Elemente größer als x kommen in A[q + 1 . . . r]. • Dazu wird A von links und rechts gleichzeitig durchlaufen. • Indizes: i von links und j von rechts • Durchlaufen, bis man zwei Elemente A[i] und A[j] findet mit – A[i] ≥ x – A[j] ≤ x diese werden dann vertauscht • Wenn sich i und j treffen ist man fertig. 169 PARTITION PARTITION( A,p,r) 1 x ← A[p] 2 i←p−1 3 j ←r+1 4 while TRUE 5 do repeat j ← j − 1 6 until A[j] ≤ x 7 repeat i ← i + 1 8 until A[i] ≥ x 9 if i < j 10 then exchange A[i] ←→ A[j] 11 else return j 170 A[p..r] 5 i 3 2 6 4 1 3 7 j (a) 5 3 2 6 4 i 3 3 3 2 6 4 1 2 6 5 7 j (c) 3 7 j (b) i 3 1 4 i 1 5 7 j (d) A[p..q] 3 3 2 A[q+1..r] 1 4 6 return j i (e) 171 5 7 PARTITION • konzeptionell einfach: schiebe alle Elemente ≥ x in den unteren Teil von A und alle ≤ x in den oberen Teil von A. • Pseudocode ist aber trickreich: – i und j bleiben immer in A[p . . . r]. (kein range-Fehler) nicht offensichtlich im Pseudocode. – wichtig: A[p] wird benutzt. falls A[r] benutzt und A[r] ist Maximum: Endlosschleife in QUICKSORT, da PARTITION q = r zurückgibt. • LAUFZEIT: Θ(n), falls n = r − p + 1. 172 QUICKSORT Analyse Feststellung: • Laufzeit abhängig von Balancierung (Verhältnis der Größen beider Partitionen): – balanciert: asymptotische Laufzeit O(n lg n) – unbalanciert: asymptotische Laufzeit O(n2 ) 173 QUICKSORT worst-case Behauptung: • worst-case, falls eine Region n − 1 Elemente, die andere 1 Element beinhaltet Annahme: • Dies geschieht in jedem Schritt Laufzeitkosten: • Partitionierungen: Θ(n) und Θ(1). Rekurrenz: T (n) = T (n − 1) + Θ(n) Rekursionsbaum: 174 Rekursionsbaum 1 n 1 n n-1 1 n n n-1 n-2 1 n-2 n-3 .. . 3 2 1 1 2 (n2 ) 175 QUICKSORT worst-case • Wir lösen diese Rekurrenz durch Iteration. • Dazu benötigen wir Beobachtung: T (1) = Θ(1) T (n) = T (n − 1) + Θ(n) n X Θ(k) = k=1 = Θ( n X k=1 2 = Θ(n ) 176 k) QUICKSORT worst-case Anmerkungen: • worst-case tritt auf, falls A schon sortiert • in diesem Fall braucht INSERTION-SORT Θ(n) 177 QUICKSORT best-case Behauptung: • best-case, falls beide Partitionen n/2 groß sind Annahme: • Dieser Fall kommt bei jedem Aufruf vor Rekurrenz: T (n) = 2T (n/2) + Θ(n) Rekursionsbaum: 178 Rekursionsbaum n n n/2 n/2 n/4 n/4 n n/4 n/4 n lg n n/8 1 1 1 1 . . 1 1 n/8 . 1 1 1 n/8 . . 1 . n/8 . 1 . n/8 . 1 n/8 . . . 1 n/8 . . . 1 n/8 1 1 n . . . . 1 n (n lg n) 179 QUICKSORT best-case Lösen durch Fall 2 des Master-Theorems: Seien • a ≥ 1 und b > 1 Konstanten, • f (n) eine Funktion, • T (n) definiert für die natürlichen Zahlen durch T (n) = aT (n/b) + f (n) wobei n/b interpretiert wird als dn/be oder bn/bc. (2) Falls f (n) = Θ(nlogb a ) dann T (n) = Θ(nlogb a lg n) 180 QUICKSORT best-case Mit a = b = 2: T (n) = Θ(n lg n) • Also ist QUICKSORT hier viel schneller. • Also ist eine “geschickte” Partitionierung sehr wichtig. • Aber: avg-case nah an best-case und nicht an worst-case 181 Einfluß Partitionierung auf Rekurrenz Annahme: • Partition partitioniert im Verhältnis 9:1 Rekurrenz: T (n) = T (9n/10) + T (n/10) + n Rekursionsbaum: 182 Rekursionsbaum x n n 1 10 log n 10 1 100 n 9 n 10 n 9 100 9 n 100 n n 81 100 n n n log 10/9 81 729 n n 1000 1000 1 n n 1 n θ 183 (n lg n) Einfluß Partitionierung auf Rekurrenz Mit • log10/9 n = Θ(lg n) haben wir • Laufzeit immer noch Θ(n lg n) Ähnlich: • 99/1 • 999/1 Allgemein: • Falls Verhältnis durch Konstante beschränkt ist, so ist die Laufzeit von QUICKSORT Θ(n lg n) 184 Intuition avg-case Normalerweise: • in einem Rekursionsbaum gibt es: – gute Partitionierungen und – schlechte Partitionierungen Wie sind diese verteilt? 185 Ausflug Gleichverteilung Was ist Gleichverteilung (von n Zahlen im Bereich [1, k])? • Wenn jede Zahl von [1, k] gleich häufig vorkommt. Wie kommt Gleichverteilung zustande? • Durch Würfeln. Was ist keine Gleichverteilung? • Telefonbucheintragungen: Meier häufiger als Moerkotte 186 Intuition avg-case • Annahme: Eingaben gleichverteilt • Dann: – mehr als 80% besser als 9:1 – weniger als 20% schlechter als 9:1 • Gute und schlechte Partitionierungen zufällig auf Rekursionsbaum verteilt. • vereinfachende Annahme: – gute und schlechte Partitionierungen abwechselnd. – gute sind 1:1 und schlechte n-1:1. Basiskosten für n = 1 seien 1. 187 Intuition avg-case Die Folge von einer guten und einer schlechten Partitionierung liefert • 3 Teilarrays der Größen 1, (n − 1)/2 und (n − 1)/2 • mit Kosten 2n − 1 = Θ(n) • Also: dieser Fall ist “fast” der optimale Fall. • Nur: Konstanten für Partitionierung werden größer 188 QUICKSORT: weiteres Vorgehen • RANDOMIZED-QUICKSORT damit wir nicht in eine Falle laufen (alles oder weite Strecken schon sortiert) • Analyse von RANDOMIZED-QUICKSORT 189 Randomized Quicksort Beobachtung (Ex 13.4-4): • Für jede Konstante k > 0 gilt: – Für alle bis auf O(1/nk ) der n! Permutationen des Eingabearrays hat QUICKSORT die Laufzeit Θ(n lg n). Konsequenz: • permutiere Zahlen im Array zufällig, da dann Chance auf worst-case sehr gering Dies ändert nichts an der asymptotischen Laufzeit. Realisierung: • Funktion RANDOM(a,b): liefert eine Zahl zwischen a und b. • die Folge dieser Zahlen sieht zufällig aus. 190 RANDOMIZED QUICKSORT RANDOMIZED-PARTITION(A, p, r) 1 i ← RANDOM(p,r) 2 exchange A[p] ↔ A[i] 3 return PARTITION(A,p,r) RANDOMIZED-QUICKSORT(A, p, r) 1 if p < r 2 then q ← RANDOMIZED-PARTITION(A, p, r) 3 RANDOMIZED-QUICKSORT(A, p, q) 4 RANDOMIZED-QUICKSORT(A, q + 1, r) 191 Randomized Quicksort worst-case Vorher gesehen: • worst-case split ergibt O(n2 ) Laufzeit • unter der Annahme, daß dies tatsächlich der schlechteste Fall ist Jetzt: • Genauere Analyse 192 Randomized Quicksort worst-case Sei T (n) worst-case Laufzeit von RANDOMIZED-QUICKSORT. Dann T (n) = max (T (q) + T (n − q)) + Θ(n) 1≤q≤n−1 wobei q über 1, . . . , n − 1 läuft, da PARTITION zwei nicht-leere Regionen erzeugt. Lösung der Rekurrenz: • Mit der Substitutionsmethode Wir raten: T (n) ≤ cn2 193 Damit: T (n) = ≤ max (T (q) + T (n − q)) + Θ(n) 1≤q≤n−1 max (cq 2 + c(n − q)2 ) + Θ(n) 1≤q≤n−1 = c∗ max (q 2 + (n − q)2 ) + Θ(n) 1≤q≤n−1 Da 2. Ableitung von q 2 + (n − q)2 nach q positiv (Übung): • Maximum bei 1 oder n − 1 Beide Werte gleich! Also: max (q 2 + (n − q)2 ) 1≤q≤n−1 = 12 + (n − 1)2 = 1 + (n2 − 2n + 1) = n2 − 2(n − 1) 194 Also T (n) ≤ c ∗ n2 − 2c(n − 1) + Θ(n) ≤ c ∗ n2 falls c so groß, daß −2c(n − 1) +Θ(n) dominiert. Also: • worst case O(n2 ) gilt auch für RANDOMIZED-QUICKSORT 195 RANDOMIZED-QUICKSORT avg-case Vorgehen: • Analyse von RANDOMIZED-PARTITION • Erstellen der Rekurrenz • Lösen der Rekurrenz 196 RANDOMIZED-PARTITION Analyse vereinfachende Annahme: • Alle Elemente sind unterschiedlich. • Dies macht Analyse einfacher. • Ergebnis O(n lg n) avg-case für RANDOMIZED-QUICKSORT gilt aber auch sonst 197 RANDOMIZED-PARTITION Analyse Feststellungen: • Wenn PARTITION aufgerufen wird, hat RANDOMIZED-PARTITION A[p] mit einem zufälligen Element A[i] vertauscht. • Return-Wert q von PARTITION hängt nur vom Rang von x = A[i] ab. (Rang(x) = k, falls x die k-t kleinste Zahl ist.) anders ausgedrückt: es gibt k Elemente, die ≤ x sind. • Falls A[p . . . r] n = r − p + 1 Elemente hat, so ist rank(x) = i (1 ≤ i ≤ n) mit Wahrscheinlichkeit 1/n. 198 Wir berechnen jetzt die Wahrscheinlichkeit, der verschiedenen Partitionierungen (Größen). rank(x) = 1: Dann hält PARTITION mit i = j = p. Die linke Partition enthält nur A[p]. Die Wahrscheinlichkeit hierfür ist 1/n. (= Wahrscheinlichkeit rank(x) = 1) rank(x) ≥ 2: Dann gibt es mindestens ein Element ≤ x = A[p]. Der erste Durchlauf durch die while-Schleife hält bei i = p, j > p. A[p] wird also in die rechte Partition geschoben. Wenn PARTITION anhält, ist jedes Element links von x echt kleiner als x. Also: für i = 1, . . . , n − 1, rank(x) ≥ 2: linke Partition hat mit Wahrscheinlichkeit 1/n Größe i 199 Zusammenfassend: Die Größe q − p + 1 der linken Partition ist 1 mit Wahrscheinlichkeit 2/n und i (i = 2, . . . , n − 1) mit Wahrscheinlichkeit 1/n 200 Erstellen der Rekurrenz Sei T (n) die avg-case Laufzeit für das Sortieren eines Arrays der Größe n. Es gilt T (1) = Θ(1). PARTITION benötigt Θ(n) und gibt q zurück. Rekursives sortieren von Teilarrays der Länge q und n − q. Also T (n) =: 1 n T (1) + T (n − 1) + n−1 X q=1 (T (q) + T (n − q)) ! + Θ(n) (q ist gleichverteilt, wie oben ausgerechnet, ausser daß q = 1 zweimal vorkommt; P einmal aus herausgezogen.) 201 Mit T (1) = Θ(1), T (n − 1) = O(n2 ) (aus worst-case): 1 (T (1) + T (n − 1)) n 1 = (Θ(1) + O(n2 )) n = O(n) Θ(n) absortiert O(n), also: T (n) = = = n−1 1X (T (q) + T (n − q)) + Θ(n) n q=1 n−1 2X (T (q)) + Θ(n) n q=1 n−1 2X (T (k)) + Θ(n) n k=1 202 Lösen der Rekurrenz: Substitutionsmethode Annahme: T (n) ≤ an lg n + b für Konst. a, b > 0 Weiter: a, b so groß, daß an lg n + b > T (1) Dann: Für n > 1: T (n) = n−1 2X (T (k)) + Θ(n) n k=1 ≤ = n−1 2X (ak lg k + b) + Θ(n) n k=1 n−1 2b 2a X (k lg k) + (n − 1) + Θ(n) n n k=1 Wir zeigen unten: n−1 X k=1 1 2 1 2 (k lg k) ≤ n lg n − n 2 8 203 Damit: T (n) ≤ n−1 2b 2a X (k lg k) + (n − 1) + Θ(n) n n k=1 1 2b 2a 1 2 ( n lg n − n2 ) + (n − 1) + Θ(n) n 2 8 n a ≤ an lg n − n + 2b + Θ(n) 4 a = an lg n + b + (Θ(n) + b − n) 4 ≤ an lg n + b ≤ dabei a so groß, daß a/4n majorisiert Θ(n) + b. Also: T (n) = O(n lg n) 204 zu zeigen: n−1 X k=1 leicht: 1 2 1 2 (k lg k) ≤ n lg n − n 2 8 n−1 X k=1 da jedes lg k ≤ lg n. (k lg k) ≤ n2 lg n Dies reicht aber nicht, da wir für die Lösung der Rekurrenz eine Schranke der Form 1 2 n lg n − Ω(n2 ) 2 benötigen. Also berechnen wir strengere Schranke. Trick: Spalten der Summe. 205 n−1 X (k lg k) = k=1 dn/2e−1 X n−1 X (k lg k) + k=1 (k lg k) k=dn/2e • erste Summe: lg k ≤ lg(n/2) = lg n − 1. • zweite Summe: lg k ≤ lg n. Also: n−1 X k=1 (k lg k) ≤ (lg n − 1) = lg n n−1 X k=1 ≤ ≤ dn/2e−1 k− X k + (lg n) k=1 dn/2e−1 X k=dn/2e k k=1 1 n n 1 n(n − 1) lg n − ( − 1) 2 2 2 2 1 1 2 n lg n − n2 2 8 für n ≥ 2. qed. 206 n−1 X k Zusammenfassung Bis jetzt: • mehrere Algorithmen in Θ(n lg n) worst-case oder avg-case. • alle Algorithmen basierten auf Vergleichen Jetzt: • Beweis einer unteren Schranke für vergleichbasierte Algorithmen • schnellere Algorithmen, die nicht auf Vergleichen basieren 207 Untere Schranken für Sortieren Annahme: • Es werden nur ≤, <, >, ≥, = genutzt um zwei Elemente einer Folge zu sortieren. • Keine andere Information über die Elemente wird benutzt. Einschränkung: • Alle Elemente der Folge sind unterschiedlich. Daher: • < reicht aus. 208 Entscheidungsbaum Beobachtung: • Es existieren n! Fakultät Permutationen der Eingabefolge. • Nur eine ergibt sortierte Folge. Entscheidungsbaum: • Jeder interne Knoten ist mit einem ai : aj markiert. 1 ≤ i, j ≤ n. • Jedes Blatt ist mit einer Permutation markiert. 209 Entscheidungsbaum Zusammenhang Algorithmen: • nur interessant: Vergleiche vernachlässige: Kontrolle, Zuweisungen, usw. • jeder vom Algorithmus durchgeführter Vergleich entspricht einem Vergleich im Entscheidungsbaum: – Wurzel=erster Vergleich – Nachfolgeknoten: abhängig vom Ausgang vorheriger Vergleiche. • Jedes Blatt im Entscheidungsbaum ist genau eine der möglichen Permutationen. • Alle Permutationen müssen vorkommen. 210 a 1: a 2 < − < − > a1: a 3 a2 : a 3 > a1: a 1,2,3 < − 1,3,2 < − > a2 : a3 2,1,3 3 > < − 3,1,2 2,3,1 211 > 3,2,1 Untere Schranke für den worst-case Beobachtung: • Der längste Pfad von der Wurzel zu einem Blatt entspricht der worst-case-Anzahl an Vergleichen. Daher: • Untere Schranke für die Höhe des Entscheidungsbaums ist daher untere Schranke der Laufzeit. Nächster Satz liefert diese Schranke. 212 Untere Schranke für den worst-case Satz Jeder Entscheidungsbaum, der n Elemente sortiert, hat die Höhe Ω(n lg n). Bew.: Ex. n! Permutationen. Binärer Baum der Höhe h hat höchstens 2h Blätter. Daher: n! lg n! Mit Sterlings Formel n! = ergibt sich √ ≤ 2h ≤ h n 1 2πn( )n 1 + Θ( ) e n n h ≥ lg ( )n e ≥ n lg n − n lg e = Ω(n lg n) 213 Korollar: HEAP-SORT und MERGE-SORT sind asymptotisch optimale vergleichsbasierte Sortieralgorithmen. Bew.: Die O(n lg n) worst-case Schranke beider Algorithmen fällt mit der unteren Schranke für den worst-case zusammen. 214 Count Sort Annahme: • Eingabelemente im Bereich [1, k] Dann: • k = O(n) impliziert Laufzeit O(n) Idee: • Für jedes x bestimme die Anzahl der Elemente ≤ x. Falls 17 Elemente ≤ x, dann muß x an die 18te Stelle. (falls keine Duplikate) 215 Count Sort Annahmen für Algorithmus: • A[1 . . . n] für Eingabe • B[1 . . . n] für sortierte Ausgabe • C[1 . . . n] für Zwischenergebnisse 216 COUNTING-SORT (A, B, k) 1 for i ← 1 to k 2 do C[i] ← 0 3 for j ← 1 to length[A] 4 do C[A[j]] ← C[A[j]] + 1 5 BC[i] now contains the number of elements = i. 6 for i ← 2 to k 7 do C[i] ← C[i] + C[i − 1] 8 BC[i] now contains the number of elements ≤ i. 9 for j ← length[A] downto 1 10 do B[C[A[j]]] ← A[j] 11 C[A[j]] ← C[A[j]] − 1 217 A C 1 2 3 4 5 6 7 8 3 6 4 1 3 4 1 4 1 2 3 4 5 6 2 0 2 3 0 1 7 8 (a) C 1 2 3 4 5 6 2 2 4 7 7 8 5 6 (b) 1 2 3 4 4 B C 1 2 3 4 5 6 2 2 4 6 7 8 (c) 218 1 B C 2 3 4 5 6 1 7 8 4 1 2 3 4 5 6 1 2 4 6 7 8 4 5 6 7 4 4 (d) 1 3 1 B 1 C 2 1 2 3 4 5 6 2 4 5 7 8 8 (e) B 1 2 3 4 5 6 7 8 1 1 3 3 4 4 4 6 (f) 219 COUNTING-SORT Analyse Zeile Aufwand 1-2 : O(k) 3-4 : O(n) 6-7 : O(k) 9-11 P : O(n) : O(n + k) COUNTING-SORT wird benutzt, falls k = O(n). Dann ist der Aufwand O(n) Besser als Ω(n lg n), da nicht vergleichsbasiert. Mehr noch: es kommt nicht ein einziger Vergleich vor. 220 COUNTING-SORT Eigenschaft COUNTING-SORT ist ein • stabiles Sortierverfahren. heißt: • gleiche Zahlen kommen in der Ausgangsfolge in der gleichen Reihenfolge wie in der Eingangsfolge vor. Nur nützlich, falls Records sortiert werden (z.B. nach mehreren Schlüsseln). 221 Radix Sort Ursprung: • Kartenstapel sortieren Idee: • erst nach signifikantester Stelle sortieren • dann naechst signifikante Stelle (rekursiv) usw. Problem: • Bei 10-System: 9 von 10 Stapeln müssen beim Sortieren zur Seite gelegt werden. Daher (TRICK): • Sortiere (stabil) nach am wenigsten signifikanter Stelle • dann naechst signifikantere Stelle usw. 222 RADIX-SORT Sortiere d-stellige Zahl. RADIX-SORT(A, d) 1 for i ← 1 to d 2 do use a stable sort to sort array A on digit i 223 RADIX-SORT 329 720 720 329 457 355 329 355 657 436 436 436 839 436 ⇒ 457 657 ⇒ 839 355 ⇒ 457 657 720 329 457 720 355 839 657 839 ↑ ↑ ↑ 224 RADIX-SORT Analyse Analyse abhängig vom benutzten Sortierverfahren. Falls eine Stelle von 1 . . . k läuft und k nicht zu groß ist: • COUNTING-SORT gute Wahl Dann ist der Aufwand für RADIX-SORT: • Θ(dn + dk) Falls d konstant ist und k = O(n) dann hat RADIX-SORT • linearen Aufwand 225 Bucket Sort Annahme für Bucket Sort: • Zahlen zufällig in [0, 1[ Dann: • avg-case: linear Idee: • Verteilen der Zahlen auf m Körbe (buckets) • z.B. 10: [0, 0.1[, [0.1, 0.2[, . . . • Dann buckets sortieren und fertig. 226 Bucket Sort Annahmen im Code: • n-elementiges Array A • 0 ≤ A[i] < 1 f.a. i • B[0 . . . n − 1] Hilfsarray mit Listen von Zahlen (kleiner Vorgriff) 227 BUCKET-SORT(A) 1 n ← length[A] 2 for i ← 1 to n 3 do insert A[i] into list B[bnA[i]c] 4 for i ← 0 to n − 1 5 do sort list B[i] with insertion sort 6 concatenate B[0], B[1], . . . , B[n − 1] 228 B A 1 .78 0 2 .17 1 3 .39 2 4 .26 3 5 .72 4 6 .94 5 7 .21 6 .68 8 .12 7 .72 9 .23 8 10 .68 9 .12 .17 .21 .23 .39 .78 .94 (a) (b) 229 .26 BUCKET-SORT Analyse • Jede Zeile außer Zeile 5 benötigt O(n). • Zeile 5: n2i , falls Länge von bucket i gleich ni . • Falls die Zahlen gut verteilt sind und es viele buckets gibt (ungefähr n), so ist der Aufwand trotzdem Θ(1). Insgesamt: • Aufwand: O(n) (unter obigen Annahmen) 230 Datenstrukturen 231 Mengen Operationen: • insert • delete • member Variiert aber mit Anwendung. “Parameter”: • Worauf basiert Mengenzugehörigkeit? Schlüssel (key), Sekundärdaten (satellite data) • Ordnungen? • Mehrfachmengen? 232 Mengen Operationen • SEARCH(S,k) gibt ↑ x zurück, falls x ∈ S ∧ key(x) = k • INSERT(S,x) fügt x in S ein • DELETE(S,x) entfernt x aus S • MINIMUM(S) falls S total geordnet, gibt minimales Element zurück • MAXIMUM(S) analog • SUCCESSOR(S,x) falls S total geordnet, gibt Nachfolgeelement von x zurück • PREDECESSOR(S,x) analog Bei Mehrfachmengen: SUCC/PRED durchlaufen erst Elemente mit gleichem Schl üssel. Komplexität der Operationen: • Gemessen in |S| 233 Übersicht 1. sehr einfache sehr wichtige Datenstrukturen (Brot&Butter) 2. Hash-Tabellen 3. Binäre Suchbäume 4. Rot-Schwarz-Bäume 234 Stacks and Queues Stapel und Schlangen: • Ordnung: Zeitpunkt des Einfügens – Stack: last-in-first-out (LIFO) – Queue: first-in-first-out (FIFO) • basierend auf Array 235 Stack STACK-EMPTY(S) 1 if top[S] = 0 2 then return TRUE 3 else return FALSE PUSH(S, x) 1 top[S] ← top[S] + 1 2 S[top[S]] ← x POP(S) 1 if STACK-EMPTY(S) 2 then error “underflow” 3 else top[S] ← top[S] − 1 4 return S[top[S] + 1] 236 Beispiel S 1 2 3 4 15 6 2 9 5 6 7 7 top[S] = 4 S 1 2 3 4 5 6 15 6 2 9 17 3 top[S] = 6 S 1 2 3 4 5 6 15 6 2 9 17 3 top[S] = 5 237 7 Stack • Jede Operation: O(1) • Nachteil: Array begrenzt Tiefe • Häufig noch: TOP(S) 238 Queue ENQUEUE(Q, x) 1 Q[tail[Q]] ← x 2 if tail[Q] = length[Q] 3 then tail[Q] ← 1 4 else tail[Q] ← tail[Q] + 1 DEQUEUE(Q) 1 x ← Q[head[Q]] 2 if head[Q] = length[Q] 3 then head[Q] ← 1 4 else head[Q] ← head[Q] + 1 5 return x 239 Beispiel 1 2 3 4 5 6 (a) Q 7 8 9 15 6 9 10 8 head[Q] = 7 (b) Q 1 2 3 5 3 4 5 6 tail[Q] = 3 (c) Q 1 2 3 5 3 4 11 4 12 17 teil[Q] = 12 7 8 9 10 11 12 15 6 9 8 4 17 head{Q] = 7 5 6 7 8 9 10 11 12 15 6 9 8 4 17 head[Q] = 8 tail[Q] + 3 240 Queues • Jede Operation: O(1) • Nachteil: Array begrenzt Länge • Häufig noch: FIRST(S) 241 Verkettete Listen Reihenfolge durch: • zeigerbasierte Verkettung Ausprägungen: • einfach verkettete Liste: (succ,key) • doppelt verkettete Liste: (prev,succ,key) Optional: • zyklisch: letztes Element verweist auf erstes (und umgekehrt) Operationen: • im Prinzip alle oben genannten, aber einige ineffizienter 242 Beispiel prev key next (a) head[L] 9 (b) head[L] 25 9 16 4 (c) head[L] 25 9 16 1 16 4 243 1 1 doppelt verkettete Listen Attribute: • head[L] für Liste L • tail[L] für Liste L • next[x] für Listenelement x • prev[x] für Listenelement x Operationen: • LIST-SEARCH(L,k) • LIST-INSERT(L,x) • LIST-DELETE(L,x) für Liste L, Schlüssel k und Listenelement x. nicht betrachtet: • woher kommt x 244 Listenoperationen LIST-SEARCH(L,k) 1 x ← head[L] 2 while x 6= NIL and key[x] 6= k 3 do x ← next[x] 4 return x Laufzeit (worst-case): • Θ(n) 245 Listenoperationen LIST-INSERT(L, x) 1 next[x] ← head[L] 2 if head[L] 6= NIL 3 then prev[ head[L]] ← x 4 head[L] ← x 5 prev[x] ← NIL Laufzeit: • O(1) 246 Listenoperationen LIST-DELETE(L, x) 1 if prev[x] 6= NIL 2 then next[ prev[x]] ← next[x] 3 else head[L] ← next[x] 4 if next[x] 6= NIL 5 then prev[ next[x]] ← prev[x] 247 Beispiel prev key next (a) head[L] 9 (b) head[L] 25 9 16 4 (c) head[L] 25 9 16 1 16 4 248 1 1 Dummy-Elemente Falls kein Grenzfall zu berücksichtigen ist, ist der Code von LIST-DELETE viel einfacher (und effizienter): LIST-DELETE’(L, x) 1 next[ prev[x]] ← next[x] 2 prev[ next[x]] ← prev[x] Mit Dummy-Element (nil[L]) kann man dies erreichen: 249 Beispiel (a) nil[L] (b) nil[L] 9 (c) nil[L] 25 (d) nil[L] 16 25 4 1 9 16 4 9 16 4 250 1 Operationen auf Listen mit Sentinel LIST-SEARCH’(L,k) 1 x ← next[ nil[ L]] 2 while x 6= nil [ L] and key[ x] 6= k 3 do x ← next[ x] 4 return x LIST-INSERT’(L,k) 1 next[ x] ← next [ nil[ L]] 2 prev[ next[ nil[L]]] ← x 3 next[nil[L]] ← x 4 prev[x] ← nil[L] 251 Dummy-Elemente Bewertung Beachte: • Einführung kostet Speicherplatz Nur Einführen, falls erhebliche Vereinfachung im Code, oder sogar erheblicher Laufzeitvorteil. Ansonsten, besonders bei kurzen Listen nicht angebracht. 252 Implementierung Zeiger und Objekte Wann? • falls die Sprache keine zur Verfügung stellt • falls eigene Speicherverwaltung sinnvoll ist Idee: • Benutze Arrays und Arrayindizes Unterscheide: • Mehr-Array-Implementierung • Ein-Array-Implementierung 253 Mehr-Array-Implementierung Beispiel: • Liste: – 3 Arrays: next, key, prev 254 Beispiel 1 L 2 3 4 5 6 7 7 next 3 key 4 prev 5 2 5 1 16 9 2 7 Linked List 255 8 Ein-Array-Implementierung Beispiel: • Liste: – Array von Tupeln (records) mit 3 Feldern: next, key, prev (kann auch durch offset realisiert sein) 256 Beispiel 1 L 19 2 3 4 5 6 7 4 7 13 1 8 9 10 11 12 13 4 16 key 257 14 15 4 next 19 prev 16 17 18 19 9 20 13 21 22 23 24 Speicher Anfordern und Freigeben Alternativen für Freigabe von Speicher: • automatisch – garbage collector (Freispeichersammler) • manuell – storage manager (Freispeicherverwaltung) Hier muß Programmierer Speicher explizit freigeben Diskussion: • Freispeicherverwaltung • nur gleich große Stücke • basiert auf Mehr-Array-Implementierung 258 Freispeicherverwaltung Idee: • freien Speicher in Liste organisieren free list (Freispeicherliste) • Anfordern: von Freispeicherliste ausfügen • Freigabe: in Freispeicherliste einfügen • free list als Stack organisiert, nur next benutzt 259 Operationen zur Freispeicherverwaltung ALLOCATE-OBJECT() 1 if free = NIL 2 then error “out of space” 3 else x ← free 4 free ← next[x] 5 return x FREE-OBJECT(x) 1 next[x] ← free 2 free ← x 260 1 free 4 L 7 2 3 4 5 6 7 8 8 2 1 5 6 next 3 key 4 1 16 prev 5 2 7 9 (a) 1 free 8 L 4 2 3 next 3 key 4 1 prev 5 2 4 5 6 7 7 2 1 5 25 16 9 7 4 8 6 (b) 1 free 5 L 4 2 4 3 next 3 key 4 1 prev 7 2 5 7 8 25 1 7 2 9 4 (c) 261 6 8 6 free 10 1 next 5 2 3 4 6 8 5 6 7 2 9 10 1 7 4 k9 L2 9 key k1 k 2 k3 k5 k6 k7 L1 3 prev 7 6 1 3 9 Zwei Listen L1 und L2 262 8 Repräsentation von Wurzelbäume Binärbäume: • Felder: – p: parent – left: left child – right: right child • p[x]==NIL =⇒ x ist Wurzel • root[T]: ist Wurzel • root[T]==NIL: Baum ist leer. Schema kann verwendet werden, solange der Knotengrad beschränkt ist. 263 Beispiel root[T] 264 Unbegrenzter Grad • Binärbaume können hierfür benutzt werden: • left-child, right-sibling Repräsentation: • Felder: – p: parent – left-child[x]: leftmost child of x – right-child[x]: sibling to the right of x • left-child[x]==NIL: no children • right-child[x]==NIL: x is rightmost child 265 Beispiel root[T] 266 Andere Repräsentationen • Heap • einige Zeiger können wegfallen • vieles möglich 267 Hash-Tabellen Oft benötigt: • dynamische Menge von Elementen mit Operationen – INSERT – SEARCH – DELETE • Realisiert Abbildung Key7→Element • Ergibt Dictionary (Map). 268 Hash-Tabellen Idee: • Benutze Schlüssel als Arrayindex Bei Strings, großen Zahlen etc. schwierig. Daher: • Berechne Arrayindex aus Schlüssel Eigenschaften: • Alle Operationen in O(1) 269 Direkte Adressierung Annahme: • Alle Schlüssel aus U = {0, 1, . . . , m − 1} • Array T ist unsere Tabelle (direct address table) • ein Feld in T heißt slot 270 Abbildung T 0 1 9 1 2 U (universe of keys) 6 0 7 4 3 key 2 3 4 2 K 3 (actual5 keys) 8 5 5 6 7 8 9 271 8 satellite data Operationen DIRECT-ADDRESS-SEARCH(T,k) return T[k] DIRECT-ADDRESS-INSERT(T,x) T[key[x]] ← x DIRECT-ADDRESS-DELETE(T,x) T[key[x]] ← NIL 272 Hash-Tabellen Probleme mit direkter Adressierung offensichtlich: • großes U • nicht dichtes U • Speicheraufwand Θ(U ), selbst wenn nur Schlüssel aus K mit |K| << |U | tatsächlich gespeichert werden. Daher Hashtabellen mit • O(1) Zeit (jetzt allerdings avg-case) • Θ(|K|) Speicher 273 Hash-Funktion Bei direkter Adressierung wurde ein Element k an Adresse k gespeichert. Jetzt: • speichere k an Adresse h(k) • h heißt Hash-Funktion • h : U −→ {0, . . . , m − 1} • h(k) heißt hash value von k. • h(k1 ) = h(k2 ): Kollision • h sollte möglichst zufällig aussehen, um Kollisionen zu vermeiden • Treten trotzdem auf, da normalerweise |U | > m • Daher wichtig: Kollisionsbehandlung 274 T 0 U (universe of keys) h(k1) h(k 4 ) k1 K (actualk4 k5 keys)k2 k3 h(k2 ) = h (k5) h(k3) m-1 275 Kollisionsbehandlung durch Listen Idee: • an jedem Slot hängt eine Liste, die alle kollidierenden Elemente enthält CHAINED-HASH-INSERT(T,x) insert x at the head of list T [h(key[x])] CHAINED-HASH-SEARCH(T,k) search for an element with key k in list T [h(k)] CHAINED-HASH-DELETE(T,x) delete x from the list T [h(key[x])] 276 Analyse • INSERT: O(1) worst-case • SEARCHING: proportional zur Länge der Liste • DELETE: wie suchen SEARCHING untersuchen wir noch genauer. 277 Analyse Problem: • Gegeben: Tabelle T mit m Plätzen und n Einträgen • Gesucht: durchschnittliche Länge der Überlauflisten Wir definieren noch: • Füllgrad α := n/m 278 Analyse • worst-case: Liste hat Länge n: Θ(n) Aufwand für SEARCHING • avg-case: Verteilung durch h kritisch • Annahmen für avg-case: – h verteilt gleichmäßig auf alle Plätze von T – h berechenbar in Θ(1) • Aufwand steckt im Ablaufen der Kollisionsliste • 2 Fälle: Suche nicht erfolgreich/erfolgreich 279 Nicht erfolgreiche Suche Theorem 12.1 In einer Hash-Tabelle mit Kollisionslisten hat eine nicht erfolgreiche Suche den durchschnittlichen Aufwand von Θ(1 + α) Beweis: • Unter der Gleichverteilungsannahme: Jedes k wird auf einen der m Slots mit gleicher Wahrscheinlichkeit gehasht. • durchschnittlicher Aufwand entspricht also dem Durchschnittsaufwand zum vollständigen Durchlaufen einer Kollisionsliste • Durchschnittslänge der Liste = α = n/m • n/m Elemente durchschnittlich durchlaufen • avg-case (mit Berechnung von h(k)): Θ(1 + α) 280 Erfolgreiche Suche Theorem 12.2 In einer Hash-Tabelle mir Kollisionslisten hat eine erfolgreiche Suche den durchschnittlichen Aufwand von Θ(1 + α). Beweis Annahmen: • Jedes der n in der Tabelle gespeicherten Elemente ist mit gleicher Wahrscheinlichkeit das gesuchte Element. • CHAINED-HASH-INSERT fügt am Ende ein (Übung: beeinflußt nicht avg-case Suchzeit im Erfolgsfall) Beobachtung: • Erfolgreiche Suche benötigt 1 plus die Zeit der nicht erfolgreichen Suche VOR dem Einfügen des gesuchten Elements. 281 Um die durchschnittliche Anzahl der besuchten Elemente zu berechnen, nehmen wir den Durchschnitt • über allen n gespeicherten Elementen (i) • von 1+ der erwarteten Länge der Liste zu welcher i hinzugefügt wurde. Diese erwartete Länge ist (i − 1)/m, da • bei Einfügen von i bereits i − 1 Elemente in der Tabelle sind 282 Also ist die durchschnittliche Anzahl von besuchten Elementen: n n X 1 X i−1 = 1+ (i − 1) 1+ 1/n m nm i=1 i=1 (n − 1)n 1 = 1+ nm 2 1 α = 1+ − 2 2m Also Gesamtaufwand (mit h(k) ausrechnen): Θ(2 + 1 α − ) 2 2m 283 = Θ(1 + α) Analyse Falls • α = n/m = O(m)/m = O(1) dann • SEARCH braucht O(1). Auch: • INSERTION: O(1) • DELETION: Wie SEARCH 284 Hash-Funktionen Frage: • Wie bastel ich mir eine Hash-Funktion? 285 Hash-Funktionen Idee: • Interpretiere Schlüssel als natürliche Zahlen Zum Beispiel: • integer: z.B. Vorzeichen weg machen (als unsigned interpretieren) • ASCII string: Zahlen von 0-127, einzelnen Buchstaben multipliziert mit ihrer Wertigkeit (∗128i ) aufaddieren Annahme: • Schlüssel ist natürliche Zahl 286 Divisionsmethode Definition: h(k) := k mod m Wahl von m: • keine Zweierpotenz: nur letzten i bit berücksichtigt • keine Zehnerpotenzen, falls wir im Dezimalsystem sind. • Falls – m = 2p − 1 für Primzahl p so bildet h zwei Strings in Basis-128-darstellung bei denen nur 2 Zeichen vertauscht wurden, auf den gleichen Wert ab. • GUT: Primzahl die nicht in der Nähe einer Zweierpotenz ist. Beispiel: n = 2000: Wähle m = 701. 287 Multiplikationsmethode Definition: mit 0 < A < 1. h(k) := bm((kA) − bkAc)c Vorteil: • Güte nicht abhängig von m • Daher Wahl von m: kann 2-Potenz sein. Güte von A abhängig. Gute Wahl: √ • A = ( 5 − 1)/2 = 0.6180339887 . . . (nach Knuth) 288 Offene Adressierung Bisher: • Elemente in Liste gespeichert Jetzt: • Elemente in Hashtabelle speichern Idee: • Falls frei: Nutze h(k) zum Speichern • Sonst: Nutze andere freie Felder Damit α ≤ 1. 289 Offene Adressierung Wir suchen freies Feld abhängig von einer erweiterten Hashfunktion h : U × {0, 1, . . . , m − 1} → {0, 1, . . . , m − 1} wobei für jeden Schlüssel k die Probensequenz hh(k, 0), h(k, 1), . . . , h(k, m − 1)i eine Permutation von {0, 1, . . . , m − 1} sein muß. Probensequenz wird dann genutzt um Speicherplatz für ein Element zu finden und auch um es zu suchen. 290 Offene Adressierung HASH-INSERT(T,k) 1 i←0 2 repeat j ← h(k, i) 3 if T [j] = NIL 4 then T [j] ← k 5 return j 6 else i ← i + 1 7 until i = m 8 error “hash table overflow” 291 Offene Adressierung HASH-SEARCH(T,k) 1 i←0 2 repeat j ← h(k, i) 3 if T [j] = k 4 then return j 5 i←i+1 6 until T [j] = NIL or i = m 7 return NIL 292 Offene Adressierung Lineares Hashing: h(k, i) := (h0 (k) + i) mod m Quadratisches Hashing: h(k, i) := (h0 (k) + c1 i + c2 i2 ) mod m Doppeltes Hashing: h(k, i) := (h1 (k) + ih2 (k)) mod m mit h2 (k) prim zu m. 293 Offene Adressierung Analyse • Analyse in termini von α = n/m (n Elemente in m Slots) • Annahme: gleichmäßiges Hashen: – Sequenz hh(k, 0), h(k, 1), . . . , h(k, m − 1)i – ist mit gleicher Wahrscheinlichkeit jede der Permutationen von h0, . . . , m − 1i. • Wir berechnen nun die erwartete Anzahl von Vergleichen für Hashing mit offener Adressierung. • Zuerst nicht-erfolgreiche, dann erfolgreiche Suche. 294 Offene Adressierung Analyse Satz Gegeben sei eine Hashtabelle mit offener Adressierung und einem Füllgrad α = n/m < 1. Dann ist die erwartete Anzahl von Vergleichen im erfolglosen Fall höchstens 1/(1 − α), falls wir gleichmäßiges Hashen annehmen. 295 Beweis Bei der erfolglosen Suche vergleicht man zunächst mit einer Reihe von besetzten Slots, bis man zum Schluß auf einen leeren Slot trifft. Wir definieren pi = Pr{genau i Vergl. greifen auf besetzte Slots zu} für i = 0, 1, . . .. Für i > n ist pi = 0, da nur n Slots besetzt sind. Der Erwartungswert für die Anzahl der Vergleiche ist also 1+ ∞ X i=0 (nach Definition Erwartungswert) 296 ipi Wir definieren qi = P r{mind. i Vergl. greifen auf besetzte Slots zu} Es gilt ∞ X ipi = i=0 da E[X] = = = ∞ X i=0 ∞ X i=0 ∞ X i=1 ∞ X qi i=1 iP r{X = i} i(P r{X ≥ i} − P r{X ≥ i + 1}) P r{X ≥ i} 297 Für i = 1 ist qi : n q1 = ( ) m Für i = 2 ist qi : q2 = ( Allgemein: qi n n−1 )( ) m m−1 n−i+1 n n−1 )...( ) = ( )( m m−1 m−i+1 n i ≤ ( ) m = αi 298 und 1+ ∞ X ipi = 1+ i=0 ∞ X qi i=1 ≤ 1 + α + α2 + . . . 1 = 1−α 299 Falls die Hashtabelle halb voll ist, so brauchen wir im Schnitt höchstens 1/(1 − 0.5) = 2 Vergleiche im erfolglosen Fall. Ist die Hashtabelle zu 90% voll, so brauchen wir im Schnitt höchstens 1/(1 − 0.9) = 10 Vergleiche im erfolglosen Fall. 300 Korollar Einfügen eines Elementes in eine Hashtabelle mit offener Adressierung kostet im Durchschnitt nicht mehr als 1/(1 − α) Vergleiche, falls α = n/m der Füllgrad ist. Wir nehmen wieder gleichmäßiges Hashing an. Beweis Wir können nur Einfügen, wenn α < 1. Dann Suchen wir erst einen freien Platz. Das ist genau das gleiche wie erfolglose Suche. 301 Satz Gegeben sei eine Hashtabelle mit Füllgrad α < 1. Weiter sei das Hashverfahren gleichmäßig. Der Erwartungswert für die durchschnittliche Anzahl der Vergleiche im Erfolgsfall ist höchstens 1 1 1 ln + α 1−α α falls jeder Schlüssel mit der gleichen Wahrscheinlichkeit gesucht wird. 302 Beweis Die Suche nach k bewirkt die gleiche Folge an Vergleichen, die auch benutzt wurde, um k in die Hashtabelle einzufügen. Nach Korollar: Falls k der i + 1-te eingefügte Schlüssel ist, so ist die maximale Anzahl von Vergleichen bei der Suche nach k: 1/(1 − i/m) = m/(m − i) Der Durchschnitt über alle n enthaltenen Elemente ist n−1 1X m n i=0 m − i n−1 mX 1 n i=0 m − i = 1 (Hm − Hm−n ) α = mit Hn ist die harmonische Zahl Hn = 1 + 1/2 + 1/3 + 1/4 + . . . + 1/n = ln n + O(1) 303 Mit Gilt: ln i ≤ Hi ≤ ln i + 1 1 (Hm − Hm−n ) α ≤ = = 1 (ln m + 1 − ln(m − n)) α m 1 1 ln + α m−n α 1 1 1 ln + α 1−α α 304 Falls die Hashtabelle halb voll ist, so brauchen wir im Schnitt höchstens 3.387 Vergleiche im erfolgreichen Fall. Ist die Hashtabelle zu 90% voll, so brauchen wir im Schnitt höchstens 3.670 Vergleiche im erfolgreichen Fall. 305 Binäre Suchbäume Benutzt für Mengen mit Operationen wie • SEARCH • MINIMUM,MAXIMUM • PREDECESSOR,SUCCESSOR • INSERT, DELETE Operationen in Θ(h) mit h Höhe des Baumes. Variiert: • h = O(n) (Baum degradiert zu Liste o.ä.) • h = Θ(lg n) (Baum ist balanciert.) 306 Binäre Suchbäume (BSB) sind 1. binäre Bäume mit Attributen left, right, p und key 2. erfüllen binäre Suchbaumeigenschaft: • Sei x ein Knoten. Dann gilt (a) key[lef t[x]] ≤ key[x] (b) key[right[x]] ≥ key[x] 307 5 3 2 7 5 8 (a) 2 3 7 5 8 5 (b) 308 Durchläufe Elemente in Suchbaum können leicht in aufsteigender Reihenfolge durchlaufen werden: INORDER-TREE-WALK(x) 1 if x 6= NIL 2 then INORDER-TREE-WALK(left[x]) 3 print key[x] 4 INORDER-TREE-WALK(right[x]) Analog: PREORDER-TREE-WALK und POSTORDER-TREE-WALK 309 Suchen TREE-SEARCH(x,k) 1 if x = NIL or k = key[x] 2 then return x 3 if k < key[x] 4 then return TREE-SEARCH(left[x],k) 5 else return TREE-SEARCH(right[x],k) Laufzeit: O(h) 310 15 18 6 3 2 7 4 17 13 9 311 20 Suchen (iterativ) ITERATIVE-TREE-SEARCH(x,k) 1 while x 6= NIL and k 6= key[x] 2 do if k < key[x] 3 then x ← left[x] 4 else x ← right[x] 5 return x 312 Minimum und Maximum TREE-MINIMUM(x) 1 while left[x] 6= NIL 2 do x ← left[x] 3 return x TREE-MAXIMUM(x) 1 while right[x] 6= NIL 2 do x ← right[x] 3 return x Laufzeit: O(h) 313 Successor TREE-SUCCESSOR(x) 1 if right[x] 6= NIL 2 then return TREE-MINIMUM (right[x]) 3 y ← p [x] 4 while y 6= NIL and x = right[y] 5 do x ← y 6 y ← p[y] 7 return y Predecessor analog. 314 Änderungsoperationen Änderungen sind • insert • delete Diese müssen die binäre Suchbaumeigenschaft bewahren. Beide werden Laufzeit O(h) haben. 315 Insert TREE-INSERT(T, z) 1 y ← NIL 2 x ← root[T ] 3 while x 6= NIL 4 do y ← x 5 if key[z] < key[x] 6 then x ← left[x] 7 else x ← right[x] 8 p[z] ← y 9 if y = NIL 10 then root[T ] ← z 11 else if key[z] < key[y] 12 then left[y] ← z 13 else right[y] ← z 316 12 18 5 2 9 19 15 inserted: 13 317 17 Delete TREE-DELETE(T, z) 1 if left[z] = NIL or right[z] = NIL 2 then y ← z 3 else y ← TREE-SUCCESSOR(z) 4 if left[y] 6= NIL 5 then x ← left[y] 6 else x ← right[y] 7 if x 6= NIL 8 then p[x] ← p[y] 9 if p[y] = NIL 10 then root[T ] ← x 11 else if y = left[p[y]] 12 then left[p[y]] ← x 13 else right[p[y]] ← x 14 if y 6= z 15 then key[z] ← key[y] 318 16 17 return y B If y has other fields, copy them, too. Die drei Fälle: 319 15 15 5 5 16 3 12 10 3 20 13 z 18 16 12 10 23 6 6 7 7 (a) 320 20 13 18 23 15 15 16 z 5 3 12 10 5 3 20 13 18 20 12 23 10 6 6 7 7 (b) 321 18 13 23 y 15 z 5 12 10 18 z 16 12 23 10 6 15 5 3 20 13 15 z 16 3 y 6 18 7 12 23 10 7 7 (c) 322 16 3 20 13 6 20 13 18 23 Rot-Schwarz-Baum Motivation: • einfacher binärer Suchbaum kann unbalanciert sein • Mehrere Möglichkeiten Balanciertheit zu erreichen • Eine: Jeder Knoten ist entweder Schwarz oder Rot. • Nur bestimmte Farbmuster erlaubt. Dies garantiert: • längster Pfad von Wurzel zu einem Blatt kann höchstens doppelt so lang sein wie kürzester Pfad von der Wurzel zu einem Blatt. • Daher Höhe O(lg n) für n Elemente. 323 Rot-Schwarz-Baum • Jeder Knoten hält die Attribute: color, key, left, right, p • Falls kein Sohn oder Vater existiert enthält das entsprechende Feld NIL. Diese NILs sind für uns Zeiger auf externe Knoten (Blätter). • Ein Binärer Suchbaum ist ein Rot-Schwarz-Baum, falls 1. Jeder Knoten ist entweder Schwarz oder Rot. 2. Jedes Blatt (NIL) ist schwarz. 3. Falls ein Knoten rot ist, so sind beide Söhne schwarz. 4. Jeder einfache Pfad von einem Knoten zu einem Blatt enthält die gleiche Anzahl von schwarzen Knoten. 324 Rot-Schwarz-Baum • Die Anzahl von schwarzen Knoten in einem Pfad von x, ohne x mitzuzählen, zu einem Blatt heißt schwarze Höhe (black-height) von x. (bh(x)). Nach Eigenschaft 4 wohldefiniert. Lemma Ein rot-schwarz-Baum mit n innere Knoten hat eine Höhe von maximal 2 lg(n + 1). 325 Beweis Wir zeigen zunächst durch Induktion, daß ein Teilbaum mit Wurzel x mindestens 2bh(x) − 1 innere Knoten hat. height(x)=0: =⇒ x ist Blatt und Teilbaum von x enthält 20 − 1 = 0 innere Knoten. height(x)> 0: Jeder Sohn von x hat entweder die schwarze Höhe bh(x) oder bh(x) − 1, abhängig von seiner Farbe. Also hat der Teilbaum von x mindestens 1 + 2 ∗ (2bk(x)−1 − 1) = 2bh(x) − 1 Knoten. Sei h die Höhe des R-S-Baums. Eigenschaft 3 impliziert, daß mindestens die Hälfte der Knoten auf einem einfachen Pfad von der Wurzel zu einem Knoten schwarz sind. Also ist die schwarze Höhe mindestens h/2. Also n ≥ 2h/2 − 1 und damit h ≤ 2 lg(n + 1) 326 Rot-Schwarz-Baum • Die Operationen SEARCH, MINIMUM, MAXIMUM, SUCCESSOR, PREDECESSOR laufen in O(lg n), da O(h) bereits nachgewiesen und ein rot-schwarz Baum mit n Knoten eine Höhe von O(lg n) hat. • Die bisherigen INSERT und DELETE Operationen können aber die Eigenschaften zerstören. • Wiederherstellen mit LEFT-ROTATE und RIGHT-ROTATE. • Modifiziertes INSERT und DELETE nutzen dann diese Prozeduren. 327 Left-/Right-Rotate RIGHT-ROTATE (T, y) y α α γ x x y LEFT-ROTATE(T, x) β β Suchbaumeigenschaft bleibt erhalten. α≤x≤β≤y≤γ 328 γ Rotationen LEFT-ROTATE(T, x) 1 y ← right[x] B Set y 2 right[x] ← left[y] B y’s left becomes x’s right subtree. 3 if left[y] 6= NIL 4 then p[lef t[y]] ← x 5 p[y] ← p[x] B Link x’s parent to y. 6 if p[x] = NIL 7 then root[T ] ← y 8 else if x = left[p[x]] 9 then left[p[x]] ← y 10 else right[p[x]] ← y 11 left[y] ← x B Put x on y’s left. 12 p[x] ← y 329 7 11 4 3 2 6 x 9 18 y 14 LEFT-ROTATE(T,x) 12 19 17 22 20 7 4 3 18 y x 11 6 19 9 14 2 12 330 22 17 20 RB-INSERT 1. Einfügen von x mit normalen TREE-INSERT 2. Setzen der Farbe von x auf rot 3. Korrigieren der Farbe beim Durchlaufen des Pfads von x zur Wurzel. ( 4. Dabei werden (2*) drei Fälle unterschieden (x immer rot, p[x] rot): 1. onkel[x] rot p[x] = onkel[x] = schwarz; grossvater[x] = rot; sprung zu grossvater[x] • onkel[x] schwarz 2. x == right[p[x]] x := p[x]; LEFT-ROTATE(T, x) 3. x == left[p[x]] p[x] = schwarz; p[p[x]] = rot; RIGHT-ROTATE(T, p[p[x]]) 331 RB-INSERT(T, x) 1 TREE-INSERT(T, x) 2 color[x] ← RED 3 while x 6= root[T ] and color[p[x]] = RED 4 do if p[x] = left[p[p[x]]] 5 then y ← right[p[p[x]]] 6 if color[y] = RED 7 then color[p[x]] ← BLACK 8 color[y] ← BLACK 9 color[p[p[x]]] ← RED 10 x ← p[p[x]] 11 else if x = right[p[x]] 12 then x ← p[x] 13 LEFT-ROTATE(T, x) 14 color[p[x]] ← BLACK 15 color[p[p[x]] ← RED 332 BCase1 BCase1 BCase1 BCase1 BCase2 BCase2 BCase3 BCase3 RIGHT-ROTATE(T, p[p[x]]) BCase3 else (same as then clause with “right” and “left” exchanged) 18color[ root[T ]] ← BLACK 16 17 333 Rot-schwarz Baum Diskussion in 3 Schritten: 1. Verletzungen durch 1 und 2 2. globales Ziel der while-Schleife 3. Unterfälle nächste Abbildung: Arbeitsweise von RB-INSERT. 334 11 2 (a) 14 7 1 5 15 8 y x 4 Case 1 11 14 y 2 (b) 1 7 x 5 15 8 Case 2 4 11 14 y 7 x 2 (c) 1 8 15 5 Case 3 4 7 x 2 (d) 11 1 5 8 4 14 15 335 11 2 (a) 14 7 1 5 15 8 y x 4 Case 1 11 14 y 2 (b) 1 15 7 x 5 8 Case 2 4 336 11 14 y 2 (b) 1 7 x 5 15 8 Case 2 4 11 14 y 7 x 2 (c) 1 8 15 5 Case 3 4 337 11 14 y 7 x 2 (c) 1 8 15 5 Case 3 4 7 x 2 (d) 11 1 5 8 14 15 4 338 1. Jeder Knoten ist entweder Schwarz oder Rot: Kann nicht verletzt werden. 2. Jedes Blatt (NIL) ist schwarz: Kann nicht verletzt werden. 3. Falls Knoten rot, so sind beide Söhne schwarz: verletzt, falls p[x] rot. 4. Jeder einfache Pfad von einem Knoten zu einem Blatt enthält die gleiche Anzahl von schwarzen Knoten: Kann nicht verletzt werden, da ein schwarzer Knoten (NIL) durch einen roten (x) mit schwarzem Sohn (NIL) ersetzt wird. • Die while-Schleife schiebt die mögliche Verletzung von 3 in Richtung Wurzel. Die Fälle im einzelnen: 339 Fall Eins new x C C α D y A B x β γ case 1 δ α ε D A δ B β γ (a) p[x] koennte rot sein! 340 ε Fall Eins new x C x A α γ β case 1 δ D y B D y B C ε α (b) 341 γ A β δ ε Fall Zwei/Drei Case 2 C α Case 3 C δy B A B x β γ x α A γ β c) 342 B δy α x A C β γ δ RB-INSERT • Laufzeit: O(lg n) • never more than 2 rotations/insert 343 Delete • Delete wird O(lg n) beanspruchen • um Code zu vereinfachen: Dummies für NIL • RB-DELETE ist fast identisch zum allgemeinen TREE-DELETE, es wird jedoch zusätzlich eine Prozedur RB-DELETE-FIXUP aufgerufen, die sich durch Umfärben und Rotieren um die Red-Black-Eigenschaften kümmert. 344 Dummy Ein Dummy-Knoten nil[T ] für einen Rot-Schwarz-Baum hat die gleichen Felder wie die anderen Knoten. • color hat den Wert black • p, left, right, und key sind beliebig • alle Zeiger auf NIL werden durch Zeiger auf nil[T ] gesetzt • Es gibt nur einen Dummy-Knoten nil[T ] • Falls aber ein Sohn von x ein Dummy ist und wir mit p[nil[t]] operieren müssen, so müssen wir diesen erst auf x setzen. 345 RB-DELETE(T, z) 1 if left[z] = nil[T ] or right[z] = nil[T ] 2 then y ← z 3 else y ← TREE-SUCCESSOR(z) 4 if left[y] 6= nil[T ] 5 then x ← left[y] 6 else x ← right[y] 7 p[x] ← p[y] 8 if p[y] = nil[T ] 9 then root[T ] ← x 10 else if y = left[p[y]] 11 then left[p[y]] ← x 12 else right[p[y]] ← x 13if y 6= z 14 then key[z] ← key[y] 15 B If y has other fields, copy them, too. 346 16if color[y] = Black 17 then RB-DELETE-FIXUP(T, x) 18return y 347 Es gibt 3 Unterschiede zu TREE-DELETE: 1. Alle Auftauchen von NIL wurden durch nil[T ] ersetzt 2. Der Test x 6= NIL in Zeile 7 wurde eliminiert • falls x=nil[T], dann gilt zeigt p[nil[T]] auf p[y] ( y ist der ausgefügte Knoten). 3. Der Aufruf von RB-DELETE-FIXUP falls y schwarz (Z. 16/17) • y rot: schwarze Höhe unverändert • y schwarz: fixup von x aufwärts. – NOTE: p[x]=p[y] durch Z.7 schwarze Höhe eins weniger, Eigenschaft 4 somit verletzt. Wiederherstellen: Sohn schwarz machen gibt Probleme falls dieser schon schwarz ist (er ist jetzt doppelt schwarz). Daher mit Verletzung zur Wurzel laufen und dabei “irgendwo” beheben. 348 RB-DELETE-FIXUP(T, x) 1 while x 6= root[T ] and color[x] = BLACK 2 do if x = left[p[x]] 3 then w ← right[p[x]] 4 if color[w] = RED 5 then color[w] ← BLACK 6 color[p[x]] ← = RED 7 LEFT-ROTATE(T, p[x]) 8 w ← right[p[x]] 9 if color[ left[w]] = BLACK and color[ right[w]] = BLACK 10 then color[w] ← RED 11 x ← p[x] 12 else if color[ right[w]] = BLACK 13 then color[ left[w]] ← BLACK 14 color[w] ← RED 349 BC1 BC1 BC1 BC1 BC2 BC2 BC3 BC3 15 16 17 18 19 20 21 22 RIGHT-ROTATE(T, w) w ← right[p[x]] color[w] ← color[p[x]] color[p[x]] ← BLACK color[ right[w]] ← BLACK LEFT-ROTATE(T, p[x]) x ← root[T ] else (same as then clause with “right” and “left” exchanged) 23color[x] ← BLACK 350 BC3 BC3 BC4 BC4 BC4 B Case 4 B Case 4 RB-DELETE-FIXUP • Ziel der Schleife: das doppelte Schwarz nach oben schieben bis man 1. auf einen roten Knoten trifft. Dieser wird dann schwarz gefärbt. ODER 2. bei der Wurzel ankommt (dann schwarz einfach entfernbar) ODER 3. Rotationen und Umfärbungen mit Bruder vorgenommen werden können. • Innerhalb der Schleife: color[x]==black und x ist doppelt schwarz • Sei w Onkel von x. Da x doppelt schwarz folgt: w 6= nil[T] (Eigenschaft 4) 351 Case 1 B (a) x A α β D D w γ C δ ε E B new w x A C α β γ δ ζ Dies entspricht Case 2, 3, oder 4. dunkel: schwarz mittel: rot hell: rot oder schwarz (kommt auf dieser Folie nicht vor) 352 E y ε ζ Case 2 B c (b) x A α β D w γ C δ ε E α new x B c A D y β γ ζ 353 C δε E ζ Case 3 B c B c (c) α A x β x A α D w γ C δ ε E ζ β γ C new w δ D E ε 354 ζ Case 4 D c B c (d) A x α β D w γ E C c’ δ ε ζ B α 355 A β γ E C c’ δ ζ ε new x = root [T] RB-DELETE-FIXUP Laufzeit: • Fälle 1,3,4: Schleife endet, also constant time. • Fall 2: x ← p[x], also maximal Höhe. (Falls wir von Fall 1 kommen, verlassen wir die Schleife) Laufzeit also: O(lg n). 356 Anreichern von Datenstrukturen • oft werden mehr Operationen als die Grundoperationen benötigt • Diese können oft auf existierenden Datenstrukturen implementiert werden • Beispiele: – suche x für gegebene Ordnung (Rang, rank) – suche Position von x bei inorder Durchlauf • Dazu nützlich: – size[x]: # innere Knoten des Teilbaums von x (incl. x) – Es gilt (offensichtlich): size[x] = size[lef t[x]] + size[right[x]] + 1 – Dummy bekommt Size 0. Im Bild: 357 26 20 41 7 17 12 16 2 10 4 7 2 12 1 30 5 21 4 14 7 14 1 21 1 19 2 20 1 key size 3 1 358 47 1 28 1 38 3 35 1 39 1 Bestimme i-t kleinstes Element OS-SELECT(x, i) 1 r ← size[ left[x]]+1 2 if i = r 3 then return x 4 else if i < r 5 then return OS-SELECT(left[x], i) 6 else return OS-SELECT(right[x], i − r) 359 Bestimme Position von x bei inorder Durchlauf OS-RANK(T, x) 1 r ← size[ left[x]] + 1 2 y←x 3 while y 6= root[T ] 4 do if y = right[p[y]] 5 then r ← r + size[ left[p[y]]] + 1 6 y ← p[y] 7 return r 360 Wartung der Teilbaumgrößen • Falls nicht effizient möglich, OS-SELECT und OS-RANK wertlos. • Einfügen: – Beim Suchen der Einfügestelle, jedem besuchten Knoten von der Wurzel zur Einfügestelle eins aufaddieren. Aufwand: O(lg n) – Rotationen (s. Bild): size[y] ← size[x] size[x] ← size[lef t[x]] + size[right[x]] + 1 Aufwand: O(1) – Gesamtaufwand: O(lg n). • Delete: Analog 361 93 y 19 RIGHT−ROTATE (T,y) 7 42 11 6 42 19 x 6 LEFT−ROTATE (T,x) 93 12 x 4 4 362 y 7 Anreicherung von Datenstrukturen Schritte: 1. wähle eine zugrundeliegende Datenstruktur 2. bestimme zusätzlich benötigte Information 3. prüfe daß zusätzliche Information für Mutatoren berechnet werden kann 4. entwickle die neuen Operationen 363 Anreicherung von R-S-Bäumen Theorem: Sei f ein neues Feld in einem angereicherten Rot-Schwarz-Baum. Für jeden Knoten x sei der Inhalt von f aus den Feldinhalten von x, left[x], und right[x] in O(1) berechenbar. Dann können wir die Information in f verwalten, ohne die asymptotische Laufzeit von O(lg n) der Mutatoren RB-INSERT und RB-DELETE zu verändern. 364 Intervallbäume Bäume für Intervalle: • Wir betrachten nur geschlossene Intervalle i = [t1 , t2 ] von reellen Zahlen. • low[i] = t1 , high[i] = t2 • Verallgemeinerung auf (halb-) offene Intervalle einfach. Intervalltrichotomy: Je zwei Intervalle i und i0 erfüllen genau eine der folgenden Eigenschaften: 1. i und i0 überschneiden sich 2. high[i] < low[i0 ] 3. high[i0 ] < low[i] 365 i i i i i’ i’ i’ i’ (a) i’ i i’ i (c) (b) 366 Intervallbäume Ein Intervallbaum enthält eine dynamische Menge von Intervallen int[x] und unterstützt die folgenden Operationen: • INTERVAL-INSERT(T, x) fügt ein Element x mit assoziiertem Intervall int[x] zu T hinzu. • INTERVAL-DELETE(T, x) entfernt x aus T . • INTERVAL-SEARCH(T, i) gibt einen Zeiger auf ein Element x aus T hinzu, das mit dem Intervall i überlappt. Falls kein solches existiert, so wird NIL zurückgegeben. Wir reichern Rot-Schwarz-Bäume zu Intervallbäumen an. 367 Schritt 1: Anreichern der Datenstruktur • Jeder Knoten x wird um ein Intervall int[x] angereichert. • Der Schlüssel (key) von x ist low[ int[x]]. • inorder-Durchlauf ergibt also enthaltene Intervalle nach Anfangspunkt sortiert. 368 Schritt 2: Zusätzliche Information • Jeder Knoten x enthält in max[x] das Maximum aller Endpunkte aller Intervalle im Teilbaum von x. 369 Schritt 3: Wartung der Information Es gilt: max[x] = max( high[ int[x]], max[left[x]], max[right[x]] ) Unser Theorem schlägt also zu. Die Details: Übung. 370 Schritt 4: Neue Operationen INTERVAL-SEARCH(T, i) 1 x ← root[T ] 2 while x 6= NIL and i does not overlap int[x] 3 do if left[x] 6= NIL and max[ left[x]] ≥ low[i] 4 then x ← left[x] 5 else x ← right[x] 6 return x Da jeder Schritt O(1) kostet und in jedem Schritt x auf einen Sohn gesetzt wird, ist die Laufzeit O(lg n). Wie es funktioniert: Suche nach [22, 25] in: 371 26 26 19 17 19 16 8 0 3 0 5 6 21 15 9 30 25 20 23 10 8 5 10 15 20 (a) 372 25 30 [16,21] 30 [8,9] 23 [5,8] 10 [0,3] 3 [25,30] 30 [15,23] 23 [17,19] 20 [6,10] 10 [19,20] 20 (b) 373 int max [26,26] 26 26 19 17 0 3 0 30 19 21 15 9 6 25 20 16 8 26 23 10 5 8 5 10 15 20 25 30 [16.21] 30 [8,9] 23 [5,8] 30 [15,23] 23 10 [0,3] 3 [25,30] 20 [19,20] 20 374 max [26,26] 26 [17,19] [6,10] 10 int Korrektheit Um zu sehen, daß INTERVAL-SEARCH korrekt ist, müssen wir einsehen, daß es genügt, einen einzigen Pfad herunterzulaufen: • z.z.: falls int[x] nicht mit i überlappt, so findet INTERVAL-SEARCH eines, falls eines existiert. Genauer: Theorem Man betrachte einen beliebigen Durchlauf der while-Schleife von INTERVAL-SEARCH(T, i). Dann gilt: 1. Falls Zeile 4 ausgeführt wird, so enthält left[x] ein mit i überlappendes Intervall, oder es existiert keines in right[x]. 2. Falls Zeile 5 ausgeführt wird, so enthält left[x] kein mit i überlappendes Intervall. 375 Beweis Fall 2: • Z. 5 ausgeführt =⇒ left[x] == NIL ∨ max[left[x]] < low[i] √ • Falls left[x] == NIL: • Falls max[left[x]] < low[i]. Sei i0 ein Intervall in left[x]. Dann gilt: high[i0 ] ≤ max[lef t[x]] < low[i] i und i0 können also nicht überlappen. i’ i’ i (a) 376 Fall 1: Annahme: kein Intervall in left[x] überlappt mit i. • z.z.: kein Intervall in right[x] überlappt mit i. • Z. 4 ausgeführt =⇒ max[left[x]] ≥ low[i] • nach Def. von max gibt es ein i0 in left[x] mit high[i0 ] = max[lef t[x]] ≥ low[i] Da i und i0 nicht überlappen, und auch nicht high[i0 ] < low[i] gilt muß high[i] < low[i0 ] gelten. 377 Wir haben: max[left[x]] high[i’] = max[left[x]] high[i] ≥ low[i] < low[i’] ≥ low[i] Da der Schlüssel im R-S-Baum low[x] ist, impliziert die Suchbaumeigenschaft für jedes i00 in right[x]: high[i] < low[i0 ] ≤ low[i00 ] i und i00 überlappen also nicht. i ’’ i’’ i ’’ i’ i (b) 378 Datenstrukturen für Hintergrundspeicher • Buch: Gio Wiederhold • wir besprechen: – extensible hashing – B-Baum 379 B-Baum • Ein B-Baum-Knoten hat viele Nachfolger (>> 2). • Jeder Knoten paßt auf eine Seite. • Jeder Teilbaum repräsentiert einen Schlüsselbereich. • Wir machen keine Behandlung der Satelliteninformation. • Normalerweise Satelliteninformation nur in den Blättern gespeichert (B+ -Baum) oder nur Zeiger auf Satelliteninformation in Blättern gespeichert (B∗ -Baum). Dies maximiert Verzweigungsgrad. 380 B-Baum Ein B-Baum ist ein Wurzelbaum mit den folgenden Eigenschaften: 1. Jeder Knoten x hat die folgenden Felder: (a) n[x] Anzahl der in x gespeicherten Schlüssel (b) die n[x] Schlüssel in aufsteigender Reihenfolge: key1 [x] ≤ . . . ≤ keyn[x] [x] (c) leaf [x] ist TRUE, falls x leaf, FALSE sonst 2. Falls x ein innerer Knoten ist, so enthält x n[x] + 1 Zeiger: c1 [x] ≤ . . . ≤ cn[x] [x] 3. Falls Schlüssel ki in Teilbaum ci gespeichert ist, so gilt: k1 ≤ key1 [x] ≤ k2 ≤ key2 [x] ≤ . . . ≤ keyn[x] [x] ≤ kn[x] 381 4. Jedes Blatt hat dieselbe Höhe. 5. Es gibt obere und untere Schranken für n[x]. Diese werden in t ≥ 2 ausgedrückt: (a) ∀ x 6= root: n[x] ≥ t − 1 und Anz. Söhne ≥ t. (b) n[x] ≤ 2t − 1, also Anz. Söhne ≤ 2t. x ist voll, falls er genau 2t − 1 Schlüssel enthält. 382 Beispiel für B-Baum root[T] M D H B C F G Q J K L N P 383 T R S X V W Y Z B-Baum Theorem Sei n ≥ 1. Dann gilt für jeden B-Baum der Höhe h mit n Schlüsseln und minimalem Verzweigungsgrad t ≥ 2: n+1 ) h ≤ logt ( 2 384 Beweis Schlechtester Fall: • Wurzel enthält einen Schüssel. • Andere Knoten enthalten t − 1 Schlüssel Dann gibt es • 2 Knoten der Höhe 1 • 2t Knoten der Höhe 2 • 2t2 Knoten der Höhe 3, etc. Also: n ≥ 1 + (t − 1) h X i=1 = 1 + 2(t − 1) = 2th − 1 Hieraus folgt die Behauptung. 385 2ti−1 th − 1 ) t−1 B-Baum • Vorteil: hoher Verzweigungsgrad garantiert weniger Seitenzugriffe. • Typische Höhe: 3. Operationen: • B-TREE-SEARCH • B-TREE-CREATE • B-TREE-INSERT • B-TREE-DELETE Annahmen: • Wurzel immer im Hauptspeicher • Parameterknoten wurden immer schon in Hauptspeicher gelesen. 386 B-TREE-SEARCH(x, k) 1 i←1 2 while i ≤ n[x] and k > keyi [x] 3 do i ← i + 1 4 if i ≤ n[x] and k = keyi [x] 5 then return (x, i) 6 if leaf[x] 7 then return NIL 8 else DISK-READ (ci [x]) 9 return B-TREE-SEARCH (ci [x], k) • Z.2-3: Besser: binäre Suche. • Komplexität: O(logt n) 387 root[T] M D H B C F G Q J K L N P 388 T R S X V W Y Z B-TREE-CREATE CREATE und INSERT benötigen ALLOCATE-NODE: • Diese erzeugt einen neuen Knoten auf einer neuen Seite in O(1). • Die neue Seite wird nicht geschrieben, da noch nichts zu schreiben ist. B-TREE-CREATE(T ) 1 x ← ALLOCATE-NODE() 2 leaf [x] ← TRUE 3 n[x] ← 0 4 DISK-WRITE(x) 5 root[T ] ← x 389 Splitten • Falls eine Seite überläuft, so wird sie gesplittet. • Dazu: Teilen entlang Median-Schlüssel keyt [y]. • Dieser wird dann dem Vater hinzugefügt. • Falls kein Vater vorhanden: Tiefe wächst um eins. • B-TREE-SPLIT-CHILD hat als Argument einen nicht vollen Knoten x, bei dem ein Sohn y = ci [x] voll ist. Dieser wird dann gesplittet. 390 keyi−1[x] keyi [x] keyi−1[x] keyi [x] x ... N x ... N W.. . y =c i [ x ] P T1 T2 Q S keyi+1[x] W.. . z = ci+1[x] y = ci[ x] R S T U V T3 T4 T5 T 6 T7 T8 P T1 T2 391 Q R T3 T T4 T5 U T6 V T7 T8 B-TREE-SPLIT-CHILD(x, i, y) 1 z ← ALLOCATE-NODE() 2 leaf [z] ← leaf [y] 3 n[z] ← t− 1 4 for j ← 1 to t - 1 5 do keyj [z] ← keyj+t [y] 6 if not leaf [y] 7 then for j ← 1 to t 8 do cj [z] ← cj+t [y] 9 n[y] ← t - 1 10 forj ← n[x] + 1 downto i + 1 11 do cj+1 [x] ← cj [x] 12 ci+1 [x] ← z 13 for j ← n[x] downto i 14 do keyj+1 [x] ← keyi [x] 15 keyi [x] ← keyt [y] 392 16 17 18 19 n[x] ← n[x] + 1 DISK-WRITE(y) DISK-WRITE(z) DISK-WRITE(x) 393 • x’s voller Sohn y wird gesplittet. • Dazu wird die Hälfte der Einträge auf einen neuen Knoten z kopiert und aus y entfernt. • z wird dann in x der Eintrag direkt nach y. • Z 1-8: erzeuge z, fülle z, setzen der Zeiger in z, falls z kein Blatt ist • Z 9: Schlüsselzähler anpassen. • 10-16: weiterrücken von Schlüsseln und Zeigern in x und einfügen von z • Z 17-19: Speichern der Änderungen auf Platte 394 B-TREE-INSERT(T, k) 1 r ← root[T ] 2 if n[r] = 2t − 1 3 then s ← ALLOCATE-NODE() 4 root[T ] ← s 5 leaf[s] ← FALSE 6 n[s] ← 0 7 c1 [s] ← r 8 B-TREE-SPLIT-CHILD(s, 1, r) 9 B-TREE-INSERT-NONFULL(s, k) 10 else B-TREE-INSERT-NONFULL(r, k) • Z 3-9: Wurzel voll: neue Wurzel erzeugen und splitten • Z 10: sonst. • B-TREE-INSERT-NONFULL: einfügen von k in x, wobei x nicht voll ist. 395 Split mit Erzeugung einer neuen Wurzel root [T] s H root [T] r A D F H L r N P A T1 T 2 T3 T4 T5 T6 T7 T8 D F T1 T2 T 3 T 4 396 L N P T5 T 6 T7 T8 B-TREE-INSERT-NONFULL(x, k) 1 i ← n[x] 2 if leaf [x] 3 then while i ≥ 1 and k < keyi [x] 4 do keyi+1 [x] ← keyi [x] 5 i←i-1 6 keyi+1 [x] ← k 7 n[x] ← n[x] + 1 8 DISK-WRITE(x) 9 else while i ≥ 1 and k < keyi [x] 10 do i ← i - 1 11 i←i+1 12 DISK-READ(ci [x]) 13 if n[ci [x]] = 2t - 1 14 then B-TREE-SPLIT-CHILD(x, i, ci [x]) 15 if k > keyi [x] 397 16 17 then i ← i + 1 B-TREE-INSERT-NONFULL(ci [x], k) 398 (a) initial tree G A C D E J K M P N O 399 X R S T U V Y Z (b) B inserted G A C D E J K M P N O 400 X R S T U V Y Z (c) Q inserted G A C D E J K M P N O 401 X R S T U V Y Z (d) L inserted P G A B C D E T M J K L N O 402 Q R S U X V Y Z (e) F inserted P C G A B D E F T M J K L N O 403 Q R S U X V Y Z Erläuterungen • Z 3-8: x Blatt: – Z 3-5: weiterrücken der Schlüssel, – Z 6: einfügen k. • Z 9-17: stelle Sohn fest, in den einzufügen ist – Z 9-11: Sohn suchen – Z 12: Sohn lesen – Z 13-16: falls Sohn voll, splitten. – Z 17: rek. Aufruf 404 Delete • delete k aus x • falls n[x] > t und x Blatt: einfach entfernen • Ansonsten: – mit Nachbarn balancieren – mit Nachbarn verschmelzen • es sind einige Fälle zu betrachten. 405 Delete • Annahme: k aus x entfernen. • B-TREE-DELETE garantiert, daß wenn immer sie rekursiv gerufen wird, in x mindestens t Schlüssel verbleiben. • Damit muß vor dem Aufruf mindestens ein Schlüssel mehr da sein, als die B-Baum-Bedingung erfordert. • Daher muß manchmal ein extra Schlüssel von woanders her eingefügt werden. 406 1. x Blatt, k ∈ x: lösche k aus x. 2. x kein Blatt, k ∈ x: (a) falls Vorgängersohn y von k mindestens t Schlüssel hat, ersetze k durch Vorgängerschlüssel k 0 (b) analog für Nachfolgersohn z (c) merge y und z 3. k nicht in einem internen Knoten x: bestimme ci [x] in dem k vorkommen kann. Falls ci [x] weniger als t Schlüssel hat: (a) falls Nachbar y genug Knoten hat: ausgleichen: Schlüssel aus y nach x, einer aus x nach ci [x] (b) merge ci [x] mit einem Nachbarn. rek. Aufruf auf ci [x] Aufwand: O(h), also O(logt n) 407 Deletion of F,M,G,D und B (a) initial tree P C G A B D E F T M J K L N O 408 Q R S U X V Y Z (b) F deleted: case 1 P C G A B D E T M J K L N O Next: M 409 Q R S U X V Y Z (c) M deleted: case 2a P C A B D E G T L J K N O Next: G 410 Q R S U X V Y Z (d) G deleted: case 2c P C A B D E T L J K N O Q Next: D 411 R S U X V Y Z (e) D deleted: case 3b C A B E J K L P T N O X R S U V Y Z R S U V Y Z Q (e’) tree shrinks in height C A B E J K L P T N O Q 412 X (f) C deleted: case 3a E A B J K L P T N O Q 413 X R S U V Y Z Extensible Hashing 414 Dynamisches Programmieren 415 Dynamisches Programmieren: Ziel Finde 1. das Beste, Billigste, etc., 2. mit wenig Aufwand. Methoden: 1. dynamisches Programmieren 2. Memoization 3. Greedy-Algorithmen 4. + . . . Aber: diese Methoden nicht nur auf Optimierungsprobleme anwendbar. 416 Dynamisches Programmieren Anwendbar, falls bei • Problemlösung Teilprobleme häufig gleich sind • und diese unabhängig voneinander lösbar sind. Daher prädestiniert für so manches Optimierungsproblem. Beispielklasse: • gegeben ein Ausdruck (mit Operanden und Operationen) • gesucht billigste Auswertung 417 Dynamisches Programmieren Schritte: 1. Charakterisiere Struktur des Problems 2. Def. rekursiv die Kosten des Optimums 3. Berechne Optimum bottom-up 4. Konstruiere Optimum aus bereits berechneter Information 418 Bsp: Multiplikation mehrerer Matrizen Gegeben • Sequenz hA1 , . . . , An i von Matrizen Gesucht • Produkt der Matrizen A1 , . . . , An Bem. • Wir werden Produkt ausrechnen, indem wir fortlaufend zwei Matrizen (Original oder Zwischenergebnis) multiplizieren. • Es gilt das Assoziativgesetz. Daher gibt es viele verschiedene Auswertungsreihenfolgen. Diese sind unterschiedlich teuer, wie wir sehen werden. 419 Vollständige Klammerung Def. Ein Produkt von Matrizen heißt vollständig geklammert, falls es aus einer einzelnen Matrix besteht, oder aus zwei vollständig geklammerten Produkten. Bsp. (A1 (A2 (A3 A4 ))) (A1 ((A2 A3 )A4 )) ((A1 A2 )(A3 A4 )) ((A1 (A2 A3 ))A4 ) (((A1 A2 )A3 )A4 ) 420 MATRIX-MULTIPLY(A, B) 1 if columns[A] 6= rows[B] 2 then error “incompatible dimensions” 3 else for i ← 1 to rows[A] 4 do for j ← 1 to columns[B] 5 do C[i, j] ← 0 6 for k ← 1 to columns[A] 7 do C[i, j] ← C[i, j] + A[i, k] · B[k, j] 8 return C 421 Analyse Kosten werden durch die Anzahl der Multiplikationen in Schritt 7 dominiert. Sei A eine p × q Matrix und B eine q × r Matrix, dann wird Zeile 7 p∗q∗r mal ausgeführt. 422 Optimierungspotential Seien folgende Matrizen gegeben: A1 A2 A3 : 10 × 100 : 100 × 5 : 5 × 50 Dann ergeben sich folgende Kosten: cost((A1 A2 )A3 ) = 10 ∗ 100 ∗ 5 + 10 ∗ 5 ∗ 50 = 7500 cost(A1 (A2 A3 )) = 100 ∗ 5 ∗ 50 + 10 ∗ 100 ∗ 50 = 75000 Optimierungspotential bei nur drei Matrizen: • Faktor 10. 423 Problemdefinition Gegeben • Sequenz hA1 , . . . , An i von Matrizen Gesucht • vollst. geklammertes Produkt der Matrizen A1 , . . . , A n mit minimalen Kosten (minimaler Anzahl von Multiplikationen). 424 Anzahl der Klammerungen Wir überzeugen uns, daß alle Klammerungen zu betrachten sehr ineffizient ist. Bezeichne P (n) die Anzahl der Klammerungen von n Matrizen. In der obersten Klammerungsebene (letztes Produkt): • splittet hA1 , . . . , An i an einer Stelle k in zwei Teilsequenzen hA1 , . . . , Ak i und hAk+1 , . . . , An i (1 ≤ k < n). 425 Anzahl der Klammerungen Es ergibt sich die Rekurrenz Es gilt: 1 falls n = 1 P (n) = Pn−1 P (k)P (n − k) falls n > 1 k=1 P (n) = C(n − 1) mit C(n) gleich den catalanschen Zahlen: C(n) = 2n 1 n+1 n = Ω(4n /n3/2 ) Also ist P (n) exponentiell in n. 426 Bestimmung der Struktur des Optimums Sei Ai...j das Produkt von Ai , . . . , Aj . Das Optimum splittet für ein k zwischen Ak und Ak+1 (1 ≤ k < n): A1...k Ak+1...n Die Kosten ergeben sich zu cost(A1 . . . Ak ) + cost(Ak+1 . . . An ) + cost(A1...k Ak+1...n ) wobei die vollständigen Klammerungen von A1 . . . Ak und Ak+1 . . . An optimal sein müssen! Anm.: Diese Bedingung heißt auch Optimalitätsprinzip. 427 Rek. Def. (der Kosten) des Optimums Sei m[i, j] die minimale Anzahl von Multiplikationen, die notwendig sind, um A i...j zu berechnen. Die Billigste Lösung des Problems hat dann die Kosten m[1, n]. Sei Ai eine Matrix der Dimension pi−1 × pi . Für m[i, j] gilt: 0 i=j m[i, j] = mini≤k<j (m[i, k] + m[k + 1, j] + pi−1 pk pj ) i < j Um nicht nur die minimalen Auswertungskosten zu berechnen, sondern auch entsprechend optimal Klammern zu können, merken wir uns die optimalen Splits k in einer Matrix s[i, j]. 428 Berechnung der optimalen Kosten Wir bestimmen zunächst die Anzahl #T P der Teilprobleme Ai...j , für die eine optimale Klammerung gefunden werden muß. Es muß gelten: Also gilt für #T P : 1≤i≤j≤n n + n = Θ(n2 ) #T P = 2 Trick des dyn. Programmierens: • Anstelle Lösung rekursiv zu berechnen (top-down) • werden alle Teillösungen bottom-up bestimmt. Dadurch: Unnötigen Berechnungen werden vermieden. 429 Annahmen für Algorithmus • Matrizen: Ai für 1 ≤ i ≤ n • Dimension von Ai : pi−1 × pi für 1 ≤ i ≤ n • Eingabe: Sequenz hp0 , . . . , pn i mit length[p] = n + 1 • Hilfsstrukturen: – m[1. . . n, 1 . . . n] für minimalen Kosten Ai...j – s[1. . . n, 1 . . . n] für optimale Splits von Ai...j 430 MATRIX-CHAIN-ORDER(p) 1 n ← length[p] − 1 2 for i ← 1 to n 3 do m[i, j] ← 0 4 for l ← 2 to n 5 do for i ← 1 to n − l + 1 6 do j ← i + l − 1 7 m[i, j] ← ∞ 8 for k ← i to j − 1 9 do q ← m[i, k] + m[k + 1,j] + pi−1 pk pj 10 if q < m[i, j] 11 then m[i, j] ← q 12 s[i, j] ← k 13 return m and s 431 Anmerkungen Z1-3: Kosten m[i, i] sind 0. Z4-12: Bestimme Kosten m[i, i + l − 1]: 1. m[i, i + 1] zuerst (Länge l der Sequenz = 2) 2. m[i, i + 2] dann, usw. Z9-12: Bestimmung des besten Splits (k) Anschaulich 432 m [i, i + l - 1] j j i i j i j l-1 weit 433 m 6 15.125 5 j 9.375 15.750 2 10.500 7.125 4.375 7.875 2 1 11.875 4 3 1 5.375 2.500 750 2.625 i 3 4 3.500 1.000 5 5.000 6 0 0 0 0 0 0 A1 A2 A3 A4 A5 A 6 s 6 j 1 3 3 3 2 2 i 3 3 1 2 3 5 4 3 1 3 3 3 3 5 4 434 4 5 5 Laufzeit Laufzeit: • O(n3 ) (viel besser als alle Klammerungen zu bestimmen) 435 Konstruktion des Optimums Input: • Matrizen A = hA1 , . . . , An i • Splits s[i, j] • Indizes i, j, deren Bereich zu multiplizieren ist. Output: • Ai...j 436 Konstruktion des Optimums s[i, j] beinhaltet das optimale k zum Splitten. Die letzte Multiplikation, um A1...n zu berechen ist: A1...s[1,n] As[1,n]+1...n Die letzte Multiplikation, um A1...s[1,n] zu berechnen ist: A1...s[1,s[1,n]] As[1,s[1,n]]+1,s[1,n] Die letzte Multiplikation, um As[1,n]...n zu berechnen ist: As[1,n]+1...s[s[1,n]+1,n] A + s[s[1, n] + 1, n] + 1, n Entsprechend arbeitet der folgende Algorithmus rekursiv: 437 MATRIX-CHAIN-MULT(A, s, i, j) 1 if j > i 2 then X ← MATRIX-CHAIN-MULT(A, s, i, s[i, j]) 3 Y ← MATRIX-CHAIN-MULT(A, s, s[i, j] + 1, j) 4 return MATRIX-MULT(X, Y ) 5 else return Ai 438 Zutaten für DP Wann ist DP anwendbar? Wenn zwei Voraussetzungen gegeben sind: 1. Optimalitätsprinzip 2. gemeinsame Teilprobleme häufiges Vorkommen derselben weniger Teilprobleme als potentielle Lösungen 439 Optimalitätsprinzip Langversion: Eine optimale Folge von Entscheidungen besitzt die Eigenschaft, dass unabhängig vom Anfangszustand und von der Anfangsentscheidung, die übrigen Entscheidungen eine optimale Entscheidungsreihenfolge bilden müssen, unter Berücksichtigung des aus der ersten Entscheidung resultierenden Zustands. Kurzversion: Die in einer optimalen Lösung vorkommenden Teillösungen sind wiederum optimal. 440 Gemeinsame Teilprobleme liegen vor, falls eine rekursive Lösung eines Problems die gleichen Teilprobleme mehr als einmal generiert. 441 Bsp REC-MATRIX-CHAIN(p, i, j) 1 if i = j 2 then return 0 3 m[i, j] ← ∞ 4 for k ← i to j − 1 5 do q ← REC-MATRIX-CHAIN(p, i, k) + REC-MAT-CHAIN(p, k + 1, j) + pi−1 pk pj 6 if q < m[i, j] 7 then m[i, j] ← q 8 return m[i, j] 442 1..4 1..1 2..4 2..2 3..4 2..3 4..4 1..2 3..4 3..3 4..4 1..1 2..2 1..3 4..4 1..1 2..3 1..2 3..3 2..2 3..3 1..1 2..2 3..3 4..4 2..2 3..3 443 Analyse Behauptung: Die Laufzeit T (n) des Algorithmus RECURSIVE-MATRIX-CHAIN ist zumindest exponentiell. Die Schritte 1 − 2 und 6 − 7 benötigen mindestens eine Zeiteinheit. Es ergibt sich die Rekurrenz: T (1) ≥ 1 T (n) ≥ 1+ = n−1 X k=1 2 n−1 X (T (k) + T (n − k) + 1) T (i) + n i=1 Wir zeigen T (n) = Ω(2n ) durch die Substitutionsmethode. 444 Wir zeigen: T (n) ≥ 2n−1 I.A.: T (1) ≥ 1 = 20 I.S.: T (n) ≥ 2 = 2 n−1 X 2i−1 + n i=1 n−2 X 2i + n i=0 n−1 = 2(2 − 1) + n = (2n − 2) + n ≥ 2n−1 445 Memoization Problem bei rekursiver Variante: • Teilprobleme werden mehrfach gelöst Idee: • errechnete Lösungen werden abspeichert • Vor dem Lösen eines Teilproblems wird nachgeschaut, ob es bereits gelöst wurde. In diesem Fall wird es natürlich nicht neu gelöst, sondern es wird die bereits errechnete Lösung verwendet. 446 MEMOIZED-MATRIX-CHAIN MEMOIZED-MATRIX-CHAIN(p) 1 n ← length[p] − 1 2 for i ← 1 to n 3 do for j ← i to n 4 do m[i, j] ← ∞ 5 return LOOKUP-CHAIN(p, 1, n) 447 LOOKUP-CHAIN LOOKUP-CHAIN(p, i, j) 1 if m[i, j] < ∞ 2 then return m[i, j] 3 if i = j 4 then m[i, j] ← 0 5 else for k ← i to j − 1 6 do q ← LOOKUP-CHAIN(p, i, k) + LOOKUP-CHAIN(p, k + 1,j) + pi−1 pk pj 7 if q < m[i, j] 8 then m[i, j] ← q 9 return m[i, j] Laufzeit: O(n3 ) 448 Wann DP, wann Memoization? • Wenn alle Teilprobleme berechnet werden müssen ist DP schneller. • Memoization oft einfacher zu implementieren. 449 Längste gemeinsame Teilsequenz Definition. Gegeben seien zwei Sequenzen X = hx1 , . . . , xm i und Z = hz1 , . . . , zk i. Z heißt Untersequenz von X, falls es eine aufsteigende Sequenz hi1 , . . . , ik i von Indizes gibt, so daß für alle 1 ≤ j ≤ k gilt: xi j = z j . 450 Beispiel ist eine Untersequenz von Z = hB, C, D, Bi X = hA, B, C, B, D, A, Bi 451 Längste gemeinsame Teilsequenz Definition. Gegeben zwei Sequenzen X und Y . Eine Sequenz Z heißt gemeinsame Untersequenz von X und Y , falls Z eine Untersequenz von X und Y ist. Beispiel. Für X Y = hA, B, C, B, D, A, Bi = hB, D, C, A, B, Ai ist hB, C, Ai eine gemeinsame Untersequenz von X und Y . Sie ist aber nicht die längste gemeinsame Untersequenz (LCS). LCSs von X und Y sind hB, C, B, Ai und hB, D, A, Bi. 452 Längste gemeinsame Teilsequenz Problemdefinition Gegeben • Sequenzen – X = hx1 , . . . , xm i – Y = hy1 , . . . , yn i Gesucht • längste gemeinsame Teilsequenz Z von X und Y 453 Brute Force Solution 1. Bestimme alle Teilsequenzen von X 2. Test jede, ob sie auch Teilsequenz von Y ist 3. Nimm längste solche 454 Brute Force Solution (Aufwand) • jede Teilsequenz von X korrespondiert zu einer Menge {1, . . . , m} von Indizes von X • es gibt 2m solche Teilsequenzen • Gesamtaufwand also mindestens exponentiell 455 Optimalitätsprinzip Präfixe bilden bei unserem Problem die Grundlage für die Etablierung des Optimalitätsprinzips. Definition. Sei X = hx1 , . . . , xm i eine Sequenz. Für i ≤ m ist Xi = hx1 , . . . , xi i ein Präfix von X. Beispiel. • X = hA, B, C, B, D, A, Bi • X4 = hA, B, C, Bi 456 Optimalitätsprinzip Satz Seien X = hx1 , . . . , xm i und Y = hy1 , . . . , yn i Sequenzen und sei Z = hz1 , . . . , zk i eine LCS. Dann gilt: 1. xm = yn =⇒ • zk = xm = yn und • Zk−1 ist eine LCS von Xm−1 und Yn−1 2. xm 6= yn und zk 6= xm =⇒ • Z ist eine LCS von Xm−1 und Y 3. xm 6= yn und zk 6= yn =⇒ • Z ist eine LCS von X und Yn−1 Anm. Jede LCS enthält eine LCS von zwei Präfixen von X und Y . 457 Beweis (1) Falls zk 6= xm , so könnten wir xm = yn an Z hängen und hätten eine längere LCS von X und Y . (Widerspruch). Also muß gelten zk = xm = yn . Offensichtlich: Zk−1 ist eine (k − 1)-lange CS von Xm−1 und Yn−1 . zu zeigen: Zk−1 ist LCS. Annahme: ∃ W CS von Xm−1 und Yn−1 , W länger als k − 1. Dann erhalten wir durch anhängen von xm = yn eine CS länger als k. (Widerspruch). (2) Da zk 6= xm ist Z eine CS von Xm−1 und Y . Annahme: ∃ W CS von Xm−1 und Y und |W | > k. Dann ist W auch CS von X und Y . (Widerspruch). (3) Analog. 458 Rekursive Lösung Nach Satz: Zur Lösung des Problems sind entweder eine oder zwei Alternativen zu untersuchen: • Falls xm = yn , müssen wir eine LCS von Xm−1 und Yn−1 finden und xm = yn anhängen. • Falls xm 6= yn , so müssen wir die LCS von 1. X und Yn−1 2. Xm−1 und Y suchen und die Längere zurückgeben. Beide dieser Probleme haben das Unterproblem – finde LCS von Xm−1 und Yn−1 . 459 Rekursive Lösung Wie beim Matrizensequenzenmultiplikationsproblem erarbeiten wir eine Rekurrenz zur Beschreibung der Kosten der optimalen Lösung. Sei c[i, j] die Länge einer LCS der Sequenzen Xi und Yj . Falls entweder i = 0 oder j = 0, so ist c[i, j] = 0. Rekurrenz: i = 0 oder j = 0 0 c[i, j] = c[i − 1][j − 1] + 1 i, j > 0 und xi = yj max(c[i, j − 1], c[i − 1, j]) i, j > 0 und xi 6= yj 460 Berechnung der Länge einer LCS Mit dieser Rekurrenz könnten wir schnell einen rekursiven Algorithmus schreiben. Dieser hätte aber exponentiellen Aufwand. Da die Anzahl der Teilprobleme aber nur Θ(mn) ist, benutzen wir dyn. Programmieren (also einen bottom-up Ansatz). Die Prozedur LCS-LENGTH nimmt als Argumente zwei Sequenzen X = hx1 , . . . , xm i und Y = hy1 , . . . , yn i und berechnet die c[i, j] in einem Feld c[0 . . . m, 0 . . . n]. (Berechnung der c[i, j] erfolgt zeilenweise von links nach rechts und dann von oben nach unten.) Um die LCS zu bestimmen, verwalten wir noch b[i, j]. Dies enthält Verweise auf die optimalen Teillösungen, die für c[i, j] gewählt wurden. 461 LCS-LENGTH(X, Y ) 1 m ← length[X] 2 n ← length[Y ] 3 for i ← 1 to m 4 do c[i,0] ← 0 5 for j ← 0 to n 6 do c[0,j] ← 0 462 7 for i ← 1 to m 8 do for j ← 1 to n 9 do if xi = yj 10 then c[i, j] ← c[i −1, j − 1] + 1 11 b[i, j] ← “ -00 12 else if c[i − 1, j] ≥ c[i, j − 1] 13 then c[i, j] ← c[i − 1, j] 14 b[i, j] ← “ ↑00 15 else c[i, j] ← c[i, j − 1] 16 b[i, j] ← “ ←00 17 return c and b 463 Erläuterungen Laufzeit: O(mn). Die Zeiger merken sich, wo man die größte Sublösung gefunden hat: - in b[i, j]: xi = yj ist in LCS ↑ in b[i, j]: LCS enthält nicht xi = yj , schaue bei b[i − 1, j] ← in b[i, j]: LCS enthält nicht xi = yj , schaue bei b[i, j − 1] Z1-6 Initialisierung Z7/8 Schleife zum Füllen von c[i, j] und b[i, j] Z9-16 Fallunterscheidung gemäß Rekurrenz 464 Beispiel 465 Konstruktion der LCS PRINT-LCS(b, X, i, j) 1 if i = 0 or j = 0 2 then return 3 if b[i, j] = “-” 4 then PRINT-LCS(b, X, i − 1, j − 1) 5 print xi 6 elseif b[i, j] = “↑” 7 then PRINT-LCS(b, X, i − 1,j) 8 else PRINT-LCS(b, X, i, j − 1) Initialer Aufruf: LCS-LENGTH(b,X,length[X],length[Y]). Laufzeit: O(n + m). Achtung: Besuchsreihenfolge ist umgekehrte Ausgabereihenfolge. (linksrekursiv) 466 Codeverbesserungen b[i, j] ist nicht notwendig, da wir anhand von c[i − 1, j − 1], c[i, j − 1] und c[i − 1, j] die Entscheidung bei c[i, j] rekonstruieren können. Details: Übung. 467 Einleitung Optimierungsalgorithmen durchlaufen üblicherweise eine Anzahl von Schritten, bei denen dann eine Wahl getroffen werden muß. Ein gieriger Algorithmus (greedy algorithm) nimmt die Wahl, die im Moment am günstigsten aussieht: Nimm immer das größte Stück Kuchen. Die Hoffnung hier ist dann, dass die global optimale Lösung sich aus lokal optimalen Entscheidungen zusammensetzt. Dies ist aber nicht immer garantiert!!! 468 Anwendungen Viele Algorithmen, die wir noch kennenlernen werden, sind gierige Algorithmen: • Huffman-Kompression • minimale spannende Bäume • kürzeste Wege 469 Aktivitätsauswahlproblem Wir wollen Aktivitäten für eine Resource einplanen. 470 Aktivitätsauswahlproblem Gegeben 1. eine Resource R, die zu einem Zeitpunkt nur von einer Aktivität genutzt werden kann. Bsp: R ist ein Hörsaal. 2. n Aktivitäten S = {1, 2, . . . , n} 3. jede Aktivität i hat • eine Startzeit si und • eine Endzeit fi . mit si ≤ fi . Zwei Aktivitäten i und j heißen kompatibel, falls die Intervalle [si , fi [ und [sj , fj [ nicht überlappen. Gesucht: maximale Teilmenge kompatibler Aktivitäten von S 471 Der gierige Algorithmus Annahme • die Aktivitäten sind sortiert nach aufsteigenden Endzeiten: f1 ≤ f 2 ≤ . . . ≤ f n • Repräsentation der s undf als arrays. • nimm immer diejenige Aktivität, die nicht mit den vorherigen Aktivitäten kollidiert und die kleinste Endzeit hat • somit wird die Zeit, die noch für die Resource zur Verfügung steht maximiert 472 GREEDY-ACTIVITY-SELECTOR(s, f ) 1 n ← length[s] 2 A ← {1} 3 j ←1 4 for i ← 2 to n 5 do if si ≥ fj 6 then A ← A ∪ {i} 7 j←i 8 return A 473 Anmerkungen 1. fj = max{fk |k ∈ A} 2. Z2-3: initialisiere A und j. 3. Z4-7: untersuche jede Aktivität i (Z4) auf Kompatibilität mit den Aktivitäten in A (Z5). Falls i kompatibel zu A, so kann A um i erweitert werden (Z6) und j muß angepaßt werden. 4. Z8: gib Ergebnis A zurück 5. Laufzeit: Θ(n), falls die Aktivitäten schon nach fi sortiert. Zusätzliches Sortieren kostet höchstens O(n lg n). 474 Optimalität Satz Der Algorithmus GREEDY-ACTIVITY-SELECTOR findet für ein Aktivitätsauswahlproblem eine optimale Lösungen. 475 Beweis Sei S = {1, 2, . . . , n} die Menge der Aktivitäten. Sei f1 ≤ f2 ≤ . . . ≤ fn . Wir zeigen, dass es eine optimale Lösung gibt, die Aktivität 1 enthält. Sei A ⊆ S eine optimale Lösung. Seien die Aktivitäten in A gemäß Endzeiten sortiert. Sei k die erste Aktivität in A. Falls k = 1, so sind wir fertig. Falls k 6= 1, so zeigen wir, dass B = A \ {k} ∪ {1} eine optimale Lösung ist (die 1 enthält). Da f1 ≤ fk , sind die Aktivitäten in B kompatibel. Da |B| = |A| ist B auch eine optimale Lösung. Also gibt es immer eine optimale Lösung, die 1 enthält. Einfache Induktion ergibt die Behauptung: 476 Optimalitätsprinzip Falls A eine optimale Lösung (für S) ist, die 1 enthält, so ist A0 = A \ {1} eine optimale Lösung für S 0 = {i ∈ S|si ≥ f1 } Da Falls es eine Lösung B 0 von S 0 gibt mit |B 0 | > |A0 |, so wäre B = B 0 ∪ {1} eine Lösung von S mit |B| > |A|. (Widerspruch.) Daher verkleinert sich das Problem nach einer gierigen Auswahl. Induktion über die Anzahl der Auswahlen vervollständigt den Beweis. 477 Zutaten für gierige Algorithmen 1. Gierige-Auswahl-Eigenschaft 2. Optimalitätsprinzip 478 Gierige-Auswahl-Eigenschaft Eine global optimale Lösung kann durch eine Folge von lokal optimalen (gierige) Auswahlen konstruiert werden. Zur Erinnerung: Beim DP wurde hing die Auswahl von der optimalen Lösung von Teilproblemen ab. Dies darf hier nicht sein. Die Auswahl bei gierigen Algorithmen kann von vorherigen Auswahlen abhängig sein, aber niemals von noch zu machenden. Gierige Algorithmen arbeiten top-down: ein Problem wird durch eine Wahl in ein kleineres transformiert. Optimalität ist aber immer zu beweisen. Typische Vorgehensweise wie oben: Man nehme eine optimale Lösung und zeige, dass man sie in eine optimale Lösung transformieren kann, die von gierigen Algorithmus erzeugt wird. (per Induktion) 479 Optimalitätsprinzip wie bei DP, war oben bei Induktionsschluss notwendig. 480 Wann Dynamisches Programmieren, wann Greedy-Algorithmus? Zwei Fehler: 1. Man könnte Dynamisches Programmieren verwenden, wenn Greedy-Algorithmus ausreicht. 2. Man könnte Greedy-Algorithmus verwenden, wenn Dynamisches Programmieren notwendig ist. Zur Illustration zwei Beispielprobleme: 1. 0-1-Rucksack-Problem 2. fraktale Rucksack-Problem 481 0-1-Rucksackproblem Raub eines Ladens. Jeder Gegenstand i kostet vi SF und wiegt wi Kilogramm. Der Dieb will natürlich den Wert seines Raubs maximieren. Leider kann er aber maximal W Kilogramm tragen. Heißt 0-1, da der Dieb einen Gegenstand entweder ganz oder gar nicht und nicht mehrfach mitnehmen kann. 482 Fraktales Rucksackproblem Gleiches Szenario, aber der Dieb kann auch Teile von Gegenständen mitnehmen. (Juwelierladen, Brillianten rausbrechen, Brilliantsplitter, Goldbarren, halbe Goldbarren, Goldstaub) 483 Eigenschaften Beide Probleme gehorchen Optimalitätsprinzip 0-1: Man nehme einen optimal gefüllten Rucksack mit Gewicht W . Entfernt man einen Gegenstand i, so muß der Rucksack eine optimale Lösung für das Gewicht W − wi sein. Frak: Nimm optimalen Rucksack mit Gewicht ≤ W . Falls wir das (einen Teil eines Gegenstandes mit) Gewicht w entfernen, so muß das Ergebnis eine optimale Lösung für W − w sein. Dabei kommen alle Gegenstände außer j in Frage plus der Teil von j mit Gewicht wj − w. 484 Eigenschaften Obwohl die Probleme ähnlich sind gilt: • Frak. Rucksack kann mit gierigem Algorithmus gelöst werden. • 0-1 Rucksack kann nicht mit gierigem Algorithmus gelöst werden, erfordert also DP. 485 Frak. Rucksack gieriger Algorithmus: 1. sortiere die Gegenstände nach abfallenden vi /wi Koeffizienten. (also nach Wert pro Gewicht). 2. nimm immer soviel wie möglich von dem Gegenstand mit höchstem vi /wi Koeffizienten. Grenzen: W , Gegenstand ganz geschluckt. Beweis, dass die gierige Auswahleigenschaft gilt: Übung. Aufwand: O(n lg n) 486 0-1 Rucksack gieriger Algorithmus suboptimal (Bsp.,W = 50): Gegenstand Wert Gewicht w/g 1 60 10 6 2 100 20 5 3 120 30 4 Wahl Gesamtgewicht Gesamtwert Ergebnis: 1 10 60 2 30 160 Wahl Gesamtgewicht Gesamtwert Besser: 2 20 100 3 50 220 487 item 3 item 2 item 1 30 20 10 $100 $60 50 $120 (a) knapsack 10 30 20 $80 $120 + 20 $100 = $220 20 $100 + 30 $120 10 $60 10 = $160 + $60 = $180 (b) 20 $100 + 10 $60 = $240 488 (c) Huffman Kompression Aufgabe: Gegegeben eine Datei. Kann man die enthaltenen Daten so komprimieren, so dass die komprimierte Datei weniger Plattenplatz weg nimmt, und die Originaldatei wieder rekonstruiert werden kann? Dies ist oft mit Huffman Kompression möglich. Kompressionsraten: 20%-80%. Grundlage: Kodierung aller Zeichen einer Datei mittels unterschiedlich langer Bitmuster. Die Kodierung heißt dann Huffman Code. 489 Huffman Code Gegeben: Datei mit 100.000 Zeichen, aber nur 6 verschiedenen (a-f). Normaler Speicheraufwand: 100KB. Kodieren: Häufigkeit (in k) Code fester Länge Code variabler L. a b c d e f 45 13 12 16 9 5 000 001 010 011 100 101 0 101 100 111 1101 1100 Aufwand: • feste Länge: 37.5kB • (45 ∗ 1 + 13 ∗ 3 + 12 ∗ 3 + 16 ∗ 3 + 9 ∗ 4 + 5 ∗ 4)/8 = 28.0 kB 490 Präfix-Kodierungen Damit wir auch wieder dekodieren können, ist es sinnvoll nur Präfixkodierungen zu betrachten. Definition. Eine Präfixkodierung ist eine Kodierung bei der kein Codewort Präfix eines anderen ist. Satz. Für jede Zeichenkodierung, die zu einer optimalen Kompression führt, gibt es eine nicht schlechtere Präfixkodierung. 491 Vorteile Präfixkodierung einfache Codierung: abc 0 · 101 · 100 = 0101100 einfache Dekodierung: 0101100 a101100 1. erster Buchstabe kann nur a sein. 2. dann kann zweiter Buchstabe nur b sein und 3. dritter nur c. 492 ab100 abc Repräsentation von Codierungen Repräsentation als Binärbäume: Linker Sohn für 0 rechter Sohn für 1. Diese sind zum Dekodieren sehr nützlich. Optimale Kodierungen sind immer vollständige Binärbäume, das heißt, jeder nicht-Blattknoten hat zwei Söhne. Wir werden nur solche betrachten. Falls C das Alphabet ist (unsere Zeichenmenge), dann gibt es genau |C| Blätter, eines für jedes Zeichen, und genau |C| − 1 innere Knoten. 493 Beispiel 100 0 1 a:45 55 1 0 30 25 0 c:12 b:13 f :5 494 d:16 14 0 (b) 1 0 1 1 e:9 Anzahl der benötigten Bits Gegeben sei • zu einem Präfixcode ein Baum T • für jedes Zeichen c die Häufigkeit f (c) in der zu kodierenden Datei • und dT (c) die Tiefe des Blattes von c, also die Anzahl der Bits, die notwendig sind um c zu kodieren. Dann ist die Anzahl der Bits, die notwendig sind um eine Daten zu kodieren: X f (c)dT (c) B(T ) = c∈C Dies seien die Kosten des Codes. 495 Konstruktion von Huffmankodierungen Huffman erfand einen gierigen Algorithmus, der den optimalen Präfixcode konstruiert. Der Algorithmus baut einen entsprechenden Baum T auf. Er startet mit |C| Blättern und verbindet dann je zwei zu einem Baum solange bis nur noch ein Baum übrigbleibt. Im Algorithmus: • |C| = n • f [c] gleich f (c) • Q ist eine Priority-Queue mit f als Schlüssel Das f für einen verbundenen Baum ist die Summe der zwei Teil-f . 496 Konstruktion des Kodierungsbaums nach Huffman HUFFMAN(C) 1 n ← |C| 2 Q←C 3 for i ← 1 to n − 1 4 do z ← ALLOCATE-NODE() 5 x ← lef t[z] ← EXTRACT-MIN(Q) 6 y ← right[z] ← EXTRACT-MIN(Q) 7 f [z] ← f [x] + f [y] 8 INSERT (Q, z) 9 return EXTRACT-MIN(Q) 497 Erläuterungen Z2: initialisiert Q Z3-8: holt die beiden Knoten x und y mit der geringsten Häufigkeit aus der Queue Q und fügt den neuen Knoten z der x und y als Söhne hat wieder ein. 498 Korrektheit Um zu zeigen, dass HUFFMAN immer den optimalen Präfixcode liefert, zeigen wir, dass das entsprechende Problem die gierige Auwahleigenschaft hat und das Optimalitätsprinzip gilt. Lemma 17.2 Sei C ein Alphabet und seien f [c] die Häufigkeit von c ∈ C. Sind x und y die beiden Zeichen mit geringster Häufigkeit, dann gibt es einen optimalen Präfixcode für C, in dem die Codes für x und y die gleiche Länge haben und der nur um ein Bit differiert. 499 Beweis Sei T ein Baum, der einen optimalen Präfixcode repräsentiert. Wir modifizieren T solange, bis die Folgerung des Satzes gilt, also x und y Brüder sind und beide die maximale Tiefe im neuen Baum haben. Dabei achten wir darauf, dass die Kosten von T gleich bleiben. Seien b und c zwei Zeichen, die Brüder sind und die größte Tiefe in T haben. ObdA: f [b] ≤ f [c] und f [x] ≤ f [y]. Da x und y die geringste Häufigkeit haben, gilt f [x] ≤ f [b] und f [y] ≤ f [c]. Wie in der folgenden Abbildung gezeigt, modifizieren wir • T indem wir x und b vertauschen, um T 0 zu erhalten und modifizieren T 0 weiter • indem wir y und c vertauschen. Dies ergibt T 00 . 500 Illustration der Transformationen im Beweis T T T ’’ ’ x y c y b c b b c x 501 x y Wir erhalten B(T ) − B(T 0 ) X X f (c)dT (c) − f (c)dT 0 (c) = c∈C c∈C = f [x]dT (x) + f [b]dT (b) − f [x]dT 0 (x)f [b]dT 0 (b) = f [x]dT (x) + f [b]dT (b) − f [x]dT (b)f [b]dT (x) = (f [b] − f [x])(dT (b) − dT (x)) ≥ 0 da f [b] − f [x] ≥ 0 und dT [b] − dT [x] ≥ 0 Analog: B(T 0 ) − B(T 00 ) ≥ 0. Also: B(T 00 ) ≤ B(T ). Da T optimal gilt B(T 00 ) = B(T ). 502 Korrektheit Lemma 17.3 Sei T ein vollständiger Binärbaum (jeder nicht-Blattknoten hat zwei Nachfolger), der einen optimalen Präfixcode eines Alphabets C mit Häufigkeiten f [c] für c ∈ C darstellt. Seien x und y zwei Brüder in T und z der Vater. Falls wir dann z als einen (neuen) Buchstaben mit Häufigkeit f [z] := f [x] + f [y] betrachten, so ist T 0 = T \ {x, y} ein optimaler Präfixbaum für C 0 = C \ {x, y} ∪ {z}. 503 Beweis Es gilt: 1. ∀c ∈ C \ {x, y} dT (c) = dT 0 (c) und damit 2. f [c]dT [c] = f [c]dT 0 (c). Weiter gilt 3. dT (x) = dT (y) = dT 0 (z) + 1. Also: f [x]dt (x) + f [y]dT (y) = (f [x] + f [y])(dT 0 (z) + 1) = f [z]dT 0 (z) + (f [x] + f [y]) Es folgt: B(T ) = B(T 0 ) + f [x] + f [y] Falls T 0 keinen optimalen Präfixcode für C 0 repräsentiert, so gibt es einen optimalen T 00 mit B(T 00 ) < T (T ), indem z als Blatt vorkommt. Machen wir x und y zu Söhnen von z, so erhalten wir einen Präfixcode von C mit Kosten B(T 00 ) + f [x] + f [y] < B(T ) Widerspruch zur Optimalität von T . 504 Korrektheit Aus den beiden vorherigen Lemmata folgt direkt: Theorem Die Funktion HUFFMANN erzeugt einen optimalen Präfixcode. 505 Matroid • Die Korrektheitsbeweise für Greedy-Algorithmen sind sehr ähnlich, aber doch mühsam. • Jetzt: Einfache Kriterien, die Korrektheitsbeweise ersparen. 506 Mengensysteme Definition. Ein Mengensystem ist ein Paar (E, S), wobei E eine Menge und S eine unter Inklusion abgeschlossene Teilmenge der Potenzmenge von E ist. (S ⊆ P(E)). Die Elemente aus S heißen unabhängige Mengen. Zu (S, E) gibt es ein zugehöriges Optimierungsproblem: Für eine gegebene Gewichtsfunktion w : E → R+ 0 ist eine unabhängige Menge zu bestimmen, deren Gewicht X w(e) w(A) = e∈A maximal ist. Die Beschränkung auf nicht-negative Gewichte sichert zu, dass eine unabängige Menge maximalen Gewichts o.B.d.A. auch eine maximale unabhängige Menge ist. 507 Matroid Definition Ein Mengensystem (E, S) heißt Matroid, falls die folgende Bedingung erf üllt ist: • Für J, K ∈ S mit |J| = |K| + 1 gibt es stets ein a ∈ J \ K mit K ∪ {a} ∈ S. Diese Eigenschaft heißt auch Austauscheigenschaft. Sie ist das Analogon des Steinitzschen Austauschsatzes der linearen Algebra. Bsp. Sei G = (V, E) ein Graph. Sei S die Menge der Teilmengen von E, die Bäume bilden. Dann ist (E, S) ein Matroid. Bsp. Sei E eine endliche Teilmenge eines Vektorraums V und S die Menge aller linear unabhängigen Teilmengen von E. Dann ist (E, S) ein Matroid. 508 Greedy Algorithm Gegeben sei ein Mengensystem M = (E, S) GREEDY(M, w) 1 sort E = {e1 , . . . , en } fallend w(e1 ) ≥ . . . ≥ w(en ) 2 T←∅ 3 for i ← 1 to n 4 do if T ∪ {ei } ∈ S 5 then T ← T ∪ {ei } 9 return T 509 Matroid&Greedy Satz Sei M = (E, S) ein Mengensystem. Dann sind folgende Aussagen äquivalent: • GREEDY(M, w) produziert für all w das optimale Ergebnis • M ist ein Matroid • Für jede Teilmenge A von E haben alle maximal unabhängigen Teilmengen von A dieselbe Mächtigkeit Anm Bedingung (3) ist das Analogon für die Gleichmächtigkeit der Basen eines Unterraums. Beweis 510 (1) =⇒ (2): Angenommen, GREEDY(M, w) produziert für all w das optimale Ergebnis und M ist kein Matroid =⇒ ∃J, K ∈ S |J| = |K| + 1 ∧ (∀a ∈ J \ K K ∪ {a} 6∈ S Für k := |K|: k+2 e∈K w(e) := k+1 e∈J \K 0 sonst K keine Lösung des Optimierungsproblems, da w(K) = k(k + 2) < (k + 1)2 = w(J) GREEDY wählt aber zuerst alle Elemente aus K, da diese das größte Gewicht haben. Danach wird aber das Gewicht nicht weiter vergrößert, da für alle e entweder w(e) = 0 oder e ∈ J \ K (kann also nicht zu K dazugenommen werden). Also erzeugt GREEDY suboptimales Ergebnis. (Widerspruch.) 511 (2) =⇒ (3): Sei A ⊆ E, beliebig. Seien J, K ⊆ A zwei maximale unabhängige Teilmengen. (d.h.: keine J oder K echt enthaltende Teilmenge liegt in S) Annahme: |K| < |J|. Da S unter Inklusion abgeschlossen ist: ∃J 0 ⊆ J |K| = |J 0 | + 1 Nach (2) ∃a ∈ J 0 \ K K ∪ {a} ∈ S (Widerspruch.) 512 (3) =⇒ (1): Annahme: (3) und GREEDY suboptimal Dann existiert w so dass GREEDY K = {e1 , . . . , ek } konstruiert, obwohl es gibt mit w(J) > w(K). J = {e01 , . . . , e0h } obdA: K, J nach absteigendem Gewicht geordnet und J maximale unabh. Teilmenge von E. Nach Konstruktion ist auch K maximal. mit (3) für A = E gilt also h = k. Wir zeigen durch Induktion: (Widerspruch zu w(K) < w(J)) w(ei ) ≥ w(e0i ) 513 I.A.: Nach Definition von GREEDY hat e1 maximales Gewicht. √ I.S.: Annahme gelte für i ≤ n. Annahme: w(en+1 ) < w(e0n+1 ). Sei A = {e ∈ E|w(e) ≥ w(e0n+1 )}. Dann ist {e1 , . . . , en } eine maximale unabh. Teilmenge von A, denn wenn es ein e geben würde mit {e1 , . . . , en , e} ∈ S, so w(e) ≤ w(en+1 ) < w(e0n+1 ), also e 6∈ A. Da aber auch {e01 , . . . , e0n+1 } eine unabh. Teilmenge von A ist, erhalten wir einen Widerspruch zu (3). 514 Matroide Die maximalen unab. Teilmengen eines Matroids M = (E, S) werden Basen genannt. Damit berechnet GREEDY eine Basis von M . Der Rang ρ(A) einer Teilmenge A von E ist die Mächtigkeit einer maximalen unabh. Teilmenge von A. Jede nicht in S enthaltene Menge heißt abhängig. Eine minimale abhängige Menge heißt Zyklus. 515 Dualität Satz Sei M = (E, S) ein Matroid. Dann ist auch M ∗ = (E, S ∗ ) mit S ∗ = {J ⊆ E|∃Basis B von M mit J ⊆ E \ B} ein Matroid, dessen Rangfunktion ρ∗ durch ρ∗ (A) = |A| + ρ(E \ A) − ρ(A) gegeben ist. Definition M ∗ heißt dualer Matroid von M . Dieser Dualitätsbegriff ist ein anderer als in der linearen Algebra. Es gilt also insbesondere nicht, dass der duale Matroid eines endlichen Vektorraums der Matroid des Dualraums ist. Ein Zyklus in M ∗ heißt Cozyklus von M . 516 Gegeben sei ein Matroid M = (E, S) und eine Gewichtsfunktion w. DUAL-GREEDY(M, w) 1 sort E = {e1 , . . . , en } steigend w(e1 ) ≤ . . . ≤ w(en ) 2 T←E 3 for i ← 1 to n 4 do if (E \ T ) ∪ {ei } enthält keinen Cozyklus 5 then T ← T \ {ei } 9 return T 517 Satz Der Algorithmus DUAL-GREEDY berechnet eine Basis B von M = (E, S), die maximales Gewicht hat. 518 Datenstrukturen für disjunkte Mengen 519 Problemstellung • Eine Datenstruktur für disjunkte Mengen verwaltet eine Menge S = {S1 , . . . , Sk } von disjunkten Mengen Si . • Jede Menge wird durch einen Repräsentanten (ein Element aus der Menge) repräsentiert. • Sei x ein Objekt das so zu verwalten ist. Dann benötigen wir folgende Operationen. – MAKE-SET(x) erzeugt eine neue Menge mit x als Element und Repräsentant. Da Mengen disjunkt: x noch in keiner Menge – UNION(x,y) vereinigt die durch x and y repräsentierten Mengen und wählt entweder x odery als Repräsentant der Vereinigung. – FIND-SET(x) gibt den Zeiger auf den Repräsentanten der Menge zurück, in der sich x befindet. 520 Analyse-Parameter Wir analysieren die Laufzeit der Datenstrukturen (und ihrer Algorthmen) in Abhängigkeit von 2 Parametern: n Anzahl der MAKE-SET Operationen m Gesamtanzahl der MAKE-SET, UNION, und FIND-SET Operationen 521 Beispielanwendung Erzeugung Zusammenhangskomponenten für ungerichtete Graphen. (Geht schneller mit Tiefensuche.) CONNECTED-COMPONENTS(G) 1 for each vertex v ∈ V [G] 2 do MAKE-SET(v) 3 for each edge (u, v) ∈ E[G] 4 do if FIND-SET(u) != FIND-SET(v) 5 then UNION(u,v) SAME-COMPONENT(u,v) 1 if FIND-SET(u) = FIND-SET(v) 2 then return true; 3 else return false; 522 Verkettete Listen für disjunkte Mengen 523 c h e f g d f g d b c 524 h e b Analyse • Problem: Anpassen der Zeiger bei Union. Es müssen soviele Zeiger angepaßt werden, wie in einer der beiden Liste stehen. Wir passen die Zeiger der 1. Argumentliste an. • Dies führt zu Aufwand Θ(m2 ) für eine Folge von m Operationen. • Sei n = dm/2e + 1 • Sei q = m − n = bm/2c − 1 • Seien x1 , . . . , xn die einzufügenden Elemente. • Die Folge auf der nächsten Folie hat m = n + q Operationen. 525 Analyse Operation Number of objects updated MAKE-SET(x1 ) 1 MAKE-SET(x2 ) 1 MAKE-SET(x3 ) 1 ... 1 MAKE-SET(xn ) 1 UNION(x1 , x2 ) 1 UNION(x2 , x3 ) 2 UNION(x3 , x4 ) 3 ... · UNION(xq−1 , xq ) P = q−1 Θ(n + q 2 ) = Θ(m2 ) 526 Weighted-Union Heuristic • Liste verwalten (materialisieren) ihre Länge • bei UNION wird die kürzere an die längere Liste gehängt, so das weniger Zeiger umgebogen werden müssen. • Diese Heuristik heißt weighted-union heuristic. Theorem: Eine auf verketteten Listen basierende Implementierung einer Datenstruktur für disjunkte Mengen benötigt bei Verwendung der weighted-union heuristic eine Laufzeit von O(m + n lg n). 527 Beweis • Für ein festes x: – beim ersten Update des Zeigers auf den Repräsentanten hat die Menge nach dem UNION die Mindestgröße 2. – beim zweiten Update: 4, etc. • Für alle k ≤ n: Für eine Menge mit k Elementen, in der x ist, wurde der Zeiger von x höchstens dlg ke mal geändert. • Da die größte Menge höchstens n Elemente hat, ergibt sich O(n lg n) für alle UNION-Operationen. • Jedes MAKE-SET und jedes FIND-SET braucht O(1) und es gibt O(m) von diesen Operationen. • Die Gesamtlaufzeit ist also O(m + n lg n). 528 Wald disjunkter Mengen • Schneller: Wald • Jeder Knoten nur Zeiger auf Vaterknoten • Wurzelknoten zeigt auf sich selbst • Wird schneller als Listen nur durch 2 Heuristiken • MAKE-SET: neuer Baum mit nur einem Knoten • FIND-SET: Zeiger bis zur Wurzel • UNION: Eine Wurzel zeigt auf andere Wurzel (siehe Bild) 529 Wald disjunkter Mengen 530 c h f e d b g f c h d e g b 531 Die Heuristiken Union by Rank: • Idee: Baum mit weniger Knoten wird angehängt • Rang: Approximiert Logarithmus der Anzahl der Knoten • UNION: Baum mit kleinerem Rang wird unter den mit größerem Rang gehängt Path Compression: • Während FIND-SET werden Zeiger auf dem Pfad zur Wurzel auf die Wurzel umgebogen (BILD) 532 MAKE-SET(x) 1 p[x] ← x 2 rank[x] ← 0 UNION(x,y) 1 LINK(FIND-SET(x),FIND-SET(y)) LINK(x,y) 1 if rank[x] > rank[y] 2 then p[y] ← p[x] 3 else p[x] ← p[y] 4 if rank[x] = rank[y] 5 then rank[y] ← rank[y] + 1 533 FIND-SET(x) 1 if x 6= p[x] 2 then p[x] ← FIND-SET(p[x]) 3 return p[x] 534 Analyse • Ohne Heuristiken: Θ(m2 ) • nur mit Union by Rank: O(m lg n) • nur mit Path Compression: – O(f log(1+f /n) n) für f ≥ n – O(n + f lg n) für f < n f = Anzahl der FIND-SET Operationen • beide Heuristiken: O(mα(m, n)) wobei α ist die inverse Ackermannfunktion. In jeder vorstellbaren Anwendung ist α(m, n) ≤ 4. 535 Ackermannfunktion A(1, j) = 2j (1) A(i, 1) = A(i − 1, 2) (2) A(i, j) = A(i − 1, A(i, j − 1)) j=1 j=2 j=3 j=4 i=1 21 22 23 24 i=2 22 22 ... ... i=3 22 ... ... 2 2 ... 536 (3) Graphalgorithmen: Motivation Graphen kommen immer und immer wieder vor. Wir betrachten einige elementare Algorithmen auf Graphen, die Probleme lösen, die man immer wieder zu lösen hat. 1. Repräsentation von Graphen 2. Graphtraversierungen 3. topologische Sortierung 4. Zerlegung in strenge Zusammenhangskomponenten 5. minimale Spannbäume 6. kürzeste Pfade 7. Flußprobleme 537 Vereinbarungen Die Komplexität von Algorithmen, die auf einem Graphen G = (V, E) arbeiten, hängt sowohl von |V | als auch |E| ab. Wir haben daher zwei Parameter. Innerhalb asymptotischer Notationen kürzen wir |V | und |E| als V und E ab. Also ist O(V E) die Abkürzung von O(|V ||E|). In Algorithmen bezeichen wir für einen Graphen G die Menge der Knoten als V [G] und die Menge der Kanten als E[G]. Wir betrachten also die Kantenmenge und Knotenmenge eines Graphen als dessen Attribute. 538 Repräsentationen von Graphen Zwei gebräuchliche Möglichkeiten: 1. Adjazenzlisten 2. Adjazenzmatrizen Adjazenzlisten werden normalerweise bevorzugt, insbesondere bei spärlichen Graphen (solche, bei denen |E| viel kleiner als |V |2 ist). Bei dichten Graphen bevorzugt man auch öfter Adjazenzmatrizen. 539 Adjazenzlisten Für einen Graphen G = (V, E) besteht die Adjazenzlistenrepräsentation aus einem Feld Adj von |V | Listen. Jeder dieser Listen ist genau einem Knoten aus V zugeordnet. Für jedes u ∈ V enthält Adj[u] eine Liste von Zeigern zu denjenigen v ∈ V für die gilt (u, v) ∈ E. Für gerichtete Graphen ist die Summe aller Listenlängen gleich |E|, für ungerichtete Graphen gleich 2|E|. In beiden Fällen genügt O(max(V, E)) = O(V + E) Speicher. 540 1 2 3 4 5 1 2 5 2 1 5 3 2 4 4 2 5 3 4 1 2 5 (a) 3 4 (b) 541 1 2 5 4 3 1 2 2 5 3 6 4 2 5 4 6 6 4 5 6 (b) (a) 542 Gewichtete Graphen Adjazenzlisten können leicht auf gewichtete Graphen verallgemeinert werden. Ein Graph G = (V, E) heißt gewichtet, falls es eine Gewichtsfunktion w : E → R gibt, die jeder Kante ein Gewicht aus dem reellen Zahlen zuordnet. Dieses Gewicht wird dann einfach mit in der Adjazenzliste gespeichert. Suche ist ein bischen aufwendig. Statt Liste könnte man dann aber auch Hashtabelle o.ä. nehmen. 543 Adjazenzmatrizen Für einen Graphen G = (V, E) ist die Adjazenzmatrix eine Matrix der Dimension |V | × |V | mit 1 für (i, j) ∈ E ai,j = 0 sonst Der Speicheraufwand für Adjazenzmatrizen beträgt V 2 . 544 1 2 3 5 (a) 4 1 2 3 4 5 1 0 1 0 0 1 2 1 0 1 1 1 3 0 1 0 1 0 4 0 1 1 0 1 5 1 1 0 1 0 (c) 545 1 2 5 4 3 6 1 2 3 4 5 6 1 0 1 0 1 0 0 2 0 0 0 0 1 0 3 0 0 0 0 1 1 4 0 1 0 0 0 0 5 0 0 0 1 0 0 6 0 0 0 0 0 1 (a) (c) 546 Adjazenzmatrizen Adjazenzmatrizen A sind für ungerichtete Graphen symmetrisch, das heißt, AT = A. (Für A = (ai,j ) ist AT = (aj,i ) ist die transponierte Matrix.) Gewichtete Graphen können ebenso in Adjazenzmatrizen gespeicher werden. Die Einträge sind dann die Gewichte oder NIL. 547 Breitensuche Sei G = (V, E) ein Graph und v ∈ V ein ausgewählter Startknoten. Wir wollen systematisch alle von v aus erreichbaren Knoten besuchen. Eigenschaft: • gleichzeitig wird Breitensuchebaum aufgebaut In einem solchen ist v die Wurzel und jeder von v für aus erreichbare Knoten s besteht ist der Pfad von der Wurzel zu s der minimale Pfad. 548 Breitensuche Maxime der Breitensuche: • Besuche erst alle Söhne, bevor du einen Enkel besuchst. • genauer: Queue mit offenen Knoten+Färbung der Knoten weiss=unbesucht, grau=besucht/entdeckt, aber Söhne noch nicht, schwarz=vollständig abgearbeitet. Variablen: • Q ist FIFO-QUEUE • jeder Knoten u bekommt die Attribute 1. color (∈ { white, grey, black }) 2. d (Distanz von s) 3. π (Vorgänger von u) 549 BREADTH-FIRST-SEARCH(G, s) 1 for each vertex u ∈ V [G] - {s} 2 do color[u] ← WHITE 3 d[u] ← ∞ 4 π[u] ← NIL 5 color[s] ← GRAY 6 d[s] ← 0 7 π[s] ← NIL 8 Q ← {s} 9 while Q 6= ∅ 10 do u ← head[Q] 11 for each υ ∈ Adj[u] 12 do if color[υ] = WHITE 13 then color[υ] ← GRAY 14 d[υ] ← d[u] + 1 15 π[υ] ← u 550 16 17 18 ENQUEUE(Q,υ) DEQUEUE(Q) color[u]← BLACK 551 Erläuterungen Z1-4: alle Knoten weisseln Z5: s wird grau Z6: distanz von s zu s ist 0 Z7: s hat keinen Vorgänger Z10: ersten grauen Knoten u aus Q entnehmen Z11: für jeden Nachfolger v von u: Z12ff: falls v weiss, grau färben, d undπ von v setzen, in Q einfügen Z17: u aus Q entfernen könnte besser noch mit POP statt HEAD in Z10 gleichzeitig geschehen Z19: markiere u als abgearbeitet. 552 r s t u 8 0 8 8 Q 8 8 8 8 v w x y (a) 553 s 0 r s t u 1 0 8 8 8 8 1 8 8 v w x y (b) 554 Q w r 1 1 r s t u 1 0 2 8 8 1 2 8 Q r t x 1 2 2 v w x y c) 555 r s t u 1 0 2 8 2 1 2 8 v w x y (d) 556 Q t 2 x 2 v 2 r s t u 1 0 2 3 2 1 2 8 v w x y (e) 557 Q x 2 v u 2 3 r s t u 1 0 2 3 2 1 2 3 v w x y (f) 558 Q v u y 2 3 3 r s t u 1 0 2 3 2 1 2 v w x (g) 559 3 y Q u y 3 3 r s t u 1 0 2 3 2 1 2 3 v w x y (h) 560 Q y 3 r s t u 1 0 2 3 Q 2 1 2 3 v w x y (i) 561 Analyse Laufzeit • weisseln: O(V ) • Queue-Operationen: O(V ) nach Z7 wird kein Knoten mehr weiss gefärbt, nur weisse werden in Q eingefügt, also jeder Knoten nur einmal eingefügt und ausgefügt. • Adjazenzlisten ablaufen: Θ(E) Adjazenliste von v wird nur abgelaufen, wenn er DEQUEUEd wird, also genau einmal. • Gesamt: O(V + E) 562 Distanz Definition. Definiere die Distanz δ(s, v) zwischen zwei Knoten s und v als das minimum der Pfadlängen aller Pfade von s nach v. Was wir zeigen wollen ist d[v] = δ(s, v). Lemma 23.1 Sei G = (V, E) ein gerichteter oder ungerichteter Graph und s ∈ V . Dann gilt für alle (u, v) ∈ E: δ(s, v) ≤ δ(s, u) + 1 563 Lemma 23.2 Sei G = (V, E) ein gerichteter oder ungerichteter Graph. Man nehme an, daß BFS auf G für ein s ∈ V angewendet wird. Nach der Terminierung gilt d[v] ≥ δ(s, v) Beweis Induktion über die Anzahl i die ein Knoten v ∈ V in Q eingefügt wird. Die Induktionshypothese ist d[v] ≥ δ(s, v) für alle v ∈ V . I.A.: Situation nach Z8 von Q, direkt nachdem s in Q eingefügt wurde. Hier gilt: d[s] = 0 = δ(s, s) und ∀v ∈ V \ {s} d[v] = ∞ ≥ δ(s, v) 564 I.S.: Man betrachte einen weissen Knoten v, der von u aus entdeckt wird. I.H. =⇒ d[u] ≥ δ(s, u). aus Z14 und Lemma 23.1 folgt: d[v] = d[u] + 1 ≥ δ(s, u) + 1 ≥ δ(s, v) v wird dann in Q eingefügt (Z16), aber nicht noch einmal, da v jetzt grau ist (Z13). Daher ändert sich d[v] nicht mehr und die I.H. bleibt gültig. 565 Um zu zeigen, daß d[v] = δ(s, v) gilt, müssen wir uns genauer anschauen, wie BFS mit Q umgeht. Das nächste Lemma zeigt, daß zu jedem Zeitpuntk höchstens 2 verschiedene Werte für d in der Queue sind. Lemma 23.3 Q enthalte während der Ausführung von BFS auf G = (V, E) die Knoten hv1 , . . . , vr i. Dann gilt: d[vr ] ≤ d[v1 ] + 1 d[vi ] ≤ d[vi+1 ] für alle 1 ≤ i ≤ r − 1 Beweis durch Induktion über die Anzahl der Queueoperationen. √ I.A.: Für die initiale Queue mit Q = hsi: 566 I.S.: Wir müssen zeigen, daß die I.H. nach ENQUEUE und DEQUEUE gilt. Falls Q leer: √ DEQUEUE(v1 ): head(Q) = v2 und √ d[vr ] ≤ d[v1 ] + 1 ≤ d[v2 ] + 1 ENQUEUE(vr+1 ): dann wird die Adjazenzliste von v1 ’s durchlaufen. Also (Z16-18): d[vr+1 ] = d[v1 ] + 1 I.H. =⇒: √ d[vr ] ≤ d[v1 ] + 1 = d[vr+1 ] 567 Korrektheit von BFS Satz Sei G = (V, E) ein gerichteter oder ungerichteter Graph. Dann besucht BFS bei Anwendung auf G, s, s ∈ V jeden von s aus erreichbaren Knoten v und nach der Terminierung gilt d[v] = δ(s, v) für alle v ∈ V . Weiter gilt für jeden Knoten v 6= s, der von s aus erreichbar ist, daß ein kürzester Pfad von s nach v von s zu π[v] gefolgt von der Kante (π[v], v). 568 Beweis Fall 1: v nicht von s aus erreichbar. Lemma 23.2: d[v] ≥ δ(s, v) = ∞. Also kann d[v] nicht in Z14 auf einen endlichen Wert gesetzt werden. Falls also v nicht von s aus erreichbar ist, so wird v nie entdeckt. 569 Fall 2: v von s aus erreichbar. Sei Vk die Menge der Knoten mit δ(s, v) = k. Wir zeigen durch Induktion, daß für jeden Knoten v ∈ Vk während der Ausführung von BFS folgende Operationen genau einmal ausgeführt werden: 1. v wird grau gefärbt 2. d[v] wird auf k gesetzt 3. v 6= s =⇒ π[v] := u für ein u ∈ Vk−1 4. v wird in Q eingefügt Das dies höchstens einmal geschieht ist bereits klar. I.A.: Sei k = 0. Es gilt V0 = {s}. Während der Initialisierung passieren genau die oben erwähnten Operationen. 570 I.H.: Beobachtungen: • Q ist leer nur bei Terminierung • nachdem u in Q eingefügt wurde, bleiben d[u] und π[u] unverändert Lemma 23.3 impliziert daher, daß, falls die Knoten in der Reihenfolge hv1 , . . . , vr i in Q eingefügt werden, gilt: f.a. 1 ≤ i ≤ r − 1. d[vi ] ≤ d[vi+1 ] Für einen Knoten v ∈ Vk , k ≥ 1 impliziert dies mit d[v] ≥ k (Lemma 23.2), daß alle Knoten aus Vk−1 vor v in Q eingefügt werden. 571 δ(s, v) = k =⇒ existiert ein Pfad mit k Kanten von s nach v. Also ∃u ∈ Vk−1 (u, v) ∈ E Sei u der erste solche Knoten, der durch BFS grau gefärbt wird. Dann muß u irgendwann einmal das erste Element in Q sein. Dann wird u’s Adjazenzliste abgelaufen und v entdeckt. Dann passiert folgendes: 1. u wird grau gefärbt (Z13) 2. d[v] := d[u] + 1 = k (Z14) 3. π[v] := u (Z15) 4. ENQUEUE(Q, u) (Z16) Da v ∈ Vk beliebig, folgt die I.H. Zur Vervollständigung des Beweises beobachte man, daß v ∈ Vk =⇒ π[v] ∈ Vk−1 Also erhält man einen kürzesten Pfad von s zu v, indem man einen kürzesten Pfad von s zu π[v] nimmt und dann (π[v], v) anhängt. 572 Breitensuchbäume Wir definieren Für einen Graphen G = (V, E) mit Wurzel s definieren wir den Vorgängerteilgraphen von G als Gπ = (Vπ , Eπ ) mit Vπ Eπ = {v ∈ V |π[v] 6= NIL ∪ {s}} = {(π[v], v) ∈ E|v ∈ Vπ \ {s}} Gπ ist ein Breitensuchbaum, falls 1. Vπ besteht aus allen von s aus erreichbaren Knoten 2. f.a. v ∈ Vπ existiert ein eindeutiger Pfad von s nach v in Gπ 573 r s t u 1 0 2 3 Q 2 1 2 3 v w x y (i) 574 Breitensuchbäume Lemma 23.5 Es werde BFS auf einen gerichteten oder ungerichteten Graphen G = (V, E) angewendet. Nach Terminierung hat BFS die π-Attribute so belegt, daß Gπ ein Breitensuchebaum ist. Beweis Z15 setzt π[v] = u, falls (u, v) ∈ E und δ(s, v) < ∞. Also besteht Vπ aus den von s aus erreichbaren Knoten. Da Gπ ein Baum ist, enthält er einen eindeutigen Pfad von s nach v. Obiger Satz ergibt die Minimalität dieses Pfades. 575 PRINT-PATH(G, s, υ) 1 if υ = s 2 then print s 3 else if π[υ] = NIL 4 then print “no path form” s “to” υ “exists” 5 else PRINT-PATH(G, s, π[υ]) 6 print υ Laufzeit: O(V ) 576 Tiefensuche Sei G = (V, E) ein Graph und v ∈ V ein ausgewählter Startknoten. Wir wollen systematisch alle von v aus erreichbaren Knoten besuchen. Maxime • zuerst in die Tiefe suchen zuerst den Nachfolger besuchen, dann den Nachfolger vom Nachfolger usw. • dabei sind Rücksprünge notwendig, falls kein noch (nicht besuchter) Nachfolger mehr existiert Genauer • Stack statt Queue 577 Vorgängerteilgraph Wegen der Rücksprünge muß der Vorgängerteilgraph leicht anders definiert werden: Def Der Vorgängerteilgraph für die Tiefensuche wird definiert als ein Graph Gπ = (V, Eπ ) mit Eπ = {(π[v], v)|v ∈ V, π[v] 6= N IL} Der Vorgängerteilgraph der Tiefensuche ist ein Tiefensuchewald, der aus mehreren Tiefensuchebäumen zusammengesetzt ist. Die Kanten in Eπ werden Baumkanten genannt. 578 Tiefensuche Färbung • initial alle Knoten weiss • bei Entdeckung grau • nach Abarbeitung der Adjazenzliste schwarz Zeitstempel • d[v] Zeitstempel, wann v entdeckt wurde • f [v] Zeitstempel, wann Abarbeitung von v’s Adjazenzliste beendet Zeitstempel nützlich für mehrere Graphalgorithmen und für Nachdenken über Tiefensuche. Zeitstempel zwischen 1 und 2|V |. (Ein Entdecken und ein Beenden.) 579 DEPTH-FIRST-SEARCH(G) 1 for each vertex u ∈ V [G] 2 do color[u] ← WHITE 3 π[u] ← NIL 4 time ← 0 5 for each vertex u ∈ V [G] 6 do if color[u] = WHITE 7 then DFS-VISIT(u) 580 DFS-VISIT(u) 1 color[u] ← GRAY B White vertex u has just been discovered. 2 d[u] ← time ← time + 1 3 for each υ ∈ Adj[u] B Explore edge (u, υ). 4 do if color[υ] = WHITE 5 then π[υ] ← u 6 DFS-VISIT(υ) 7 color[u] ← BLACK B Blacken u; it is finished. 8 f [u] ← time ← time + 1 581 Erläuterungen DFS Z1-3 alle Knoten weisseln, alle π auf NIL setzen Z4 globalen Zähler rücksetzen Z5-7 besuche alle weißen Knoten mit DFS-VISIT u wird Wurzel eines neuen Baumes im Tiefensuchewald. DFS-VISIT Z1 u wird grau Z2 setzen der Entdeckungszeit d[u], Zeit erhöhen Z3-6 us Adjazenzliste ablaufen und weißen v rekursiv besuchen in Z3 sagen wir eine Kante (u, v) wird abgearbeitet Z7-8 u schwarz färben und Endzeit f [u] setzen 582 Laufzeit DFS • Schleifen Z1-3 und Z5-7 von DFS: Θ(V ) ohne DFS-VISIT • DFS-VISIT: genau einmal pro Knoten ausgeführt. • Da X v∈V |Adj[v]| = Θ(E) Gesamtkosten Z2-5 von DFS-VISIT: Θ(E). • Gesamtlaufzeit DFS: Θ(V + E) 583 u v w 1/ x y (a) v 1/ 2/ x y u v 2/ 1/ 2/ 3/ 4/ 3/ x y u v 1/ x z u y (c) w z w z (b) z (d) 584 w u v 1/ 2/ w u v 1/ 2/ B w B 4/ 3/ x y u v 1/ 2/ (e) 4/5 3/ z x y w u v 1/ 2/7 B (f) z w B 4/5 3/6 x y (g) z 4/5 3/6 x y z (h) 585 u v 1/ 2/7 B F u v 1/8 2/7 w F 4/5 3/6 x y (i) z B 4/5 3/6 x y u v w u v 1/8 2/7 9/ 1/8 2/7 F B F 4/5 3/6 x y (k) z 586 w z (j) w 9/ B 4/5 3/6 x y (l) z u v w u v w 1/8 2/7 9/ 1/8 2/7 9/ F C B F 4/5 3/6 10/ x y u v 1/8 2/7 F (m) 4/5 3/6 10/ z x y z w u v 9/ 1/8 2/7 C B 4/5 3/6 10/11 x y z (o) C B B 587 F (n) B w 9/12 C B 4/5 3/6 10/11 x y z (p) B Eigenschaften • Gπ ist ein Wald • Entdeckungs und Endzeiten haben Klammerstruktur d[u] ∼ “(u” und f [u] ∼ “u)” dann entsteht ein wohlgeformter Klammerausdruck • Klassifikation der Kanten im Graphen G in Baum-, Vorwärts-, Rückwärts- und Kreuzkanten. 588 y z s 3/6 2/9 1/10 F B 4/5 x C 7/8 C w t C 12/13 v (a) 589 11/16 B C 14/15 u s t z y v u w x 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (s (z (y (x x) y) (w w) z) s) (t (v v) (u u) t) (b) 590 t s B C z B F v C y w C x (c) 591 C u Klammertheorem Die zweite der obigen Bedingungen kann auch anders ausgedrückt werden: Satz 23.6 Sei G = (V, E) ein gerichteter oder ungerichteter Graph. Für jede Tiefensuche von G und für je zwei Knoten u und v gilt genau eine der drei folgenden Bedingungen: 1. die Intervalle [d[u], f [u]] und [d[v], f [v]] sind disjunkt 2. [d[v], f [v]] enthält [d[u], f [u]] und u ist ein Nachfolger von v im Tiefensuchebaum 3. [d[u], f [u]] enthält [d[v], f [v]] und v ist ein Nachfolger von u im Tiefensuchebaum 592 Beweis Fall 1: d[u] < d[v] Fall 1.1: d[v] < f [u] =⇒ v entdeckt als u grau =⇒ v Nachfolger von u. Weiter: da u nach v entdeckt, wird v vor u abgearbeitet. Daher [d[v], f [v]] in [d[u], f [u]] enthalten Fall 1.2: f [u] < d[v] =⇒ [d[u], f [u]] und [d[v], f [v]] disjunkt Fall 2: analog 593 Korollar 23.7(Schachtelung von Nachfolgerintervallen) Ein Knoten v ist ein Nachfolger im Tiefensuchenwald eines Knotens u ⇐⇒ d[u] < d[v] < f [v] < f [u] 594 Theorem des weißen Pfades Eine andere Charakterisierung wann ein Knoten Nachfolger eines anderen im Tiefensuchenwald ist: Satz 23.8 Im Tiefensuchenwald eines gerichteten oder ungerichteten Graphen G = (V, E) ist ein Knoten v ein Nachfolger eines Knotens u genau dann, wenn zum Zeitpunkt d[u] der Knoten v auf einem Pfad, der nur aus weißen Knoten besteht, von u aus erreicht werden kann. 595 Beweis =⇒ Sei v ein Nachfolger von u. Sei w ein Knoten auf einem Pfad von u nach v (im Tiefensuchenwald). Nach Korollar gilt d[u] < d[w], also ist w weiß. ⇐= Sei v von u aus auf einem Pfad mit nur weißen Knoten erreichbar. Annahme: v wird kein Nachfolger von u. obdA: Alle anderen Knoten vor v werden Nachfolger von u. (nimm ersten, der kein Nachfolger wird) Sei w Vorgänger von v und w Nachfolger von u. (u und w könnten der gleiche Knoten sein) Korollar: f [w] ≤ f [u] v muß nach u entdeckt und vor w abgearbeitet sein. Also: d[u] < d[v] < f [w] ≤ f [u] 23.6 impliziert [d[v], f [v]] in d[[u], f [u]] enthalten. Also ist v Nachfolger von u. 596 Kantenklassifikation Klassifikation der Kanten in Baumkanten, Rückkanten, Vorwärtskanten und Kreuzkanten. Nützlich: nächster Abschnitt: azyklisch ⇐⇒ keine Rückkanten 597 Kantenklassifikation 1. Baumkanten sind alle Kanten in Gπ Kante (u, v) ist eine Baumkante, falls v zuerst entdeckt wurde beim Ablaufen der Kante (u, v) 2. Rückkanten sind alle Kanten (u, v), die einen Knoten u mit einem Vorgänger v verbinden In einem gerichteten Graphen werden Schleifen als Rückkanten klassifiziert 3. Vorwärtskanten sind alle Kanten (u, v), die einen Knoten v mit einem Nachfolger von v verbinden 4. Kreuzkanten sind alle anderen Kanten Innerhalb eines Tiefensuchenbaumes, solange keine Vorgänger Nachfolgerbeziehung besteht oder zwischen zwei verschiedenen Tiefensuchenbäumen 598 Modifikation von DFS um Kanten (u, v) anhand der Farbe von v zu klassifizieren: 1. v ist weiß: Baumkante 2. v ist grau: Rückkante 3. v ist schwarz: entweder Vorwärts- oder Kreuzkante Anm zu (2) Graue Knoten sind immer eine lineare Kette von Kanten, die zum Aufrufstapel von DFS-VISIT korrespondieren. (3) Man kann zeigen, daß (u, v) eine Vorwärtskante ist, falls d[u] < d[v]. 599 Im ungerichteten Graph kann es Mehrdeutigkeiten bezüglich der Klassifikation geben, da (u, v) und (v, u) die gleiche Kante darstellen. In so einem Fall gilt die erste Klassifikation. Vorwärts und Kreuzkanten kommen in einem ungerichteten Graphen nicht vor: Satz 23.9 Die Kanten in einem ungerichteten Graphen G = (V, E) werden durch die Tiefensuche entweder als Baumkanten oder als Rückkanten klassifiziert. Beweis Sei (u, v) eine beliebige Kante von G. obdA: d[u] < d[v]. =⇒ d[u] < d[v] < f [v] < f [u] Falls (u, v) zuerst in Richtung u → v durchlaufen, dann wird (u, v) eine Baumkante. Falls (u, v) zuerst in Richtung v → u durchlaufen, so wird (u, v) eine Rückkante, da u noch grau ist. 600 Topologische Ordnung Def Eine topologische Ordnung < eines DAG G = (V, E) ist eine lineare Ordnung aller Knoten in G mit (u, v) ∈ E =⇒ u < v Beispiel: Netzpläne 601 11/16 socks undershorts 17/18 watch 12/15 pants shoes 6/7 belt shirt 1/8 tie 2/5 jacket socks 17/18 undershorts 11/16 9/10 13/14 3/4 pants shoes watch shirt belt tie jacket 12/15 13/14 9/10 1/8 6/7 2/5 3/4 602 TOPOLOGICAL-SORT(G) 1 call DFS(G) to compute finishing times f [υ] for each vertex υ 2 as each vertex is finished, insert it onto the front of a linked list 3 return the linked list of vertices Anm Die topologisch Sortierten Knoten erscheinen in umgekehrter Ordnung der Endzeit. Aufwand Θ(V + E) 603 Korrektheit Lemma 23.10 Ein gerichteter Graph G ist azyklisch genau dann, wenn DFS keine Rückkanten ergibt. Beweis =⇒ Annahme: es existiert eine Rückkante (u, v). Dann ist v ein Vorgänger von u im Tiefensuchewald. Also gibt es einen Pfad von v nach u in G und mit der Rückkante (u, v) ergibt sich ein Zyklus. 604 ⇐= Annahme: G enthält einen Zyklus c. Wir Zeigen das BFS eine Rückkante erzeugt. Sei v der erste entdeckte Knoten von c und (u, v) die Vorgängerkante in c. Zum Zeitpunkt d[v] gibt es einen Pfad mit weißen Knoten von v zu u. Also wird u ein Nachfolger von v. Also ist (u, v) eine Rückkante. 605 Korrektheit Satz 23.11 TOPOLOGICAL-SORT(G) erzeugt eine topologische Ordnung eines DAG G = (V, E). Beweis Es werde DFS benutzt um die Endzeiten zu bestimmen. Es genügt zu zeigen: Für je zwei verschiedene Knoten u, v ∈ V : ∃(u, v) ∈ E =⇒ f [v] < f [u] Wenn DFS (u, v) abarbeitet kann v nicht grau sein, sonst wäre (u, v) eine Rückkante. Falls v weiß ist, so wird v Vorgänger von u und damit f [v] < f [u]. Falls v schwarz ist, gilt unmittelbar f [v] < f [u]. 606 Starke Zusammenhangskomponenten Zerlegung von gerichteten Graphen in Zusammenhangskomponenten wird oft als erster Schritt in vielen Algorithmen benötigt, um ein Problem in Teilproblem zu zerlegen. Wir werden DFS benutzen, um diese Zerlegung durchzuführen. Wir benutzen dazu den transponierten Graphen GT = (V, E T ) mit E T = {(u, v)|(v, u) ∈ E} bei Adjazenzlistendarstellung kann GT in O(V + E) erzeugt werden. G und GT haben die gleichen starken Zusammenhangskomponenten. 607 a b c d 13/14 11/16 1/10 8/9 12/15 3/4 2/7 5/6 e f g (a) 608 h a b c d e f g h (b) 609 cd abe fg h (c) 610 STRONGLY-CONNECTED-COMPONENTS(G) 1 call DFS(G) to compute finishing times f [υ] for each vertex υ 2 compute GT 3 call DFS(GT ), but in the main loop of DFS, consider in order of decreasing f [u] 4 output the vertices of each tree resulting from 3 as a separate strongly connected component 611 Korrektheit Zuerst zwei nützliche Beobachtungen Lemma 23.12 Falls zwei Knoten in der gleichen Zusammenhangskomponente sind, so verläßt kein Pfad zwischen diesen die Zusammenhangskomponente Beweis 612 Korrektheit Satz 23.13 Bei jeder Tiefensuche werden alle Knoten einer strengen Zusammenhangskomponente in den gleichen Tiefensuchebaum gehängt. Beweis Sei v der zuerst besuchte Knoten einer Zusammenhangskomponente. Dann sind alle mit v verbundenen Knoten auf weissen Pfaden erreichbar und werden somit zu Nachfolgern zu v. 613 Vereinbarungen: • d[u] und f [u] beziehen sich auf den ersten Aufruf von DFS auf G. • u v bezieht sich auf einen Pfad in G • Φ(u) sei der Urahn von u: derjenige Knoten w, erreichbar von u, der zuletzt vollständig abgearbeitet wurde. Φ(u) = W mit u w und f [w] maximal Φ(u) = u möglich, da u von u aus erreichbar und damit f [u] ≤ f [Φ(u)]. 614 Es gilt Φ(Φ(u)) = Φ(u) da für je zwei Knoten u, v ∈ V : u da {w|v Da u w} ⊆ {w|u v =⇒ f [Φ(v)] ≤ f [Φ(u)] w} und der Urahn die maximale Endzeit hat. Φ(u) gilt f [Φ(Φ(u))] ≤ f [Φ(u)]. Vorher: f [u] ≤ f [Φ(u)], also f [Φ(u)] ≤ f [Φ(Φ(u))] √ Also gilt f [Φ(Φ(u))] = f [Φ(u)]. . Anm In jeder Zshgskomp. gibt es einen Urahn aller Knoten. (Repräsentant) 615 Satz 23.14 In einem gerichteten Graphen G = (V, E) ist der Urahn Φ(u) jedes Knotens v ∈ V bei jeder Tiefensuche von G ein Vorgänger von u. √ Beweis Falls Φ(u) = u Φ(u) 6= u. Betrachte die Farben der Knoten zum Zeitpunkt d[u]. Falls Φ(u) schwarz, so gilt f [Φ(u)] < f [u] (Widerspruch.). Falls Φ(u) grau, dann ist er Vorgänger von u. 616 Falls Φ(u) weiss: Zwei Unterfälle, gemäß der Farbe der Knoten auf dem Pfad von u zu Φ(u) (falls existent). 1. Falls jeder Knoten weiss, dann wird Φ(u) Vorgänger. Dann gilt aber f [Φ(u)] < f [u]. (Widerspruch.) 2. Sei t der letzte nicht-weiße Knoten. Dann muß t grau sein, da niemals eine Kante von einem schwarzen zu einem weißen Knoten existiert. Weiter ist ts Nachfolger weiss. Dann gibt es aber einen Pfad von weissen Knoten von t zu Φ(u) und somit ist Φ Nachfolger von t. Also f [t] > f [Φ(u)]. (Widerspruch Def Φ.). 617 Korollar 23.15 In jeder Tiefensuche eines gerichteten Graphen G = (V, E) liegen für alle Knoten u u und Φ(u) in der gleichen Zusammenhangskomponente. Beweis Es gilt u Außerdem gilt Φ(u) Φ(u) nach Definition Φ. u, da Φ(u) ein Vorgänger von u ist. 618 √ Satz 23.16 In einem gerichteten Graphen G = (V, E) gilt für je zwei Knoten u, v ∈ V , daß sie genau dann in der gleichen Zusammenhangskomponente liegen, wenn sie den gleichen Urahn in einer Tiefensuche von G haben. Beweis =⇒ Wenn u und v in der gleichen Zusammenhangskomponente liegen, gilt u v u. Nach Def. gilt Φ(u) = Φ(v). v und ⇐= Es gelte Φ(u) = Φ(v). Korollar 23.15 ergibt, daß u und Φ(u) in der gleichen Zusammenhangskomponente liegen. Analog für v. Also liegen auch u und v in der gleichen Zusammenhangskomponente 619 Wir können jetzt dem Algorithmus besser verstehen: • strenge Zusammenhangskomponenten sind Mengen von Knoten mit gleichem Urahn • der Urahn ist der erste in einer Zshgskomponente entdeckte und zuletzt abgearbeitete Knoten • insbesondere werden alle Knoten der Zshgskomponente vor dem Urahn vollständig abgearbeitet. • der Knoten r mit f [r] maximal ist Urahn. • in Zshgskomponente von r sind diejenigen Knoten, von denen aus man r erreichen kann, aber keinen Knoten, der eine Endzeit größer f [r] hat. • da f [r] maximal, sind in Zshgskomp. von r alle Knoten, von denen aus man r erreichen kann. D.h. alle Knoten, die man von r aus in GT erreichen kann. 620 Satz 23.17 STRONGLY-CONNECTED-COMPONENTS(G) erzeugt in korrekter Weise die strengen Zusammenhangskomponenten von G. Beweis Beweis durch Induktion über die Anzahl der Tiefensuchebäume von GT . I.H.: der erzeugte Tiefensuchenbaum ist eine starke Zusammenhangskomponente I.A.: trivial I.S.: Sei T ein Tiefensuchebaum mit Wurzel r, erzeugt durch DFS(GT ). Sei C(r) die Menge aller Knoten mit Urahn r: C(r) = {v ∈ V |Φ(v) = r} Wir zeigen: u ∈ T ⇐⇒ u ∈ C(r) (=⇒ Behauptung.) ⇐= Satz 23.13 impliziert:∀v ∈ C(r) v ∈ T . Mit r ∈ C(r) und r Wurzel von T folgt diese Richtung 621 =⇒ wir zeigen: impliziert w 6∈ T ∀w f [Φ(w)] > f [r] ∨ f [Φ(w)] < f [r] Durch Induktion über die Anzahl der Bäume, kann ein Knoten w mit f [Φ(w)] > f [r] nicht in T sein, da zu dem Zeitpunkt, zu dem r besucht wird, w bereits in einem Baum mit Wurzel Φ(w) plaziert ist. Jeder Knoten w mit f [Φ(w)] < f [r] kann nicht in T sein, da dies w würde, also f [Φ(w)] ≥ f [Φ(r)] = f [r] was ein Widerspruch zu f [Φ(w)] < f [r] ist. 622 r implizieren Minimale Spannbäume Motivation • Verkabelung G = (V, E), V sind Orte, E sind mögliche Strecken mit assoziierten Kosten w(u, v) für (u, v) ∈ E Gesucht: azyklische Teilmenge T ⊆ E mit w(T ) = X w(u, v) (u,v)∈T minimal und alle Knoten aus V durch T verbunden. T heißt minimaler Spannbaum. 623 Sichere Kanten Invariante: A ist immer eine Teilmenge eines minimalen Spannbaums. Eine Kante (u, v) ist sicher, falls auch A ∪ (u, v) eine Teilmenge eines minimalen Spannbaums ist. 624 Generische Version GENERIC-MST(G, w) 1 A←∅ 2 while A does not form a spanning tree 3 do find an edge (u, υ) that is safe for A 4 A ← A ∪ {(u, υ)} 5 return A 625 Schnitte (Cuts) Def. Ein Schnitt (S, V \ S) eines ungerichteten Graphen G = (V, E) ist eine Partitionierung von V . (u, v) ∈ E kreuzt (crosses) einen Schnitt (S, V \ S), falls u ∈ S und v ∈ V \ S. A ⊆ E respektiert einen Schnitt, falls keine Kante aus A den Schnitt kreuzt. Eine einen Schnitt kreuzende Kante heißt leicht, falls ihr Gewicht minimal unter allen kreuzenden Kanten ist. Letzteres kann generalisiert werden für andere Eigenschaften als kreuzend. 626 8 b 4 S V−S 8 d 9 2 11 a 7 c i 7 h 4 6 1 14 e 10 g 2 (a) 627 f S V−S Regel zur Bestimmung von sicheren Kanten Satz 24.1 Sei G = (V, E) ein zusammenhängender, ungerichteter Graph mit reeller Gewichtsfunktion w für E. Sei A ⊆ E in einem minimalen Spannbaum für G enthalten. Sei (S, V \ S) ein Schnitt von G, der A respektiert und (u, v) eine leichte kreuzende Kante. Dann ist (u, v) sicher für A. Beweis 628 Sei T ein minimaler Spannbaum mit A ⊆ T , der (u, v) nicht enthält. (u, v) erzeugt für einen Pfad von u nach v einen Zyklus. Da u und v auf entgegengesetzten Seiten des Schnittes liegen, gibt es mindestens eine Kante (x, y) in T , die ebenfalls den Schnitt kreuzt. (x, y) ist nicht in A, da A den Schnitt respektiert. Da (x, y) auf dem eindeutigen Pfad von u nach v in T liegt, zerlegt das Entfernen von (x, y) T in zwei Komponenten. Hinzufügen von (u, v) erzeugt einen neuen Spannbaum T 0 = T \ {(x, y)} ∪ {u, v}. Wir zeigen, daß T 0 ein minimaler Spannbaum ist. 629 x p y u v 630 Da (u, v) eine leichte Kante ist, gilt w(u, v) ≤ w(x, y), Also w(T 0 ) = w(T ) − w(x, y) + w(u, v) ≤ w(T ) Also ist T 0 ein minimaler Spannbaum. Es gilt nun zu zeigen: (u, v) ist sicher für A. Es gilt: • A ⊆ T 0 , da A ⊆ T und (u, v) 6∈ A. Also • A ∪ {(u, v)} ⊆ T 0 . Da T 0 ein minimaler Spannbaum ist, folgt die Behauptung. 631 Korollar 24.2 Sei G = (V, E) ein zusammenhängender, ungerichteter Graph mit reeller Gewichtsfunktion w für E. Sei A ⊆ E eine Teilmenge eines minimalen Spannbaums. Sei C eine Zusammenhangskomponente im Wald GA = (V, A). Falls (u, v) eine leichte Kante ist, die C mit einer anderen Komponente verbindet, so ist (u, v) sicher für A. Beweis Der Schnitt (C, V \ C) respektiert A. Daher ist (u, v) eine leichte Kante. 632 MST-KRUSKAL(G, w) 1 A←∅ 2 for each vertex υ ∈ V [G] 3 do MAKE-SET(υ) 4 sort the edges of E by nondecreasing weight w 5 for each edge (u, υ) ∈ E 6 do if FIND-SET(u) 6= FIND-SET(υ) 7 then A ← A ∪ {(u, υ)} 8 UNION(u, υ) 9 return A 633 8 b 4 h 4 11 8 i 7 h 2 (a) c 8 7 f d 9 2 14 4 6 1 e 10 g 1 14 4 6 b a 9 i 7 8 d 2 11 a 7 c e 10 g 2 (b) 634 f 8 b 4 h 4 7 c f d 9 2 11 8 2 (c) 8 i 7 h 14 4 6 1 e 10 g 1 14 4 6 b a 9 i 7 8 d 2 11 a 7 c g (d) 635 e 10 2 f Laufzeit des Algorithmus von Kruskal: • Initialisierung: O(V ) • Sortieren: O(E lg E) • |V | make-set: O(|V |) • O(E) find-set/union: O(Eα(E, V )) α ist inverse Ackermannfunktion. Hier wurde schnellster Unionalgorithmus angenommen, den es gibt. Es gilt aber: α(E, V ) = O(lg E) • Zusammen: O(E lg∗ V ) 636 Algorithmus von Prim Startet bei einer beliebigen Wurzel r und fügt Kanten zu noch nicht erreichten Knoten hinzu. Alle noch nicht erreichten Knoten werden in einer Prioritätsschlange Q verwaltet, deren Schlüssel (key) das Minimum aller Kanten vom entsprechenden Knoten bis zum bisherigen Baum darstellen. key[v]= ∞ falls so eine Kante nicht existiert. π[v] ist der Vater von v im bisherigen Baum. A wird implizit gehalten: A = {(v, π[v])|v ∈ V \ {r} \ Q} 637 MST-PRIM(G, w, r) 1 Q ← V [G] 2 for each u ∈ Q 3 do key[u] ← ∞ 4 key[r] ← 0 5 π[r] ← NIL 6 while Q 6= ∅ 7 do u ← EXTRACT-MIN(Q) 8 for each υ ∈ Adj[u] 9 do if υ ∈ Q and w(u, υ) < key[υ] 10 then π[υ] ← u 11 key[υ] ← w(u, υ) 638 8 b 4 h 4 7 f d 9 2 11 8 2 (a) c 8 i 7 h 14 4 6 1 e 10 g 1 14 4 6 b a 9 i 7 8 d 2 11 a 7 c g (b) 639 e 10 2 f 8 b 4 h 4 7 f d 9 2 11 8 2 (c) c 8 i 7 h 14 4 6 1 e 10 g 1 14 4 6 b a 9 i 7 8 d 2 11 a 7 c g (d) 640 e 10 2 f Laufzeit hängt von der Implementierung von Q ab. Binary Heaps: • Initialisierung: O(V ) • Schleife Z6-7: O(V lg V ) • Schleife Z8-11: O(E) Durchläufe • Z9: O(1), falls Bit in Knoten anzeigt, ob dieser in Q ist oder nicht • Z11: entspricht DECREASE-KEY: O(lg V ) • Summe: O(V lg V + E lg V ) Fibonacci Heaps: • O(E + V lg V ) 641 Kürzeste Pfade Oft gebraucht: Kürzester Weg von A nach B Gegeben: G = (V, E) und w : E → R Pfadlänge von p = hv0 , . . . , vk i: w(p) = Pk Länge des kürzesten Pfades von u nach v min{w(p)|u δ(u, v) = ∞ i=1 p w(vi−1 , vi ) v} falls u, v verbunden sonst Kürzester Pfad ist einer mit eben diesem Gewicht. Wir betrachten nur positive Gewichte/Längen. 642 Varianten Hier (Single-Source shortest-paths problem) Gegeben einen Startknoten s, finde die kürzesten Pfade zu allen v ∈ V . Varianten • single-destination shortest-paths problem • single-pair shortest path problem • all-pairs shortest path problem hier gibt es schnellere als die offensichtliche Lösung 643 Vereinbarungen π[v] ist Vorgänger im kürzesten Pfad PRINT-PATH aus Breitensuche kann wiederverwendet werden. Wiederholung (Vorgängerteilgraph): Vπ = {v ∈ V |π[v] 6= N IL} ∪ {s} Eπ = {(π[v], v) ∈ E|v ∈ Vπ \ {s}} 644 Kürzeste-Pfade-Baum (Jetzt in Termini der Länge nicht nur Anzahl der Kanten) Ein Kürzester-Pfade-Baum mit Wurzel s ist ein gerichteter Graph G0 = (V 0 , E 0 ) mit V 0 ⊆ V und E 0 ⊆ E und 1. V 0 ist die Menge von s aus erreichbaren Knoten 2. G0 ist ein Wurzelbaum mit Wurzel s 3. für alle v ∈ V 0 gibt es einen eindeutigen Pfad von s nach v in G0 , der dem kürzesten Pfad von s nach v entspricht Kürzeste-Pfade-Bäume sind natürlich nicht eindeutig bestimmt. 645 Optimalitätsprinzip Sei G = (V, E) ein gerichteter Graph mit Gewichtsfunktion w. Lemma 25.1 Falls p = hv1 , . . . , vk i ein kürzester Pfad von v1 nach vk ist, so ist jeder Pfad pi,j = hvi , . . . , vj i (1 ≤ i ≤ j ≤ k) ein kürzester Pfad von vi nach vj . Korollar 25.2 Sei p = hv1 , . . . , vk i ein kürzester Pfad. Dann gilt δ(v1 , vk ) = δ(v1 , vk−1 ) + w(vk−1 , vk ) Lemma 25.3 Sei s ∈ V ein Startknoten. Dann gilt für alle Kanten (u, v) ∈ E: δ(s, v) ≤ δ(s, u) + w(u, v) 646 Relaxation Für jeden Knoten v ∈ V werden wir ein Attribut d[v] verwalten, das eine obere Schranke für δ(s, v) angibt. INITIALIZE-SINGLE-SOURCE(G, s) 1 for each vertex v ∈ V [G] 2 do d[v] ← ∞ 3 π[v] ← NIL 4 d[s] ← 0 RELAX(u, v, w) 1 if d[v] > d[u] + w(u, v) 2 then d[v] ← d[u] + w(u, v) 3 π[v] ← u 647 u 5 v 2 9 RELAX(u,v) u 5 v 2 7 (a) u 5 v 6 2 RELAX(u,v) u 5 2 (b) 648 v 6 Eigenschaften der Relaxation Sei G = (V, E) ein gerichteter Graph mit Gewichtsfunktion w. Lemma 25.4 Sei (u, v) ∈ E. Dann gilt nach Relaxation von (u, v) durch Ausführen von RELAX(u, v, w): d[v] ≤ d[u] + w(u, v) Lemma 25.5 Sei s ∈ V ein Startknoten. Dann gilt direkt nach INITIALIZE-SINGLE-SOURCE(G, s) d[v] ≥ δ(s, v) für alle v ∈ V und dieses ändert sich nie durch eine Folge von RELAX aufrufen. Desweiteren gilt, daß d[v] invariant bleibt, nachdem d[v] = δ(s, v) erreicht ist. 649 Eigenschaften der Relaxation Lemma 25.7 Sei s ∈ V ein Startknoten und s u → v ein kürzester Pfad von s nach v. Zunächst werde INITIALIZE-SINGLE-SOURCE(G, s) aufgerufen, dann eine Folge von RELAX unter denen sich RELAX(u, v, w) befindet. Falls d[u] = δ(s, u) vor diesem Aufruf gilt, so auch nach jedem weiteren RELAX-Aufruf. 650 Kürzeste-Pfade-Baum Lemma 25.8 Sei s ∈ V ein Startknoten. Nach INITIALIZE-SINGLE-SOURCE(G, s) ist Gπ ein Baum mit Wurzel s und diese Eigenschaft ist invariant gegenüber RELAX-Aufrufen. Lemma 25.9 Sei s ∈ V ein Startknoten. Betrachte eine Folge von RELAX-Aufrufen nach der Initialisierung, die mit d[v] = δ(s, v) für alle v ∈ V endet. Dann ist Gπ ein Kürzester-Pfade-Baum mit Wurzel s. 651 DIJKSTRA(G, w, s) 1 INITIALIZE-SINGLE-SOURCE (G, s) 2 S ←∅ 3 Q ← V [G] 4 while Q 6= ∅ 5 do u ← EXTRACT-MIN(Q) 6 S ← S∪ {u} 7 for each vertex v ∈ Adj[u] 8 do RELAX(u, v, w) Q bisheriger minimaler Abstand zu s (key = d). S Knoten u, für die δ(u, v) schon bestimmt 652 Dijkstras Algorithmus Laufzeit • Q als Array. Dann O(V ) EXTRACT-MIN-Operationen mit Aufwand O(V ). Also O(V 2 ). • Jede Kante wird genau einmal betrachtet Z4-8: O(E) • Zusammen: O(V 2 + E) • Heap: O((V + E) lg V ) • Fibonacci Heap: O(V lg V + E) 653 u 9 2 3 8 8 2 x y (a) u v 10 8 1 10 9 2 5 6 4 7 5 s 0 8 8 1 10 s 0 v 3 4 6 7 2 (b) 654 8 5 x y u 8 1 10 3 5 x 6 (c) v 1 10 2 7 y 2 u 8 5 4 7 5 s 0 14 9 2 s 0 v 3 9 13 4 6 7 5 x 2 (d) 655 7 y u 8 1 10 2 s 0 3 5 x 4 6 (e) v 1 10 2 7 y 2 u 8 5 9 9 7 5 s 0 v 3 9 9 4 6 7 5 x 2 (f) 656 7 y Bellman-Ford Dieser Algorithmus löst das allgemeinere Problem, bei dem die Gewichte auch negativ sein können. Gegeben ein gewichteter, gerichteter Graph G = (V, E) und ein Startknoten s. Der Bellman-Ford-Algorithmus gibt dann false zurück, falls ein Zyklus mit negativem Gewicht existiert, der von s aus erreichbar ist, oder true , falls kein solcher Zyklus existiert. Falls letzteres der Fall ist, so erzeugt der Algorithmus noch die kürzesten Pfade mit Startknoten s. 657 BellmanFord(G, w, s) 1 INITIALIZE-SINGLE-SOURCE (G, s) 2 for i ← 1 to |V [G]| − 1 do 3 for each edge (u, v) ∈ E[G] do 4 RELAX(u,v,w) 5 for each edge (u, v) ∈ E[G] do 6 if d[v] > d[u] + w(u, v) then return false 7 return true Laufzeit: O(V ∗ E). 658 Im folgenden sei G = (V, E) ein gerichteter, gewichteter Graph und s ∈ V der Startknoten. Weiter sei w : E → R die Gewichtsfunktion. Lemma 25.12 Falls G keinen von s aus erreichbaren Zyklus mit negativem Gewicht enthält, so gilt nach der Terminierung von Bellman-Ford: d[v] = δ(s, v) für alle Knoten v, die von s aus erreichbar sind. 659 Bew.: Sei v von s aus erreichbar, und p = hv0 , . . . , vk i ein einfacher kürzester Pfad von v = v0 zu s = vk . Dann ist k ≤ |V | − 1. Wir zeigen per Induktion: d[vi ] = δ(s, vi ) nach der i-ten Iteration. I.A.: d[v0 ] = δ(s, v0 ) gilt und bleibt nach Lemma 24.5 erhalten. I.S.: I.H.: d[vi−1 ] = δ(s, vi−1 ) nach (i − 1)-ten Iteration Jede Kante (vi−1 , vi ) ist RELAXed in i-ter Iteration. Aus Lemma 25.7 folgt d[vi ] = δ(s, vi ) gilt nach i-ter Iteration und bleibt danach erhalten. 660 Korollar 25.13 Es existiert für jeden Knoten v ∈ V ein Pfad von s nach v, genau dann wenn Bellman-Ford mit d[v] < ∞ terminiert. Beweis ähnlich zu Lemma 25.12. 661 Theorem 25.14 (Korrektheit von Bellman-Ford) Sei G = (V, E) ein gerichteter, gewichteter Graph und s ∈ V der Startknoten. Sei w : E → R die Gewichtsfunktion. Falls G keinen von s aus erreichbaren Zyklus mit negativem Gewicht enthält, so gibt Bellman-Ford true zurück, andernfalls false. Im ersten Fall gilt zusätzlich • d[v] = δ(s, v) ∀v ∈ V und • der Vorgängerteilgraph Gπ ist ein Kürzeste-Pfade-Baum mit Wurzel s. 662 Beweis: Fall 1: G enthält keinen Zyklus mit negativem Gewicht. Wir zeigen, dass zum Terminierungszeitpunkt d[v] = δ(s, v) für alle v ∈ V . Falls v von s aus erreichbar, folgt Behauptung aus Lemma 25.12. Falls v nicht von s aus erreichbar, folgt Behauptung aus Korollar 25.6 Aus Lemma 25.9 folgt das Gπ ein kürzester Pfade-Baum ist. Fehlt nur noch: BELLMAN-FORD gibt true zurück. Zum Terminierungszeitpunkt gilt für alle (u, v) ∈ E: d[v] = δ(s, v) ≤ δ(s, u) + w(u, v) [Lemma 25.3] = d[u] + w(u, v) Also gibt kein Test in Zeile 6 false zurück. 663 Fall 2: G enthält einen von s aus erreichbaren Zyklus c = hv0 , . . . , vk i (v0 = vk ) mit negativem Gewicht. Pk Dann i=1 w(vi−1 , vi ) < 0 Annahme: BELLMAN-FORD gibt true zurück. Dann gilt d[vi ] ≤ d[vi−1 ] + w(vi−1 , vi ) für alle i = 1, . . . , k Aufsummieren dieser Ungleichungen für alle i: k X i=1 d[vi ] ≤ k X d[vi−1 ] + i=1 k X w(vi−1 , vi ) i=1 Jedes vi aus Zyklus s (v0 = vk ) taucht in den beiden ersten Summen genau einmal auf. Daher k k X X d[vi ] = d[vi−1 ] i=1 i=1 664 Da nach Cor. 25.13 jedes d[vi ] endlich gilt also: 0≤ Dies ist ein Widerspruch zu Pk i=1 k X w(vi−1 , vi ) i=1 w(vi−1 , vi ) < 0. 665 DAG’s Für DAGs gibt es einen einfacheren Algorithmus (Θ(E + V )), um die kürzesten Pfade mit Startknoten s zu berechnen: DAG-Shortest-Paths(G, w, s) 1 INITIALIZE-SINGLE-SOURCE (G, s) 2 for each vertex u taken in topologically sorted order do 3 for each vertex v ∈ Adj[u] do 4 RELAX(u,v,w) Anwendung: kritische Pfade in PERT-Diagrammen. 666 Theorem 25.15 Sei G = (V, E) ein gerichteter, azyklischer, gewichteter Graph und s ein Startknoten. Dann gilt zum Terminierungszeitpunkt von DAG-Shortest-Paths d[v] = δ(s, v) für alle v ∈ V und der Vorgängerteilgraph Gπ ist ein Kürzester-Pfade-Baum. 667 Beweis: Wir zeigen zunächst d[v] = δ(s, v). Cor. 25.6: d[v] = δ(s, v) = ∞ falls v nicht von s aus erreichbar. Sei v von s aus erreichbar und p = hv0 , . . . , vk i ein kürzester Pfad mit v0 = s, vk = v. Da wir die Knoten in topologischer Ordnung bearbeiten, werden die Kanten in der Reihenfolge (v0 , v1 ), (v1 , v2 ), . . . , (vk−1 , vk ) relaxiert. Eine einfache Induktion unter Benutzung von Lemma 25.7 (ähnlich wie in 25.12) zeigt d[vi ] = δ(s, vi ). Aus Lemma 25.9 folgt: Gπ ist ein Kürzester-Pfade-Baum. 668 Einleitung Wir betrachten nur einen Spezialfall des linearen Programmierens, den wir auf das Problem der kürzesten Pfade mit Startknoten zurückführen. Im allgemeinen Fall des linearen Programmierens ist eine m × n-Matrix A gegeben, ein m-Vektor b und ein n-Vektor c. Gesucht ist ein n-Vektor x, der die Wertfunktion n X c i xi i=1 maximiert, wobei die m Bedingungen Ax ≤ b gelten. Algorithmen für das allgemeine Problem: • Simplex-Methode (worst case: exponentiell) • Ellipsoid-Algorithmus (polynomial, für die Praxis zu hohe Konstanten) • Karmarkars Algorithmus (polynomial, o.k.) 669 Spezialisierung • Viele Probleme sind Spezialfälle des linearen Programmierens • Da allgemeine Algorithmen recht komplex, behandeln wir nur einfache Spezialfälle. • Wir betrachten das Problem, bei dem die Wertfunktion keine Rolle spielt, man also nur an Lösungen x interessiert ist, die die Menge der Bedingungen Ax ≤ b erfüllen. 670 Systeme von Differenzbedingungen Falls in der Matrix A jede Zeile genau eine 1 und eine -1 enthält, so spricht man von einem System von Differenzbedingungen. Die Bedingungen Ax ≤ b vereinfachen sich zu Bedingungen der Form für 1 ≤ i, j ≤ n und 1 ≤ k ≤ m. xj − x i ≤ b k Anwendungen: z.B. Scheduling 671 Beispiel 1 1 0 −1 −1 0 0 0 −1 0 0 0 0 −1 0 0 1 0 0 1 0 0 0 0 1 0 0 −1 1 0 0 1 0 −1 1 0 −1 0 0 −1 672 x1 x2 x3 x4 x5 0 −1 1 5 ≤ 4 −1 −3 −3 Beispiel x1 − x 2 ≤ 0 x2 − x 5 ≤ 1 ≤ 4 x1 − x 5 x3 − x 1 x4 − x 1 x4 − x 3 x5 − x 3 x5 − x 4 ≤ −1 ≤ 5 ≤ −1 ≤ −3 ≤ −3 Eine Lösung: x = (−5, −3, 0, −1, −4). Andere: (0, 2, 5, 4, 1) 673 Lösungen nicht eindeutig hat System: Lemma 25.16 Sei x = (x1 , . . . , xn ) eine Lösung eines Systems Ax ≤ b von Differenzbedingungen. Dann ist auch x + d = (x1 + d, . . . , xn + d) eine Lösung von Ax ≤ b. Beweis: folgt unmittelbar aus (xj + d) − (xi + d) = xj − xi . 674 Bedingungsgraphen Die n × m-Matrix A eines Systems von Differenzbedingungen kann man als Inzidenzmatrix eines Graphen G = (V, E) mit n Knoten und m Kanten interpretieren. Jeder Knoten entspricht einer der n Variablen xi und Jede gerichtete Kante im Graphen entspricht einer der m Ungleichungen. 675 Bedingungsgraphen Gegeben ein System Ax ≤ b von Differenzbedingungen, dann ist der korrespondierende gerichtete und gewichtete Bedingungsgraph G = (V, E) definiert durch V E = {v0 , v1 , . . . , vn } = {(vi , vj )|xj − xi ≤ bk ist eine Bedingung}∪ {(v0 , vi )|1 ≤ i ≤ n} Die Gewichte der Kanten (v0 , vi ) sind 0, die Gewichte der anderen Kanten (vi , vj ) sind die entsprechenden bk . Der Knoten v0 entspricht keiner Variablen. Von ihm aus ist jeder andere Knoten erreichbar. 676 Bedingungsgraphen Theorem 25.17 Gegeben sei ein System Ax ≤ b von Differenzbedingungen. Sei G = (E, V ) der korrespondierende Bedingungsgraph. Falls G keinen Zyklus mit negativem Gewicht enthält, so ist x = (δ(v0 , v1 ), . . . , δ(v0 , vn )) eine Lösung von Ax ≤ b. Falls G einen Zyklus mit negativem Gewicht enthält, so existiert keine Lösung für Ax ≤ b. 677 Beweis: Wir zeigen zunächst, dass falls der Graph keinen Zyklus mit negativem Gewicht enthält x = (δ(v0 , v1 ), . . . , δ(v0 , vn )) eine Lösung ist. Betrachte eine beliebige Kante (vi , vj ). Nach Lemma 25.3 gilt: δ(v0 , vj ) ≤ δ(v0 , vi ) + w(vi , vj ) Also Setze δ(v0 , vj ) − δ(v0 , vi ) ≤ w(vi , vj ) xi = δ(v0 , vi ) xj = δ(v0 , vj ) Dann gilt xj − xi ≤ w(vi , vj ). Dies ist die zu (vi , vj ) korrespondierende Bedingung. 678 Wir zeigen jetzt, dass falls der Graph einen Zyklus mit negativem Gewicht enthält, das System von Differenzbedingungen keine Lösung hat. Sei o.B.d.A. c = hv1 , . . . , vk i mit v1 = vk ein Zyklus mit negativem Gewicht. Dieser korrespondiert zu den Bedingungen: x2 − x 1 x3 − x 2 ≤ w(v1 , v2 ) ≤ w(v3 , v2 ) ≤ w(vk−1 , vk ) ... xk − xk−1 x1 − x k ≤ w(vk , v1 ) Die Summe der linken Seiten ergibt 0. Die Summe der rechten Seiten eine Zahl kleiner 0. Es kann also keine Lösung geben. 679 Lösungsverfahren Gegeben System von Differenzbedingungen mit m Bedingungen und n Unbekannten. Dann ergibt sich die Lösung durch: 1. Umkodieren des Problems in Graphproblem 2. Anwenden von Bellman-Ford Die Laufzeit beträgt: O((n + 1)(n + m)) = O(n2 + nm) Übung: Algorithmus läuft in O(nm) 680 Alle kürzesten Pfade Wir behandeln jetzt Algorithmen, die für alle Paare von Knoten den kürzesten Pfad zwischen diesen Knoten findet. Das Ergebnis entspricht z.B. einer Entfernungstabelle in einem Atlas. 681 Keine negativen Gewichte Wir können Dijkstras Algorithmus für jeden Knoten anwenden. Damit bekommen wir Algorithmen, deren Laufzeit von der Implementierung der Priority-Queue abhängt: • O(V 3 + V ∗ E) = O(V 3 ) Array-Implementierung, linearer Aufwand • O(V E lg V ) Heap-Implementierung • O(V 2 lg V + V E) Fibonacci-Heap-Implementierung 682 Auch negative Gewichte Benutze Bellman-Ford für jeden Knoten. Dies ergibt O(V 2 E). Fall der Graph dicht ist O(V 4 ). 683 Eingabe/Ausgabe Wir lernen jetzt weitere Algorithmen kennen. Viele dieser Algorithmen repräsentieren den Graphen G = (V, E) mittels Adjazenz-Matrix und nicht mittels Adjazenzliste. Im folgenden gelte immer |V | = n. Dann wird G als n × n-Matrix dargestellt. Wir identifizieren die Knoten in V mit den Zahlen 1, . . . , n. 684 Die Eingabe zu diesen Algorithmen ist eine n × n-Matrix W = (wij ), die die Gewichte der Kanten des gerichteten Graphen G = (V, E) in folgender Weise kodiert: 0 wij = w(i, j) ∞ if i=j if i 6= j and (i, j) ∈ E if i 6= j and (i, j) 6∈ E Das Ergebnis ist eine n × n-Matrix D = (dij ), mit dij = δ(i, j), nach Terminierung der Algorithmen. 685 Vorgängermatrix Zur Repräsentation der kürzesten Pfade verwenden wir eine Vorgängermatrix Π = (πij ), wobei πij = NIL, falls i = j oder falls es keinen Pfad von i nach j gibt. Der durch die i-te Zeile induzierte Teilgraph ist nach Terminierung der Kürzeste-Pfade-Baum mit Wurzel i. Für jeden Knoten i ∈ V definieren wir den Vorgängerteilgraphen von G für i als Gπ,i = (Vπ,i , Eπ,i ), wobei Vπ,i Eπ,i = {j ∈ V |πij 6= N IL} ∪ {i} = {(πij , j)|j ∈ Vπ,i , πij 6= N IL} 686 Vorgängermatrix Falls Gπ,i = (Vπ,i , Eπ,i ) der Vorgängerteilgraph ist, so können mit folgender Prozedur den kürzesten Pfad von i nach j ausgeben. PRINT-ALL-SHORTEST-PATH(π, i, j) 1 if i = j 2 then print i 3 else if πij = N IL 4 then print “no path from” i “to” j “exists” 5 else PRINT-ALL-SHORTEST-PATH(π, i, πij ) 5 print j 687 Voraussetzungen • G = (V, E) mit |V | = n • Matrizen werden durch Großbuchstaben repräsentiert • die Elemente einer Matrix M werden durch mij bezeichnet, 1 ≤ i, j ≤ n. • Um Iterationen auszudrücken (bzw. deren Ergebnis), versehen wir Matrizen mit in Klammern gesetzten Superskripten M (k) • In Algorithmen bezeichne rows[M ] die Anzahl n der Zeilen bzw. Spalten 688 Kürzeste Pfade und Matrizenmultiplikation Der erste Ansatz zur Berechnung aller kürzesten Pfade beruht auf dynamischem Programmieren. Die Schritte sind wieder: 1. Charakterisierung der Problemstruktur 2. Rekursive Definition des Wertes der optimalen Lösung 3. Bottom-Up-Berechnung des optimalen Wertes 689 Problemstruktur Wir haben bereits in Lemma 25.1 nachgewiesen, daß jeder Teilpfad eines kürzesten Pfades ein kürzester Pfad ist. • Sei p ein kürzester Pfad von i nach j mit höchstens m Kanten. • Falls es keine Zyklen mit negativem Gewicht gibt, so ist m endlich. • Falls i = j, so hat p das Gewicht 0 und die Länge 0, ansonsten • ist p von der Form p = i p0 k → j, wobei p0 höchstens m − 1 Kanten enthält. • Lemma 25.1 impliziert, daß p0 der kürzeste Pfad von i nach k ist. • Nach Korollar 25.2 gilt also δ(i, j) = δ(i, k) + wkj 690 Rekursive Lösung (m) Sei dij das minimale Gewicht eines Pfades von i nach j mit höchstens m Kanten. (Es kann durchaus sein, daß es einen längeren Pfad (mehr Kanten) mit niedrigererem Gewicht gibt.) Falls m = 0, so gilt (0) dij (m) dij = = 0 = ∞ (m−1) min(dij 1≤k≤n if i 6= j (m−1) , min1≤k≤n (dik (m−1) min (dik if i = j + wkj )) + wkj ) (wjj = 0!) Falls keine Zyklen mit negativen Gewichten existieren, so hat jeder kürzeste (k) Pfad die maximale Länge n − 1. Daher gilt: δ(i, j) = dij für alle k ≥ n − 1. 691 Berechnung der Gewichte der kürzesten Pfade Die folgende Prozedur implementiert eine Iteration zur Berechnung von D (m) aus D(m−1) und W . EXTEND-SHORTEST-PATH(D, W ) 1 n ← rows[D] 2 let D 0 = (d0ij ) be an n × n matrix 3 for i ← 1 to n do 4 for j ← 1 to n do 5 d0ij = ∞ 6 for k ← 1 to n 7 d0ij ← min(d0ij , dik + wkj ) 8 return D 0 Laufzeitkomplexität: O(n3 ) 692 Berechnung der Gewichte der kürzesten Pfade SLOW-ALL-PAIRS-SHORTEST-PATHS(W ) 1 n ← rows[W ] 2 D (1) = W 3 for m ← 2 to n − 1 do 4 D(m) ← EXTEND-SHORTEST-PATH(D (m−1) , W ) 5 return D (n−1) Laufzeitkomplexität: O(n4 ) 693 Ähnlichkeit mit Matrixmultiplikation Mit den Substitutionen D(m−1) W D(m) → → → a b c min → + ∞ → 0 + → ∗ wird EXTEND-SHORTEST-PATH zu einem Algorithmus zur Multiplikation der Matrizen a und b mit Ergebnis c: 694 MATRIX-MULTIPLY-(A, B) 1 n ← rows[A] 2 let C = (cij ) be an n × n matrix 3 for i ← 1 to n do 4 for j ← 1 to n do 5 cij = 0 6 for k ← 1 to n 7 cij ← cij + aik ∗ bkj 8 return C Laufzeitkomplexität: O(n3 ) 695 Beschleunigung Anstelle von D(1) = D(2) = D(3) = ··· D(n−1) = D(0) ◦ W = W D(1) ◦ W = W2 D(2) ◦ W = W3 D(n−2) ◦ W = W n−1 696 berechnen wir lieber so etwas wie D(1) = W D(2) = W2 D(4) = W4 D(8) = W8 ··· D(2 dlg(n−1)e ) = W (2 = W ◦W = W2 ◦ W2 = W4 ◦ W4 dlg(n−1)e ) = D(2 Methode heißt fortgesetztes Quadrieren. 697 dlg(n−1)e−1 ) ◦ D(2 dlg(n−1)e−1 ) Beschleunigung FASTER-ALL-PAIRS-SHORTEST-PATHS(W ) 1 n ← rows[W ] 2 D(1) = W 3 m←1 4 while n − 1 > m do 5 D(2m) ← EXTEND-SHORTEST-PATH(D (m) , D (m) ) 6 m ← 2m 6 return D (m) Laufzeitkomplexität: O(n3 lg(n)) 698 Floyd-Warshall-Algorithmus Der Floyd-Warshall-Algorithmus • betrachtet eine andere Substruktur, die auf den Zwischenknoten eines Pfades beruht. Sei p =< v1 , . . . , vn > ein Pfad, dann sind v2 , . . . , vn−1 die Zwischenknoten. • beruht auf einer Beobachtung, der folgende Anschauung zu Grunde liegt. Sei G = (V, E), V = {1, . . . , n} V ⊇ S = {1, . . . , k} für 1 ≤ k ≤ n. Für alle Knoten i, j ∈ V betrachte alle Pfade von i nach j, die nur Knoten aus S enthalten und sei p ein minimaler solcher Pfad. Die Beobachtung bezieht sich dann darauf, wie p aus den minimalen Pfaden, die nur Knoten aus {1, . . . , k − 1} enthalten, zusammengesetzt ist. Dabei werden 2 Fälle unterschieden, je nachdem ob k ein Zwischenknoten von p ist, oder nicht: 699 Problemstruktur 1. k kein Zwischenknoten: Dann sind alle Zwischenknoten in {1, . . . , k − 1} enthalten. Daher ist ein kürzester Pfad von i nach j mit allen Zwischenknoten in {1, . . . , k − 1} auch ein kürzester Pfad von i nach j mit allen Zwischenknoten in {1, . . . , k}. 2. k ist Zwischenknoten: Dann ist p von der Form p1 + p2 + i→ k→ j wobei p1 und p2 kürzeste Pfade sind und nur Zwischenknoten aus {1, . . . , k − 1} haben. 700 Rekursive Kostenberechnung (k) Sei dij das Gewicht des kürzesten Pfades von i nach j dessen Zwischenknoten alle in {1, . . . , k} enthalten sind. Dann gilt (k) dij w ij = min(d(k−1) , d(k−1) + d(k−1) ) ij (n) Es gilt dij = δ(i, j). ik 701 kj if k = 0 if k > 0 Berechnung der kürzesten Pfade FLOYD-WARSHALL(W ) 1 n ← rows[W ] 2 D (0) ← W 3 for k ← 1 to n do 4 for i ← 1 to n do 5 for j ← 1 to n (k) (k−1) (k−1) (k−1) , dik + dkj ) 6 dij ← min(dij 7 return D (n) Laufzeitkomplexität: O(n3 ) 702 Berechnung der kürzesten Pfade Der Vorgängergraph für die kürzesten Pfade ergibt sich aus π. π wiederum kann mit Hilfe des obigen Algorithmus berechnet werden, wobei folgende Rekurrenz einzuarbeiten ist: NIL if i = j or w = ∞ ij (0) πij = i if i 6= j and wij < ∞ und (k) πij π (k−1) ij = π (k−1) kj (k−1) if dij (k−1) if dij Übung: Einbau in Floyd-Warshall-Algorithmus. 703 (k−1) + dkj (k−1) + dkj ≤ dik > dik (k−1) (k−1) Transitive Hülle Sei G = (V, E) ein Graph. Dann ist die transitive Hülle von G definiert als der Graph G∗ = (V, E ∗ ) mit E ∗ = {(i, j)|∃ Pfad von i nach j } Ein Weg die transitive Hülle zu berechnen ist es, jeder Kante das Gewicht 1 zuzuordnen und dann den Floyd-Warshall-Algorithmus anzuwenden. Da die Länge der Pfade nicht entscheidend ist, kann man mit boolschen Operationen arbeiten. Der resultierende Algorithmus hat immer noch Laufzeitkomplexität O(n3 ), ist aber schneller und platzsparender. 704 Transitive Hülle TRANSITIVE-CLOSURE(G) 1 n ← |V [G]| 2 for i ← 1 to n do 3 for j ← 1 to n do 4 if i = j or (i, j) ∈ E[G] (0) 5 then tij ← 1 (0) 6 else tij ← 0 7 for k ← 1 to n do 8 for i ← 1 to n do 9 for j ← 1 to n (k) (k−1) (k−1) (k−1) ∨ (tik ∧ tkj ) 10 tij ← tij 11 return T (n) 705 Johnsons Algorithmus • für spärliche Graphen • Adjazenzlisten-Darstellung • O(V 2 lg V + V E) • benutzt Dijkstra und Bellman-Ford • benutzt die Technik der Neugewichtung 706 Neugewichtung 1. Beobachtung: Falls alle Kantengewichte w eines Graphen G = (V, E) nicht negativ sind, so können wir Dijkstras Algorithmus für jeden Knoten einmal laufen lassen. Benutzt man Fibonacci-Heaps, so ergibt dies O(V 2 lg V + V E) 2. Negative Gewichte: Wir berechnen neue Gewichte ŵ mit folgenden beiden Eigenschaften: (a) Für alle Knoten u, v ∈ V ist der kürzeste Pfad zwischen u und v unter Gewichtung w der gleiche wie unter Gewichtung ŵ. (b) Für alle Kanten (u, v) ∈ E ist ŵ(u, v) nicht negativ. Der Vorbereitungsschritt zur Berechnung der neuen Gewichte hat Aufwand O(V E). Haben wir die neuen Gewichte, können wir wie in Fall 1 verfahren. 707 Die 1. Eigenschaft Lemma 26.1 Sei G = (V, E) ein gewichteter gerichteter Graph mit Gewichtsfunktion w : E → R. Sei h : V → R eine beliebige Funktion. Definiere für jede Kante (u, v) ∈ E: ŵ(u, v) = w(u, v) + h(u) − h(v) Sei p = hv0 , . . . , vk i ein Pfad von v0 nach vk . Dann gilt folgendes: • w(p) = δ(v0 , vk ) ≺ ŵ(p) = δ̂(v0 , vk ) Desweitern gilt, daß G einen Zyklus mit negativem Gewicht hat genau dann wenn Ĝ einen Zyklus mit negativem Gewicht hat. 708 Beweis (Teil 1): Wir zeigen zunächst ŵ(p) = w(p) + h(v0 ) − h(vk ) (25.10) ŵ(p) = k X ŵ(vi−1 , vi ) i=1 = k X i=1 = k X i=1 (w(vi−1 , vi ) + h(vi−1 ) − h(vi )) w(vi−1 , vi ) + h(v0 ) − h(vk ) = w(p) + h(v0 ) − h(vk ) 709 Beweis (Teil 2): Wir zeigen durch Widerspruch w(p) = δ(v0 , vk ) ŵ(p) = δ̂(v0 , vk ) Annahme: Es existiert unter ŵ ein Pfad p0 von v0 nach vk mit ŵ(p0 ) < ŵ(p) Damit gilt w(p0 ) + h(v0 ) − h(vk ) = ŵ(p0 ) < ŵ(p) = w(p) + h(v0 ) − h(vk ) und damit w(p0 ) < w(p). Widerspruch. Die andere Richtung geht analog. 710 Beweis (Teil 3:) Wir zeigen, daß G eine Zyklus mit negativem Gewicht hat genau dann wenn Ĝ einen Zyklus mit negativem Gewicht hat. Sei c = hv0 , . . . , vk i mit v0 = vk ein beliebiger Zyklus. Dann gilt ŵ(c) = w(c) + h(v0 ) − h(vk ) = w(c) und somit hat c negatives Gewicht unter w genau dann wenn es negatives Gewicht unter ŵ hat. (Zyklen haben also unter w und ŵ gleiches Gewicht) 711 Eigenschaft 2 Wir wollen ŵ jetzt so definieren, daß ŵ(u, v) nicht negativ für alle Kanten (u, v) ∈ E. Dazu produzieren wir zunächst einen neuen Graphen G0 = (V 0 , E 0 ) mit • V 0 = V ∪ {s} für einen neuen Knoten s 6∈ V • E 0 = E ∪ {(s, v)|v ∈ V } • w(s, v) = 0 für alle v ∈ V Man beachte: • s hat keine einfallende Kante. Daher: • Kein kürzester Pfad mit Startknoten 6= s enthält s • G0 hat Zyklus mit negativem Gewicht ≺ G hat einen 712 Eigenschaft 2 Wir definieren h(v) = δ(s, v) (≥ 0) ŵ(u, v) = w(u, v) + h(u) − h(v) Es gilt h(v) 0 ≤ h(u) + w(u, v) (nach Lemma 25.3) ≤ h(u) + w(u, v) − h(v) linke Seite abziehen ŵ(u, v) = w(u, v) + h(u) − h(v) ≥ 0 713 JOHNSON(G) 1 compute G0 , where V [G0 ] = V [G] ∪ {s} and E[G0 ] = E[G] ∪ {(s, v)|v ∈ V [G]} 2 if not BELLMAN-FORD(G0 , w, s) 3 then print “graph has neg-weight cycle”; exit 4 for each vertex v ∈ V [G0 ] do 5 h(v) ← δ(s, v) /* BELLMAN-FORD */ 6 for each edge (u, v) ∈ E[G0 ] do 7 ŵ(u, v) ← w(u, v) + h(v) − h(v) 8 for each vertex u ∈ V [G] do 9 run DIJKSTRA(G, ŵ, u) /* ergibt δ̂ */ 10 for each vertex v ∈ V [G] do 11 duv ← δ̂(u, v) + h(v) − h(u) /* nach 25.10 */ 12 return D 714 Laufzeitkomplexität: • O(V 2 lg V + V E) (Fibonacci Heap) • O(V E lg V ) (Binary Heap) 715 Zeichenkettenabgleich Gegeben seien zwei Zeichenketten. Die Frage ist, ob die eine Zeichenkette in der anderen vorkommt. Gesucht sind Algorithmen, die dieses Problem effizient lösen. 716 Beispiel T: xabxyabxyabxz P: abxyabxz Dieses Beispiel stammt aus • Dan Gusfield: Algorithms on Strings, Trees, and Sequences. 717 Ungünstiger Fall T: aaaaaaaaaaaaaaaaa P: aaaaa 718 Formalisierung • Text ist in einem Feld T [1 . . . n] • Muster (pattern) ist in einem Feld P [1 . . . m] • Beide benutzen gemeinsames Alphabet Σ. Beide heißen Zeichenketten (strings). 719 Formalisierung P kommt mit Verschiebung (shift) s im Text T vor (oder, äquivalent: P fängt ab Position s + 1 in T an), falls • 0≤s≤n−m • T [s + 1 . . . s + m] = P [1 . . . m] also ∀1 ≤ j ≤ m T [s + j] = P [j] Falls P mit Verschiebung s im Text T vorkommt, so nennen wir s eine gültige Verschiebung, ansonsten heißt s eine ungültige Verschiebung. Das Zeichenkettenabgleichsproblem besteht in der Aufgabe alle gültigen Verschiebungen für (T, P ) zu finden. 720 Beispiel text T pattern P a b c a b a a b c a b a c s= 3 a b a a 721 Übersicht • Naiv: O((n − m + 1) ∗ m) • Simple: O(n + m) • Rabin-Karp: O((n − m + 1) ∗ m) worst case, but better on average and in practice • Automaton: O(n + m|Σ|) • Knuth-Morris-Pratt: O(n + m) • Boyer-Moore: O((n − m + 1) ∗ m) worst case, often best in practice 722 Definitionen • Σ∗ bezeichne alle endlichen Wörter über Σ • bezeichne das leere Wort. • |x| bezeichne Länge des Wortes x • xy bezeichne Konkatenation der Wörter x und y • w ist ein Präfix von x (w v x) ≺ ∃y ∈ Σ∗ x = wy • w ist ein Suffix von x (w w x) ≺ ∃y ∈ Σ∗ x = yw • Tk := T [1 . . . k] und Pk := P [1 . . . k] • Zeichenkettenabgleich: finde alle s mit P w Ts+m • x=y ∈ Θ(t + 1), t maximal mit |z| = t und z v x und z v y. Wort ≡ Zeichenkette v und w sind reflexiv und transitiv. 723 Lemma 34.1 (Overlapping-Suffix-Lemma) Seien x, y, z ∈ Σ∗ mit x w z und y w z. Dann gilt |x| ≤ |y| =⇒ |x| ≥ |y| =⇒ |x| = |y| =⇒ Beweis: x z ywx y=x x x z y xwy z y y 724 Naiver Algorithmus NAIVE-STRING-MATCHER(T, P ) 1 n ← length[T ] 2 m ← length[P ] 3 for s ← 0 to n − m do 4 if P [1..m] = T [s + 1..s + m] 5 then print “Pattern occurs with shift” s Laufzeit • Laufzeit x = y: Θ(t + 1) mit t = |z| und z längster String mit z v x und z v y • Laufzeit Algorithmus: O((n − m + 1) ∗ m) • Worst-Case: Θ(n2 ), P und T bestehen nur aus ’A’ und |P | = b|T |/2c 725 Vorverarbeitung Vorverarbeitung für einen String S. Dieser ist oft das Muster P . Definition: Für einen String S ist für eine Position i > 1 der Wert Zi (S) die Länge des längsten Teilstrings mit S[i . . . k] v S. Falls S durch den Kontext klar ist, schreiben wir auch einfach nur Zi . 726 Beispiel S= 1 2 3 4 5 6 7 8 9 0 1 a a b c a a b x a a z Z5 (S) = 3 Z6 (S) = 1 Z7 (S) = 0 Z8 (S) = 0 Z9 (S) = 2 727 Z-Box α α S Z li li i ri • Jede Box repräsentiert einen maximalen Teilstring der Präfix von S matcht. • ri ist das am weitesten rechts liegende Ende einer Z-Box, die bei i oder vorher beginnt. • li ist das linke Ende dieser Z-Box. 728 Z-Box Definition: Für jede Position i > 0 für die Zi > 0 startet die Z-Box Zi bei i und endet bei i + Zi − 1. Definition: Für i > 0 ist ri li = max{j + Zj − 1|1 < j ≤ i, Zj > 0} = j für das obiger Ausdruck maximal Falls es mehrere Z-Boxen gibt, die bei ri enden, so kann man li als das Minimum aller linken Startpunkte dieser Z-Boxen wählen. 729 Beispiel Pos 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 S a a b a a b c a x a a b a a b c y Z 1 0 3 7 l 2 0 4 10 r 3 0 6 16 730 Z-Algorithmus • Algorithmus berechnet alle li und ri für i = 2, 3, 4, . . . • für Berechnung von li und ri werden nur die Werte li−1 und ri−1 benötigt • diese werden in Variablen l und r gehalten. • zu Anfang werden l2 und r2 durch direkten Vergleich von S und S2 bestimmt • für alle k > 2: – falls wir nicht in einer Z-Box sind (k > r): direkter Vergleich – Ansonsten: eventuelle Verlängung der Z-Box 731 Eventuelle Verlängung der Z-Box k≤r =⇒ S[k] ist in einer Z-Box enthalten =⇒ S[k] enthalten in S[l . . . k](= α) mit l > 1, α v S =⇒ S[k] kommt auch an Position k 0 = k − l + 1 von S vor (S[k] = S[k 0 ]) Analog: S[k . . . r] = S[k 0 . . . Zl ] Definiere: β = S[k . . . r] Der Teilstring, der bei Position k anfängt muss also einen Präfix von S mit Mindestlänge min(Zk0 , |β|). (|β| = r − k + 1) (siehe 1. Bild) 732 Fallunterscheidung: 1. Fall: Zk0 < |β| =⇒ Zk = Zk0 , r, l unverändert (siehe 2. Bild) 2. Fall: Zk0 ≥ |β| =⇒ S[k . . . r] ist Präfix von S und Zk ≥ |β| = r − k + 1 Zk könnte echt größer als |β| sein. Also: Vergleiche S[r + 1, . . .] mit S[|β| + 1 . . .] Sei q ≥ r + 1 die Position mit dem ersten nicht übereinstimmenden Buchstaben. Dann setzen wir Zk = q − k, r = q − 1, l = k. (siehe 3. Bild) 733 Veranschaulichung α S β k0 S γ β l Zl α k0 6 k 0 + Zk 0 − 1 α k0 β r k α γ β Zk 0 S α γβ k 6r l k + Zk − 1 α β 6 l k + Zk 0 − 1 734 ? β k r Z-ALGORITHMUS(S) 1 compute Z2 by explicit comparison of S2 with S 2 if Z2 > 0 then 3 r ← r2 ← Z2 + 1, l ← l2 4 else 5 l←r←0 735 6 for k = 3 to |S| 7 if k > r then 8 compute Zk by explicit comparison 9 if Zk > 0 10 r ← k + Zk − 1; l ← k 11 else B k ≤ r 12 k0 ← k − l + 1 13 β ← S[k, r] B |β| = r − k + 1 14 if Zk0 < |β| then 15 Zk ← Zk 0 16 else 17 compare Sr+1 and S|β|+1 18 q sei erster mismatch B q ≥ r + 1 19 Zk ← q − k, r ← q − 1, l ← k 736 Korrektheit Theorem: Der Z-Algorithmus berechnet die Zi -Werte von S richtig. Er hat Laufzeit O(|S|). 737 Einfacher Algorithmus SIMPLE-MATCHER(P , T ) 1 S ← P $T B $ neues Zeichen nicht in P oder T 2 Z-ALGORITHMUS(S) 3 for (i = |P | + 1; i ≤ |P | + |T | + 1; ++i) 4 if Zi = |P | 5 print i 738 Warum weitermachen? • Algorithmen an sich interessant • Knuth-Morris-Pratt: nur marginale Verbesserung gegenüber einfachem Algorithmus. Wird aber zu Aho-Corsick verallgemeinert, der viele Muster auf einmal matchen kann. • Varianten von Boyer-Moore haben ebenfalls O(n + m) worst case, laufen aber oft in sublinearer Zeit. 739 Rabin-Karp-Algorithmus Zur Veranschaulichung • Σ = {0, . . . , 9} i.A.: Wort ≡ Zahl in d−ärer Darstellung mit d = |Σ| • das Wort “123” entspricht somit der Zahl 123. • Für P [1 . . . m] Muster bezeichne p die zugehörige Zahl. • Für T [1 . . . n] Text bezeichen ts die Zahl des Teilwortes T [s + 1, . . . , s + m]. • T [s + 1, . . . , s + m] = P [1 . . . m] ≺ ts = p. Falls wir p in O(m) berechnen können, und die ts in O(n), so hätten wir einen O(m + n)-Algorithmus, da wir ja nur noch in einem letzten Schritt p mit allen ts vergleichen müssten. 740 Anwenden des Horner-Schemas Wir berechnen p in O(m) mittels Horner-Schema p = P [m] + 10(P [m − 1] + 10(P [m − 2] + . . . + 10(P [2] + 10P [1]) . . .)) Berechnung von t0 aus T [1 . . . m] in gleicher Weise in O(m). Berechnung der t1 , . . . , tn−m in O(n − m) ts+1 = 10(ts − 10m−1 T [s + 1]) + T [s + m + 1] 741 Beispiel P [1 . . . 3] = 456 p = 6 + 10(5 + 10 ∗ 4) = 6 + 10(45) = 456 T [1 . . . 5] = 34567 t0 t1 t2 = 5 + 10(4 + 10 ∗ 3) = 5 + 10(34) = 345 = 10(345 − 100 ∗ 3) + 6 = 450 + 6 = 456 = 10(456 − 100 ∗ 4) + 7 = 560 + 7 = 567 742 Verbesserung Nachteil dieser Methode • Die Zahlen werden zu groß Abhilfe • Führe Berechnungen modulo eines geeigneten q durch. Dies kann dann zwar für einige Verschiebungen zu Treffern führen, obwohl diese nicht gültig sind, aber diese sind selten und können durch erneutes Testen herausgefiltert werden. 743 Wahl von q Wahl q • q prim und so, daß 10q noch gerade in ein Wort (Rechner, z.B. 32 bit) paßt • i.A.: bei d-ärem Alphabet wähle q so, daß d*q noch gerade so in ein Rechnerwort paßt. Anpassen der Rekurrenz (jetzt modulo q) Berechnung der t1 , . . . , tn−m in O(n − m) ts+1 = (d(ts − hT [s + 1]) + T [s + m + 1]) mod q mit h ≡ dm−1 ( mod q) der Wert der Ziffer “1” an der höchsten Stelle in einer Zahl mit m Ziffern 744 745 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 mod 13 7 (a) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1 ... ... ... mod 13 (b) 8 9 3 11 0 1 7 8 4 5 10 11 7 9 11 valid match spurious hit old high−order digit new low−order digit old high−order digit 3 1 4 1 5 2 14152 7 8 (c) 746 shift new low−order digit (31415 − 3*10000)*10 + 2 (mod 13) (7 − 3*3)*10 + 2 (mod 13) 8 (mod 13) RABIN-KARP-MATCHER(T, P, d, q) 1 n ← length[T ] 2 m ← length[P ] 3 h ← dm−1 mod q 4 p←0 5 t0 ← 0 6 for i ← 1 to m 7 do p ← (dp + P [i]) mod q 8 t0 ← (dt0 + T [i]) mod q 9 for s ← 0 to n − m 10 do if p = ts 11 then if P [1..m] = T [s + 1..s + m] 12 then “Pattern occurs with shift” s 13 if s < n − m 14 then tt+s ← (d(ts − hT [s + 1]) + T [s + m + 1]) mod q 747 Laufzeit Rabin-Karp-Algorithmus Worst case (P = am , T = an ): Θ((n − m + 1) ∗ m) Falls die Abbildung von Σ∗ → Zq so gewählt wird, dass jeder Wert in Zq mit der gleichen Wahrscheinlichkeit vorkommt, so beträgt die erwartete Laufzeit O(n) + O(m(v + n/q)), wobei v die Anzahl der gültigen Verschiebungen ist. Gibt es nur O(1) gültige Verschiebungen und wählen wir q ≥ m, so ist die Laufzeit O(n). 748 Endliche Automaten Der nächste Algorithmus benutzt endliche Automaten. Definition Ein endlicher deterministischer Automat M ist ein 5-Tupel (Q, q 0 , A, Σ, δ) mit Q ist eine endliche Menge von Zuständen q0 ∈ Q ist der Startzustand A ⊆ Q ist eine Menge von Endzuständen Σ ist ein endliches Alphabet δ ist die Zustandsübergangsfunktion Q × Σ → Q 749 Beispiel state 0 1 input a b 1 0 0 0 a b 0 a b (b) (a) 750 1 Akzeptor-Funktion Die Zustandsübergangsfunktion δ kann kanonisch auf Wörter fortgesetzt werden: Φ() Φ(wa) = q0 = δ(Φ(w), a) Ein Automat M akzeptiert ein Wort w :≺ Φ(w) ∈ A. 751 Musterautomat Jedes Muster kann auf einen Automaten abgebildet werden, der dann für einen gegebenen Text erkennt, ob das Muster in diesem Text vorkommt. Beispiel: P = ababaca. 752 0 state 0 1 2 3 4 5 6 7 a 1 input a b c 1 0 0 1 2 0 3 0 0 1 4 0 5 0 0 1 4 6 7 0 0 1 2 0 a a a b 2 a a 3 b 4 a b c 5 6 a 7 b P a b a b a c a a) i T [i ] state φ ( Ti ) 1 2 3 4 5 6 7 8 9 10 11 a b a b a b a c a b a 0 1 2 3 4 5 4 5 6 7 2 3 (c) (b) 753 Suffix-Funktion Konstruktion Zeichenkettenabgleichsautomat • zuerst Suffixfunktion σ : Σ∗ → {1, . . . , m} σ(w) ist die Länge des längsten Präfixes von P , der ein Suffix von w ist. σ(w) = max{k|Pk w w} Beispiel mit P = ab σ() = 0 σ(ccaca) = 1 σ(ccab) = 2 754 Zeichenkettenabgleichsautomat Sei P [1 . . . m] ein Muster. Dann definieren wir den Zeichenkettenabgleichsautomat M = (Q, q0 , A, Σ, δ) wie folgt: Q = {0, 1, . . . , m} q0 = 0 A = {m} Σ = gegeben δ(q, a) = σ(Pq a) Invariante während der Akzeptierung Φ(Ti ) = σ(Ti ) wobei Ti Präfix des Textes T ist. 755 FINITE-AUTOMATON-MATCHER(T, δ, m) 1 n ← length[T ] 2 q←0 3 for i ← 1 to n 4 do q ← δ(q, T [i]) 5 if q = m 6 then s ← i − m 7 print “Pattern occurs with shift” s Laufzeit O(n) ohne δ(q, T [i])-Aufrufe 756 Lemma 34.2 (Suffix-Function inequality) Sei w ∈ Σ∗ ein Wort und a ∈ Σ ein Buchstabe. Dann gilt σ(wa) ≤ σ(w) + 1. 757 34.3 (Suffix-function recursion lemma) Sei w ∈ Σ∗ ein Wort und a ∈ Σ ein Buchstabe. Dann gilt q = σ(w) =⇒ σ(wa) = σ(Pq a). 758 Theorem 34.4 Sei Φ die Akzeptorfunktion eines Zeichenkettenabgleichautomaten f ür ein Muster P und Text T [1 . . . n]. Dann gilt ∀0 ≤ i ≤ n Φ(Ti ) = σ(Ti ) 759 COMPUTE-TRANSITION-FUNCTION(P, Σ) 1 m ← length[P ] 2 for q ← 0 to m do 3 for each character a ∈ Σ do 4 k ← min(m + 1, q + 2) 5 repeat k ← k − 1 6 until Pk w Pq a 7 δ(q, a) ← k 8 return δ Laufzeit: O(m3 |Σ|) Verbesserte Version: O(m|Σ|) 760 P = ababaca, m = 7 q x k Pk Pq x δ(q, x) 0 a 1 a a 1 b 1 a b 0 b c 1 a c c 0 c a 2 ab aa 1 a aa 1 b 2 ab ab 2 c 2 ab ac 1 a ac 1 a 3 aba aba 3 b 3 aba abb 2 ab abb 3 aba abc 2 ab abc 1 2 c 761 0 0 2 2 Knuth-Morris-Pratt Idee: Vermeiden der Zustandsübergangsfunktion δ. Dazu wird eine Funktion π[1 . . . m] verwendet, die unabhängig von a diejenige Information enthält, die notwendig ist, um δ(q, a) zu berechnen. Dadurch wird ein Faktor |Σ| eingespart. 762 Präfixfunktion für Muster Die Präfixfunktion enthält Informationen darüber, wie ein Muster sich zu sich selbst verhält, falls es gegenüber sich selbst verschoben wird. Mit Hilfe dieser Information kann vermieden werden nutzlose Verschiebungen zu testen. Dazu ist die Antwort auf folgende Frage interessant: Das Teilmuster P [1 . . . q] sei identisch mit T [s + 1 . . . s + q]. • Was ist die kleinste Verschiebung s0 > s mit P [1 . . . k] = T [s0 + 1 . . . s0 + k] unter der Bedingung s0 + k = s + q. 763 Illustration P = ababaca b a c b a b a b a a b c b a b s a b a b a c a P q (a) b a c b a b a b a a b c b a b s’ = s + 2 (b) T a b a b a c a k 764 P T a b a b a Pq a b a Pk (c) Anmerkungen (a) Das Muster P = ababaca ist gegenüber dem Text um s verschoben. q = 5 Buchstaben sind auf beiden Seiten identisch. (b) Mit dem Wissen um diese 5 Buchstaben kann man herleiten, dass eine weitere Verschiebung um 1 keinen Abgleich erzeugt, da ja der Anfangsbuchstabe a des Musters ungleich dem 2-ten Buchstaben b im Muster (und in Text) ist. Mit dem gleichen Wissen leiten wir her, dass ein Abgleich bei Verschiebung s0 = s + 2 für die ersten 3 = q − 2 Buchstaben möglich ist. (c) Diese Information kann vorberechnet werden, indem man das Muster mit sich selbst vergleicht. Im Beispiel ist der längste Präfix von P , der gleichzeitig auch Suffix von P5 ist, P3 . (=⇒ π[5] = 3). Die nächste “erfolgreiche” Verschiebung liegt dann bei s0 = s + (q − π[q]). 765 Präfixfunktion Sei P [1 . . . m] ein Muster. Die Präfixfunktion π : {1, . . . , m} für das Muster P ist die definiert als π[q] = max{k| k < q ∧ Pk w Pq } [Vgl: Zi ] 766 KMP-MATCHER(T, P ) 1 n ← length[T ] 2 m ← length[P ] 3 π ← COMPUTE-PREFIX-FUNCTION(P ) 4 q←0 5 for i ← 1 to n 6 do while q > 0 and P [q + 1] 6= T [i] 7 do q ← π[q] 8 if P [q + 1] = T [i] 9 then q ← q + 1 10 if q = m 11 then print “Pattern occurs with shift” i − m 12 q ← π[q] Laufzeit: amortisierte Analyse: Θ(n) 767 COMPUTE-PREFIX-FUNCTION(P ) 1 m ← length[P ] 2 π[1] ← 0 3 k←0 4 for q ← 2 to m 5 do while k > 0 and P [k + 1] 6= P [q] 6 do k ← π[k] 7 if P [k + 1] = P [q] 8 then k ← k + 1 9 π[q] ← k 10 return π Laufzeit: amortisierte Analyse: Θ(m) 768 Heuristics Judea Pearl • Heuristics (intelligent search strategies for computer problem solving) • Addison Wesley 1984 (out of print :() 769 Heuristics Heuristics are criteria, methods, or principles for deciding which among several alternative courses of action promises to be the most effective in order to achieve some goal. They represent compromises between two requirements: • the need to make such criteria simple • the desire to see them discriminate correctly between good and bad choices A heuristic may be a rule of thumb. 770 Heuristics Beispiele: • Melonen kaufen • Schach 771 Heuristics Viele Probleme können nicht exakt gelöst werden. (Zu aufwändig) 772 Example Problems • 8-Queens Problem • 8-Puzzle Problem • Road-Map Problem • Traveling Salesman Problem • Counterfeit Coin Problem 773 8-Queens Problem Q Q Q x x 774 x 8-Queens Problem • 8 Queens on chess board which cannot attack each other • solve incrementally by a series of decisions • do so systematically (do not generate same configuration over and over) • construct rather than transform • in case of dead end: backtrack Heuristics: number of unattacked cells (f ) 775 8-Queens Problem Q Q Q x x f = 8, 9, 10 776 x 8-Queens Problem Another Heuristics: • rows with few unattacked cells will be blocked earlier • focus attention to row with least number of unattacked cells f 0 • minimum over rows of unattacked cells in row f 0 = 1, 1, 2 777 8-Puzzle initial state 2 1 7 3 1 2 3 8 4 8 4 6 5 7 3 1 8 7 6 A 6 or or 2 goal state 8 4 2 1 5 7 6 B 3 2 3 4 1 8 4 5 7 6 5 C 778 5 8-Puzzle • which alternative is the best? • the one with the smallest number of moves to the goal state • exhaustive search (shortest path) • this might be too expensive • needed: presearch judgement • needed: guess how close we are to the goal 779 8-Puzzle Estimates: • number of mismatched tiles (h1 ) • sum of the (horizontal and vertical) distances of the mismatched tiles between two states (h2 ) (Manhattan or city-block distance) h1 (A) = 2 h1 (B) = 3 h1 (C) = 4 h2 (A) = 2 h2 (B) = 4 h2 (C) = 4 780 Road Map Problem Find shortest path between city A and B 781 Road Map Problem Find shortest path between city A and B • start from A • procede to C or D? • intuitively: C further apart from D, hence go to D • use Euclidian distance h(i) from city i to goal city B • heuristics: compare d(A, C) + h(C) with d(A, D) + h(D) Note: h(i) lower bound of d(i, B) 782 Traveling Salesman Problem Find cheapest tour that visits every node of an edge-weighted graph exactly once. • TSP is NP-hard • with a good bounding function finding good tours is possible • what is a bounding function? • good estimate for cost of completion of a partial tour • example: minimum spanning tree through remaining nodes Note: MST gives lower bound of completion cost 783 Counterfeit Coin Problem • given: 12 coins, one with a different weight • given: two-pan scale to weigh coins • task: find the one with deviating weight • task: find optimal weighing strategy heuristics should allow us to focus on the most promising weighing strategies [no more than three weighings] 784 Counterfeit Coin Problem Two alternatives: 1. start weighing two coins (one in each pan) 2. start weighing eight coins (four in each pan) 2) is superior as 1. high probability of leaving us with the problem of size 10 2. leaves us for sure with a problem of size 4 Note: scale answer has three possibilities: balanced, tip left, tip right 785 Optimizing, Satisficing, Semi-Optimizing Tasks Optimizing TSP, Road Map Problem Satisficing 8-Queens, [8-Puzzle] Semi-Optimizing find “good” solution TSP since constructing optimal solution often two expensive subcategories: • near-optimization: solution guaranteed to be near optimal (within bounds) • approximate optimization: solution near optimal one with high probability Satisficing tasks can be turned into optimizing tasks and vice versa 786 Systematic Search and the Split-Prune Paradigm Basic ingredients for computer-based problem solving: 1. A symbol structure or code representing candidate objects/states in the search space. 2. Computational tools that are capable of transforming the encoding of one object to another in order to scan the space of candidate objects systematically. 3. An effective method of scheduling these transformations so as to produce the desired object as quickly as possible. Nomenclature: 1. database [or representation] 2. operators or production rules 3. control strategy 787 Control Strategy A control strategy is called systematic if it complies with the following colloquially stated directives: 1. Do not leave any stone unturned (unless you are sure there is nothing under it) 2. Do not turn any stone more than once. [Revisit 8-Queens Problem: transformation approach] 788 Representation and Control Strategy Efficiency of control strategy depends on representation! Example: 1. phone book ordered alphabetically 2. phone book with random arrangement Search entry for Maier 1: If first lookup is Pötter: skip everything afterwards Conclusion: Encoding must not only handle single objects but also subsets thereof. 789 Control Strategy: split-and-prune method First requirement: • every object must be in an eventually investigated subset Second requirement: • if a subset of objects has been eliminated from consideration • then subsequent operations will not generate any object contained in it Both together: only operation allowed: splitting of subsets Pruning: eliminate subset if it for sure does not contain solution 790 Example: 8-Queens problem • empty board represents the whole space • placing one queen at some position represents the subspace that has a queen at exactly this position • same for k ≤ 8 queens Prune: • if conflict with k < 8 queens: conflict does not go away with placing more queens. • hence, subsets are pruned. Bad: • place all 8 queens onto board • iterate: try resolving conflicts by moving a queen Problem: no represenation of subsets but only of single objects 791 Refinement of Ingredients 1. A symbol structure called a code or database that represents subsets of potential solutions (henceforth called candidate subset) 2. A set of operations or production rules that modify the symbols in the database, so as to produce more refined sets of potential solutions 3. A systematic search procedure or control strategy that decides at any given time which production rule is to be applied to the database If sufficient storage: database should keep multiple codes 792 State-Space-Graph Often: State-Space-Graph State: addditional information in code which explicitly defines the remaining subproblem Example TSP: A → B → C → D → {E, F } → A where A → B → C is partial tour and {D, E, F } are the remaining cities to be visited (stands for set of possible tours through them) Another: A → C → B → D → {E, F } → A if latter partial tour more expensive than former, we can prune it [pruning by dominance] 793 Counterfeit Coin Problem and And-Or-Graphs • Start: weigh 12, 10, 8, 6, 4, or 2 coins • every weigh results in subproblems, depending on the outcome (balanced, tip left, tip right) • all the subproblems must be solved 794 [left (l), right (r), balanced (b)] problem size [l,r,b] subproblem size(s) 12 6:6 A,B,- 6 10 5:5 A,B,C 5 and 2 8 4:4 A,B,C 4 6 3:3 A,B,C 3, 6 4 2:2 A,B,C 2, 8 2 1:1 A,B,C 1 (answer!), 10 Represent as And-Or-Graph, bounded depth (3): 795 796 And-Or-Graph Solve-Labeling Rule A node is “solved” if 1. it is a terminal node, or 2. it is a non-terminal OR node, and at least one child is solved, or 3. it is a non-terminal AND node and all its children are solved Label “unsolvable” rules: exchange AND and OR [sorry, we can’t do more on this] 797 Basic Heuristic-Search Procedures Given: Weighted Graph G = (V, E) and edge weights c : E → R c may represent costs or rewards • Elementary operation: node generation generates a representation of a node from a parent node • The new node is then generated and the parent node explored • A node in which all (direct) successors have been generated is called expanded (Note: not the whole graph given but constructed during exploitation) 798 Node Classification At any point in time we can distinguish between the following classes of nodes: 1. Nodes that have been expanded, i.e. all child nodes are generated 2. Nodes that have been explored but not yet expanded 3. Nodes that have been generated but not yet explored 4. Nodes that are not yet generated 799 OPEN and CLOSED It is fashionable to keep two queues (lists, stacks, sets): OPEN contains generated but not yet expanded CLOSED contains expanded nodes 800 Search procedure, policy, strategy determines in which order nodes are to be generated We distinguish • blind (uniformed) search • informed (guided, directed) search The latter uses information about the problem domain and about the nature of the goal to help guide the search. 801 Hill-Climbing this is another name for a Greedy Algorithm • best choice is explored • choices are irrevocable • only useful if highly informed [in general, algorithms in AI termed slightly different than in Corman] 802 Depth-First Search 1. put the start node on OPEN 2. while OPEN is not empty (a) remove topmost node n from OPEN and put it on CLOSED (b) expand all of n’s successors and put them on top of OPEN (c) if any of n’s successors is a goal state return with success (d) if any of n’s successors is a dead end, remove it from OPEN and put it on CLOSED 3. exit with failure [OPEN is a stack; key = counter, EXTRACT-MAX] [different from our former DFS: expand all successors] 803 Backtracking 1. Put the start node on OPEN 2. while OPEN is not empty (a) retrieve topmost node n from OPEN and put it on CLOSED (b) if n is fully expanded remove it from OPEN and put it on CLOSED; continue (c) generate one new successor s of n and put it on OPEN (d) if s is a goal state return with success (e) if s is a dead end remove it from OPEN and put it on CLOSED 3. exit with failure [OPEN is a stack] [this is our DFS] 804 Breadth-First Search 1. put the start node on OPEN 2. while OPEN is not empty (a) remove first node n from OPEN and put it on CLOSED (b) expand all of n’s successors and put them at the end of OPEN (c) if any of n’s successors is a goal state return with success (d) if any of n’s successors is a dead end, remove it from OPEN and put it on CLOSED 3. exit with failure [OPEN used as a QUEUE; key = counter, EXTRACT-MIN] 805 Best-First Search Uses heuristic function f to estimate the gain of expanding a node. 1. put the start node s on OPEN after assigning s.key = f (s) to it 2. while OPEN is not empty (a) n = EXTRACT-MIN(OPEN); put n on CLOSED (b) expand all of n’s successors and put them at the end of OPEN (c) for all successors n0 of n i. x = f (n0 ) ii. if n0 is not on OPEN or ClOSE: n0 .key = x; put it on OPEN iii. if n0 is on OPEN or CLOSE /* n0 from queue */ A. if x ≥ n0 .key B. then discard n0 C. else n0 .key = x; if it was on CLOSED, move it to OPEN 3. exit with failure 806 A∗ Search two functions g(n) calculates the costs for reaching n h(n) estimates costs for reaching goal from n if h(n) is a lower bound, the A∗ algorithm finds the optimal solution and has many other nice properties. A∗ uses the heuristic function f (n) := g(n) + h(n) [For an example see Road-Map Problem] Let s be the start node 807 A∗ Algorithm 1. s.key = f (s); OPEN.insert(s) 2. while (OPEN is not empty) (a) n = OPEN.extractMin; CLOSED.insert(n); (b) if (n is goal node) return n (c) for all successors n0 of n generated while expanding n i. ii. iii. iv. n0 .π = n x = f (n0 ) if (n0 6∈ OPEN ∪ CLOSE) { n0 .key = x; n0 .π = n; OPEN.insert(n0 ); } else /* n0 from queue */ A. if (x ≥ n0 .key) B. discard n0 ; C. else { n0 .key = x; n0 .π = n; if (n0 ∈ CLOSED) move it to OPEN; } 3. exit with failure 808