Skript

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