Pseudo-Code

Werbung
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
Herunterladen