Sie sind auf Seite 1von 263

Kapitel 6

Einführung in den Gegenstand der


Vorlesung Informatik III

6.1 Gegenstände der Vorlesung


Im ersten Teil dieser Vorlesung geht es um den Entwurf und die
Analyse effizienter Algorithmen für wichtige Probleme. Wir wer-
den das Suchen und Sortieren, Entwurfs- und Analysetechiken und
die effiziente Handhabung von Operationen auf Graphen behan-
deln.
Leider gibt es für sehr viele, sehr wichtige Probleme aus den un-
terschiedlichsten Anwendungsbereichen keine effizienten Algorith-
men, um sie exakt zu lösen. Aufgaben aus der Transportoptimie-
rung gehören ebenso dazu wie die Bestimmung optimaler Modelle
in der Bioinformatik. Alle diese Probleme haben sich zudem bisher
hartnäckig jedem Versuch widersetzt, den Nachweis zu führen, daß
es zu ihrer Lösung keine effizienten Algorithmen gibt. Im zweiten
Teil werden wir diejenigen Grundbegriffe der Theoretischen Informatik kennenlernen, die
es uns gestatten, solche Probleme unter einheitlichen Gesichtspunkten zu klassifizieren:
NP-vollständige Entscheidungsprobleme und als Ausblick NP-äquivalente Optimierungs-
probleme.
Diese Begriffe sind für jeden Informatiker von großer Bedeutung. Das trifft auch dann
zu, wenn seine Interessen vorwiegend auf praktischem Gebiet liegen. Sie helfen, die Möglich-
keiten und Grenzen der Praxis besser einzuschätzen. Zudem kann man viele neuere Ent-
wicklungen, z.B. die Konzepte der Computersicherheit, ohne die Methoden der Theoreti-
schen Informatik nicht verstehen.

Um eine Idee davon zu bekommen, was uns erwarten wird, wenn wir den Entwurf und
die Analyse effizienter Algorithmen studieren, betrachten wir das folgende Problem.
Das Problem MAXSUM

3
Eingabe: I = {aj | j ∈ J, aj ∈ Z}, wobei J := [1, n] ein Intervall natürlicher Zahlen von 1
bis n ist.
Ausgabe: maxsum(I) := max{f (i, j) | i, j ∈ J, i ≤ j}, wobei f (i, j) als jk=i ak definiert
P
ist.

Es bietet sich hier an, die Anzahl n = |I| der in Rede stehenden Zahlen als Problemgröße
zu bezeichnen.
Unser erster Algorithmus ist ein Vertreter der Kategorie Auschöpfende Suche“. Es

werden alle zugelassenen Werte f (i, j) berechnet und dann das Maximum gebildet.

Algorithmus 6.1 (Algorithmus A1 für MAXSUM)


Großschritt 1.
Berechne jeden Wert f (i, j) für sich.
Lege die Werte in einem Feld f ab.
Großschritt 2.
Durchlaufe f und bestimme das Maximum.
Grundsätzlich haben wir zwei Fragen zu beantworten, wenn uns ein Algorithmus vor-
gelegt wird:
1. Arbeitet der Algorithmus korrekt?
2. Wieviele Ressourcen verbraucht er? (Um diese Frage beantworten zu können, müssen
wir zunächst festlegen, welches Maß uns interessiert.)
Frage eins läßt sich für Algorithmus 6.1 durch Draufschaun“ positiv beantworten:

Jawohl, A1 löst MAXSUM. Was Frage zwei angeht, so legen wir uns zunächst darauf fest,
daß wir
– arithmetische Operationen und
– Zahlenvergleiche
zählen wollen.
Als Aufwärmübung bestimmen wir nun die Länge des Feldes f : Es gibt n2 Indizes


(i, j) miti < j. Hinzu kommen noch n Paare (i, j) mit i = j. Folglich hat das Feld f die
Länge n2 + n = n+1 2
.
Wir kommen zur eigentlichen Laufzeitanalyse von Algorithmus A1 . Wir bezeichnen mit
timeA (n) die maximale Anzahl von arithmetischen Operationen und Zahlenvergleichen, die
der Algorithmus A auf eine Eingabe der Größe n benötigt.
Zur Berechnung von f (i, j) in Großschritt 1 müssen wir j − i Additionen durchführen.
n−1 X
X n n−1 X
X n−i n−1 X
X i
timeGS1 von A1 (n) = (j − i) = j= j
i=1 j=i+1 i=1 1 i=1 j=1

4
Unter Verwendung der Gleichungen 2.1 und 2.2 aus Abschnitt 2.1 erhalten wir:
1
timeGS1 von A1 (n) = (n3 − n).
6
Für die Analyes von GS 2 können wir die oben berechnete Feldlänge gut gebrauchen. Es
sind
 
n+1
timeGS2 von A1 (n) = −1
2
Vergleiche notwendig. Alles in allem erhalten wir:
1 1 1
timeA1 (n) = n3 + n2 + n − 1.
6 2 3
Natürlich ist es unvernünftig, in Algorithmus A1 Zwischenergebnisse bei der Berechnung
von f (i, j) nicht zur Bestimmung eines anderen f (i′ , j ′ ) zu verwenden:

Algorithmus 6.2 (Algorithmus A2 für MAXSUM)


Großschritt 1.
Berechne jeden Wert f (i, j) unter Verwendung der folgenden Regel:
f (i, j + 1) = f (i, j) + aj+1 .
Großschritt 2.
Durchlaufe f und bestimme das Maximum wie bei Algorithmus 6.1.
In Großschritt 1 von Algorithmus 6.2 benötigen wir für jedes i = 1, 2, . . . , n zur Berech-
nung der Werte f (i, i), f (i, i + 1), . . . , f (i, n) genau n − i viele Additionen:
n−1
X n−1
X
timeGS1 von A2 (n) = (n − i) = i
i=1 i=1

Unter Verwendung von Gleichung 2.1 aus Abschnitt 2.1 erhalten wir:

n2 n
 
n
timeGS1 von A2 (n) = = − .
2 2 2
n2 n
 
n+1
timeGS2 von A2 (n) = −1= + −1
2 2 2
Alles in allem erhalten wir:

timeA2 (n) = n2 − 1.

Viele nützliche Algorithmen haben eine rekursive Struktur. Sie sind gemäß dem Teile–
und–Herrsche Paradigma organisiert und zerfallen auf jeder Rekusionsebene in zwei Pha-
sen. Dazu nehmen wir an, daß die Zahl n eine Zweierpotenz ist:

5
Teile die Eingabeproblemstellung in mehrere Problemstellungen kleinerer Eingabengröße.
Beherrsche das Gesamtproblem durch
– rekursives Lösen der Teilprobleme
– Zusammensetzen der Lösungen der Teilproblem zu einer Lösung des Gesamt-
problems.
Wir wollen einen rekursiven Algorithmus zur Lösung des MAXSUM–Problems entwer-
fen.
Vorüberlegung. Wir zerlegen die Indexmenge einer Problemstellung unseres MAXSUM–
Problems in drei Teilbereiche. Dazu sei J = [1, n].
J1 := [1, n/2] J2 := [n/2 + 1, n] K := {(i, j) | i ∈ J1 , j ∈ J2 }
Nun können wir MAXSUMn auf die folgende Weise lösen:
1. Löse die beiden Problemstellungen von MAXSUMn/2 bezogen auf die Indexmengen
J1 und J2 .
2. Berechne max{f (i, j) | (i, j) ∈ K}.
3. Berechne aus diesen drei Zwischenergebnissen durch zwei weitere Vergleiche deren
Maximum und damit die Lösung der Eingabeproblemstellung.
So kann man das MAXSUM–Problem lösen. Leider handelt es sich dabei nicht um
einen rekursiven Algorithmus. Das Problem max{f (i, j) | (i, j) ∈ K}“ ist keine Instanz

von MAXSUM.
Um unser MAXSUM–Problem in zwei Teilprobleme gleicher Art teilen zu können,
erweitern wir die Problemstellung etwas:
l(i) := ai + ai+2 + . . . + an/2
r(j) := an/2+1 + an/2+2 + . . . + aj

und erkennen, daß

f (i, j) := l(i) + r(j) ((i, j) ∈ K)


ist. Das ist die Motivation für die folgende Erweiterung des MAXSUM–Problems.
Das erweiterte Problem MAXSUM∗
Eingabe: I = {aj | j ∈ J}, wobei J ein abgeschlossenes Intervall natürlicher Zahlen ist.
Ausgabe: (maxsum(I), maxprefix(I), maxsuffix(I), ). Ist min J das Minimum und max J
das Maximum über alle Elemente aus J, so sind
maxsum(I) := max{f (i, j) | i, j ∈ J, i ≤ j} (wie bisher)
maxprefix(I) := max{f (min J, j) | j ∈ J}
maxsuffix(I) := max{f (j, max J) | j ∈ J}.

6
Algorithmus 6.3 (Algorithmus A3 für MAXSUM∗ )
Großschritt 1.
Falls |I| = 1 ist, so gib amin J = amax J aus und brich ab.
Anderfalls fahre fort.
Großschritt 2.
Teile die aktuelle Problemstellung I für MAXSUM∗n mit Indexmenge J in zwei
Problemstellungen für MAXSUM∗n/2 mit Indexmengen J1 und J2 .
Großschritt 3.
Löse I1 := {ai | i ∈ J1 } und I2 := {ai | i ∈ J2 } durch rekursiven Aufruf.
Großschritt 4.
Setze die Lösung für I wie folgt zusammen:
maxsum(I) ← max{maxsum(I1 ), maxsum(I2 ), maxsuffix(I P 1 ) + maxprefix(I2 )}
maxprefix(I) ← max{maxprefix(I1 ), maxprefix(I2 ) + j∈J1 aj }
P
maxsuffix(I) ← max{maxsuffix(I2 ), maxsuffix(I1 ) + j∈J2 aj }
Zur Analyse der Laufzeit von Algorithmus 6.3 stellen wir die folgende Rekursion auf:
timeA3 (1) = 1
timeA3 (n) = 2 · timeA3 (n/2) + (n + 3).

Da es nur log2 n Halbierungen einer Problemstellung der Größe n geben kann, folgt leicht

timeA4 (n) = Θ (n · log n) .

Der folgende Algorithmus hat noch eine schnellere Laufzeit als Algorithmus 6.3. Es
handelt sich dabei um einen besonders einfachen Algorithmus zur dynamischen Program-
mierung.

Algorithmus 6.4 (Algorithmus A4 für MAXSUM)


Annahme: J = [1, n].

Max ← a1
MaxSuffix ← a1
k←1

Solange k < n führe aus


k ←k+1
MaxSuffix ← max{MaxSuffix + ak , ak }
Max ← max{MaxSuffix, Max}

Ausgabe: Max

7
Algorithmus 6.4 ist der erste, bei dem man sich einen Moment überlegen muß, warum
er korrekt ist. Man sieht leicht ein, daß

Invk : Max = max{f (i, j) | 1 ≤ i ≤ j ≤ k}


MaxSuffix = max{f (i, k) | 1 ≤ k}

eine Invariante der while–Schleife in Algorithmus 6.4 ist.


Die Laufzeit von Algorithmus 6.4 ist denkbar einfach zu analysieren: Die while–Schleife
wird (n − 1) mal durchlaufen. Bei jeder Iteration werden zwei Vergleiche und eine Addition
ausgeführt.

timeA4 (n) = 3n − 3.

Wie sich die Verbesserung der asymptotischen Laufzeit für konkrete Eingabenlängen
auswirkt, sehen wir an der folgenden Tabelle.

n A1 A2 A3 A4
2
2 19 15 13 9 (6.1)
210 ≈ 180 · 106 ≈ 106 19457 3069

Nun ist es an der Zeit zu sagen, daß wir uns bei unseren Analysen in der Regel um
konstante Faktoren nicht kümmern werden. Das heißt zum Beispiel, daß wir, wenn wir die
Laufzeit von Algorithmus 6.4 angeben, statt 3 · n − 3 lieber O (n) schreiben. Warum das
angebracht ist, werden wir in Abschnitt 7.1 sehen.

Definition 6.5 Seien f, g : N → N zahlentheoretische Funktionen.

1. Die Funktion f ist genau dann ein Element von O (g), wenn es eine positive re-
elle Konstante c und eine natürliche Zahl n0 derart gibt, daß für alle n ≥ n0 die
Ungleichung f (n) ≤ c · g(n) gilt.

2. g ∈ Ω(f ) ⇐⇒ f ∈ O (g).

3. g ∈ Θ(f ) ⇐⇒ f ∈ O (g) und f ∈ Ω(g).

Bezeichnungen: Obwohl es eigentlich falsch ist, werden wir unverdrossen statt der Ele-
mentrelationen f ∈ O (g), f ∈ Ω(g) und f ∈ Θ(g) die Gleichungen f = O (g), f = Ω(g)
bzw. f = Θ(g) schreiben.

Beispiele.
100
1. 28·101000 ·n3 +1010 ·n2 ∈ O (10−1000 · n3 ): Wir sehen, daß Konstanten hier irrelevant
100
sind. Das gleiche gilt für niederwertige Terme (hier 1010 · n2 ).

2. Ist k ≤ ℓ, so ist nk ∈ O nℓ .


8
Der Term f = O (g)“ steht nach Definition 6.5 dafür, daß die Funktion f von einem

Anfangsstück abgesehen in gewissem Sinne kleiner ist als die Funktion g. Schärfer ist der
folgende Begriff.

Definition 6.6 Seien f, g : N → N zahlentheoretische Funktionen.

1. Die Funktion f ist genau dann ein Element von o (g), wenn limn→∞ fg(n)
(n)
= 0. Man
sagt, die Funktion f sei asymptotisch kleiner als die Funktion g.

2. g ∈ ω(f ) ⇐⇒ f ∈ o (g).

3. Gilt limn→∞ fg(n)


(n)
= 1, so heißen f und g asymptotisch äquivalent. Wir schreiben
f ∼ g.

Es gelten analoge Bezeichnungsvereinbarungen wie in Definition 6.5.

Wie man leicht sieht, folgt aus f = o (g) die Relation f = O (g). Das ist die Begründung
dafür, daß man sich um Terme geringerer Ordnung nicht zu kümmern braucht, wenn man
die O–Notation verwendet.
Beispiele.

1. Für alle festen reellen Zahlen 0 < c, d ist (log n)c ∈ o nd .




2. Für alle festen reellen Zahlen 0 < c < d ist nc ∈ o nd .




3. Für alle festen reellen Zahlen 0 < c, d ist nc ∈ o 2d·n .




4. Für je zwei feste reelle Zahlen 0 ≤ c < d ist 2c·n ∈ o 2d·n .




Bemerkung. An dieser Stelle gilt es dem Eindruck entgegenzuwirken, es ginge in dieser


Vorlesung darum, für zwei Funktionen f und g festzustellen, ob f ∈ O (g) oder sogar
f ∈ Θ(g) gilt. Das ist nicht der Fall. Wir sind vielmehr in der folgenden Lage. Wir haben
einen Algorithmus A, der ein Problem P löst. Wir studieren die Laufzeit des Algorithmus
und stellen beispielsweise folgendes fest:

Obere Laufzeitschranke. Für jede Problestellung I des Problems P benötigt unsere Algo-
rithmus A (höchstens) O (|I|2) Schritte. Wir sagen dann, die Laufzeit des Algorithmus
A sei ein O (n2 ).

Untere Laufzeitschranke. Für jede Eingabenlänge n gibt es eine Eingabe I derart, daß der
Algorithmus A (mindestens) Ω(n · log n) Schritte benötigt, um I zu bearbeiten. Dann
ist die Laufzeit von A ein Ω(n · log n).

Die Laufzeitanalyse für einen Algorithmus besteht also aus zwei Teilen, der Abschätzung
nach oben und der Abschätzung nach unten. Fallen die ermittelte obere und untere Lauf-
zeitsschranke zusammen, benötigt ein Algorithmus auf jede Eingabe der Länge n höchsten

9
O (f (n)) und mindestens Ω(f (n)) Schritte, so sprechen wir von einer scharfen Laufzeit-
schranke und sagen, die Laufzeit sei ein Θ(f (n)).
Im obigen Beispiel ist das nicht der Fall. Die obere und die untere Laufzeitschranke
fallen auseinander. Hier ist noch etwas zu tun. Man sollte versuchen, entweder die obere
Schranke zu drücken oder aber die untere Schranke anzuheben.

In den Kursvorlesungen Informatik I/II“ (siehe Teil II für eine Zusammenfassung) ha-

ben wir Kenntnisse darüber erworben, wie man die syntaktische Korrektheit von Program-
men begrifflich erfaßt und algorithmisch überprüft. Dabei war ein Computerprogramm als
Zeichenkette über der Menge der 128 ASCII–Zeichen selbst Gegenstand einer Rechnung.
Was erwarten wir von einem Algorithmus, der in der Lage ist, über die syntaktische
Korrektheit eines Computerprogramms zu entscheiden? Ein solcher Algorithmus muß auf
jede Eingabe anhalten und genau einem Bit ausgeben: 1“ steht für Jawohl, das Pro-
” ”
gramm ist syntaktisch korrekt“, 0“ heißt Nein, das Eingabe–Programm ist syntaktisch
” ”
fehlerhaft.“ Natürlich muß die Ausgabe der Wahrheit entsprechen. Wir erwarten also genau
das, was in der folgenden Definition erfaßt ist.

Definition 6.7 Sei Σ ein endliches Alphabet, über dem die Eingabe codiert ist.
1. Ein Entscheidungsproblem ist eine Funktion

f : Σ∗ −→ {0, 1}

wobei Σ∗ die Menge der Wörter über dem Alphabet Σ ist. Dabei wird entschieden,
ob ein Eingabewort w ∈ Σ∗ zu der formalen Sprache

L := f −1 (1) ⊆ Σ∗

des Urbildes der 1 unter f gehört oder nicht. Man spricht auch vom dem Entschei-
dungsproblem für L.

2. Ein Algorithmus A löst das Entscheidungsproblem für eine formale Sprache L ⊆ Σ∗


(oder heißt Entscheidungsalgorithmus für L), wenn A auf jede Eingabe stoppt und
entweder 1 oder 0 ausgibt, wobei gilt:

x ∈ L ⇐⇒ A(x) = 1 (x ∈ Σ∗ ).

Dabei steht A(x) für die Ausgabe des Algorithmus A auf die Eingabe x.

3. Formale Sprachen L, für die es einen Entscheidungsalgorithmus gibt, heißen ent-


scheidbar.

Im Teil II ist also die Lösung des folgenden Problems skizziert:


Korrektheitsproblem für Computer–Programme
Eingabe: ein Computer–Programm als Folge von ASCII–Zeichen.

10
(
1 falls das Eingabe–Programm syntaktisch korrekt ist;
Ausgabe:
0 andernfalls.

Ein Algorithmus zur Lösung des Korrektheitsproblem für Computer–Programme allein


wäre nutzlos. Einerseits wäre man ziemlich hilflos, wenn man nur dahingehend beschieden
würde, daß das Eingabe–Programm nicht korrekt wäre. Andererseits soll ja nicht nur über
die syntaktische Korrektheit entschieden werden. Vielmehr geht es darum, das Eingabe-
programm in ausführbaren Code zu übersetzen. Wir benötigen den folgenden Begriff.

Definition 6.8 Seien Σ1 und Σ2 zwei endliche Alphabete. Wir sagen, daß ein Algorithmus
A eine Funktion

f : Σ∗1 −→ Σ∗2

berechnet, wenn A auf jede Eingabe w ∈ Σ∗1 hält und

f (w) = A(w)

gilt, wobei A(w) die Ausgabe von A auf w bezeichnet.

Man hat Interesse an der Lösung des folgenden Problems.


Übersetzungsproblem für Computer–Programme
Eingabe: ein Computer–Programm P als Folge von ASCII–Zeichen.
(
ein ausführbares Programm falls P syntaktisch korrekt ist;
Ausgabe:
eine aussagekräftige Fehlermeldung andernfalls.

Auf den ersten Blick scheint es beim Übersetzungsproblem für Computer–Programme


um die Berechnung einer Funktion zu gehen. Bei näherem Hinsehen stellt sich jedoch her-
aus, daß sich weder für den Fall der syntaktischen Korrektheit von P , noch für den anderen
Fall ein Funktionswert eindeutig bestimmen läßt: Was eine aussagekräftige Fehlermeldung
ist, steht bis zu einem gewissen Grade im Ermessen des Nutzers. Und sicherlich gibt es
mehrere äquivalente ausführbare Programme. Wir stehen also vor einem Relationsproblem.

Definition 6.9 Seien Σ1 und Σ2 zwei endliche Alphabete und sei

R ⊆ Σ∗1 × Σ+
2

eine Relation in Σ1 und Σ2 . (Die Menge Σ+


2 umfaßt alle Wörter über Σ2 mit Ausnahme
des leeren Wortes.) Eine Funktion

f : Σ∗1 −→ Σ∗2

heißt Lösung für R, wenn für alle w ∈ Σ∗1 gilt:

11
– Wenn f (w) 6= ǫ, so ist (w, f (w)) ∈ R.

– Wenn f (w) = ǫ, so ist gibt es kein w ′ ∈ Σ+ ′


2 mit (w, w ) ∈ R.

ist.
Ein Algorithmus berechnet R (oder löst das Relationsproblem R), wenn er eine Lösung
für R im Sinne von Definition 6.8 berechnet.

Ein Polynomialzeit–Algorithmus kommt auf jede Eingabe w nach |w|O(1) Schritten zu


einem Ergebnis. Im Sinne der Theoretischen Informatik heißt ein solcher Algorithmus ef-
fizient.
In dieser Vorlesung werden zahlreiche effiziente Algorithmen vermittelt. Auch die Al-
gorithmen aus Teil II haben polynomial beschränkte Laufzeit. Bei soviel Effizienz gepaart
mit jugendlichem Optimismus kann man leicht zu dem Glauben gelangen, jedes Problem
im Sinne von Definition 6.9 habe einen effizienten Algorithmus, der es löst. Warum das
nicht so ist, ist Gegenstand dieser Vorlesung ab dem Kapitel 12.
Es ist unmittelbar klar, daß man jedes endliche Alphabet Σ in algorithmisch leicht nach-
zuvollziehender Weise über {0, 1} codieren kann. (Üblicherweise wählt man einen Blockco-
de. Hier wird jedes a ∈ Σ als Wort der Länge ⌈log2 |Σ|⌉ über {0, 1} dargestellt.) Folglich
reicht es aus theoretischer Sicht aus, formale Sprachen L über {0, 1}, Funktionen f von
{0, 1}∗ nach {0, 1}∗ und Relationen R aus {0, 1}∗ × {0, 1}+ zu betrachten. (In vielen kon-
kreten Fällen wäre das freilich sehr lästig.)

Definition 6.10 Die Komplexitätsklasse P besteht aus allen formalen Sprachen L ⊆


{0, 1}∗, die durch einen Polynomialzeit–Algorithmus entschieden werden können.

Wir betrachten das folgende Beispiel.


Rucksack–Entscheidungsproblem

Eingabe: eine Folge natürlicher Zahlen

I := (w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , C, W ) ∈ N2n+2

in kanonischer Darstellung (siehe Abschnitt 1.1).


(Die Indizes i = 1, 2, . . . , n symbolisieren Gegenstände, die in einen Rucksack gepackt
werden können. Die Zahlen (w1 , w2 , . . . , wn ) stehen für die Gewichte der Gegenstände,
die Zahlen (c1 , c2 , . . . , cn ) für deren Nutzen. Das zulässige Gesamtgewicht des Ruck-
sacks ist W . Die Zahl P C ist der mindestens anzustrebende Nutzen. Die Größe |I|
n Pn
der Instanz I ist gleich i=1 | bin ci | + i=1 | bin wi | + | bin C| + | bin W |, wobei für
eine natürliche Zahl m die Zeichenkette bin m deren kanonische Binärdarstellung, die
Größe | bin m| deren binäre Länge bezeichnet.)

12
Ausgabe: eine Entscheidung darüber, ob es eine sogenannte zulässige Lösung

(β1 , β2 , . . . , βn ) ∈ {0, 1}n

gibt, für die


n
X
βi ci ≥ C
i=1

gilt.
(Eine Lösung (β1 , β2 , . . . , βn ) — hier: eine Befüllung des Rucksacks — heißt
P zulässig,
wenn ihr Gewicht das zulässige Gesamtgewicht von W nicht übersteigt: ni=1 βi wi ≤
W .)

Es ist nicht bekannt, ob das Rucksack–Entscheidungsproblem in P liegt. Vermutlich ist


dies nicht der Fall. Man hat aber den folgenden Algorithmus“.

Algorithmus 6.11 (NP–Algorithmus für das Rucksack–Problem)


Großschritt 1 [Rate–Phase].
Rate eine Lösung (β1 , β2 , . . . βn ).
Großschritt 2 [Verifikationsphase].
Überprüfe, ob Pni=1 βi wi ≤ W gilt.
P
Überprüfe, ob ni=1 βi ci ≥ C gilt.
Ist beides der Fall, gib 1 aus.
Andernfalls gib 0 aus.
Zunächst ist klar, daß Algorithmus 6.11 auf jede Eingabe mit mehreren Rechengängen
reagieren kann. Er akzeptiert die formale Sprache, die zum Rucksack–Entscheidungsproblem
gehört. Das heißt folgendes:

– Gibt es für eine Eingabe Pn I = (w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , C, W ) eine zulässige Lösung


(β1 , β2 , . . . , βn ) mit i=1 βi ci ≥ C, so kann diese in Großschritt 1 geraten werden.
Folglich gibt es auf I einen Rechengang mit Ausgabe 1.

– Gibt es für eine Eingabe I eine solche zulässige Lösung nicht, so gibt es auf I auch
keinen Rechengang mit Ausgabe 1, denn jeder Vorschlag aus der Rate–Phase scheitert
in der Verifikationsphase.

Was die Laufzeit angeht, so zählen nur die Eingaben, die akzeptiert werden. Und hier
wird auch nur der beste Rechengang in Anschlag gebracht. Die Rechenzeit für einen Ra-
tevorgang ist gleich der Zeit, die es bedarf, um die geratene Sequenz zur Verfügung zu
stellen: je Bit ein Zeittakt. Folglich handelt es sich bei Algorithmus 6.11 um einen nicht-
deterministischen Polynomialzeitalgorithmus.

13
Die Komplexitätsklasse NP besteht aus allen Entscheidungsproblemen, für die es einen
Algorithmus von der Art des Algorithmus 6.11 gibt. Natürlich gilt P ⊆ NP. Das P–
NP–Problem ist die Frage, ob diese Inklusion echt ist.
Das Rucksack–Entscheidungsproblem ist ein sogenanntes NP–vollständiges Problem:
Jedes andere Problem aus NP läßt sich effizient darauf reduzieren. Folglich ist P genau
dann gleich NP, wenn es ein NP–vollständiges Problem in P gibt.
Es gibt sehr viele interessante NP–vollständige Entscheidungsproblem. Wir werden
einige kennenlernen.
Für mögliche Anwendungen erscheint die folgende Optimierungsvariante des Rucksack–
Entscheidungsproblems interessanter.
Rucksack–Optimierungsproblem

Eingabe: eine Folge

(w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , W ) ∈ N2n+1 .

n
Ausgabe: eine optimale zulässige Lösung (β1 , β2 , . . . , βn ) ∈ {0, 1}
P.n(Eine zulässige Lösung
(β1 , β2 , . . . , βn ) heißt optimal, wenn deren Wert, die Zahl i=1 βi ci , maximal unter
allen zulässigen Lösungen ist.)

Diskrete Optimierungsprobleme sind in der Regel Relationsprobleme, denn es wird


meist mehrere optimale zulässige Lösungen geben. Das Rucksack–Optimierungsproblem
ist insbesondere ein NP–äquivalentes Optimierungsproblem: Es hat genau dann einen
Polynomialzeit–Optimierungsalgorithmus, wenn P gleich NP ist. Wenn man statt einer
optimalen auch mit einer suboptimalen Lösung zufrieden ist, gibt es einen Ausweg: Ap-
proximation. Gerade das Rucksack–Optimierungsproblem ist, wie wir sehen werden, sehr
gut approximierbar.
Sollte sich entgegen der allgemeinen Erwartung herausstellen, daß die Komplexitäts-
klassen P und NP gleich sind, hätte das für das praktische Leben nicht nur positive Kon-
sequenzen. Manche der Folgen ließen sich vielleicht mit einigem Humor ertragen. Andere
wären schlicht eine Katastrophe:
Das Lebenswerk nicht weniger Komple-
xitätstheoretiker hat die Ungleichheit der
Klassen P und NP zum Fundament. Ihre
Ergebnisse würden gegenstandslos. Kompli-
zierte Hierarchien kollabierten.
Keines der gängigen Verschlüsselungsverfah-
ren wäre aus theoretischer Sicht mehr sicher.
Zur Sicherung wichtiger oder gar brisanter
Informationen müßte man sich etwas ganz
Neues einfallen lassen.

14
6.2 Ausblick
Es gibt zahlreiche NP-äquivalente Optimierungsprobleme aus sehr vielen Anwendungsbe-
reichen. Viele von ihnen sind zu wichtig, um sich einfach mit der Aussage zu begnügen,
daß man für sie vermutlich keine effizienten Algorithmen wird entwerfen können, die sie
exakt lösen. Einen Ausweg stellt, wie bereits gesagt, die approximative Lösung dar: Man
sucht nach schnellen Algorithmen für suboptimale Lösungen hoher Qualität. Man kann nun
NP-äquivalente Optimierungsprobleme danach klassifizieren, mit welcher Güte sie effizient
approximierbar sind.
Für das ergänzende Literaturstudium im Rahmen der Vorlesung Informatik III beson-
ders geeignet sind [CLRS01], [GD03], [Weg99], [Hro01], [Rei99], [Pap94].

15
Literaturverzeichnis

[CLRS01] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein. Introduction to


Algorithms. MIT Press, 2001.

[GD03] R. H. Güting and St. Dieker. Datenstrukturen und Algorithmen. Leitfäden der
Informatik. Teubner Verlag, Stuttgart, Leipzip, Wiesbaden, 2003.

[Hro01] J. Hromkovic̆. Algorithmische Konzepte der Informatik. Leitfäden der Informa-


tik. Teubner Verlag, 2001.

[Pap94] C. H. Papadimitriou. Computational Complexity. Addison–Wesley, 1994.

[Rei99] K. R. Reischuk. Komplexitätstheorie. Leitfäden der Informatik. Teubner Verlag,


1999.

[Weg99] I. Wegener. Theoretische Informatik — eine algorithmische Einführung.


Leitfäden der Informatik. Teubner Verlag, 1999.

16
Kapitel 7

Das Berechnungsmodell

7.1 Die Registermaschine (RAM) in ihrer Göttinger


Variante (GRAM)
Unserer Rechnermodell ist die in Abbildung 7.1 dargestellte GRAM.

Programm Register Hauptspeicher


Speichereinheiten Adressen
Befehlsadressen Befehle
1: Befehl 1 .. ..
RO 0 . .
2: Befehl 2
3:
.. Befehl
.. 3 R1 ∈Z Halde µ(2) 2
. .
R2 ∈Z µ(1) 1
.. ..
. . µ(0) 0

R31 ∈Z µ(−1) −1

Laufzeitstapel µ(−2) −2

PC ∈Z µ(−3) −3
.. ..
. .

Abbildung 7.1: Die GRAM

Zum besseren Verständnis von Abbildung 7.1 bemerken wir folgendes:

17
– Register und Speichereinheiten (Speicherzellen) können beliebige ganze Zahlen bein-
halten.

– Die Bezeichner für die Register und für die Speichereinheiten (z.B. R5 oder µ(7))
stehen sowohl für diese selbst als auch für deren Inhalt.

– Unsere GRAM hat kein Ein- und Ausgabewerk. Wir begnügen uns an dieser Stelle mit
der Vorstellung, daß die Eingabe bereits eingelesen ist und an einer wohldefinierten
Stelle der Halde, z.B. in den ersten N Einheiten, gespeichert ist.

– Um zu verhindern, daß nichtberechenbare Informationen in die Rechnung der GRAM


einfließen, gilt für jeden Zeitpunkt der Rechnung und fast alle i ∈ Z, daß µ(i) = 0
ist.

– Die GRAM hat 32 Universalregister. Lediglich für das Register R0 gilt die folgende
Besonderheit. Es ist nicht verwendungsbeschränkt. Es gilt jedoch, daß sein Inhalt
stets gleich 0 ist.

– Einige Register werden nur für bestimmte Zwecke eingesetzt:

1. Das Register R31 dient der Verwaltung der Rücksprungadressen im Rahmen der
Methodenaufrufe.
2. Das Register R30 verweist auf die erste freie Speicherzelle unterhalb des Lauf-
zeitstapels. Es wird deshalb Stack Pointer genannt und mit SP abgekürzt.
3. Das Register R29 wird auch Frame Pointer (FP) genannt und hat eine spezielle
Funktion im Rahmen des Prozeduraufrufs.
4. Das Register R28 heißt Heap Pointer (HP) und verweist auf die erste freie
Speicherzelle oberhalb des Heaps.

Ein Programm ist eine konsekutiv wachsend durchnumerierte Folge von Befehlen aus
der Tabelle 7.1. Für Programme haben wir abweichend von der in Informatik I/II bespro-
chenen von–Neumann–Struktur des Rechners einen eigenen Programmspeicher. Man stelle
sich vor, daß die einzelnen Befehle in je einer Speichereinheit gehalten werden, die über eine
Adresse erreichbar sind. Die Adresse eines Befehls ist seine Ordnungszahl im Programm.
In Hinblick auf den Programmablauf legen wir folgendes fest:

1. Der Inhalt des Befehlszählers (engl. program counter) PC vor Beginn der Ausführung
des Programms ist die Adresse des ersten auszuführenden Befehls.

2. Der Inhalt des Befehlszählers nach der Ausführung eines Befehls ist die Adresse des
nächsten auszuführenden Befehls.

3. Ist der Inhalt des Befehlszählers außer Bereich“, so ist die Abarbeitung des Pro-

gramms beendet. Diese Situation entspricht dem Ergebnis der Ausführung des Be-
fehls end.

18
Programmende
END
Transportbefehle
LOAD r1,a(r2) PC ← PC + 1 & r1 ← µ(a + r2)
STORE a(r1),r2 PC ← PC + 1 & µ(a + r1) ← r2
C-LOAD r,m PC ← PC + 1 & r ← m
Additive Arithmetik
ADD r1,r2,r3 PC ← PC + 1 & r1 ← r2 + r3
C-ADD r1,r2,m PC ← PC + 1 & r1 ← r2 + m
SUB r1,r2,r3 PC ← PC + 1 & r1 ← r2 − r3
Multiplikative Arithmetik
MULT r1,r2,r3 PC ← PC + 1 & r1 ← r2 · r3
DIV r1,r2,r3 PC ← PC + 1 & r1 ← ⌊r2/r3⌋
MOD r1,r2,r3 PC ← PC + 1 & r1 ← r2 − ⌊r2/r3⌋ · r3
Ordnung
SLT r1,r2,r3 PC ← PC + 1 & if r2 < r3 then r1 ← 1 else r1 ← 0
Sprünge
JMP r PC ← PC + 1 & PC ← r
JAL r PC ← PC + 1 & R31 ← PC & PC ← r
Verzweigungen
BEQ r,a PC ← PC + 1 & if r = 0 then PC ← PC + a
BNQ r,a PC ← PC + 1 & if r 6= 0 then PC ← PC + a
Legende
r, r1, r2, r3 Platzhalter für die konkreten Register aus R0, R1, . . . , R31
a, m Platzhalter für konkrete ganze Zahlen
& steht für und dann“.

Tabelle 7.1: Assemblerbefehle der GRAM

Auf diese Weise ist jedem Programm ein eindeutig bestimmter Programmablauf zugeord-
net.
Ähnliche Formalisierungen der Registermaschine finden sich in (fast) jedem Lehrbuch
zur Algorithmen- oder Komplexitätstheorie. Hier sei nur auf [Pap94] verwiesen.

7.2 Das Einheitskostenmaß


Wir wollen uns in diesem Teil der Vorlesung mit effizienten Algorithmen beschäftigen.
Dazu benötigen wir (mindestens) ein Komplexitätsmaß.
Aus dem Grundkurs Informatik I/II wissen wir, daß der Befehlssatz eines Rechners
hardwareunterstützt ist. Für unsere GRAM unterstellen wir dasselbe. Daher ist es gerecht-
fertigt, das Einheitskostenmaß zu verwenden:

19
Einheitskostenmaß.

– Die Ausführung jedes Befehls aus Tabelle 7.1 geschieht in einem Zeittakt.

– Jede Speicherzelle und jedes Register, das während der Laufzeit benutzt wird,
geht als eine Einheit in den Speicherbedarf des Programms auf die entsprechende
Eingabe ein.
Ein Programm A ist nach dem vorangegangenen Abschnitt eine numerierte Folge von
GRAM–Assemblerbefehlen.
Definition 7.1 Sei A ein Programm und I eine Eingabe.
Der Zeitbedarf timeA (I) des Programms A auf die Eingabe I ist die Anzahl der vom
Programm A auf die Eingabe I ausgeführten GRAM–Assemblerbefehle.
Der Speicherbedarf spaceA (I) des Programms A auf die Eingabe I ist die Anzahl der
vom Programm A auf die Eingabe I benützten Register und Speicherzellen.
Unter welchen Voraussetzungen ist das Einheitskostenmaß realistisch? Das ist immer
dann der Fall, wenn mit Zahlen gerechnet wird, die im Rahmen der Verarbeitungsbreite
des verwendeten Rechners liegen. Nur dann ist gesichert, daß diese in eine Speicherzel-
le/Register passen und durch die Schaltkreise des Rechners in einem verarbeitet werden
können. Ist das nicht der Fall, führt die Verwendung des Einheitskostenmaßes zu einer
Unterschätzung des tatsächlichen Ressourcenverbrauchs. Dasselbe gilt natürlich, wenn der
Arbeitsspeicher des benutzten Rechners zu klein ist.
Vor allem um die Laufzeit und den Speicherdarf zweier Programme miteinander ver-
gleichen zu können, müssen die Zahlen timeA (I) und spaceA (I) zur Größe |I| der Eingabe
I in einen funktionalen Zusammenhang gestellt werden:
timeA (n) := sup{timeA (I) | |I| = n} (7.1)
spaceA (n) := sup{spaceA (I) | |I| = n} (7.2)
Die in den Gleichungen 7.1 und 7.2 definierten Komplexitätsmaße heißen Zeit– bzw.
Raumbedarf von A im schlechtesten Fall (engl. worst case).
Manche Algorithmen haben für jedes n in geringer Anzahl Eingaben der Länge n mit
relativ hoher Laufzeit. Sei A ein solcher Algorithmus. Startet man ihn auf eine zufällige
Eingabe fester Länge, so ist es sehr wahrscheinlich, daß man es mit keinem der schlechten
Fälle zu tun hat. Man hat keine große Laufzeit zu erwarten. Das richtige Komplexitätsmaß
in dieser Situation ist der mittlere Zeitbedarf von A:
1 X
timeA (n) := timeA (I), (7.3)
πn
I: |I|=n

wobei hier πn die Anzahl der Eingaben des Programms A der Größe n bezeichnet.
Wir haben bereits betont, daß wir in unseren Analysen auf konstante Faktoren und
damit auch auf Terme niederer Ordnung selten Wert legen werden. Warum diese Beschei-
denheit? Die folgenden zwei Gründe mögen genügen.

20
– Der Befehlssatz aus Tabelle 7.1 ist sicher nur einer von vielen möglichen anderen. Wir
können jedoch unterstellen, daß sich jeder Befehl aus dem Befehlssatz eines beliebigen
realen Rechners durch eine Auswahl aus unserem Satz in konstanter Zeit simulieren
läßt.
Soll eine Laufzeitanalyse für (fast) alle Rechner gelten, ist daher der Verzicht auf
konstante Faktoren methodisch nahezu unumgänglich.

– Die mathematische Analyse der Laufzeit wird deutlich einfacher.

Wir
 sind vor allem an Polynomialzeitalgorithmen interssiert. Deren Laufzeit ist ein
O nk für eine möglichst kleine Konstante k. Zum Abschluß dieses Abschnittes geht es
uns um die folgenden zwei Fragen:

– Warum gilt uns eine polynomial beschränkte Laufzeit als Ausweis der Effizienz?

– Warum lohnt es sich, bei einem Algorithmus mit O nk Laufzeit den Exponenten
k so klein wie nur irgend möglich zu machen? Spezieller gefragt, warum ist es der
Mühe wert, einen quadratischen Algorithmus (Laufzeit O (n2 )) so zu verbessern, daß
er nur noch O (n) Zeit benötigt?

Wir beantworten beide Frage zusammen. Ein Zeittakt unseres Einheitskostenmaßes läßt
sich nicht unmittelbar in reale Zeit“ umrechnen. Eine grobe Näherung bietet die Anzahl

der Taktschritte auf einem Rechner. (Aus dem Grundkurs Informatik I/II wissen wir al-
lerdings, daß ein Takt eines Rechners nicht ausreicht, um einen Befehl des Befehlssatzes
auszuführen.) Wir nehmen meinen alten Laptop (maL), also eine Taktfrequenz von 300
MHz. (Damit mache ich ihn etwas schlechter, als er ist.)
Sei T = 1 Sekunde (oder T = 1Tag) auf maL. Was ist dann T 2 , und was ist 2T ?

T 1 Sekunde = 300 Mio. Takte 1 Tag = 2, 592 · 1013 Takte


T2 9.5 Jahre = 9 · 1016 Takte 71 Mrd. Jahre = 6, 718 · 1026 Takte
8 13
2T unglaublich viele Jahre: 23·10 Takte fast unendlich viele Jahre: 22,592·10 Takte

Bei der Beurteilung der vorstehenden Tabelle muß man beachten, daß maL pro Jahr nur
9, 4608 · 1015 Takte ausführen kann.

7.3 Die Einheitskosten für höhere Befehle


In diesem Abschnitt interessieren uns die Einheitskosten für die Instruktionen einer (belie-
bigen) objektorientierten Programmiersprache. Eine Laufzeitanalyse setzt voraus, daß das
Programm in elementare Instruktionen zerlegt wurde.

Definition 7.2 Eine Instruktion heißt elementar, wenn sie in Zeit O (1) ausführbar ist.

21
Bemerkung. Der Zeitbedarf einer elementaren Instruktion kann vom Algorithmus A
abhängen, im Rahmen dessen sie ausgeführt wird. Die Bezeichnung O (1)“ besagt nur,

daß die Zeit nicht von der Größe der Eingabe I abhängt, die das Programm A gerade zu
bearbeiten hat.
Eine objektorientierte Programmiersprache modelliert Datenobjekte, die durch zugehöri-
ge Prozeduren (Methoden) verändert werden. Ein Objekt fristet zur Laufzeit des Pro-
gramms sein Dasein als Objektblatt auf der Halde. Dieses enthält die Datenfelder des Ob-
jekts. Ein Methodenaufruf führt zur Ablage eines Inkarnationsblattes (Rahmen, Frame)
dieser Methode auf dem Laufzeitstapel. Dieser Rahmen ist Träger aller Informationen, die
zur Erledigung des in Rede stehenden Methodenaufrufs nötig sind. Ist der Methodenaufruf
beendet, so wird sie abberufen: Das Inkarnationsblatt wird vom Laufzeitstapel enfernt.
In den Abschnitten 7.3.1 und 7.3.2 zeigen wir, daß die Objekterzeugung und der
Methodenaufruf/-abruf elementare Instruktionen sind. Dazu unterstellen wir die Existenz
einer Klasse XYZ mit a Datenfeldern df1, df2, . . ., dfa und (mindestens) einer Methode
void proc(farg1,farg2,. . ., fargk)
mit k Formalargumenten. Wir nehmen weiter an, daß die Methode proc l lokale Variablen
alpha1, alpha2, . . ., alphal habe.
Im Abschnitt 7.3.3 gehen wir kurz auf den Rest“ der Programmiersprache ein.

7.3.1 Erzeugung eines Objektes auf der GRAM


Ein Objektblatt des Typs XYZ hat die folgende Gestalt:

df1 Zeiger auf den Wert des ersten Datenfeldes


df2 Zeiger auf den Wert des zweiten Datenfeldes
.. .. (7.4)
. .
dfa Zeiger auf den Wert des letzten Datenfeldes

Wie sieht der GRAM–Assemblercode aus, der zur Ablage des Blattes (7.4) führt?
Der HP verweist auf die erste freie Zelle der Halde. Hier soll das neue Objekt beginnen.
Die Adresse dieser Zelle ist gleichzeitig die Adresse des gesamten Objekts. Wir nehmen
an, daß die den Datenfeldern zu übergebenden Objekte an die Register R1, R2, . . ., Ra
gebunden sind. Das zu erzeugende Objekt seinerseits soll an die Speicherzelle mit der
Adresse addr gebunden werden. Von dieser Adresse nehmen wir an, sie stehe im Register
Ra+1.

-- Übergabe der Objektadresse an die dafür vorgesehene Speicherzelle


STORE 0(Ra+1), HP
-- Speicheranforderung von der Halde
C-LOAD R27, a
ADD HP, HP, R27
-- Binden des ersten Wertes an df1
STORE -1(HP), R1

22
-- Binden des zweiten Wertes an df2
STORE -2(HP), R2
.
.
.
-- Binden des a-ten Wertes an dfa
STORE -a(HP), Ra

Wir haben die folgende Aussage bewiesen:


Die Erzeugung und Initialisierung eines Objektes ist eine elementare Instruktion.

7.3.2 Methodenaufruf auf der GRAM


Wir betrachten den Zeitbedarf des Aufrufs und des Abrufs der Methode proc. Es geht uns
also um die Kosten für den Anfang und das Ende der Instruktion
x.proc(arg1,arg2,. . ., argk),
wobei zum Zeitpunkt der Ausführung an die Variable x ein Objekt vom Typ XYZ gebunden
ist, und die arg1,arg2,. . ., argk nunmehr Aktualargumente, in der Regel Ausdrücke,
sind.
Annahmen.
1. In dem Gesamtprogramm“ ist der GRAM–Assemblercode jeder Methode eine kon-

sekutive Folge. Die Startadresse (niederwertigste Adresse eines Befehls) der Methode
proc sei add(proc).
2. Die Aktualargumente arg1,arg2,. . ., argk seien Ausdrücke, die bereits ausgewertet
sind. Die Adressen der zugehörigen Objekte stehen in den Register R1, R2, . . ., Rk.

3. Die Adresse des Objektes Ox vom Typ XYZ, das an die Variable x gebunden ist, steht
im Register Rk+1. (Natürlich darf k + 1 nicht zu groß sein, damit die Register auch
ausreichen.)
Die Ausführung der Methode proc zerfällt in fünf Teile.
Der Aufruf gehört zur rufenden Routine. Hier wird der Speicherplatz für das aktuelle Ob-
jekt und die Parameter der Methode proc vom Stapel angefordert und entsprechend
initialisiert. Anschließend erfolgt der Sprung zum Code der gerufenen Methode.

Der Vorspann ist schon Bestandteil der gerufenen Methode proc. Hier geschieht folgen-
des:
– Retten der Register, die von der Methode proc benutzt werden. (Wir unterstel-
len im weiteren, daß es sich dabei um die Register R1, R2, . . ., R26 handelt.)
– Retten der Rücksprungadresse und des aktuellen Framepointers.
– Anfordern und initialisieren von Speicherplatz für die lokalen Variablen im
Rumpf der Methode.

23
Der Methodenrumpf ist der Haupteil des Methodenaufrufs. Er dient dazu, die Daten-
felder des aktuellen Objekt gemäß den Absichten des Programmierers zu verändern.
Im Abspann wird zunächst das, was im Vorspann geschehen ist, rückgängig gemacht. An-
schließend erfolgt der Rücksprung zur rufenden Routine, nachdem der entsprechende
Speicher (unterer und mittlerer Teil des Inkarnationsblattes) wieder freigegeben wur-
de.
Der Abruf ist wieder Bestandteil der rufenden Routine. Durch Zurücksetzen der Stack-
pointers wird der im Aufruf angeforderte Speicher des Laufzeitstapels wieder freige-
geben.
Ein Inkarnationsblatt (Rahmen, Frame) der Methode proc sieht so aus:

this Zeiger auf Ox


farg1 Zeiger auf arg1
farg2 Zeiger auf arg2
.. ..
. .
fargk Zeiger auf argk
R1 Inhalt von R1
R2 Inhalt von R2
.. .. (7.5)
. .
R26 Inhalt von R26
return Rücksprungadresse
oldFP Zeiger auf die oldFP–Zeile des vorigen Rahmens
alpha1 0
alpha2 0
.. ..
. .
alphal 0

Liegt der Rahmen (7.5) auf dem Laufzeitstapel, verweist der Framepointer FP auf die Zeile
oldFP des mittleren Teils. Wie sieht der GRAM–Assemblercode aus, der zur Ablage des
Rahmens (7.5) führt?
Zum Aufruf.
-- Anforderung des obereren Teils vom Stapel
C-LOAD R27, k+1
SUB SP, SP, R27
-- Binden von argk an fargk
STORE 1(SP), Rk
-- Binden von argk-1 an fargk-1
STORE 2(SP), Rk-1
.
.
.

24
-- Binden von arg1 an farg1
STORE k(SP), R1
-- Binden von Ox an this
STORE k+1(SP), Rk+1
-- Sprung zum Code von proc und Sichern der Rücksprungadresse
C-LOAD R27, add(proc)
JAL R27

Zum Vorspann.
--- Anforderung und Initialisierung des mittleren Teils
--- Anforderung des mittleren Teils
C-LOAD R27, 28
SUB SP, SP, R27
--- Sichern des alten Frames, starten eines neuen
STORE 1(SP), FP
ADD FP, SP, 1
-- Retten der Rücksprungadresse
STORE 2(SP), R31
-- Retten der Register
STORE 3(SP), R26
STORE 4(SP), R25
.
.
.
STORE 28(SP), R1

-- Anforderung und Initialisierung des unteren Teils


-- Anforderung des unteren Teils
C-LOAD R27, ℓ
SUB SP, SP, R27
-- Initialisierung des unteren Teils
STORE 1(SP), R0
STORE 2(SP), R0
.
.
.
STORE l(SP), R0

Bemerkungen. Mit Hilfe des Framepointers FP ist es einem Garbage-Collector möglich,


den Laufzeitstapel zu durchmustern.
Jenseits der letzten lokalen Variablen alphal beginnt der sogenannte temporäre Spei-
cher der Methodeninkarnation. Wir haben hier angenommen, daß die Auswertung von
Ausdrücken mit Hilfe der Register geschieht. Es ist aber auch möglich, daß deren Adressen
in Speicherzellen des temporären Speichers stehen. Dieser ist sehr gut über FP erreichbar.
Abspann und Abruf machen Vorspann und Aufruf rückgängig. Es ist eine leichte Übungs-
aufgabe, den GRAM–Assemblercode dafür anzugeben.

25
Wir haben uns folgendes klargemacht:
Für jede Methode sind Aufruf, Vorspann, Abspann und Abruf elementare Instruktionen.
Insbesondere gilt für jede Eingabe I:

timeproc (I) = O (1) + timeRumpf von proc (I).

7.3.3 Arithmetische Ausdrücke, Programmverzweigungen


Die Feinheiten einer hochentwickelten Programmiersprache sind im Rahmen dieser Vorle-
sung ohne Interesse. Wir benötigen (fast) nur arithmetische Ausdrücke und Programmver-
zweigungen.
Wir stellen fest:

1. Der Zeitbedarf zur Auswertung arithmetischer Ausdrücke ist ein O (op), wobei op
die Anzahl der arithmetischen Operationen in dem zur Auswertung anstehenden
Ausdruck ist.

2. Zur Umsetzung von bedingten Anweisungen und von Schleifen werden die Verzwei-
gungsbefehle BNQ und BEQ verwendet, um den Kontrollfluß zu steuern. Diese Steue-
rung (ohne die dafür notwendigen Evaluierungen von Verzweigungs– bzw. Abbruch-
bedingungen) entspricht an jeder Gabelung einer elementaren Instruktion.

7.4 Bemerkungen zur Notation


Wir werden unsere Algorithmen in einem gepflegten Pseudocode niederschreiben, der für
jeden, der bereits in C oder Java programmiert hat, selbsterklärend ist.
Sei beispielsweise ExampleClass eine Klasse mit den Datenfeldern df1, df2 und df3,
so sind df1(), df2() und df3() die get-Methoden. Auf set-Methoden verzichten wir. Liegt
beispielsweise eine Methode

proc1() returns Integer

der Klasse ExampleClass bereits vor, und sind wir gerade dabei, eine Methode proc2()
zu schreiben, die das Integer-Datenfeld df1 unter Verwendung des Integer-Datenfeldes
df2 und der Methode proc1() modifiziert, so schreiben wir im Rumpf von proc2()

df1 ← proc1()  df2(),

um anzuzeigen, daß sich der neue Wert des Datenfeldes df1 aus dem Produkt des Rück-
gabewertes der Methode proc1() mit dem Wert des Datenfeldes df2 ergibt.
Ferner treffen wir die folgenden Verabredungen:

26
Man beachte jetzt und später die Semantik der folgenden Befehle:

return: Beende den aktuellen Methodenaufruf.

return exp: Werte den Ausdruck exp aus und gib den Wert an die rufende Methode
zurück. Anschließend beende den aktuellen Methodenaufruf.

Ist der Wert eines Ausdrucks ein Objekt, so erfolgt die Wertübergabe stets durch Über-
gabe der Objektadresse.
Wir werden die Nullreferenz vorzugsweise durch das Symbol ↑“ bezeichnen. Daneben

kann null“ Anwendung finden.

27
Literaturverzeichnis

[Pap94] C. H. Papadimitriou. Computational Complexity. Addison–Wesley, 1994.

28
Kapitel 8

Internes Suchen und Sortieren

Wir werden in diesem Kapitel die interne Verwaltung (die Verwaltung im Arbeitsspeicher
des Rechners) von Datenobjekten aus einer Grundmenge U × R studieren.
Unsere Daten bestehen aus
1. einem Schlüssel aus einem sehr großen, aber endlichen Universum U von Schlüsseln
des zunächst generischen Typs Key;
2. dem eigentlichen Datum, das im Rahmen unserer Darstellung allerdings weniger
wichtig ist und deshalb Satellitendatum heißt, aus einer sehr großen, aber endlichen
Grundgesamtheit R des generischen Typs Range.
Zur Laufzeit soll es sich bei den Schlüsseln der gehaltenen Paare stets um Schlüssel-
informationen handeln: Es existieren auf der Halde niemals zwei Paare mit dem gleichen
Schlüssel.
Das Universum U ist total geordnet. Sind uns zwei Schlüssel k1 und k2 gegeben, so
sollen die Tests k1 < k2 ? und k1 = k2 ? effizient ausführbar sein. In der Regel heißt das
Laufzeit O (1).
Beispiele.
• Das Universum U ist eine Menge von ganzen Zahlen [u, o] ⊂ Z und die Ordnung ist
die gewöhnliche Ordnung;
• Das Universum U ist eine Menge von Zeichenketten Σℓ über einem endlichen Alpha-
bet Σ mit der lexikographischen Ordnung.
Um welche Probleme geht es in diesem Kapitel?
1. Wie kann man Mengen von den in Rede stehenden Paaren effizienter als durch ver-
kettete Listen implementieren? Es geht uns dabei um die Wörterbuch– und die Ite-
ratoroperationen (siehe Abschnitt 8.1).
2. Wie kann man Felder von Schlüsseln schnell sortieren? (Natürlich treten Schlüssel
und Satellitendaten stets im Doppelpack auf. Aber für unsere Algorithmen spielen
letztere kaum eine Rolle.)

29
8.1 Spezifikation des Datentyps Dictionary und der
Iteratoroperationen
Unter einem Wörterbuch von Elementen aus U × R, wobei es sich bei den Schlüsseln stets
um Schlüsselinformationen handelt, verstehen wir eine Verwaltung von Teilmengen aus
U × R, welche die Operationen empty, insert, delete und lookUp unterstützt.
Den Zustand eines Wörterbuchs können wir durch eine partielle Funktion

f : U −→ R.

beschreiben. In unserem Wörterbuch sind genau jene Paare (k, t) ∈ U × R gespeichert, die
den Graphen graph f der partiellen Funktion ausmachen:

graph f := {(k, r) ∈ U × R | f (k) = r}.

Anders ausgedrückt, das aktuelle Wörterbuch wird durch eine solche Funktion bzw. deren
Graphen vollständig beschrieben.
Auf dieser Grundlage können wir die Wörterbuchoperationen spezifizieren. (Wir spre-
chen auch von Datentyp Dictionary.)

empty(). Erzeugt wird das leere Wörterbuch. Dessen Zustand f hat den leeren Graphen:
graph f = ∅.

lookUp(Key k) returns Range. Diese Operation verändert das aktuelle Wörterbuch mit
dem Zustand f nicht. Sie gibt f (k) zurück, sofern die Funktion f auf k definiert ist.
Andernfalls wird ↑ zurückgegeben.

insert(Key k, Range r). Wird das aktuelle Wörterbuch durch die Funktion f beschrie-
ben, so verändert diese Operation f zu g mit
(
graph f ∪ {(k, r)} falls f auf k nicht definiert ist;
graph g :=
(graph f \ {(k, f (k))}) ∪ {(k, r)} sonst.

delete(Key k). Transformiert den Zustand f des aktuellen Wörterbuchs zu g mit


(
graph f \ {(k, f (k))} falls f auf k definiert ist;
graph g :=
graph f sonst.

Um aus einem Wörterbuch eine Menge zu machen — wir spechen auch vom Datentyp
Set — benötigen wir eine Iterator-Klasse.
Für unsere Zwecke reicht die Vorstellung, daß ein Iterator-Objekt zwei Hauptdatenfel-
der hat:

30
partner Zeiger auf das zu durchmusternde Mengenobjekt
(8.1)
cursor Zeiger auf den Träger“ des aktuellen Elements

Der Wert des Datenfeldes cursor aus Gleichung 8.1 heißt auch Läufer. In den Bei-
spielen dieses Kapitels handelt es sich stets um einen Knoten aus einem Graphen (binärer
Suchbaum oder verkettete Liste). Der Knoten wiederum hat ein Datenfeld, das auf ein
Element-Objekt der Menge verweist.
Die Klassen dieses Kapitels, die den Datentyp Set implementieren, benötigen eine Me-
thode
elements() returns Iterator,
die ein Iterator-Objekt erzeugt und initialisiert. Zu der Initialisierung gehört in jedem Falle,
daß das Datenfeld partner auf das aktuelle Mengenobjekt verweist.
Ist die aktuelle Menge nichtleer, so ist der Läufer unmittelbar nach der Initialisierung
derjenige Knoten des Trägers“, der das Datum mit dem kleinsten (oder größten) Schlüssel

trägt. Anderfalls ist sein Wert gleich ↑
Die Iterator-Klasse enthält Methoden, vermöge derer die Elemente der Menge in auf-
steigender (oder auch in absteigender) Ordnung ihrer Schlüssel durchlaufen werden können.
Wir verlassen den Pfad der Tugend, indem wir diese Iterator-Operationen nicht ganz im-
plementationsunabhängig spezifizieren. Nach den Regeln eines Weltweisen, dessen Name
leider in Vergessenheit geraten ist, kann man jedoch eine Sache ruhig falsch machen, wenn
man weiß, wie es richtig geht.
Die Methode firstElement() weist dem Läufer denjenigen Knoten des Trägers zu, der
das Element der aktuellen Menge mit dem kleinsten Schlüssel trägt. Sie hat als Vor-
bedingung, daß die aktuelle Menge nichtleer ist.
Die Methode lastElement() weist dem Läufer denjenigen Knoten des Trägers zu, der
das Element der aktuellen Menge mit dem größten Schlüssel trägt. Sie hat als Vor-
bedingung, daß die aktuelle Menge nichtleer ist.
Die Methode hasMoreElements() gibt an, ob das Datenfeld cursor einen Wert ver-
schieden von ↑ hat.
Die Methode nextElement() gibt das zum Läufer gehörige Element zurück. Der Läufer
wird durch seinen Nachfolger ersetzt, sofern dieser existiert. Ist der Schlüssel des
zurückgegebenen Elements jedoch der größte der aktuellen Menge, so erhält das Da-
tenfeld cursor den Wert ↑.
Die Methode nextElement() hat hasMoreElements() = true zur Vorbedingung.
Die Methode previousElement() gibt das zum Läufer gehörige Element zurück. Der
Läufer wird durch seinen Vorgänger ersetzt, sofern dieser existiert. Ist der Schlüssel
des zurückgegebenen Elements der kleinste der aktuellen Menge, so erhält das Da-
tenfeld cursor den Wert ↑.

31
Die Methode previousElement() hat hasMoreElements() = true zur Vorbedingung.

8.2 Binäre Suchbäume


8.2.1 Der Begriff
In diesem Abschnitt nehmen wir an, daß das Universum U der möglichen Schlüssel, die
im Abschnitt 8.1 erwähnt worden sind, ein (relativ großes) Teilinterval [u, o] der ganzen
Zahlen Z ist. Der Datentyp Key ist also gleich Integer. Was die Satellitendaten angeht, so
bleibt es beim generischen Typ Range und der Menge R als der Gesamtheit aller möglichen
Datenobjekte dieses Typs.
Wir verwenden völlig zwanglos die Begriffe und Bezeichnungen aus Abschnitt 1.5.2.

Definition 8.1 Ein nichtleerer binärer Suchbaum T (mit Knotenmenge V (T )) über Z und
R ist ein geordneter binärer Wurzelbaum (siehe Abschnitt 1.5.2), zu dem Markierungsfunk-
tionen der Knotenmenge

key : V (T ) −→ Z
data : V (T ) −→ R

gehören. Die Markierungsfunktion key erfüllt die strenge Suchbaumeigenschaft: Für jeden
(left) (right)
Knoten v ∈ V (T ) und alle Knoten w1 ∈ Tv und w2 ∈ Tv ist

key(w1 ) < key(v) < key(w2 ).

8.2.2 Die symmetrische Ordnung der Knoten eines Suchbaums


Wir betrachten den Suchbaum aus Abbildung 8.1. Wir wollen die Anordnung der Knoten
begrifflich fassen, bei der die zugehörigen Schlüssel eine monoton wachsende Folge bilden:

Knoten v7 v11 v4 v8 v2 v5 v1 v3 v12 v9 v6 v14 v13 v15 v10


Schlüssel 30 40 50 60 70 80 100 130 133 136 140 145 150 155 160
(8.2)
In Tabelle 8.2 wie auch in der folgende Definition erklären wir die Ordnung der Knoten,
indem wir sie anordnen. Für das Beispiel aus Tabelle 8.2 heißt das:

v7 < v11 < v4 < v8 < v2 < v5 < v1 < v3 < v12 < v9 < v6 < v14 < v13 < v15 < v10 .
(left) (right)
Definition 8.2 Sei T = (Tr , r, Tr ) ein nichtleerer binärer Suchbaum. Die symme-
trische Ordnung (engl. inorder) der Knoten von T definieren wir induktiv über die Tiefe d
von T .
Induktionsanfang: d = 0. Dann besteht T genau aus der Wurzel r. Auf einer einelemen-
tigen Menge gibt es genau eine Ordnung.

32
100 v1

70 v2 130 v3

50 v4 80 v5 140 v6

136 v9 160 v10


30 v7 60 v8

133 v12 150 v13


40 v11

145 v14 155 v15

Abbildung 8.1: Ein Suchbaum mit 15 Knoten

Induktionsschritt: d ր d + 1. Dann ist die symmetrische Ordnung (Anordnung) inorder T


der Knoten von T wie folgt definiert:

inorder T := inorder Tr(left) , r, inorder Tr(right) .


| {z } | {z }
bekannt nach bekannt nach
Induktions- Induktions-
voraussetzung voraussetzung

Bezeichnung: <inorder .

Die nun folgenden Lemmas 8.3, 8.4 und 8.5 sind leicht zu beweisen.

Lemma 8.3 Ist T ein nichtleerer binärer Suchbaum, dann gilt für je zwei von einander
verschiedene Knoten u 6= v

u <inorder v ⇐⇒ key u < key v.

Lemma 8.3 zeigt, daß die Knoten des Baumes aus Abbildung 8.1 wie in Tabelle 8.2
angegeben symmetrisch angeordnet sind.

Lemma 8.4 Ist T ein nichtleerer binärer Suchbaum, und ist Tu ein Teilbaum von T , so
ist die Knotenmenge von Tu bzgl. der symmetrischen Ordnung ein Intervall innerhalb der
symmetrischen Ordnung von T .
Darüber hinaus erhält man die symmetrische Ordnung des Baumes Tu , indem man die
symmetrische Ordnung von T auf die Knotenmenge von Tu einschränkt.

33
Ist P eine nichtleere, endliche geordnete Menge. Wir sagen, ein Element v ∈ P überdeckt
ein Element u ∈ P , wenn v größer ist als u, aber das offene Intervall (u, v) von P leer ist.
Wir sagen auch, das Element v sei der Nachfolger des Elements u in der Ordnung P .

Lemma 8.5 (Hauptlemma über die symmetrische Ordnung) Sei T ein nichtleerer
binärer Suchbaum.
Maximum. Man erhält den maximalen Knoten der symmetrischen Ordnung der Knoten
von T , indem man von der Wurzel aus der jeweils rechten Kante solange folgt, bis
daß es eine solche Kante nicht mehr gibt.

Minimum. Man erhält den minimalen Knoten der symmetrischen Ordnung der Knoten
von T , indem man von der Wurzel aus der jeweils linken Kante solange folgt, bis daß
es eine solche Kante nicht mehr gibt.
Ordnungsrelation. Seien u 6= v ∈ T , und sei Tu,v = (T1′ , r ′ , T2′ ). Dann gilt:

1. Der Knoten u ist genau dann kleiner als der Knoten v, wenn eine der folgenden
drei Bedingungen erfüllt ist:
(a) u = r ′ und v ∈ T2′ .
(b) u ∈ T1′ und v = r ′ .
(c) u ∈ T1′ und v ∈ T2′ .
2. Der Knoten u wird genau dann vom Knoten v überdeckt, wenn eine der beiden
folgenden Bedingungen erfüllt ist:
(a) u = r ′ und v ∈ T2′ ist dort der kleinste Knoten.
(b) u ∈ T1′ und v = r ′ , und u ist der größte Knoten in T1′ .

Lemma 8.5 gibt uns die Möglichkeit, die Vorgänger- bzw. Nachfolgerrelation der sym-
metrischen Ordnung in einer Weise zu beschreiben, die eine effiziente algorithmische Um-
setzung gestattet.

Lemma 8.6 Sei T ein nichtleerer binärer Suchbaum, und sei v ein Knoten von T . Dann
gilt für die symmetrische Ordnung der Knoten von T :
Fall 1. Der Knoten v hat keinen Vorgänger, weil er der kleinste Knoten ist. Das ist genau
dann der Fall, wenn

– der Knoten v keinen linken Sohn hat, und


– v die Wurzel ist, oder alle Vorfahren von v mit Ausnahme der Wurzel von T
linke Söhne sind.

Fall 2. Der Knoten v hat einen Vorgänger p. Sei Tu := Tv,p der kleinste Teilbaum, der
(left) (right)
v und p umfaßt. Sei Tu := (Tu , u, Tu ). Dann sind die folgenden zwei Fälle
möglich.

34
Fall 2.1. Es ist v = u und der Knoten p ist der maximale Knoten im Teilbaum
(left)
Tu . Das ist genau dann der Fall, wenn v einen linken Sohn hat.
(right)
Fall 2.2. Es ist p = u und der Knoten v ist minimaler Knoten im Teilbaum Tu .
Das ist genau dann der Fall, wenn
– der Knoten v keinen linken Sohn hat, und
– v rechter Sohn ist, oder es einen Vorfahren von v gibt, der rechter Sohn ist.

Eine zu Lemma 8.6 analoge Aussage über den Nachfolger eines Knotens v des aktuellen
Suchbaums T sieht so aus:

Lemma 8.7 Sei T ein nichtleerer binärer Suchbaum, und sei v ein Knoten von T . Dann
gilt für die symmetrische Ordnung der Knoten von T .

Fall 1. Der Knoten v hat keinen Nachfolger, weil er der größte Knoten ist. Das ist genau
dann der Fall, wenn

– der Knoten v keinen rechten Sohn hat, und


– v die Wurzel ist, oder alle Vorfahren von v mit Ausnahme der Wurzel von T
rechte Söhne sind.

Fall 2. Der Knoten v hat einen Nachfolger s. Sei Tu := Tv,s der kleinste Teilbaum, der
(left) (right)
v und s umfaßt. Sei Tu := (Tu , u, Tu ). Dann sind die folgenden zwei Fälle
möglich.

Fall 2.1. Es ist v = u und der Knoten s ist der minimale Knoten im Teilbaum
(right)
Tu . Das ist genau dann der Fall, wenn v einen rechten Sohn hat.
(left)
Fall 2.2. Es ist s = u und der Knoten v ist maximaler Knoten im Teilbaum Tu .
Das ist genau dann der Fall, wenn
– der Knoten v keinen rechten Sohn hat, und
– v linker Sohn ist, oder es einen Vorfahren von v gibt, der linker Sohn ist.

8.2.3 Bemerkungen zur Implementation


Die Knoten eines binären Suchbaum werden durch Objekte des Typs Node dargestellt. Ein
Objektblatt hat im wesentlichen die folgende Gestalt:

key Schlüssel aus dem Bereich der ganzen Zahlen


data Zeiger auf ein Satellitendatum vom Typ Range
lson Zeiger auf das Knotenobjekt des linken Sohnes vom Typ Node (8.3)
rson Zeiger auf das Knotenobjekt des rechten Sohnes vom Typ Node
father Zeiger auf das Knotenobjekt des Vaters vom Typ Node

35
Das Datenfeld father erlaubt es uns, den Baum auch in Richtung der Wurzel zu durch-
laufen. Hat ein Datenfeld aus 8.3 den Wert ↑, so existiert das entsprechende Objekt nicht.
Der binäre Suchbaum selbst ist eine Instanz der Klasse BinarySearchTree, deren
Hauptdatenfeld root vom Typ Node auf das Wurzelobjekt verweist. (Der dargestellte binäre
Suchbaum ist genau dann leer, wenn root = ↑ ist.)
Die Iterator-Klasse für binäre Suchbäume hat als Wert des partner-Datenfeldes ein
Objekt der Klasse BinarySearchTree, als Wert des cursor-Datenfeldes einen Knoten des
Partners.
Alle Methoden, die wir im folgenden besprechen werden, gehören entweder zur Klasse
BinarySearchTree oder zur Iterator-Klasse für binäre Suchbäume. Folglich können wir
bei der Besprechnung der Algorithmen insbesondere von dem aktuellen binären Suchbaum
sprechen.

8.2.4 Die Iterator-Operationen


Die Aussagen von Abschnitt 8.2.2 bilden die Grundlage für die Implementation der Iterator-
Operationen firstElement(), lastElement(), nextElement() und previousElement().
(Alle anderen im Zusammenhang mit Iteratoren stehenden Methoden sind algorithmisch
uninteressant.)
Wir schreiben zunächst Hilfsmethoden, die den bzgl. der symmetrischen Ordnung klein-
sten bzw. größten Knoten des im übergebenen Knoten wurzelnden Teilbaums berechnen.
Beide gehören zur Klasse BinarySearchTree.

Die Algorithmen 8.8 und 8.9 sind wegen Lemma 8.5 korrekt.

Algorithmus 8.8 (Suche nach dem kleinsten Knoten im aktuellen Suchbaum)


Methodenkopf: 
least Node u returns Node
Vorbedingung:
Der Knoten u gehört zum aktuellen Suchbaum T .
Nachbedingung:
Es wird der kleinste Knoten aus Tu zurückgegeben.
Rumpf:
Falls u.lson() = ↑,
return u
return least(u.lson())

Der folgende Algorithmus ist das Dual zu Algorithmus 8.8.

Algorithmus 8.9 (Suche nach dem größten Knoten im aktuellen Suchbaum)

36
Methodenkopf: 
greatest Node u returns Node
Vorbedingung:
Der Knoten u gehört zum aktuellen Suchbaum T .
Nachbedingung:
Es wird der größte Knoten aus Tu zurückgegeben.
Rumpf:
Falls u.rson() = ↑,
return u
return greatest(u.rson())

Die folgende Aussage ist offensichtlich.

Aussage 8.10 Die Laufzeit für die Aufrufe von greatest(u) bzw. least(u) ist ein
O (depth Tu ), wobei T der aktuelle Suchbaum ist.

Unter Verwendung von greatest(u) und least(u) ist die Implementation der Metho-
den firstElement() bzw. lastElement() der Iteratorklasse für binäre Suchbäume leicht.
Wir beschränken uns auf firstElement().

Algorithmus 8.11 (Erstes Element einer Menge)

Methodenkopf:
firstElement()
Rumpf:
cursor ← partner().least(partner().root())
return

Aus Aussage 8.10 folgt, daß die Laufzeit von Algorithmus 8.11 und vom analogen Al-
gorithmus für lastElement() ein O (depth T ) ist. Dabei ist T der Wert des partner-
Datenfeldes.
Die nun folgenden Hilfsmethoden previous(u) (Algorithmus 8.12) und next(u) (Algo-
rithmus 8.13) bilden die Grundlage für die Iterator-Operationen previousElement() bzw.
nextElement(). Sie gehören zur Klasse BinarySearchTree und berechnen den Vorgänger–
bzw. den Nachfolgerknoten des übergebenen Knotens im aktuellen Suchbaum T bzgl. der
symmetrischen Ordnung. Algorithmus 8.12 setzt Lemma 8.6 um, Algorithmus 8.13 dagegen
Lemma 8.7.

Algorithmus 8.12 (Berechnung des Vorgängerknotens im Suchbaum)

37
Methodenkopf: 
previous node u returns Node
Vorbedingung:
Der übergebene Knoten u gehört zum aktuellen Suchbaum T .
Nachbedingung:
Falls least(root()) 6= u, so wird der Vorgänger von u zurückgegeben.
Falls least(root()) = u, so wird ↑ zurückgegeben.
Großschritt 1. [Fall 2.1 von Lemma 8.6]
Falls u.lson() 6= ↑
return greatest(u.lson())
Großschritt 2. [Fälle 1 und 2.2 von Lemma 8.6]
Gehe vermöge der father-Referenz von u in Richtung root.
Suche den ersten Vorfahren p von u, so daß p.rson() gleich u oder Vorfahre von u ist.
Falls ein solcher Vorfahre nicht existiert,
return ↑
Anderfalls
return p

Algorithmus 8.13 (Berechnung des Nachfolgerknotens im Suchbaum)


Methodenkopf:
next node u returns Node
Vorbedingung:
Der übergebene Knoten u gehört zum aktuellen Suchbaum T .
Nachbedingung:
Falls greatest(root()) 6= u, so wird der Nachfolger von u zurückgegeben.
Falls greatest(root()) = u, so wird ↑ zurückgegeben.
Großschritt 1. [Fall 2.1 von Lemma 8.7]
Falls u.rson() 6=↑
return least(u.rson())
Großschritt 2. [Fälle 1 und 2.2 von Lemma 8.7]
Gehe vermöge der father-Referenz von u in Richtung root.
Suche den ersten Vorfahren p von u, so daß p.lson() gleich u oder Vorfahre von u ist.
Falls ein solcher Vorfahre nicht existiert,
return ↑
Anderfalls
return p
Offensichtlich gilt:
Aussage 8.14 Die Laufzeit für die Aufrufe von previous(u) bzw. next(u) ist ein O (depth T ),
wobei T der aktuelle Suchbaum ist.

38
Unter Verwendung von previous(u) und next(u) sind die Methoden previousElement()
bzw. nextElement() der Iterator-Klasse leicht zu implementieren. Wir beschränken uns
auf previousElement().

Algorithmus 8.15 (Vorgänger-Element einer Menge)


Methodenkopf:
previousElement() returns Integer × Range
Rumpf:
c ← partner().cursor()
cursor ← partner().previous(c)
k ← c.key()
r ← c.data()
return (k, r)
Aus Aussage 8.14 folgt, daß die Laufzeit von Algorithmus 8.15 und dem analogen Al-
gorithmus für nextElement() ein O (depth T ) ist. Dabei ist T der Wert des partner-
Datenfeldes.
Wir fassen zusammen.
Satz 8.16 Die Laufzeit der Iterator-Operationen ist ein O (depth T ). Dabei ist T der zu
durchlaufende Suchbaum.

8.2.5 Die Wörterbuch-Operationen


Alle Methoden des folgenden Abschnitts gehören zur Klasse BinarySearchTree.
Der folgende Begriff ist in diesem Abschnitt von zentraler Bedeutung.
Definition 8.17 Sei T ein binärer Suchbaum. Für jeden Schlüssel k ∈ U ist der Suchpfad
searchpathT k in T nach k wie folgt induktiv definiert.
Tiefe −1. Der Suchpfad searchpathT k ist leer. (Ein binärer Suchbaum der Tiefe −1 ist
ja selbst leer.)
Tiefe 0. Es ist searchpathT k = r(T ). (Ein binärer Suchbaum T der Tiefe 0 besteht nur
aus der Wurzel r(T ).)
d ր (d + 1). Ist
T = (Tr(left) , r, Tr(right) )

ein binärer Suchbaum der Tiefe d + 1, so ist



r
 falls key r = k;
searchpathT k = r, searchpathTr(left) k falls key r > k;


r, searchpathTr(right) k falls key r < k.

39
Uns interessiert der Endknoten von searchpathT k. Das folgende Lemma ist offensicht-
lich.
Lemma 8.18 Sei T ein nichtleerer binärer Suchbaum, sei k ein Schlüssel und sei searchpathT k
der Suchpfad nach k in T .
Ist k in T nicht gespeichert, so ist ein Knoten v des Suchpfades searchpathT k genau
dann dessen Endknoten, wenn folgendes gilt:
– Ist k < key v, so hat der Knoten v keinen linken Sohn.
– Ist key v < k, so hat der Knoten v keinen rechten Sohn.
Ist k in T gespeichert, so ist ein Knoten v des Suchpfades searchpathT k genau dann
dessen Endknoten, wenn key v = k ist.
Beispiel. In Abbildung 8.1 ist v7 der Endknoten des Suchpfades nach 27, Knoten v9 der
Endknoten des Suchpfades nach 137. Der Knoten v13 der Endknoten des Suchpfades nach
150.
Wir schreiben eine Hilfsmethode, die uns zu jedem Schlüssel k den Endknoten des
Suchpfades nach k berechnet.

Algorithmus 8.19 (Berechnung des letzten Knotens eines Suchpfades)


Methodenkopf: 
searchPath Integer k, Node v returns Node
Vorbedingung:
Der übergebene Knoten v gehört zum aktuellen Suchbaum T .
Nachbedingung:
Rückgabe des letzten Knotens des Suchpfades nach k in Tv .
Großschritt 1. [Basis]
Führe
return v
aus, falls eine der folgenden drei Bedingungen erfüllt ist:
- v.key() = k
- v.key() < k und v.rson() = ↑
- v.key() > k und v.lson() = ↑
Großschritt 2. [Rekursion]
Falls (v.key() > k) 
return searchPath k, v.lson()
Falls (v.key() < k) 
return searchPath k, v.rson()

Aussage 8.20 Die Laufzeit von Algorithmus 8.19 ist ein O (depth T ), wobei T der aktuelle
binäre Suchbaum ist.

40
Unter Verwendung von Algorithmus 8.19 können wir die Operationen lookUp imple-
mentieren, die zum Datentyp Dictionary gehört.

Algorithmus 8.21 (Suche im aktuellen Wörterbuch)


Methodenkopf: 
lookUp Integer k returns Range
Nachbedingungen:
Gibt es einen Knoten u im aktuellen Suchbaum T mit u.key() = k, so
Rückgabe von u.data().
Andernfalls Rückgabe von ↑.
Rumpf:
Falls root() = ↑,
return ↑ 
v ← searchPath k, root()
Falls (v.key() = k)
return v.data()
return ↑

Aussage 8.22 Die Laufzeit von Algorithmus 8.21 ist ein O (depth T ), wobei T der aktuelle
binäre Suchbaum ist.

Wir kommen zum Einfügen eines Paares (k, r) ∈ U × R in einen binären Suchbaum T .
Ist T leer, so muß der Wurzelknoten erzeugt und geeignet initialisiert werden.
Ist T nichtleer, so machen wir den Endknoten v des Suchpfades in T nach k ausfindig.
Ist v der Träger des Schlüssels k, so wird das Satellitendatum aktualisiert. Ist das nicht
der Fall, so machen wir uns Lemma 8.18 zu Nutze:
Ist k < key v, so erzeugen wir einen neuen Knoten u als Träger für das Paar (k, r) und
machen ihn zum linken Sohn von v. (Der neue Knoten u ist ein Blatt.)
Ist key v < k, so erzeugen wir einen neuen Knoten u als Träger für das Paar (k, r) und
machen ihn zum rechten Sohn von v. (Der neue Knoten u ist ein Blatt.)

Algorithmus 8.23 (Einfügen)


Methodenkopf: 
insert Integer k, Range r
Großschritt 1. [Der aktuelle Suchbaum T ist leer.]
Falls root() = ↑
root ← neues Objekt vom Typ Node
root().father ← root().lson ← root().rson ← ↑
root().key ← k, root().data ← r
return

41
Großschritt 2. [Der aktuelle Suchbaum T ist nichtleer.]
v ← searchPath(k, root())
Falls v.key() = k
v.data ← r
return
u ← neues Objekt vom Typ Node
u.key ← k, u.data ← r
u.lson ← u.rson ← ↑
u.father ← v
Falls v.key() > k
v.lson ← u
return
Falls v.key() < k
v.rson ← u

Aussage 8.24 Die Laufzeit von Algorithmus 8.23 ist ein O (depth T ), wobei T der aktuelle
binäre Suchbaum ist.

Etwas anspruchsvoller ist der folgende Algorithmus.

Algorithmus 8.25 (Streichen)


Methodenkopf: 
delete Integer k
Großschritt 1. [Basis.]
Falls root() = ↑, so führe aus:
return
v ← searchPath(k, root())
Falls v.key() 6= k, so führe aus:
return
Großschritt 2. [Der Knoten v ist Blatt.]
Falls v.lson() = v.rson() = ↑, so führe aus:
Falls v = root(), so führe aus: root ← ↑
Andernfalls führe aus:
w ← v.father()
Falls w.rson() = v, so führe aus: w.rson() ← ↑
Andernfalls führe aus: w.lson() ← ↑
return
Großschritt 3. [Der Knoten v hat genau einen Sohn.]
Falls entweder v.lson() = ↑ oder v.rson() = ↑, so führe aus:
w ← einziger Sohn von v
Falls v = root(), so führe aus:

42
w.father ← ↑, root ← w
return
u ← v.father()
Falls u.rson() = v, so führe aus: u.rson ← w
Anderfalls führe aus: u.lson ← w
w.father ← u
return
Großschritt 4. [Der Knoten v hat zwei Söhne.]
w ← previous(v)
Vertausche v.key mit w.key.
Streiche w gemäß Großschritt 2 oder 3.
Kommentar: w hat keinen rechten Sohn.

Die Großschritte 3 und 4 von Algorithmus 8.25 werden in den Abbildungen 8.2 und 8.3
graphisch verdeutlicht.

k v = root

w = root

einziger Sohn von v

Vater von v

u u

k v

w w

einziger Sohn von v

Abbildung 8.2: Illustration zu Algorithmus 8.25, Großschritt 3

43
k v k′ v

k′ w Streichen nach GS 3 k w

Abbildung 8.3: Illustration zu Algorithmus 8.25, Großschritt 4

Aussage 8.26 Die Laufzeit von Algorithmus 8.25 ist ein O (depth T ), wobei T der aktuelle
binäre Suchbaum ist.

Wir erhalten.

Satz 8.27 Die Laufzeit der Wörterbuch-Operationen lookUp, insert und delete ist ein
O (depth T ). Dabei ist T der aktuelle Suchbaum.

Bemerkungen.
• Wir haben in diesem Abschnitt einige rekursive Algorithmen betrachtet. Deren Vor-
teil besteht in ihrer großen Übersichtlichkeit. Insbesondere kann man den Korrekt-
heitsbeweis durch vollständige Induktion besonders einfach führen.
• Der Nachteil rekursiver Algorithmen im Vergleich zu einer analog arbeitenden ite-
rativen Variante ist die vergleichsweise größere Laufzeit. Die Größenordnungen sind
zwar gleich, aber ein Methodenaufruf ist deutlich aufwendiger als z.B. das Durchlau-
fen einer Kante.

8.2.6 Die mittlere Tiefe kanonischer binärer Suchbäume


Wir bemerken, daß man sich beim Studium der Eigenschaften eines binären Suchbaumes
mit n Knoten o.B.d.A. auf den Fall beschränken kann, daß die Menge der verwendeten
Schlüssel gleich {1, 2, . . . , n} ist: Bei n paarweise verschiedenen Schlüsseln kommt es nicht
auf die absolute Größe des einzelnen Schlüssel, sondern auf deren Größenverhältnis unter-
einander an.
Die Satellitendaten sind für uns in diesem Abschnitt ohne Bedeutung und werden des-
halb in der Notation unterdrückt.

44
Definition 8.28 Für jede Permutation π der Schlüsselmenge {1, 2, . . . , n} erhält man den
kanonischen Suchbaum Tπ durch die Folge der Einfügeoperationen

T∅ .insert(π(1)).insert(π(2)). . . . .insert(π(n)) (8.4)

Die Gesamtheit der Permutationen von n Elementen bezeichnen wir mit Sn . Formal
gesehen ist eine Permutation π der Menge {1, 2, . . . , n} eine bijektive Abbildung von
{1, 2, . . . , n} nach {1, 2, . . . , n}. Wir verwenden für solche Permutationen π die folgenden
Notationen:
 
1 2 ... n 
oder gerne auch π(1) π(2) . . . π(n) .
π(1) π(2) . . . π(n)


Beispiel. Für n = 6 und π = 2 3 5 6 4 1 sieht der kanonische Suchbaum Tπ wie
in Abbildung 8.4 angegeben aus.

1 3

4 6


Abbildung 8.4: Der kanonischer Suchbaum Tπ für π = 2 3 5 6 4 1

In diesem Abschnitt geht es uns darum, den Term

1 X
depth Tπ
n! π∈σ
n

nach oben abzuschätzen. Zur Vereinfachung der Notation führen wir die folgenden Bezeich-
nungen ein:

d(π) := depth Tπ (π ∈ Sn )
¯ := 1
X
d(n) d(π)
n! π∈S
n

45
¯
Statt d(π) und d(n) werden wir die folgenden Größen studieren:
X
D(π) := 2depthTπ (v)
v ist Blatt
von Tπ
1 X
D̄(n) := D(π)
n! π∈S
n

Lemma 8.29 Es ist


¯ ≤ log D̄(n).
d(n) (8.5)
2

Beweis. Es gilt:
X 1 
¯ =
d(n) log2 2d(π)
π∈Sn
n!
!
X 1
≤ log2 2d(π) (Satz 2.9: Jensensche Ungleichung)
π∈S
n!
 n 
1 X X
2depthTπ (v) 

≤ log2 
n! π∈S v ist Blatt
n
von Tπ

= log2 D̄(n).


Lemma 8.30 Die Funktion D̄ erfüllt die folgende Rekursion.


D̄(0) = 0 (8.6)
D̄(1) = 1 (8.7)
n−1
4X
D̄(n) = D̄(i) (n ≥ 2) (8.8)
n i=0

Beweis.
Schritt 1. Der Induktionsanfang für n = 0 und n = 1 folgt daraus, daß eine leere Summe
nach Definition gleich null ist, bzw. ein Baum mit genau einen Knoten auch genau ein
Blatt der Tiefe null hat.
Schritt 2. Um den Induktionsschritt ausführen zu können, führen wir die folgenden Be-
zeichnungen ein:
– Die Menge Sn,i besteht aus allen Permutation π aus Sn mit π(1) = i. Das heißt,
ein π aus Sn,i hat die Gestalt

i π(2) π(3) . . . π(n) . (8.9)

46
– Für jedes π aus Sn,i definieren wir die Permutation π<i der Menge {1, 2, . . . , i − 1},
indem wir aus der Folge aus Gleichnung 8.9 alle Schlüssel k mit k ≥ i streichen.
(Beispiel: Ist n = 8, i = 4 und π gleich 4 6 2 1 8 7 5 3 , so ist π<4 gleich
2 1 3 .) Die Menge all dieser Permutationen ist Si−1 .
– Für jedes π aus Sn,i definieren wir die Permutation π>i der Menge {i + 1, i +
2, . . . , n}, indem wir aus der Folge aus Gleichnung 8.9 alle Schlüssel k mit k ≤ i
streichen. (Beispiel: Ist n= 8, i = 4 und π gleich 4 6 2 1 8 7 5 3 , so
ist π>4 gleich 6 8 7 5 .) Die Menge all dieser Permutationen bezeichnen wir
nicht ganz korrekt Sn−i . (Die Elemente, die permutiert werden, kommen ja nicht
aus der Menge {1, 2, . . . , n − i} sondern der Menge {i + 1, i + 2, . . . , n} gleicher
Mächtigkeit.)
– Die Abbildung ρn,i ordnet jeder Permutation π aus Sn,i das Paar (π<i , π>i ) aus
Si−1 × Sn−i zu.

Wir beobachten, daß für jedes π ∈ Sn,i der kanonische Suchbaum

Tπ = (Tπ<i , r, Tπ>i ) (8.10)

ist, wobei der Wurzelknoten r mit dem Schlüssel i markiert ist. Es folgt:

D(π) = 2 · (D(π<i ) + D(π>i )) . (8.11)

Gleichung 8.11 ist der Grund dafür, daß man statt der Tiefe d(π) den Wert D(π)
betrachtet.
Schritt 3. Wir zeigen, daß für je zwei Permuationen π ′ ∈ Si−1 und π ′′ ∈ Sn−i die Anzahl
′ ′′ n−1
derjenigen Permutationen π aus Sn,i , für die ρn,i(π) = (π , π ) ist, gleich i−1 ist:
 
−1 ′ ′′ n−1
#ρn,i (π , π ) = . (8.12)
i−1

Wieviele Möglichkeiten hat man, aus einem Paar (π ′ , π ′′ ) ∈ Si−1 ×Sn−i eine Permutation
π ∈ Sn,i zu konstruieren, für die π<i = π ′ und π>i = π ′′ ist?
Die innere Ordnung der Permutationen der Elemente sowohl aus {1, 2, . . . , i − 1} als
auch aus {i + 1, i + 2, . . . , n} steht durch die Vorgabe von π ′ bzw. π ′′ fest. Ferner kann
nur ein solches π gewählt werden, für das π(1) = i ist. Folglich können wir lediglich die
Teilmenge {j1 , j2 , . . . , ji−1 } mit j1 < j2 < . . . < ji−1 aus {1, 2, . . . , n} \ {1} frei wählen,
für die dann π(ik ) = π ′ (k) (k = 1, 2, . . . i − 1) ist. Da die Anzahl
 der (i − 1)–Teilmengen
n−1
einer (n − 1)–Menge gleich dem Binomialkoeffizienten i−1 ist, folgt die Behauptung.
Schritt 4. Für i = 1, 2, . . . , n betrachten wir den folgenden paaren Multigraphen Gi .
Die linke Knotenmenge Vi,1 ist gleich Sn,i . Die rechte Knotenmenge Vi,2 ist Si−1 × Sn−i .
Die Kantenmenge Ei ⊆ Vi,1 × Vi,2 von Gi ist wie folgt definiert: Ein Knoten π aus Vi,1 =
Sn,i und ein (π ′ , π ′′ ) aus Vi,2 = Si−1 × Sn−i sind durch genau D(π) = 2 · (D(π ′) + D(π ′′ ))

47
(siehe Gleichung 8.11) Kanten verbunden, wenn π<i = π ′ und π>i = π ′′ ist. Andernfalls
gibt es zwischen den beiden Knoten keine Kante.
Wir wenden das wohlbekannte Prinzip des Zählens längs zweier Wege an. Es gilt:
X
|Ei | = degree v (Zählung von links)
v∈Vi,1
X
= degree w (Zählung von rechts)
w∈Vi,2

Angewendet auf unsere spezielle Situation erhalten wir für die Zählung von links und
rechts:
X
|Ei | = D(π) (8.13)
π∈Sn,i

bzw. unter Verwendung von Gleichung 8.11 und 8.12


X X  n − 1
|Ei | = 2 · (D(π ′ ) + D(π ′′ ))
π ′ ∈Si−1 π ′′ ∈Sn−i
i−1
X X  D(π ′ ) D(π ′′ )

= 2 · (n − 1)! +
′ ′′
(i − 1)! · (n − i)! (i − 1)! · (n − i)!
π ∈Si−1 π ∈Sn−i
 
1 X 1 X
= 2 · (n − 1)!  D(π ′ ) + D(π ′′ )
(i − 1)! π′ ∈S (n − i)! π′′ ∈S
i−1 n−i

= 2 · (n − 1)! D̄(i − 1) + D̄(n − i) (8.14)

Wir erhalten aus den Gleichungen 8.13 und 8.14 durch Summation über i = 1, 2 . . . , n:
n
X n
X X
|Ei | = D(π)
i=1 i=1 π∈Sn,i

= n! · D̄(n) (Summe über die Gleichungen 8.13) (8.15)


n
X 
= 2 · (n − 1)! D̄(i − 1) + D̄(n − i)
i=1
n−1
X
= 4 · (n − 1)! D̄(i) (Summe über die Gleichungen 8.14) (8.16)
i=1

Die Gleichungen 8.15 und 8.16 ergeben unsere Behauptung:


n−1
4 X
D̄(n) = · D̄(i)
n i=1


48
Lemma 8.30 bildet die Grundlage für den Beweis des folgenden Lemmas:
Lemma 8.31 Für k ≥ 2 gilt:
(k + 3) · (k + 2) · (k + 1)
D̄(k) = (8.17)
30

Beweis. Man überprüft ferner leicht, daß D̄(2) = 2 ist.


Sei k ≥ 3. Wir ziehen die mit k multiplizierte Gleichung 8.8 für n = k von der mit
(k − 1) multiplizierten Gleichung 8.8 für n = k − 1 ab und erhalten:

k · D̄(k) − (k − 1) · D̄(k − 1) = 4 · D̄(k − 1) (8.18)

Es folgt:

k+3
D̄(k) = · D̄(k − 1) (8.19)
k
Indem wir Gleichung 8.19 iteriert in sich selbst einsetzen, erhalten wir:
(k + 3) · (k + 2) · (k + 1) · k · (k − 1) · . . . · 6
D̄(k) = · D̄(2)
k · (k − 1) · . . . · 3
(k + 3) · (k + 2) · (k + 1)
= · D̄(2) (8.20)
3·4·5
Aus Gleichung 8.20 folgt unter Verwendung von D̄(2) = 2 die Behauptung. 

Aus Gleichung 8.5 und Lemma 8.31 folgt Satz 8.32.


Satz 8.32 Es gilt:
1 X
depth Tπ = O (log n) .
n! π∈σ
n

Satz 8.32 scheint die folgende Aussage zu rechtfertigen: Die erwartete Laufzeit für die
Wörterbuch–Operationen lookUp(k), insert(k), delete(k) und die Iteratormethoden
nextElement(), previousElement() und hasElements() auf binären Suchbäumen mit n
Knoten ist ein O (log n).
Wie sind wir vorgegangen? Wir haben auf den binären Suchbäumen mit fester Knoten-
zahl, die ja der Größenparameter ist, eine Verteilung festgelegt. (Gleichung 8.12 zeigt, daß
in unserem Modell buschige“ Suchbäume deutlich wahrscheinlicher sind, als langgestreck-

te.) Die Laufzeit unserer Algorithmen auf einen konkreten Suchbaum wurde mit dessen
Wahrscheinlichkeit gewichtet.
Das Problem ist, daß wir dadurch ein Nutzerverhalten unterstellt haben. Wenn das reale
Nutzerverhalten mit dem angenommen schlecht übereinstimmt, beschreiben Aussagen wie
Satz 8.32 die in der Praxis vorkommenden Laufzeiten nicht. Für diesen Fall gibt es zwei
Auswege:

49
1. Die binären Suchbäume lassen sich zu den sogenannten Rot-Schwarz-Bäumen auf-
rüsten. Diese werden in jedem Schritt rebalanciert, so daß die Tiefe stets ein O (log n)
ist (siehe [CLRS01]).

2. Im Abschnitt 8.3 werden wir die Algorithmen randomisieren: Im Verlauf der Rech-
nung werden Zufallsexperimente ausgeführt. Die Laufzeit des Algorithmus auf eine
Eingabe wird zur zufälligen Variablen, deren Erwartungswert wir abschätzen werden.
Der praktische Wert dieser Ergebnisse ist nicht mehr vom Nutzerverhalten abhängig.

8.3 Hashing
Im Abschnitt 8.2 haben wir eine Datenstruktur studiert, bei der sowohl die Wörterbuch-
als auch die Iterator-Operationen mittlere Laufzeit O (log n) haben, wobei n die Anzahl
der aktuell gehaltenen Datensätze ist. In diesem Abschnitt werden wir eine Technik ken-
nenlernen, die es uns gestattet, die Wörterbuch-Operationen in mittlerer Zeit O (1) durch-
zuführen. Ja, wir werden dahin gelangen, daß wir diese Zeit auch stets erwarten können.
Der Preis dafür ist, daß sich die Effektivität der Iterator-Operationen verschlechtert. Hier
erhalten wir nur Laufzeiten der Größenordnung O (n).

8.3.1 Der Ansatz


Im Abschnitt 8.2 war das Universum U der möglichen Schlüssel ein Intervall [u, o] aus den
ganzen Zahlen Z. In unserem einleitenden Beispiel ist U gleich der Menge aller Wörter der
Länge λ ∈ [3, ℓmax ] über dem Standardalphabet {a, . . . z, A, . . . Z}.
Wir wollen in unserem Wörterbuch eine Menge S ⊂ U der Größe n halten. Das soll
vermöge einer Hashfunktion

hm = h : U −→ {0, 1, . . . , m − 1}

geschehen, welche die Schlüssel aus der Menge S auf m Buckets B0 , B1 , . . ., Bm−1 in der
Weise verteilt, daß

Bi = {x ∈ S | h(x) = i} (i = 0, 1, . . . , m − 1)

gilt.
Dazu ordnen wir zunächst jedem Buchstaben aus {a, . . . z, A, . . . Z} einen numerischen
Wert zu:

num a = num A = 0
num b = num B = 1
......
num z = num Z = 25

50
Nun betrachten wir als Beispiel den Fall m = 13 und definieren unsere Hashfunktion
h(b0 b1 b2 . . . bλ−1 ) := num b2 mod 13. (8.21)
Ist nun S := {Januar, Februar, Maerz, . . . , Dezember} diejenige Schlüsselmenge der
Größe n = 12, die es vermöge h auf die 13 Buckets zu verteilen gilt, so erhält man:

B0 = {Januar, Juni} B1 = {Februar} B2 = {September}


B3 =∅ B4 = {Maerz, April} B5 =∅
B6 = {August, Oktober} B7 =∅ B8 = {Mai, November}
B9 =∅ B10 =∅ B11 = {Juli}
B12 = {Dezember}
Die Buckets werden als Array B[0, m) der Länge m implementiert. Wir sprechen von
einer Hashtabelle. (Die Bezeichnung [0, m)“ für die Grenzen der Feldindizes lehnt sich an

die übliche Benennung halboffener Intervalle an: Die Zahl 0 ist der erste Feldindex, die
Zahl m ist keiner mehr. Der Vorteil dieser Notation besteht darin, daß sich die Feldlänge
aus der Differenz des oberen und des unteren Index ergibt.)
In unserem Beispiel gibt es Buckets, B0 = B[0] zum Beispiel, die zwei Schlüssel ent-
halten. Wir sprechen von einer Kollision. Im Idealfall liegen keine Kollisionen vor. Das ist
offensichtlich genau dann der Fall, wenn die Hashfunktion h eingeschränkt auf die zu spei-
chernde Schlüsselmenge S injektiv ist. Eine solche Hashfunktion nennen wir für S perfekt.
Natürlich stellen Kollisionen ein Problem dar, das man möglichst klein halten will. Sie
lassen sich nur dann definitiv ausschließen, wenn es immer soviele Buckets wie Schlüssel im
Universum U gibt. Das ist keine praktikable Lösung. Aber vielleicht kann man erreichen,
daß Kollisionen sehr selten sind, wenn man sich nur geschickt genug bei der Festlegung der
Anzahl der Buckets in Abhängigkeit von der Anzahl der zu speichernden Schlüssel und bei
der Auswahl der Hashfunktion verhält. Wir werden diese Frage im folgenden diskutieren.
Wir benötigen den folgenden Begriff.
Definition 8.33 Ist h : U → [0, m) eine Hashfunktion, längs derer wir eine Schlüsselmenge
S, die aus n Elementen besteht, auf die Buckets B0 , B1 , . . ., Bm−1 verteilt haben. Dann
heißt die Zahl
n
α :=
m
der (aktuelle) Auslastungsfaktor der Hashtabelle B[0, m).
12
Im vorstehenden Beispiel beträgt der Auslastungsfaktor 13 , ist also fast gleich eins.
Vielleicht muß der Auslastungfaktor nur um einige Größenordnungen kleiner sein, um Kol-
lisionen sehr unwahrscheinlich zu machen?
Um überhaupt von einer Kollisionswahrscheinlichkeit reden zu können, benötigen wir
ein stochastisches Modell, das beschreibt, wie die zu speichernde Schlüsselmenge S zufällig
aus dem Universum ausgewählt wird. Folgendes setzen wir stets voraus.
Grundannahmen (GA).

51
1. Die Mächtigkeit n der auszuwählenden Schlüsselmenge S ⊂ U ist kleiner oder gleich
der Anzahl der Buckets m. Beide Zahlen sind deutlich kleiner als die Größe des
Universums U:

n ≤ m ≪ |U|. (8.22)

n
2. Der Auslastungsfaktor α = m
ist durch eine universelle reelle Konstante α0 ∈ (0, 1)
nach unten beschränkt:

α0 < α ≤ 1, (8.23)

Der linke Teil von Ungleichung 8.23 zeigt an, daß wir keinen Platz im Hauptspeicher
zu vergeuden haben und eine lineare Auslastung der Hashtabelle wünschen.

3. Die Hashfunktion h : U → [0, m) partitioniert das Universum U in Blöcke Ui :=


{x | h(x) = i}. Wir wollen nicht soweit gehen zu fordern, daß all diese Blöcke nähe-
rungsweise die gleiche Größe haben müssen. Es muß aber wenigstens gesichert sein,
daß alle Schlüssel auch aus einem Block gewählt werden können:

n ≪ |Ui | (i = 0, 1, . . . , m − 1). (8.24)

Wie soll die zufällige Auswahl der Schlüssel erfolgen? Wir betrachten eine Folge von Zu-
fallselementen X0 , X1 , . . . , Xn−1 aus dem Universum U, welche die folgenden Eigenschaften
haben.
Uniformitätsannahme (UF).

1. Es wird eine Menge ausgewählt: Für i 6= j gilt: Xj 6= Xi .

2. Die vermöge der Hashfunktion h transformierten Zufallsschlüssel


h(X0 ), h(X1 ), . . . , h(Xn−1 ) sind eine Folge von unabhängigen Zufallsvariablen
aus {0, 1, . . . , m − 1}. Darüber hinaus ist für alle i = 0, 1, . . . , n − 1 die zufällige
Variable h(Xi ) über {0, 1, . . . , m − 1} gleichverteilt:

P (h(Xi ) = j) = 1/m, für alle j ∈ {0, 1, . . . , m − 1}.


Informatiker sprechen bei einer Folge unabhängiger gleichverteilter Zufallselemente ger-
ne von dem reinen Zufall. (UF) besagt in dieser Terminologie, daß keine zwei Schlüssel
gleich und die Hashwerte der Schlüssel rein zufällig sind.
Zur Risikoabschätzung von Kollisionen erscheint ein stochastisches Modell mit (UF) als
sinnvoll. Aber gibt es ein solches Modell überhaupt? Der folgende stochastische Prozeß ist
eine mögliche Umsetzung. Wir beschreiben ihn in Algorithmus 8.34.

Algorithmus 8.34 (Auswahl zufälliger Schlüssel unter (GA) und (UF))

52
Großschritt 1.
Initialisiere die Blöcke Uj = h−1 (j) (j = 0, 1, . . . , m − 1).
Großschritt 2.
Für i = 0, 1, . . . , n − 1 führe aus:
Wähle ein j ∈ {0, 1, . . . , m − 1} zufällig aus.
Wähle zufällig einen Schlüssel xi ∈ Uj aus.
Vermindere Uj um den soeben gezogenen Schlüssel xi .
Berechne h(xi )
Kommentar:
Die soeben beschriebene Folge zufälliger Schlüssel ist nicht unabhängig.

Warum sind die h(X0 ), h(X1 ), . . . , h(Xn−1 ) eine Folge unabhängiger gleichverteilter Zu-
fallselemente aus {0, 1, . . . , m − 1}? Algorithmus 8.34 ist so angelegt, daß für alle i =
1, 2, . . . , n − 1 und alle j0 , . . . , ji , j ∈ {0, 1, . . . , m − 1}

P (h(X0 ) = j) = P (h(Xi+1 ) = j | h(X0 ) = j0 , . . . , h(Xi ) = ji ) = 1/m

ist. Die Behauptung folgt aus den Aussagen des Abschnitts 3.1.
Man beachte, daß Algorithmus 8.34 wenigstens im Rahmen unserer Darstellung nicht
dazu gedacht ist, implementiert und ausgeführt zu werden. Er ist vielmehr der Nachweis der
Existenz unseres Modells des Nutzerverhaltens für die Abschätzung des Kollisionsrisikos.

Lemma 8.35 Aus (UF) folgt, daß

P (h ist für {X0 , X1 , . . . , Xn−1} perfekt) ≤ e−(α0 /2)·(n−1) . (8.25)

Beweisskizze. Wir betrachten den stochastischen Prozesses der Wertannahmen der Fol-
ge h(X0 ), h(X1 ), . . . , h(Xn−1 ). Es gilt:
m m−1 m−i m−n+1
P (h ist für {X0 , X1 , . . . , Xn−1 } perfekt) = · ·...· · ...· ,
m m m m

denn für i = 0, 1, . . . , n−1 sind bei für h(Xi ) m Fälle möglich und alle gleichwahrscheinlich,
aber nur m − i Fälle günstig. Natürlich ist
     
m m−i m−n+1 1 i n−1
· ...· · ...· = 1− · ...· 1 − · ...· 1− .
m m m m m m

Wegen 1 + x < ex für x 6= 0 erhalten wir:


   
1 n−1 Pn−1
1− · ...· 1 − < e−1/m· i=1 i = e−(n/2m)·(n−1) ≤ e−(α0 /2)·(n−1) .
m m


53
Lemma 8.35 zeigt uns, daß wir uns mit Kollisionen arrangieren müssen, denn die Wahr-
scheinlichkeit, daß h für S perfekt ist, geht gemäß Gleichung 8.25 exponentiell in n = |S|
gegen null.
Für n = 365 und m = 23 ist die linke Seite von Gleichung 8.25 die Wahrscheinlich-
keit dafür, daß unter 23 zufällig ausgewählten Personen wenigstens zwei an demselben Tag
Geburtstag haben. Diese Wahrscheinlichkeit ist größer als 12 . Man spricht vom Geburts-

tagsparadoxon“.

8.3.2 Offenes Hashing


Die wohl naheliegendste Kollisionsbewältigung besteht darin, die Buckets als verkettete
Listen zu implementieren:

B[0, m) of LinkedList

Jedes Bucket B[i] ist also eine verkettete Liste und eine neu auftretende Kollision wird
dadurch aufgelöst, daß die Liste des entsprechenden Buckets um eins verlängert wird.
Im Sinne der objektorientierten Programmierung schreibt man eine Klasse, der man
den Namen OpenHashTable geben kann, die als Hauptdatenfeld hashTable ein Array von
verketteten Listen enthält. (Über den Aufruf hashTable.length() haben wir den Zugriff
auf die Anzahl der Buckets m.) Wir allerdings werden die Bezeichnung hashTable selten
verwenden und die mathematische Bezeichnung B bevorzugen.
Eine Methode zur Berechnung von hm (x) = h(x), sofern es sich nicht um Standardfunk-
tionen handelt, gehört ebenso zu dieser Klasse wie alle anderen Methoden, die in diesem
Abschnitt folgen.
Wir nehmen an, daß die Knoten unserer Listen die Schlüssel (z.B. bei einem Schlüsse-
luniversum aus dem Bereich der ganzen Zahlen) oder die Zeiger auf die Schlüssel (z.B.
bei Zeichenketten als Schlüsseln) und die Zeiger auf die Satellitendaten in derselben Weise
verwalten, wie es bei den Knoten binärer Suchbäume der Fall war: Hält man den Knoten,
so kostet der Zugriff auf den Schlüssel und das Satellitendatum Zeit O (1).
Annahme. Die Natur des Datentyps Key des Universums der Schlüssel U und die Struktur
der Hashfunktionen — wir haben ja für jedes m eine — h : U → [0, m) gestatten es, für
jeden Schlüssel x aus U den Hashwert h(x) in Zeit O (1) zu berechnen.
Die nun folgenden Algorithmen sind besonders einfach.

Algorithmus 8.36 (Erzeugen einer leeren Hashtabelle mit m Buckets)


Methodenkopf: 
empty Integer m
Rumpf:
Erzeuge ein Feld von leeren verketteten Listen der Länge m.
Initialisiere das Datenfeld hashTable mit diesem Feld.

54
Algorithmus 8.37 (Einfügen in die Hashtabelle)
Methodenkopf: 
insert Key k, Range r
Nachbedingungen:
Das Paar (k, r) ∈ U × R ist in B[0, m) gespeichert.

Großschritt 1.
Berechne i ← h(k)
Großschritt 2.
Falls k in B[i] mit Satellitendatum r ′ vorkommt,
überschreibe r ′ durch r
return.
Füge einen neuen Knoten, der (k, r) trägt, der Liste B[i] hinzu.

Algorithmus 8.38 (Streichen aus der Hashtabelle)


Methodenkopf: 
delete Key k
Nachbedingungen:
Kein Paar (k, r) ∈ U × R ist in B[0, m) gespeichert.

Großschritt 1.
Berechne i ← h(k)
Großschritt 2.
Falls k in B[i] vorkommt,
streiche den Trägerknoten aus der Liste B[i].

Algorithmus 8.39 (Suche in der Hashtabelle)


Methodenkopf: 
lookUp Key k returns Range
Nachbedingungen:
Ist ein Paar (k, r) ∈ U × R in B[0, m) gespeichert, so Rückgabe von r.
Andernfalls Rückgabe von ↑.

Großschritt 1.
Berechne i ← h(k)
Großschritt 2.
Falls k in B[i] mit Satellitendatum r vorkommt,
return r.
return ↑

55
Die folgende Aussage ist offensichtlich.
Aussage 8.40 Die Laufzeit von Algorithmus 8.36 ist ein O (m), wobei m die übergebene
Anzahl von Buckets ist.
Die Algorithmen 8.37, 8.38 und 8.39 haben eine Laufzeit von

O (1 + Anzahl der Schlüsselvergleiche) .

Die Anzahl der Schlüsselvergleiche wiederum ist nach oben durch die Länge |B[h(k)]| der
h(k)-ten Liste beschränkt, wobei k der an die jeweilige Methode übergebene Schlüssel ist.

Der Kern einer mittleren Laufzeitanalyse ist nach Aussage 8.40 die Bestimmung der
mittleren Listenlänge. Eine Inspektion der Algorithmen 8.37, 8.38 und 8.39 zeigt, daß die
entscheidenden Kosten durch die Suche nach dem Schlüssel x verursacht werden, welcher
der jeweiligen Methode übergeben worden ist. Wir wollen diesen Teilalgorithmus“ mit

search(x) bezeichnen. Bei unserer Analyse unterscheiden wir drei Fälle. Dazu benötigen
wir die folgenden Bezeichnungen:
Zufällige Hashtabelle B(X0 , X1 , . . . , Xn−1 ). Sei X0 , X1 , . . . , Xn−1 , Xn eine Folge von Zu-
fallselementen aus U. Dann ist B(X0 , X1 , . . . , Xn−1 ) die Hashtabelle, die man erhält,
wenn man die Schlüssel X0 , X1 , . . . , Xn−1 in die leere Hashtabelle mit m Buckets
längs der Hashfunktion h = hm nacheinander einfügt.
Erfolglose Suche. Wir fragen nach der erwarteten Anzahl von Schlüsselvergleichen für
die Operation

B(X0 , X1 , . . . , Xn−1).search(Xn ). (8.26)

Erfolgreiche Suche nach einem festen Schlüssel aus der Hashtabelle. Wir suchen
nach einem der Schlüssel Xi , wobei i ∈ {0, 1, . . . , n − 1} beliebig aber fest ist. Uns
interessiert also die erwartete Anzahl von Schlüsselvergleichen für die Operation

B(X0 , X1 , . . . , Xn−1 ).search(Xi ). (8.27)

Erfolgreiche Suche nach einem rein zufälligen Schlüssel aus der Hashtabelle. Ge-
sucht wird nach XI , wobei der Index I aus {0, 1, . . . , n − 1} rein zufällig ist. Es geht
um die erwartete Anzahl von Schlüsselvergleichen für die Operation

B(X0 , X1 , . . . , Xn−1 ).search(XI ). (8.28)

Wir benötigen das folgende sehr einfache Lemma.


Lemma 8.41 Es ist
1
P (h(Xν ) = h(Xµ )) = (ν 6= µ).
m

56
Beweis. Die Aussage ist eine unmittelbare Folge dessen, daß die h(X0 ), h(X1 ), . . . , h(Xk )
eine Folge von unabhängigen und gleichverteilten Zufallsvariablen aus {0, 1, . . . , m−1} sind.


Wir schwächen für unsere Zufallsschlüssel die Uniformitätsforderung (UF) ab. Für den
späteren Übergang zum universellen Hashing ist das methodisch günstig.
Eine Folge X0 , X1 , . . . , Xk aus U erfüllt die abgeschwächte Uniformitätsbedingung
(UFab ), wenn
1
P (Xν = Xµ ) = 0 und P (h(Xν ) = h(Xµ )) ≤ (ν 6= µ). (8.29)
m
ist.
Lemma 8.41 sichert, daß eine Folge zufälliger Schlüssel mit (UF) auch die abgeschwächte
Uniformitätsbedingung (UFab ) erfüllt. Wir nehmen bis zum Ende dieses Abschnittes an,
daß für die Schlüsselfolge X0 , X1 , . . . , Xn die abgeschwächte Uniformitätsbedingung (UFab )
gilt.

Lemma 8.42 Sei B := B(X0 , X1 , . . . , Xn−1 ).


Die erwartete Anzahl der Schlüsselvergleiche für die erfolglose Suche in B (siehe Glei-
chung 8.26) ist kleiner oder gleich
n
α= .
m
Die erwartete Anzahl der Schlüsselvergleiche für die erfolgreiche Suche in B nach einem
festen Schlüssel aus der Hashtabelle (siehe Gleichung 8.27) ist nach oben durch

n−1
1+ <1+α
m
beschränkt.
Die erwartete Anzahl der Schlüsselvergleiche für die erfolgreiche Suche in B nach einem
rein zufälligen Schlüssel aus der Hashtabelle (siehe Gleichung 8.28) ist nach oben durch

n−1 α
1+ <1+
2m 2
beschränkt.

Beweis. Für alle i, j ∈ {0, 1, . . . , n} mit i 6= j definieren wir die Zufallsvariable Yij wie
folgt:
(
1 falls h(Xi ) = h(Xj );
Yij =
0 andernfalls.

57
Aus Gleichung 8.29 erhalten wir unmittelbar.
1
E Yij = P (Yij = 1) = P (h(Xi ) = h(Xj )) ≤
m
Um die in Rede stehenden Anzahlen abschätzen zu können, benötigen wir n + 1 weitere
zufällige Variablen.

Zi := #{j | 0 ≤ j ≤ n − 1, h(Xi ) = h(Xj )} (i = 0, 1, . . . , n)

Für i = 0, 1, . . . , n ist Zi die Länge der Liste von B(X0 , X1 , . . . , Xn−1 ), in der sich Xi
befindet. Der Wert von Zn ist die Länge der Liste von B(X0 , X1 , . . . , Xn−1 ), in die man Xn
einordnen müßte.

Bei der erfolglosen Suche ist Zn die Länge derjenigen Liste, die wir nach Xn vollständig,
aber vergeblich durchsuchen müssen. Wir müssen den Erwartungswert von Zn ausrechnen,
um den ersten Teil des Lemmas zu beweisen. Offenbar ist
n−1
X
Zn = Yjn .
j=0

Aus der Linearität der Erwartung erhalten wir


n−1
X n
E Zn = E Yjn ≤ .
j=0
m

Bei der erfolgreichen Suche nach Xi ist Zi (i = 0, 1, . . . , n − 1) die Länge der Liste, in der
wir nach dem Schlüssel suchen werden. Da in dieser Liste in jedem Falle der Schlüssel Xi
enthalten ist, ist für i = 0, 1, . . . , n − 1
n−1
X
Zi = 1 + Yji.
j=1
j6=i

Die Linearität der Erwartung liefert in diesem Falle


n−1
X n−1 n
E Zi = 1 + E Yji ≤ 1 + <1+ .
j=1
m m
j6=i

Für die Abschätzung der erwarteten Anzahl von Schlüsselvergleichen bei der erfolgrei-
chen Suche nach einem rein zufälligen Schlüssel aus der Hashtabelle müssen wir eine etwas
feinere Klinge schlagen. Die zufällige Variable
Li := #{j | h(Xj ) = i, für ein j mit 0 ≤ j ≤ n − 1} (i = 0, 1, . . . , m − 1) (8.30)

58
beschreibt die Länge des i-ten Buckets von B(X0 , X1 , . . . , Xn−1). Sucht man nach jedem
dieser Li Schlüssel, so braucht man
   
Li + 1 Li
1 + 2 + . . . + Li = = + Li
2 2

Vergleiche. Der gesuchte Erwartungswert ist folglich gleich


m−1    m−1   m−1
1X Li 1 X Li 1X
+ Li = + Li .
n i=0 2 n i=0 2 n i=0
| {z }
=n

Wegen
m−1
X 
Li X
= Yij
i=0
2 i6=j

und (UFab ) ist er gleich

1X 1 n(n − 1) 1 n−1 α
1+ E Yij ≤ 1 + =1+ <1+ .
n i6=j n 2 m 2m 2

Nun ist der Beweis des folgenden Satzes kein Problem mehr. Er folgt direkt aus Aussage
8.40 und Lemma 8.42.
Satz 8.43 Ist α der aktuelle Auslastungsfaktor der Hashtabelle, so ist in einem stochasti-
schen Modell mit (UFab ) die erwartete Laufzeit jeder der Operationen insert, delete und
lookUp ein O (1 + α).

8.3.3 Über die Laufzeit im schlechtesten Fall


Eine Aussage über den Erwartungswert einer zufälligen Variablen X beinhaltet zunächst
nichts über die Abweichung von X von E X. Das leistet z.B. die Tschebyschewsche Unglei-
chung (siehe Satz 3.8):
Var X
P (|X − E X| ≥ α) ≤ ,
α2
wobei Var X die Varianz von X ist (siehe Definition 3.6). Leider haben wir mit den Zu-
fallsvariablen timeinsert , timedelete und timelookUp aus Satz 8.43 ein Problem: Wir kennen
ihre Varianzen nicht.
Im Satz 8.44 werden wir für zufällige Schlüssel X0 , X1 , . . . , Xn−1 mit (UF) — die Bedin-
gung (UFab ) reicht dazu leider nicht aus — Abhilfe schaffen. Wie üblich, bezeichnen wir

59
mit B(X0 , X1 , . . . , Xn−1 ) die zufällige Hashtabelle mit m Buckets, die dadurch entsteht,
daß man diese Schlüssel von links nach rechts in die leere Hashtabelle mit m Buckets längs
der Hashfunktion h = hm einordnet. Wir werden die Länge des längsten Buckets studieren.
Das rechtfertigt auch die Überschrift dieses Abschnitts.
Uns interessiert das schlechteste Bucket von B(X0 , X1 , . . . , Xn−1 ). Dessen Länge wird
durch die zufällige Variable
L := max{Li | i = 0, 1, . . . , m − 1}
beschrieben, wobei die Zufallsvariablen Li für i = 0, 1, . . . , m−1 in Gleichung 8.30 definiert
worden sind.
Schließlich benötigen wir die größte monoton wachsende zahlentheoretische Linksinverse
— siehe Abschnitt 1.3 für allgemeine Betrachtungen zu diesem Thema — der Fakultäts-
funktion fak r := r!, die wir hier mit λ bezeichnen wollen:
λ(m) := min{r | r! ≥ m}.

Natürlich heißt das insbesondere

λ(m)! ≥ m. (8.31)
ln m
Man weiß, daß λ(m) zu der Funktion ln ln m
asymptotisch äquivalent ist:
ln m
lim = 1.
m→∞ λ(m) · ln ln m

Satz 8.44 Unter den vorstehend genannten Voraussetzungen ist


1
P (L ≥ 3 · λ(m)) < 2 . (8.32)
m
Beweis.
Schritt 1. Sei zunächst i ∈ {0, 1, . . . , m} fixiert. Wir zeigen, daß für jedes natürliche r > 1
1
P (Li ≥ r) < (8.33)
r!
ist. In der Tat, es ist

Li ≥ r ⇐⇒ ∃T ⊆ {0, 1, . . . , n − 1} : |T | = r und h(xj ) = i, für alle j ∈ T .

Es folgt:
X
P (Li ≥ r) ≤ P (h(Xj ) = i, für alle j ∈ T )
| {z }
T ⊆{0,1,...,n−1}
|T |=r = m1r (wegen (UF))
 
n 1 n · (n − 1) · . . . · (n − r + 1) nr 1
≤ · r = < ·
r m r! · mr mr r!
1
≤ (wegen n ≤ m).
r!

60
Schritt 2. Nun zeigen wir, daß
m
P (L ≥ r) < (8.34)
r!
ist. Wegen

L ≥ r ⇐⇒ ∃i ∈ {0, 1, . . . , m − 1} mit Li ≥ r
folgt Gleichung 8.34 aus Gleichung 8.33, da die Wahrscheinlichkeit der Alternative von
Ereignissen durch die Summe der Wahrscheinlichkeiten der Einzelereignisse nach oben
abgeschätzt werden kann (siehe Ungleichung 3.7).
Schritt 3. Schließlich ist
m
P (L ≥ 3 · λ(m)) < (Gleichung 8.34)
(3 · λ(m))!
m 1
= ·
λ(m)! (λ(m) + 1) · (λ(m) + 2) · . . . · (3λ(m))
| {z } | {z }
≤1 1
< λ(m)!·λ(m)! ≤ 1
(Gl. 8.31)
m2

1
< .
m2


8.3.4 Die Verdopplungsstrategie


In den Anwendungen kann es sein, daß die Anzahl der zu speichernden Schlüssel nur
geringfügig um einen bekannten Mittelwert schwankt. Dann ist klar, mit welcher Größe
man die Hashtabelle anzulegen hat.
Kann die Größe der Tabelle nicht abgeschätzt werden, oder sind gar große Schwan-
kungen abzusehen, so benutzt man die Verdopplungsstrategie. Um sie implementieren zu
können, hält man in einem Datenfeld pegel die Anzahl der aktuell gespeicherten Schlüssel
n. Die Länge m der Hashtabelle ist über eine Methode length() des Feldes B jederzeit
abrufbar.
– Am Anfang wird eine Hashtabelle mit einer passenden Länge m0 angelegt.
pegel
– Hat der aktuelle Auslastungsfaktor α = B.length() eine vorher festgelegte untere
Schranke α0 ∈ (0, 1) erreicht, so wird eine neue Hashtabelle mit halbierter Länge
angelegt und der Inhalt der alten Hashtabelle Eintrag für Eintrag umgespeichert.
(Es ist sinnvoll, die Länge der Hashtabelle nicht unter eine globale untere Schranke
fallen zulassen.)
pegel
– Hat der aktuelle Auslastungsfaktor α = B.length() eine vorher festgelegte obere Schran-
ke α1 ∈ (α0 , 1] erreicht, so wird eine neue Hashtabelle mit verdoppelter Länge ange-
legt und der Inhalt der alten Hashtabelle Eintrag für Eintrag umgespeichert.

61
Wie sieht es mit der Laufzeitanalyse in diesem Fall aus? Wir haben es mit zwei unter-
schiedlichen Arten der Operationsausführung zu tun.
1. Im gewöhnlichen Geschäftsgang“— wenn nach der Operationsausführung der Aus-

lastungsfaktor α ∈ (α0 , α1 ) ist – ist Satz 8.43 anwendbar: Alle Operationen kosten
konstante Zeit.
2. Von Zeit zu Zeit muß jedoch umgespeichert werden. Verglichen mit dem konstanten
Zeitaufwand für die Operationsausführung im gewöhnlichen Geschäftsgang sind diese
Operationen äußerst kostspielig: Θ(n), wobei n die aktuelle Anzahl der gespeicherten
Elemente ist.
Unter Verwendung der sogenannten Tilgungskostenanalyse (siehe Abschnitt 11.2)
werden wir sehen, daß wenn wir dennoch im Mittel je Operation nur einen Zeitbedarf
von O (1) haben.

8.3.5 Einige einfache Hashfunktionen


Die Kriterien für die Qualität von Hashfunktionen h sind
1. die schnelle Berechenbarkeit von h(x) (möglichst in Zeit O (1)) und
2. die gute Streuung oft“ vorkommender Schlüsselmengen über die Buckets.

Das Universum besteht aus natürliche Zahlen
Wir betrachten Hashfunktionen
h : U = {0, 1, . . . , u − 1} → {0, 1, . . . , m − 1}.
Die Divisionsrestmethode. h(x) := x mod m, für eine Primzahl m.
Diese Hashfunktionen sind sehr einfach und effizient. Allerdings gibt es Beispiele ka-
nonisch auftretender Schlüsselmengen (z.B. in Zahlen übertragene Strings, bei denen
große Buckets auftreten können.)
Die Multiplikationsmethode. h(x) := ⌊((ϑ · x) mod 1) · m⌋, für ein 0 < ϑ < 1 (ideal
ist ein irrationales ϑ). Dabei ist (ϑ · x) mod 1 der gebrochene Anteil von ϑ · x, also
gleich ϑ · x − ⌊ϑ · x⌋.

Beispiel. Für ϑ = 12 ( 5 − 1) ≈ 0, 6180 und m = 100 ist
h(100) = ⌊(100ϑ mod 1) · 100⌋
= ⌊(61, 80 . . . mod 1) · 100⌋
= 80
h(101) = ⌊(101ϑ mod 1) · 100⌋
= ⌊(62, 42 . . . mod 1) · 100⌋
= 42

62
Man kann zeigen, daß die Hashfunktion mit diesem ϑ konsekutive Schlüssel sehr gut
verteilt.

Das Universum besteht aus Strings


Ist U = Σℓ für ein Alphabet Σ (z.B. die ASCII-Zeichen), dann gibt es stets eine kanonische
Funktion

num : Σ → {0, 1, . . . , |Σ| − 1},

die jedem Buchstaben seinen numerischen Wert zuordnet. Wir betrachten Hashfunktionen

h : Σℓ → {0, 1, . . . , m − 1}.

Lineare Funktionen über Zp . Dazu muß m = p > |Σ| eine Primzahl sein. Die Bedin-
gung p > |Σ| sichert, daß der numerische Wert num σ jedes Buchstabens σ ∈ Σ im
kanonischen Repräsentantensystem {0, 1, . . . , p − 1} mod p liegt. Man wählt Koef-
fizienten a0 , . . . , aℓ−1 ∈ {0, 1, . . . , p − 1} und setzt
ℓ−1
!
X
h(σ0 σ1 . . . σℓ−1 ) := ai · num σi mod p (8.35)
i=0

Ein nachweisbar gutes Verhalten liegt für den Fall vor, daß die Koeffizienten zufällig
gewählt sind.

Lineare Funktionen über {0, 1}β . Hier ist β die Verarbeitungsbreite des Rechners und
m = 2κ für κ ≤ β. Viele Rechner und Programmiersprachen bieten für eine Bitfolge
der Länge β das bitweise XOR (wir schreiben lieber ⊕) an.
Beispiel. 110011 ⊕ 110101 = 000110.
Wir wählen ein Feld T[0, ℓ)[0, |Σ| − 1) von Wörtern aus {0, 1}β und setzen
ℓ−1
!
M
h(σ0 σ1 . . . σℓ−1 ) := ν T[i][num σi ] mod 2κ (8.36)
i=0

Wir erinnern uns, daß ν(w) für die natürliche Zahl steht, die kanonisch durch die
Bitfolge w repräsentiert wird.
Man beachte, daß für eine Bitfolge w der Länge ≥ κ die Operation ν(w) mod 2κ
keine echte Division ist. Im Ergebnis werden lediglich alle Bits links von den κ nie-
derwertigsten abgetrennt.
L P
Das Zeichen “ steht zu Zeichen ⊕“ in demselben Verhältnis wie das Zeichen “
” ” ”
zum Zeichen +“.

63
Die durch Gleichung 8.36 definierte Hashfunktion hat für eine zufällig gewählte Ma-
trix T[0, ℓ)[0, |Σ|) nachweisbar ausgezeichnete Eigenschaften.
Bemerkung. Handelt es sich bei Σ zum Beispiel um alle 1-Byte-Zeichen, steht ein 64-
Bit-Rechner zur Verfügung, und stehen Schlüsselwörter der Länge ℓ = 20 in Rede,
so beansprucht die Matrix T 20 · 256 · 8 = 40.960 Bytes, eine Kleinigkeit.

Fazit. Die vorstehenden Hashfunktionen lassen unter bestimmten anwendungsbezoge-


nen Umständen die Uniformitätsannahme (UF) oder die abgeschwächte Uniformitätsan-
nahme (UFab ) — sie sollen das Nutzerverhalten simulieren — als vernünftig erscheinen.
Das Nutzerverhalten kann jedoch anders sein. Dann aber sind unsere Sätze 8.43 und 8.44
für das praktische Verhalten unserer Hashtabellen nicht aussagekräftig. Einen Ausweg stellt
das universelle Hashing aus Abschnitt 8.3.6 dar.

8.3.6 Universelles Hashing


Kern des universellen Hashings stellt die Randomisierung des Algorithmus empty(Integer
m) zur Erzeugung einer leeren Hashtabelle und damit auch die Randomisierung der Um-
speicherung der ganzen Tabelle (siehe Abschnitt 8.3.4) dar. Die Algorithmen 8.37, 8.38
und 8.39 bleiben im Wesentlichen unverändert.
Wir nehmen an, daß wir über einen (Pseudo-)Zufallsgenerator verfügen, der in der Rou-
tine
random() returns Real
implementiert ist. (Ein Purist der objektorientierten Programmierung mag sich vorstel-
len, daß sie zu einer Serviceklasse Math gehört. Trotzdem verwenden wir in unserem Pseu-
docode den qualifizierten Aufruf Math.random() nicht und schreiben nur random().)
Diese Routine setzt auf dem Intervall [0, 1] ⊂ R die Gleichverteilung um: Ist [a, b] ⊆ [0, 1]
ein Teilintervall, so gibt ein Aufruf von random() mit Wahrscheinlichkeit b − a eine Zahl
aus diesem Intervall zurück. Mit Wahrscheinlichkeit 1 − (b − a) liegt der Rückgabewert
in [0, 1] \ [a, b].
Die Laufzeit eines Aufrufs von random() sei ein O (1).
Um zu randomisieren, benötigen wir eine unabhängige Folge

Υ0 , Υ1 , . . . , Υk

von gleichverteilten Zufallselementen aus {0, 1}, eine Folge zufälliger Bits. Der folgende
Algorithmus erzeugt eine Folge zufälliger Bits und gibt sie als Feld zurück.

Algorithmus 8.45 (Erzeugen von zufälligen Bits)

64
Methodenkopf: 
randomBits Integer k returns Array r[0, k) of {0, 1}
Großschritt 1:
Erzeuge Feld r[0, k).
Großschritt 2:
Für i = 0, 1, . . . k − 1 führe aus:
z ← random()
Falls z ≤ 21 , so r[i] ← 0.
Andernfalls r[i] ← 1.
Großschritt 3:
return r.
Warum ist die durch Algorithmus 8.45 erzeugte Folge zufälliger Bits unabhängig? Man
überlegt sich leicht, daß die Ausführung von Großschritt 2 Gleichung 3.19 aus Kapitel 3
sichert.

Aussage 8.46 Algorithmus 8.45 arbeitet in Zeit O (k), wobei k die übergebene natürliche
Zahl ist.

Wir folgen der von Carter und Wegman 1979 vorgeschlagenen Vorgehensweise. Zunächst
müssen Klassen von Hashfunktionen

H = Hm := {h | h : U → {0, 1, . . . , m − 1}} (8.37)

festgelegt werden, auf denen wir gleichverteilte Zufallselemente H betrachten:


1
P (H = h) = (∀h ∈ H) (8.38)
|H|
Bemerkung. Bei der Analyse des offenen Hashings war die Hashfunktion h fest, der
Schlüssel X jedoch zufällig. Folglich war der Bucketindex h(X) eine zufällige Variable aus
{0, 1, . . . , m−1}. Jetzt ist die Hashfunktion H ein Zufallselement aus H, und jeder beliebige,
aber feste Schlüssel k ∈ U definiert eine Zufallsvariable H(k) aus {0, 1, . . . , m − 1}. Es
handelt sich dabei, wie auch im Falle des offenen Hashings, um eine Transformation des
Zufallselements H im Sinne von Abschnitt 3.1.2.

Wir können wiederum eine Rahmenklasse schreiben, die z. B. UniversalHashTable


heißt. Sie enthält als Hauptdatenfeld eine Variable hashTable vom Typ Array von verket-
teten Listen. Daneben muß es Datenfelder geben, die eine Identifikation und Berechnung
der aktuellen Hashfunktion h ∈ Hm ermöglichen.
Der Algorithmus zur Erzeugung einer leeren Hashtabelle sieht nun so aus.

Algorithmus 8.47 (Erzeugen einer leeren Hashtabelle mit m Buckets)

65
Methodenkopf: 
empty Integer m
Großschritt 1:
Wähle ein h ∈ Hm zufällig und mit gleicher Wahrscheinlichkeit.
Initialisiere die Datenfelder zur Beschreibung von h entsprechend.
Großschritt 2:
Erzeuge ein Feld der Länge m von leeren verketteten Listen.
Initialisiere das Datenfeld hashTable mit diesem Feld.

Die Mengen von Hashfunktionen aus Gleichung 8.37 müssen zwei Eigenschaften haben.

1. Das Kollisionsrisiko läßt sich explizit begrenzen.

2. Großschritt 1 von Algorithmus 8.47 ist effizient ausführbar: Die Elemente aus Hm
sind durch wenige Parameter beschreibbar, die dann als Datenfelder gehalten wer-
den. Genauer gesagt, müssen O (log2 |U|) Bits genügen, um ein h aus Hm , so zu
beschreiben, daß für jedes x ∈ U der Wert h(x) wenn schon nicht immer in Zeit
O (1), so doch wenigstens in Zeit O (log2 |U|) berechnet werden kann.
Diese Bits werden in Goßschritt 1 von Algorithmus 8.47 mithilfe von Algorithmus
8.45 ausgewürfelt.
Um das Kollisionsrisiko beschränken zu können, ist uns der folgende Begriff unentbehr-
lich.

Definition 8.48 Sei c eine positive reelle Konstante. Eine Menge von Hashfunktionen Hm
aus Gleichung 8.37 heißt c–universelle Klasse von Hashfunktionen, wenn für je zwei feste
Schlüssel k1 6= k2 aus dem Universum und ein Zufallselement H aus H
c
P (H(k1) = H(k2)) ≤ (8.39)
m
ist.
Wir kommen zur Analyse der Anzahl der Schlüsselvergleiche beim universellen Hashing.
Da im Vergleich zum offenen Hashing die Algorithmen kaum verändert sind, ist die Aus-
gangssituation der aus Abschnitt 8.3.2 sehr ähnlich. Wir analysieren wieder die erfolgreiche
und die erfolglose Suche. Es gibt jedoch einen fundamentalen Unterschied: Jetzt sind die
Schlüssel beliebig aber fest, wohingegen die Hashfunktion ein rein zufälliges Element aus
H ist.

Bezeichnungen. Sei x0 , x1 , . . . , xn−1 , xn eine beliebige aber feste Folge von paarweise ver-
schiedenen Schlüsseln aus U, und sei h ∈ Hm eine beliebige aber feste Hashfunktion.
Dann bezeichnet

Bh = Bh (x0 , x1 , . . . , xn−1 )

66
die Hashtabelle, die man erhält, wenn man die Schlüssel x0 , x1 , . . . , xn−1 in die leere
Hashtabelle unter Verwendung von h nacheinander einfügt.
Ist H ∈ Hm dagegen eine zufällige Hashfunktion, so bezeichnet
BH = BH (x0 , x1 , . . . , xn−1 )
die zufällige Hashtabelle, die man erhält, wenn man die Schlüssel x0 , x1 , . . . , xn−1 in
die leere Hashtabelle unter Verwendung von H nacheinander einfügt.
Der Teilalgorithmus“ search(x) (x ∈ U beliebig, aber fest) ist genauso definiert,

wie im Abschnitt 8.3.2. Für ein zufälliges H ist die Anzahl der Schlüsselvergleiche
keyComp (BH .search(x)) eine zufällige Variable, die durch Transformation aus H
ensteht.
Die erfolglose Suche. Wir fragen nach der erwarteten Anzahl von Schlüsselvergleichen
bei der Suche nach xn in BH (x0 , x1 , . . . , xn−1 ), wobei H eine zufällige Hashfunktion
aus H ist:
1 X
E (keyComp (BH .search(xn ))) := keyComp (Bh .search(xn )) . (8.40)
|H| h∈H

Erfolgreiche Suche nach einem festen Schlüssel aus der Hashtabelle. Wir fragen
nach der erwarteten Anzahl von Schlüsselvergleichen bei der Suche nach xi (für ein
i ∈ {0, 1, . . . , n−1}) in BH (x0 , x1 , . . . , xn−1 ), wobei H eine rein zufällige Hashfunktion
aus H ist:
1 X
E (keyComp (BH .search(xi ))) := keyComp (Bh .search(xi )) . (8.41)
|H|
h∈H

Erfolgreiche Suche nach einem rein zufälligen Schlüssel aus der Hashtabelle. Ge-
sucht wird nach xI , wobei der Index I aus {0, 1, . . . , n − 1} rein zufällig ist. Uns
interessiert
n−1
1 XX
E (keyComp (BH .search(xI ))) := keyComp (Bh .search(xi )) . (8.42)
|H|n h∈H i=0

Das folgende Lemma gleicht Lemma 8.42 aus Abschnitt 8.3.2 fast wie ein Ei dem
anderen.
Lemma 8.49 Sei H = Hm eine c–universelle Klasse von Hashfunktionen (c > 0).
Die erwartete Anzahl der Schlüsselvergleiche für die erfolglose Suche in BH (siehe Glei-
chung 8.40) ist kleiner oder gleich c · α.
Die erwartete Anzahl der Schlüsselvergleiche für die erfolgreiche Suche in BH nach
einem festen Schlüssel aus dieser Hashtabelle (siehe Gleichung 8.41) ist kleiner als 1 + c · α.
Die erwartete Anzahl der Schlüsselvergleiche für die erfolgreiche Suche in BH nach
einem rein zufälligen Schlüssel aus dieser Hashtabelle (siehe Gleichung 8.42) ist kleiner als
1 + c · α/2.

67
Auf den Beweis von Lemma 8.49 können wir verzichten. Er ist fast identisch mit dem
Beweis von Lemma 8.42. An Stelle der abgeschwächten Uniformitätsbedingung (UFab ) wird
im Beweis von Lemma 8.49 Gleichung 8.39 aus Definition 8.48 verwendet.
Wir erhalten:
Satz 8.50 Unter den Voraussetzungen von Lemma 8.49 ist die erwartete Laufzeit jeder
der Operationen insert, delete und lookUp ein O (1 + α), wobei α der aktuelle Ausla-
stungsfaktor der Hashtabelle ist.

8.3.7 Die Verdopplungsstrategie für das universelle Hashing


Die Verdopplungsstrategie des universellen Hashings ist derjenigen aus Abschnitt 8.3.4 sehr
ähnlich.

– Am Anfang wird eine leere Hashtabelle mit einer passenden Länge m0 vermöge des
Algorithmus 8.47 angelegt.
pegel
– Hat der aktuelle Auslastungsfaktor α = B.length() eine vorher festgelegte obere Schran-
ke erreicht, so wird mit Hilfe von Algorithmus 8.47 eine neue Hashtabelle mit ver-
doppelter Länge angelegt und der Inhalt der alten Hashtabelle Eintrag für Eintrag
umgespeichert.
pegel
– Hat der aktuelle Auslastungsfaktor α = B.length() eine vorher festgelegte untere
Schranke erreicht, so wird mit Hilfe von Algorithmus 8.47 eine neue Hashtabelle
mit halbierter Länge angelegt und der Inhalt der alten Hashtabelle Eintrag für Ein-
trag umgespeichert. (Auch hier ist es natürlich sinnvoll, die Länge der Hashtabelle
nicht unter eine globale untere Schranke fallen zulassen.)

Alles, worauf es jetzt ankommt, ist die Konstruktion vernünftiger universeller Klassen
von Hashfunktionen.

8.3.8 Einige universelle Klassen von Hashfunktionen


Wir überlegen uns, daß die Menge H = {0, 1, . . . , m − 1}U aller Funktionen von U nach
{0, 1, . . . , m − 1} 1-universell ist. Dazu fixieren wir zwei Schlüssel k1 6= k2 aus dem Uni-
versum. Da wir uns auf gleichverteilte Zufallselemente aus H beschränken, reicht es, den
Bruch
|{h ∈ H | h(k1) = h(k2 )}| |{h ∈ H | h(k1 ) = h(k2 )}|
=
|H| m|U |

zu betrachten. Die Mächtigkeit der Menge im Nenner ist genau m|U |−1 :

|{h ∈ H | h(k1) = h(k2 )}| m|U |−1 1


= |U |
= .
|H| m m

68
Die praktische Anwendung der Klasse H = {0, 1, . . . , m − 1}U , d.h., die effiziente
Ausführbarkeit von Großschritt 1 aus Algorithmus 8.47, stößt auf zwei unüberwindliche
Hindernisse:

1. Man benötigt mindestens log2 m · |U| zufällige Bits, um ein h ∈ U auszuwürfeln“.



2. Das Problem der effizienten Speicherung der gewählten Hashfunktion ist für die Klas-
se aller Funktionen genausowenig lösbar.

Es folgen einige universelle Klassen von Hashfunktionen, für die Großschritt 1 von
Algorithmus 8.47 effizient ausführbar ist.

Beispiel 1.
Es sei m (die Anzahl der Buckets) eine Primzahl und U = {0, 1, . . . , m − 1}r für ein r ≥ 1.
Wir definieren
ip
Hm,r := {ha | a = (a0 , a1 , . . . , ar−1 ) ∈ U} (8.43)

wobei

ha : U → {0, 1, . . . , m − 1}

wie folgt definiert ist.

r−1
!
X
ha (x) := ai · xi mod m (x = (x0 , . . . , xr−1 ) ∈ U).
i=0

An dieser Stelle sei daran erinnert, daß es sich für jede ganze Zahl z bei z mod m um den
kanonischen Repräsentanten von z modulo m handelt, um eine Zahl aus {0, 1, . . . , m − 1}
also, die man als Rest erhält, wenn man z durch m ganzzahlig dividiert (siehe Definition
1.4 und Aussage 1.6).

ip
Satz 8.51 Die Klasse Hm,r ist 1–universell.

Beweis. Der Beweis ist außerordentlich einfach, sofern man mit elementaren Aussagen
der linearen Algebra vertraut ist.
Wir identifizieren jedes Element des Körpers Fm mit seinem kanonischen Representan-
ten mod m. (Das ist erlaubt, weil wir repräsentantenunabhängig rechnen können.) Dann
ist das Universum U im vorliegenden Falle gleich dem r-dimensionalen Vektorraum Frm
über dem Körper Fm , und ha (x) ist gleich dem Wert der kanonischen Bilinearform

h·, ·i : Frm × Frm → Fm

69
an der Stelle (a, x):
ha (x) = ha, xi.
Sind nun k1 und k2 zwei voneinander verschiedene Schlüssel aus Frm , so ist deren Differenz
x := k1 − k2 ein von Null verschiedener Vektor in Frm . Folglich hat der Kern der linearen
Abbildung
h·, xi : Frm → Fm
a 7→ ha, xi = (ha (k1 ) − ha (k2 )) mod m
die Dimension r − 1. Anders ausgedrückt, es ist
|{a | ha(k1 ) = ha (k2 )}| = mr−1 ,
woraus die Behauptung folgt. 

Beispiel 2.
Sei U = {0, 1, . . . , p − 1}, wobei p eine (große) Primzahl ist.
mult
Hp,m := {ha | a ∈ {1, 2 . . . , p − 1}} (8.44)
wobei für jedes in Rede stehende a die Hashfunktion
ha : U → {0, 1, . . . , m − 1}
wie folgt
ha (x) := ((a · x) mod p) mod m
definiert ist.
mult
Satz 8.52 Die Klasse Hp,m ist 2–universell.

Beweis. Seien k1 6= k2 zwei beliebige, aber feste Schlüssel aus U = {0, 1, . . . , p − 1}. Wir
betrachten für jedes in Rede stehende a ∈ {1, 2, . . . , p − 1} die Zahl
 
δ(a) := (a · k1 ) mod p − (a · k2 ) mod p
die wegen a 6= 0 und k1 6= k2 offenbar in der Menge {−(p − 1), −(p − 2), . . . , −1} ∪
{1, 2, . . . , p − 1} liegt. Man sieht sofort, daß für alle a′ , a′′ ∈ {1, 2, . . . , p − 1} mit a′ 6= a′′
δ(a′ ) 6= δ(a′′ )
ist. Von den 2(p−1) möglichen Werten für δ(a) wird also einerseits keiner doppelt vergeben.
Andererseits ist höchstens jeder m-te von ihnen durch m teilbar. Es folgt
2
|{a ∈ {1, 2, . . . , p − 1} | ha(k1 ) = ha (k2 )}| ≤ · (p − 1),
m
woraus sofort die Behauptung folgt. 

70
Beispiel 3.
Seien U = {0, 1, . . . , 2k − 1} und m = 2l .
lin
Hk,l := {ha | a ∈ {0, 1 . . . , 2k − 1}, a ungerade} (8.45)

wobei

ha : U → {0, 1, . . . , m − 1}

wie folgt definiert ist.

ha (x) := ((a · x) mod 2k ) div 2k−l .


lin
Satz 8.53 Die Klasse Hk,l ist 2–universell.

Fazit. Bisher haben wir bei der Analyse die Eingaben als zufällig angesehen. Beim
universellen Hashings dagegen wählen wir die Hashfunktion zufällig aus. Infolgedessen
gibt es keine schlechten Eingaben mehr. Für jede Eingabe gibt es nun gute und weniger
gute Rechengänge, je nachdem auf welche Hashfunktion die zufällige Wahl gefallen ist.
Die Laufzeit streut für alle Eingaben um denselben Erwartungswert: O (1 + α). Sofern wir
über gute Pseudozufallsgeneratoren verfügen, können wir uns auf unsere Analyseaussagen
in der Praxis voll und ganz in dem Sinne verlassen, daß die theoretisch erwartete gleich
der in der Praxis zu erwartende Laufzeit ist.
Manche Hashfunktionenklassen setzen voraus, daß die Anzahl der Buckets eine Prim-
zahl ist. Das macht die Verdopplungsstrategie im engeren Sinne unmöglich. Es ist eine
leichte Übungsaufgabe, sich Auswege zu überlegen.
Wir müssen uns auch hier der Frage stellen, was eine Aussage des Typs Die erwartete

Laufzeit für eine Eingabe ist ein O (1 + α)“ für den Einzelfall bedeutet:

– Wird die zufällige Hashfunktion häufig neu zufällig ausgewählt, so wird mit hoher
Wahrscheinlichkeit die benötigte Laufzeit linear in der Anzahl der ausgeführten Ope-
rationen sein. Das ist eine Folgerung aus der Hoeffdingschen Ungleichung (siehe Bei-
spiel nach Satz 3.10). Allerdings hat man dann das Problem der hohen Umspeicher-
kosten.
– Bei einmaliger Wahl einer Hashfunktion kann die Wahrscheinlichkeit für das Auf-
treten einer inakzeptablen Laufzeit recht groß sein. Die Laufzeit kann breit streuen,
obwohl ihr Erwartungswert nach Satz 8.50 konstant ist. Leider läßt sich Satz 8.44
nicht auf den Fall des universellen Hashings übertragen, da im Beweis die Unifor-
mitätsbedingung (UF) und nicht deren Abschwächung (UFab ) benötigt wird. Helfen
würden Aussagen des Typs
1
P (n Operationen dauern mindestens c · n Schritte) ≤ ,
n2

71
wobei c eine positive reelle Konstante ist.
Es gibt Hashklassen, die solche Aussagen zulassen. Ein Beispiel für

U = {0, 1, . . . , p − 1} (p prim)

und

m = die Bucketgröße

ist
3
Hm := {ha,b,c,d | a, b, c, d ∈ {0, 1, . . . , p − 1}},

wobei

ha,b,c,d (x) = ((ax3 + bx2 + cx + d) mod p) mod m

ist.

8.3.9 Geschlossenes Hashing


Begriff. Lineares Sondieren
Das Wesen des geschlossenen Hashings besteht darin, niemals zwei Schlüssel in ein Bucket
zu legen. Die Hashtabelle ist ein Feld von Paaren aus Schlüsseln und Satellitendaten:

B[0, m) of Key×Range.

Man verwendet Hashfunktionen

h : U × [0, m) → [0, m), (8.46)

die für jeden Schlüssel x eine Sondierungspermutation

(h(x, 0), h(x, 1), . . . , h(x, m − 1))

der Bucketindizes liefern. Ist der Schlüssel X zufällig, so ist auch

(h(X, 0), h(X, 1), . . . , h(X, m − 1)) (8.47)

eine zufällige Permutation der Bucketindizes {0, 1, . . . , m − 1}. Die Kollisionsbewältigung


beim Einfügen eines Schlüssels besteht nun darin, die Sondierungsfolge von links nach
rechts auf der Suche nach einem freien Bucket für diesen Schlüssel zu durchlaufen. Man
sagt, man sondiere. Man bricht das Sondieren ab, wenn man ein freies Bucket gefunden
hat. In dieses legt man den Schlüssel ab. Um stets ein freies Bucket finden zu können,

72
stellt man sicher, daß der Auslastungsfaktor α immer kleiner 1 ist. Ggf. muß nach der
Verdopplungsstrategie umgespeichert werden.
Sucht man noch einem Schlüssel in der Tabelle, so verfährt man genauso wie beim
Einfügen: Man sondiert von links nach rechts längs der Sondierungsfolge, bis daß man
den Schlüssel gefunden hat (erfolgreiche Suche) oder auf ein leeres Bucket trifft (erfolglose
Suche). Das funktioniert nur, wenn man nicht wirklich streicht. Stattdessen markiert man
Buckets, aus denen der Schlüssel gestrichen wurde, mit deleted. Will man dann einen
Schlüssel einfügen, so verhält sich dieser Eintrag wie ↑. Sucht man nach einem Schlüssel k,
so verhält dieser sich wie ein von k verschiedener Schlüssel.
Die einfachste Ausprägung des geschlossenen Hashings (man sagt auch open addres-
sing) ist das lineare Sondieren. Man verwendet eine Prähashfunktion h : U → [0, m). Die
Sondierungspermutation ist gleich
(h(x) + 0) mod m, (h(x) + 1) mod m, . . . , (h(x) + m − 1) mod m.
Dieses vielangewendete Verfahren hat das die folgenden Vorteile:
– Es ist einfach.
– Es wird kein Platz für Zeiger und leere Plätze im Zeiger-Array verschwendet“.

– Es vermeidet den Zeitaufwand für die Erzeugung von Listenelementen zur Laufzeit.
Wir modifizieren das Beispiel aus Abschnitt 8.3.1 ein wenig. Sei U wiederum gleich der
Menge aller Wörter der Länge λ ∈ [3, ℓmax ] über dem Standardalphabet {a, . . . z, A, . . . Z},
sei m = 13 und sei
num a = num A = 0
num b = num B = 1
......
num z = num Z = 25.
Wir modifizieren die Definition der Hashfunktion aus Gleichung 8.21 wie folgt:
h(b0 b1 b2 . . . bλ−1 ) := (num b2 + 4) mod 13. (8.48)
Fügen wir die Monate Juli, Dezember, Januar, Februar, Juni, September, Maerz, April,
August, Oktober, Mai und November in dieser Reihenfolge in die leere Hashtabelle ein, so
erhalten wir das folgende Bild, wobei die Werte der Prähashfunktion in Klammern stehen.

B0 = {November(12)} B1 =∅ B2 = {Juli(2)}
B3 = {Dezember(3)} B4 = {Januar(4)} B5 = {Februar(5)}
B6 = {Juni(4)} B7 = {September(6)} B8 = {Maerz(8)}
B9 = {April(8)} B10 = {August(10)} B11 = {Oktober(10)}
B12 = {Mai(12)}

73
Das folgende Analyseergebnis, dessen Beweis wir schuldig bleiben, basiert auf der Uni-
formitätsannahme (UF) von Seite 8.3.1.

Satz 8.54 Bei linearem Sondieren in einer Hashtabelle der Größe m mit Auslastungsfaktor
α < 1 gilt unter der Uniformitätsannahme (UF):

1. Die erwartete Anzahl von Schlüsselvergleichen bei erfolgloser Suche (siehe Gleichung
1
8.26) geht bei festem α für m → ∞ gegen 12 (1 + (1−α)2 ).

2. Die erwartete Anzahl von Schlüsselvergleichen bei erfolgreicher Suche nach einem
rein zufälligen Schlüssel aus der Tabelle (siehe Gleichung 8.28) geht bei festem α für
m → ∞ gegen 12 (1 + 1−α 1
).

Die erwartete Anzahl von Schlüsselvergleichen beim linearen Sondieren gemäß Satz 8.54
ist für verschiedene Auslastungsfaktoren α in der folgenden Tabelle dargestellt.
erfolglos“ erfolgreich nach rein zufälligem Schlüssel“
”1 1 ”1 1
α 2
· (1 + (1−α)2) 2
· (1 + 1−α )
0,5 2,5 1,5
0,6 3,625 1,75
0,7 6,06 2,16
0,75 8,5 2,5
0,8 13 3
0,9 50,5 5,5
0,95 200,5 20

Ideales Hashing
Das lineare Sondieren zeigt nicht immer befriedigende Ergebnisse. Wir gehen der Frage
nach, was man im Idealfall erwarten kann. Die für das geschlossene Hashing besten Bedin-
gungen liegen offensichtlich dann vor, wenn für zufällige Schlüssel X die Sondierungspermu-
tation (8.47) eine rein zufällige Permutation der Menge der Bucketindizes {0, 1, . . . , m − 1}
ist. Wir sprechen dann vom uniformen oder idealen Hashing.
Die reine Zufälligkeit der Sondierungspermutation ist zu der folgenden Bedingung gleich-
wertig: Für jede beliebige, aber feste Permutation (b0 , b1 , . . . , bm−1 ) der Bucketindizes und
jedes i = 0, 1, 2, . . . , m − 1 ist
m−i+1
P (h(X, i) = bi | h(X, 0) = b0 , . . . , h(X, i − 1) = bi−1 ) = . (8.49)
m
Für den Beweis des nächsten Satzes brauchen wir die folgende (leicht zu beweisende)
Aussage. Ist Z ∈ {0, 1, 2, . . .} eine zufällige Variable, so ist

X
EZ = P (Z ≥ i) . (8.50)
i=0

74
Satz 8.55 Unter Gleichung 8.49 ist die erwartete Anzahl von Schlüsselvergleichen bei er-
1
folgloser Suche (siehe Gleichung 8.26) nach oben durch 1−α beschränkt.

Beweis. Ist Z die Anzahl der in Rede stehenden Schlüsselvergleiche, und ist Ai das
Ereignis, daß die vermöge der zufälligen Sondierungspermutation (8.47) inspezierten ersten
i Slots alle besetzt sind, dann ist einerseits
P (Z ≥ i) = P (A1 ∩ A2 ∩ . . . ∩ Ai−1 )
und andererseits nach Gleichung 3.18
P (A1 ∩ A2 ∩ . . . ∩ Ai−1 ) = P (A1 ) · P (A2 | A1 ) · . . . · P (Ai−1 | A1 ∩ . . . ∩ Ai−2 ) .
Wegen Gleichung 8.49 ist die rechte Seite der vorstehenden Gleichung gleich
n n−1 n−i+2  n i−1
· ... < = αi−1 .
m m−1 m−i+2 m
Unter Verwendung von Gleichung 8.50 folgt

X 1
EZ < αi = .
i=0
1−α


Ohne Beweis nehmen wir zur Kenntnis.


Satz 8.56 Unter Gleichung 8.49 ist die erwartete Anzahl von Schlüsselvergleichen bei er-
folgreicher Suche nach einem rein zufälligen Schlüssel aus der Tabelle (siehe Gleichung
1
8.28) nach oben durch α1 ln 1−α beschränkt.

Quadratisches Sondieren
Sei h : U → [0, m) gewöhnliche Hashfunktion, eine Prähashfunktion, wie wir indiesem
Abschnitt sagen.
Wird die Sondierungsfolge wie folgt berechnet, sprechen wir vom quadratischen Son-
dieren.
h(x, 0) = h(x)
h(x, 1) = (h(x) + 1) mod m
h(x, 2) = (h(x) − 1) mod m
h(x, 3) = (h(x) + 4) mod m
h(x, 4) = (h(x) − 4) mod m
h(x, 5) = (h(x) + 9) mod m
h(x, 6) = (h(x) − 9) mod m
..
.
  2 
k k+1
h(x, k) = h(x) + · (−1) mod m.
2

75
Man kann mit Methoden der elementaren Zahlentheorie folgendes zeigen:

Lemma 8.57 Ist m Primzahl, m = 4j + 3 für ein j ∈ N, so ist {h(x, k) | 0 ≤ k < m} =


[0, m), d.h. die Sondierungsfolge ist eine Permutation.

Bemerkung. Quadratisches Sondieren verhält sich der Erfahrung nach sehr gut, nicht
viel schlechter als ideales Hashing (h gut verteilend, Auslastungsfaktor ≤ 0,9.)

Doppel-Hashing
Man benutzt zwei (unabhängig berechnete) Hashfunktionen

h1 : U → [0, m)
h2 : U → [0, m − 1)

und setze, für k = 0, 1, 2, . . .:

h(x, k) := (h1 (x) + k · (1 + h2 (x))) mod m

Wiederum kann man mit elementaren Methoden zeigen, daß das folgende Lemma gilt:

Lemma 8.58 Ist m Primzahl, so ist {h(x, k) | 0 ≤ k < m} = [0, m), d.h. die Sondie-
rungsfolge ist eine Permutation.

Bemerkung. Man kann zeigen, daß wenn m prim ist und h1 (X) und h2 (X) rein zufällig
sind, sich Doppel-Hashing ausgezeichnet verhält, fast wie ideales Hashing.

8.4 Vergleichsorientiertes Sortieren


Die Spezifikation von vergleichsorientierten Sortieralgorithmen sieht so aus:

Eingabe: ein Feld A[0, n) paarweise verschiedener ganzer Zahlen, den Schlüsseln.

Ausgabe: das gleiche Feld, dessen Einträge so permutiert wurden, daß nunmehr

A[0] < A[1] < . . . < A[n − 1]

gilt.

Einschränkung. Einem Algorithmus ist es nur erlaubt, aus einem Schlüssel in der Wei-
se Informationen zu gewinnen, daß er ihn mit einem anderen Schlüssel vergleicht.
(Beispielsweise ist die Analyse des Bitmusters eines Schlüssels nicht erlaubt. Man
darf nicht einmal einen Schlüssel aus dem Feld A mit einem nicht zu A gehörigen
Referenzschlüssel vergleichen.)

76
Im Sinne der objektorientierten Programmierung nehmen wir stets an, daß wir eine
Rahmenklasse haben, die als Hauptdatenfeld das zu sortierende Array A hat. Die ver-
gleichsorientierten Sortieralgorithmen, die wir nun besprechen werden, gehören zu dieser
Klasse.
Wie bei den binären Suchbäumen auch schon, kommt es beim vergleichsorientierten
Sortieren nicht auf die absolute Größe der Schlüssel, sondern nur auf deren Verhältnis
untereinander an. Folglich können wir, wenn es um die Analyse geht, stets annehmen, daß
für das Eingabefeld A[0, n)

A[0] = π(0), A[1] = π(1), . . . , A[n − 1] = π(n − 1)

ist. Dabei is π eine Permutation aus der Menge Sn aller Permutationen der Schlüsselmenge
[0, n) := {0, 1, . . . , n−1}. Wir können also, wenn wir es wollen, zu Beginn unserer Rechnung
das Feld A mit der Permutation π identifizieren.
Bei der Analyse eines vergleichsorientierten Sortieralgorithmus A werden wir zwei Kom-
plexitätsmaße im Auge behalten:
Die Anzahl der Schlüsselvergleiche keyCompA π (π ∈ Sn ) ist für uns von besonderer
Bedeutung. Zwar haben wir soeben angenommen, daß es sich bei unseren Schlüsseln
stets um ganze Zahlen handeln soll. Das ist aber nur um der lieben Einfachheit willen
geschehen. Schlüssel können auch Zeichenketten, ja ganze Objekte sein. Zwar soll die
Laufzeit für einen Schlüsselvergleich ein O (1) sein, aber dieser kann deutlich teurer
werden, als beispielsweise eine Addition. Deshalb ist es gerechtfertigt, die Anzahl der
Schlüsselvergleiche separat aufzuführen.

Die Laufzeit timeA π (π ∈ Sn ) wird bei unseren Algorithmen stets ein O (keyCompA π)
sein.

Die in diesem Abschnitt zu studierenden Sortieralgorithmen sollen im Arbeitsspeicher


ablaufen. Dieser ist beschränkt. Folglich sollen unsere Algorithmen zusätzlich zur Eingabe
nur ein geringes Quantum an Speicherplatz benötigen.

Definition 8.59 Ein Sortieralgorithmus arbeitet in situ, wenn er zum Sortieren des Ein-
gabefeldes A[0, n) nur O (log n) zusätzlichen Speicher benötigt.

Man darf bei der Beurteilung des Speicherplatzbedarfs eines Algorithmus den Laufzeit-
stapel nicht vergessen. Dessen Höhe ist insbesondere dann nicht zu vernachlässigen, wenn
es sich um einen rekursiven Algorithmus handelt: Im Rumpf des Algorithmus wird dieser
selbst für ein Teilproblem des zu bearbeitenden Problems aufgerufen.
Der Prozeß, der bei der Ausführung eines rekursiven Algorithmus rekAlg auf eine
Eingabe abläuft, kann man sich gut veranschaulichen, indem man sich den Baum der
rekursiven Aufrufe der Methode rekAlg vor Augen hält.
Wir betrachten ein generisches Beispiel. Angenommen, im Rumpf von rekAlg auf eine
Eingabe der Größe n — wir schreiben dafür rekAlgn — gibt es zwei rekursive Aufrufe

77
rekAlgn1 und rekAlgn2 mit n1 + n2 = n − 1 und n1 ≤ n2 , wobei rekAlgn2 endständig ist:
Der rekursive Aufruf von rekAlgn2 steht unmittelbar vor dem Ende der rufenden Routine.
Ansonsten gibt es im Rumpf von rekAlg keine Methodenaufrufe.
Der Baum Tn der rekursiven Aufrufe von rekAlg auf eine Eingabe der Größe n ist in
Gleichung 8.51 dargestellt.

n
 
n1 n2
Tn = (8.51)
   
n11 n12 n21 n22
... ... ... ... ...... ... ... ... ...

Da bei jedem Aufruf von rekAlg ein Inkarnationsblatt auf den Laufzeitstapel gelegt wird,
ist dessen maximale Höhe gleich der Tiefe des Baumes.
Ist n1 = ⌊(n − 1)/2⌋ und n2 = ⌈(n − 1)/2⌉, so ist depth Tn = O (log n). Diese Tiefe ist
für uns akzeptabel.
Leider können wir nicht immer voraussetzen, daß die Eingabe schön halbiert wird. Ist
im Extremfall n1 = 0 und n2 = n − 1, so gilt depth Tn = O (n). Wenn wir einen am Platze
(Definition 8.59) arbeitenden Algorithmus im Auge haben, ist das zuviel.
Der Ausweg ist überraschend einfach. Wir haben gesagt, die größere Rekursion sei
endständig. Das heißt zur Laufzeit, daß unmittelbar nachdem der Rahmen für rekAlgn2
vom Laufzeitstapel entfernt worden ist, auch der Frame von rekAlgn beseitigt wird. Da
man nach dem Ende von rekAlgn2 keinerlei Vorteil mehr aus der Existenz des Rahmens
von rekAlgn zieht, kann er schon bei Aufruf von rekAlgn2 vom Laufzeitstapel entfernt
werden. Wir sprechen von der Beseitigung der Endrekursion.
Die Umsetzung dieser Idee scheint ein dynamisches Problem zu sein, das zur Lauf-
zeit gelöst werden muß. Doch der Schein trügt. Die Erkennung der Endständigkeit eines
rekursiven Aufrufs und die Beseitigung der Endrekursion kann schon zur Compile–Zeit
vorgenommen werden. Ein Compiler, der soetwas leistet, heißt endrekursiv.
Wenn wir unterstellen, daß bei der Abarbeitung von rekAlg Endrekursionen beseitigt
werden, so ist es um die Höhe des Laufzeitstapels bei der Abarbeitung von rekAlgn besser
bestellt.
Lemma 8.60 Unter den genannten Voraussetzungen ist die maximale Höhe des Laufzeit-
stapels bei Abarbeitung von rekAlgn ein O (log n).

Beweis. Für einen Pfad π in Tn sei |π|, wie üblich, dessen Länge.
Die maximale Höhe des Laufzeitstapel ist

max (|π| − |{v | Der Knoten v des Pfades π gehört zu einer Endrekursion}|) ,
π ist Pfad in Tn

denn wir müssen für jeden Pfad nur diejenigen Knoten berücksichtigen, die zu den nicht-
endständigen Rekursionen gehören. Sie bearbeiten die kleinere Hälfte“. Wir rekapitulieren

78
Gleichung 1.3 aus Aussage 1.11 für den Fall b = 2:

⌊log2 n⌋ = min{i | ⌊n/2i⌋ = 1}.

Nun folgt die Behauptung. 

8.4.1 Quicksort
Gute Darstellungen von Quicksort finden sich in [MR95] und [CLRS01].
Alle Quicksort–Algorithmen beruhen auf einer rekursiven Methode
quicksort(left, right),
die als Vorbedingung die Ungleichungskette 0 ≤ left ≤ right ≤ n hat. Die Methode
permutiert die Schlüssel des Teilfeldes A[left, right). Sie garantiert (Nachbedingung),
daß dieses Teilfeld nach ihrem Ende aufsteigend sortiert ist.
Es gibt mehrere Varianten von Quicksort. Der grundsätzliche Aufbau ist bei allen gleich:
Teile das Teilfeld A[left, right) durch einen Pivotindex π ∈ [left, right) in zwei Teile
A[left, π) und A[π + 1, right). Sichere bei der Teilung, die den Pivotindex in der
Regel modifizieren wird, daß sich das Pivotelement p = A[π] nach Abschluß der
Teilung an seinem endgültigen Platz befindet, und A[i] ≤ A[π] ≤ A[j] für alle i <
π < j gilt.

Beherrsche das Gesamtproblem durch rekursives Sortieren der Felder A[left, π) und
A[π + 1, right). (Aufwand für das Zusammensetzen fällt offenbar nicht an.)
Es gibt mehrere Strategien, den Pivotindex zu Beginn des Teilungsprozesses zu initia-
lisieren:
Die einfache deterministische Strategie. Setze zum Beispiel
 
left + right
π← .
2

(Jede andere Regel leistet den gleichen Dienst.)

Die einfache randomisierte Strategie. Wähle π zufällig und mit gleicher Wahrschein-
lichkeit aus dem halboffenen Intervall [left, right).

Verfeinerte deterministische oder randomisierte Strategien. Wähle drei oder auch


fünf Indexkandidaten aus [left, right). Nimm denjenigen Index unter den drei (fünf)
Kandidaten als Pivotindex, dessen Feldeintrag der Median der drei (fünf) zugehörigen
Feldeinträge ist.

Algorithmus 8.61 (Generisches Quicksort)

79
Methodenkopf:
quicksort left, right)
Großschritt 1. [Basis]
Falls right − left ≤ 1, so führe aus: return.
Großschritt 2. [Teilung]
π ← initialer Pivotindex nach einer der genannten Strategien
p ← A[π]
– Initialisierung der Grenzen des Suchintervalls für die Vertauschungsindizes
λ ← left, ρ ← right − 1
Führe aus
Großschritt 2.1. [Bestimmung der Vertauschungsindizes im Suchintervall]
λλ ← min{j ∈ [λ, ρ] | A[j] ≥ p}
ρρ ← max{j ∈ [λ, ρ] | A[j] ≤ p}
Großschritt 2.2. [Vertauschung und Aktualisierung des Pivotindex]
Vertausche die Feldeinträge A[λλ] und A[ρρ] miteinander.
//Falls das Pivot soeben nach rechts bewegt wurde, führe seinen Index nach:
Falls π = λλ, so π ← ρρ
//Falls das Pivot soeben nach links bewegt wurde, führe seinen Index nach:
Falls π = ρρ, so π ← λλ
Großschritt 2.3. [Aktualisierung der Grenzen des Suchintervalls]
Falls λλ < π < ρρ, so λ ← λλ + 1, ρ ← ρρ − 1
Falls λλ = π < ρρ, so λ ← λλ, ρ ← ρρ − 1
Falls λλ < π = ρρ, so λ ← λλ + 1, ρ ← ρρ
Falls λλ = π = ρρ, so λ ← λλ, ρ ← ρρ
bis daß ρ − λ = 0 ist.
Großschritt 3. [Rekursion.]
Falls π − left < right − π so führe aus.
left1 ← left, right1 ← π
left2 ← π + 1, right2 ← right
Anderfalls führe aus:
left1 ← π + 1, right1 ← right
left2 ← left, right2 ← π
quicksort(left1 , right1 )
quicksort(left2 , right2 )

Bemerkung zu Großschritt 2.1. Die Berechnung der Vertauschungsindizes λλ und ρρ


erfolgt, indem man einen Zeiger j von links nach rechts bzw. rechts nach links über das
abgeschlossene Suchintervall [λ, ρ] laufen läßt und den entsprechenden Index durch Ver-
gleich von A[j] mit p sucht. Dabei achtet man durch Indexvergleich mit π darauf, daß das
Pivotelement nicht mit sich selbst verglichen wird. Beide Suchen sind erfolgreich, da der
Pivotindex π immer zum Suchintervall gehört (siehe Lemma 8.62). Daraus folgt für die
berechneten Vertauschungsindizes λλ und ρρ insbesondere λλ ≤ ρρ.

80
Beispiel. Sei n = 13, π = 6, p = 53, left = 0, right = 13. Am Anfang ist λ = 0 und
ρ = 12. Im folgenden bezeichnet ↓ die Position des Pivotelements, ⊲ ist der Zeiger left
und ⊳ der Zeiger right − 1. Ein Feldelement a, auf das der Zeiger λλ oder der Zeiger ρρ
verweist, ist eingerahmt: a . Verweisen sowohl λλ als auch ρρ auf a, zeigen wir das so an:
a . In jeder Zeile von (8.52) sehen wir den Zustand des Feldes und der genannten Zeiger
unmittelbar vor Großschritt 2.2.

⊲ ↓ ⊳
15 47 33 87 98 17 53 76 82 2 52 27 44
⊲ ↓ ⊳
15 47 33 44 98 17 53 76 82 2 52 27 87
⊲ ↓ ⊳
15 47 33 44 27 17 53 76 82 2 52 98 87
⊲ ↓⊳
15 47 33 44 27 17 52 76 82 2 53 98 87 (8.52)
⊲↓ ⊳
15 47 33 44 27 17 52 53 82 2 76 98 87
⊲ ↓⊳
15 47 33 44 27 17 52 2 82 53 76 98 87
⊲↓⊳
15 47 33 44 27 17 52 2 53 82 76 98 87

Wir erinnern uns daran, daß eine Schleifeninvariante eine logische Aussage ist, die vor
der ersten und nach jeder folgenden Iteration des Rumpfes der in Rede stehenden Schleife
den Wahrheitswert true hat.
Lemma 8.62 Algorithmus 8.61 arbeitet korrekt.
Beweis. Sei p das Pivotelement und π der Pivotindex. Entscheidend ist, daß die folgende,
aus drei Klauseln bestehende Bedingung
Klausel 1. Für alle Indizes j links“ vom Suchintervall ist das zugehörige Feldelement

kleiner als das Pivot:
∀j (j < λ ⇒ A[j] < p) .
Klausel 2. Für alle Indizes j rechts“ vom Suchintervall ist das zugehörige Feldelement

größer als das Pivot:
∀j (j > ρ ⇒ A[j] > p) .
Klausel 3. Der Pivotindex π gehört zum Suchintervall:
π ∈ [λ, ρ].
eine Invariante der Schleife innerhalb von Großschritt 2, dem Teilungsschritt, ist. 

81
Bemerkung zur Laufzeit
Eine einfache Inspektion von Algorithmus 8.61 ergibt, daß für jede der bereits erwähnten
und noch zu studierenden Quicksort-Varianten A und jede Permutation σ der Schlüssel-
menge [0, n) gilt:

timeA σ = O (keyCompA σ) . (8.53)

Wir begnügen uns im folgenden mit der Analyse der Anzahl der Schlüsselvergleiche.

Analyse der einfachen deterministischen Variante


Die einfache deterministische Variante von Quicksort — simple quicksort (sq) — erhält
man aus Algorithmus 8.61, indem man den ersten Schritt des Großschritts 2 z.B. durch
 
left + right
π←
2
konkretisiert.
Wie bei den binären Suchbäumen auch schon, können wir bei der Analyse von Quicksort
annehmen, daß die Schlüsselmenge gleich {0, 1, . . . , n − 1} ist. Für jede Permutation π aus
der Menge Sn aller Permutationen der Schlüsselmenge {0, 1, . . . , n − 1} sei

Q(π(0), π(1), . . . , π(n − 1)) = Q(π)

die Anzahl der Schlüsselvergleiche von sq auf die Eingabe A[0] = π(0), A[1] = π(1), . . . , A[n−
1] = π(n − 1).
Die mittlere Anzahl von Schlüsselvergleichen auf Eingaben der Länge n ist wie folgt
definiert:
1 X
Q̄(n) := Q(π)
n! π∈S
n

Herzstück der Analyse von sq sind Lemma 8.63 und Lemma 8.64, deren Beweise denen
von Lemma 8.30 und Lemma 8.31 aus dem Abschnitt 8.2.6 für binäre Suchbäume so ähnlich
sind, daß wir sie hier weglassen wollen. (Die Ähnlichkeit ist kein Zufall. Ein Lauf von
Algorithmus 8.61 baut implizit einen binären Suchbaum auf.)

Lemma 8.63 Für die Funktion Q̄(n) gilt die folgende Rekursion.
(
0 falls n ≤ 1;
Q̄(n) = 2
Pn−1
(n − 1) + n i=0 Q̄(i) andernfalls.

Lemma 8.64 Für die Funktion Q̄(n) gilt.

Q̄(n) = 2 ln 2 · n log2 n − Θ(n)

82
Wir erhalten.

Satz 8.65 Die mittlere Anzahl von Schlüsselvergleichen von sq auf Eingabefelder der Länge
n ist gleich

ln 2} ·n log2 n − Θ(n).
2| {z
=1,386...

Die Laufzeit im schlechtesten Fall auf Eingaben der Länge n ist ein Ω(n2 ).

Bemerkung zu verfeinerten deterministischen Varianten


Wählt man drei Pivotindex-Kandidaten aus und entscheidet sich dann für denjenigen In-
dex, dessen zugehöriger Feldeintrag der Median der drei Einträge ist, so erhält man als
mittlere Laufzeit auf auf Eingabefelder der Länge n

1, 188 . . . · n log2 (n − 1) − Θ(n).

Die Laufzeit im schlechtesten Fall auf Eingaben der Länge n bleibt ein Ω(n2 ).

Analyse der einfachen randomisierten Variante


Die einfache randomisierte Variante von Quicksort — simple randomized quicksort (srq) —
erhält man aus Algorithmus 8.61, indem man den ersten Schritt des Großschritts 2 durch
 
π ← gleichverteiltes Zufallselement aus left, right

konkretisiert.
Für jede Permutation σ der Schlüsselmenge {0, 1, . . . , n − 1} wird die Anzahl der
Schlüsselvergleiche zur Zufallsvariablen

keyCompsrq σ ∈ {1, 2, . . . , n2 }.

Satz 8.66 Für alle Permutationen σ der Schlüsselmenge {0, 1, . . . , n − 1} ist



ln 2} ·n log2 n − Ω(n).
E keyCompsrq σ = |2 {z (8.54)
=1,38...

Beweis. Sei σ eine beliebige, aber feste Permutation der Schlüsselmenge {0, 1, . . . , n−1}.
Wir beschreiben keyCompsrq σ als Summe von Bernoulli-Variablen. Für jedes Paar (i, j)
von Schlüsseln aus {0, 1, . . . , n − 1} mit i < j sei
(
1 i wird mit j während einer Rechnung auf σ verglichen;
Xij =
0 andernfalls.

83
Es ist
X
keyCompsrq σ = Xij .
0≤i<j<n

Nach dem Satz über die Linearität der Erwartung (Satz 3.5) müssen wir uns für 0 ≤ i <
j < n um die Erwartungswerte E Xij kümmern.
Ein Lauf von srq auf eine Eingabe σ ist ein zufälliger Prozeß, bei dem ein zufälliger
Suchbaum entsteht, dessen Teilbäume die zufällig ausgewählten Pivots in ihren Wurzeln
tragen. Wir numerieren die Knoten dieses zufälligen Suchbaums in Level-Ordnung von
von 0 bis n − 1 durch: Beginnend mit der Wurzel, werden die Ordnungszahlen von oben
nach unten und auf jedem Tiefenniveau von links nach rechts vergeben. (Die Knoten des
Suchbaumes aus Abbildung 8.1 sind in Level-Ordnung numeriert, allerdings mit dem Index
eins beginnend.) Sei Z0 , Z1 , . . . , Zn−1 diejenige Folge zufälliger Schlüssel, die man aus der
Folge der Knoten in Level-Ordnung erhält, wenn man jeden Knoten durch den Schlüssel
ersetzt, mit dem er markiert ist.
Nun fixieren wir ein Paar (i, j) von Schlüsseln mit i < j und definieren eine zufällige
Ordnungszahl S := S(i, j), eine sogenannte Stoppzeit, für den Zufallsprozeß Z0 , Z1 , . . . , Zn−1.
Ob S einen Wert ℓ ∈ N annimmt, hängt nur von Z0 , Z1 , . . . , Zℓ ab:

S = ℓ ⇐⇒ Z0 6∈ [i, j], Z1 6∈ [i, j], . . . , Zℓ−1 6∈ [i, j], Zℓ ∈ [i, j]. (8.55)

Die Zufallsvariable S ist die erste Ordnungszahl eines Knotens unseres zufälligen Such-
baums, der einen Schlüssel aus [i, j] trägt. Es ist klar, daß

S ∈ {0, 1, . . . , n − (j − i)}

ist.
Nun betrachten wir den zufälligen Schlüssel ZS , das sogenannte gestoppte Ereignis.
ZS ist der erste Schlüssel aus [i, j], den man erhält, wenn man die Knoten des zufälligen
Suchbaums in Level-Ordnung aufreiht. Insbesondere folgen alle Schlüssel aus dem Intervall
[i, j] in dem zufälligen Suchbaum dem Weg von der Quelle bis zu dem Knoten, der mit ZS
markiert ist. Genau an diesem Knoten wird das Intervall aufgeteilt. Folglich ist

Xij = 1 ⇐⇒ ZS ∈ {i, j}, (8.56)

denn ist ZS = i oder ZS = j, so werden bei der Aufteilung des zu sortierenden Teilintervalls
i und j mit einander verglichen. Anderfalls werden i und j mit einem Wert k aus dem
offenen Intervall (i, j) verglichen. Ihre Wege trennen sich wegen i < k < j, und es kann zu
keinem Vergleich zwischen ihnen mehr kommen.
Nun zeigen wir, daß für jedes k aus [i, j]
1
P (ZS = k) = (8.57)
j−i+1
ist. Wir behaupten also, ZS sei auf [i, j] gleichverteilt.

84
Bei

{S = ℓ} (ℓ = 0, 1, . . . , n − j + i)

handelt es sich um ein vollständiges System von Ereignissen: Es muß genau eines eintreten.
Folglich genügt es für den Nachweis von Gleichung 8.57 nach Lemma 3.4, die folgende
Gleichung zu beweisen:

1
P (ZS = k | S = ℓ ) = . (8.58)
j−i+1

Aus der Definitiongleichung 8.55 von S folgt:

P (ZS = k | S = ℓ ) = P (Zℓ = k | Z0 6∈ [i, j], Z1 6∈ [i, j], . . . , Zℓ−1 6∈ [i, j], Zℓ ∈ [i, j])
(8.59)

Die Teilbedingung Z0 6∈ [i, j], . . . , Zℓ−1 6∈ [i, j]“ auf der rechten Seite von Gleichung 8.59

bedeutet, daß das aktuelle Teilintervall [left, right), aus dem Zℓ zufällig und mit gleicher
Wahrscheinlichkeit gemäß dem Algorithmus gezogen wird, [i, j] enthält. Die Teilbedingung
Z ∈ [i, j]“ heißt, daß zum Zeitpunkt ℓ ein Schlüssel aus [i, j] ausgewürfelt wird. Aus
” ℓ
Gleichung 3.21 erhalten wir nun wie folgt Gleichung 8.58:

1 j−i+1 1
P (ZS = k | S = ℓ ) = =
right − left right − left j−i+1

Aus den Gleichungen 8.56 und 8.57 folgt

2
E Xij = P (Xij = 1) = .
j −i+1

Wir erhalten.
X X X 1
E Xij = E Xij = 2 ·
i<j i<j i<j
j−i+1
| {z }
Vermöge Gleichung 2.5
definierte Summe.

Diese Summe haben wir bereits im Lemma 2.5 des Abschnitts 2.1 analysiert:

≤ 2 · n ln n − 2 · (n − ln n − 1) (Gleichung 2.6)
= 2 ln 2 · n log2 n − Ω(n).

85
Bemerkung zu verfeinerten randomisierten Varianten
Wählt man drei Pivotindex-Kandidaten zufällig aus und entscheidet sich dann für denjeni-
gen Index, dessen zugehöriger Feldeintrag der Median der drei Einträge ist, so erhält man
für jede Eingabe der Länge n
1, 188 . . . · n log2 (n − 1) − Ω(n) (8.60)
als obere Schranke für die erwartete Laufzeit.

8.4.2 Heapsort
In diesem Abschnitt studieren wir einen Sortieralgorithmus, dessen Laufzeit im schlechte-
sten Fall ein O (n · log n) ist. Er beruht auf dem Heap als grundlegender Datenstruktur.
Obwohl eine gute Implementation von Quicksort auch die schnelleren Varianten von Heap-
sort für gewöhnlich schlägt, ist Heapsort sehr interessant. Das ist inbesondere deshalb der
Fall, weil der Heap über die Sortieralgorithmen hinaus eine sehr nützliche Datenstruktur
ist.
Ein nichtleerer Heap T (mit Knotenmenge V (T )) über Z und R ist ein geordneter
binärer Wurzelbaum, zu dem Markierungsfunktionen der Knotenmenge
key : V (T ) −→ Z
data : V (T ) −→ R
gehören. Die Markierungsfunktion key erfüllt die Heapbedingung: Für je zwei Knoten (v, v ′),
wobei v der Vater von v ′ ist, gilt
key(v ′ ) ≤ key(v). (8.61)
Wie im Falle von Quicksort auch schon, beschränken wir uns bei der Besprechung der heap-
basierten Sortierverfahren auf den Fall, daß die Schlüssel paarweise verschieden sind, ob-
wohl andernfalls alles beim Alten bliebe. Bei späteren Anwendungen des Heaps als Daten-
struktur (siehe Abschnitte 8.4.4 und 10.3.2) kommen Heaps vor, bei denen die Schlüssel“,

die dann Prioritäten heißen, zu einem Zeitpunkt mehrfach vorkommen.
Ein Beispiel für einen Heap zeigt Abbildung 8.5, wobei wir natürlich auf die Satelliten-
daten verzichtet haben. Die Knoten sind durchnumeriert. Es handelt sich um die Level-
ordnung oder auch Niveauordnung, die wir bereits in Abschnitt 8.4.1 bei der Analyse des
einfachen randomisierten Quicksort verwendet haben.
Wir fordern von einem Heap zusätzlich, daß er nahezu vollständig ist. Alle Tiefenniveaus
bis auf das letzte sind vollständig. Was dieses angeht, so ist es von links nach rechts bis zu
einer bestimmten Stelle vollständig.
Wollten wir Heaps so implementieren, wie wir es mit binären Suchbäumen getan haben,
so würden wir die Laufzeit unnötig vergrößern. Da Heaps nahezu vollständig sind, können
wir sie als Felder
A[0, n) of Integer (8.62)

86
9 0 Niveau 0

1 8 5 2 Niveau 1

3 7 4 6 5 4 6 1 Niveau 2

7 3 8 2 9 4 Niveau 3

Abbildung 8.5: Beispiel für einen Heap

implementieren. Die Knoten werden durch ihren Index in der Niveauordnung dargestellt.
Die Schlüssel, die sie halten, sind die Feldeinträge. (Der Knoten aus Abbildung 8.5, der
den Schlüssel 8 trägt, wird beispielsweise durch den Index 1 repräsentiert.) Wir werden im
weiteren das Feld A aus (8.62) als Heap A (mit n Knoten) bezeichnen. Um die Heapgröße
zu dynamisieren, führen wir in der Rahmenklasse, die A als Datenfeld hat, zusätzlich ein
Datenfeld heapsize ein, das anzeigt, bis zu welchem Index der aktuelle Heap reicht:

A[0, heapsize)

ist der aktuelle Heap. Die Feldgröße n ist die maximale Größe des Heaps. Alle Methoden,
die nun folgen, gehören zu dieser Rahmenklasse.
Diese Art der Implementation hat das folgende Lemma zur Grundlage.

Lemma 8.67 Sei A ein Heap mit n Knoten, und sei i ∈ {0, 1, . . . , n − 1} ein Knoten von
A. Dann gilt.

1. Der Knoten i hat genau dann keinen Sohn in A, wenn 2i + 1 ≥ n ist.

2. Der Knoten i hat genau einen Sohn in A, wenn 2i + 1 = n − 1 ist.

3. Der Knoten i hat genau zwei Söhne in A, wenn 2i + 2 ≤ n − 1 ist.

4. Es gilt:

(a) Falls der linke Sohn von Knoten i existiert, so ist es 2i + 1.


(b) Falls der rechte Sohn von Knoten i existiert, so ist es 2i + 2.
(c) Falls Knoten i nicht die Wurzel des Heaps ist (i > 0), so ist der Vater von i
gleich ⌊(i − 1)/2⌋.

87
5. depth A = ⌊log2 n⌋.

Beweisskizze. Alle Aussagen unseres Lemmas folgen mehr oder minder aus der folgen-
den sehr einfachen Überlegung:
Ist das d−1-Tiefenniveau vollständig vorhanden, so handelt es sich dabei um die Knoten
2 − 1, 2d−1 , . . ., 2d−1 + (k − 1), . . ., 2d − 2. Deren Söhne, soweit vorhanden, sind wie folgt
d−1

angeordnet:
2d−1 − 1 ... 2d−1 + (k − 1) ... 2d − 2
2d − 1 2d . . . 2d + 2k − 1 2d + 2k . . . 2d+1 − 3 2d+1 − 2

Die Abbildungen 8.6, 8.7, 8.8, 8.9, 8.10 und 8.11 zeigen die Arbeitsweise von Standard-
heapsort auf die Eingabe A[0, 7) = (5, 6, 3, 4, 1, 0, 2).

5 6 Vertauschung

6 3 5 3

4 1 0 2 4 1 0 2

buildHeap[]

8 Vergleiche heapsize

Abbildung 8.6: Der Aufbau des Heaps

2 5 Vertauschung

5 3 4 3

4 1 0 6 2 1 0 6

heapsize heapsize

4 Vergleiche

Abbildung 8.7: Die Auswahlphase: Schritt 1

Abbildung 8.6 zeigt die Wirkung von Algorithmus 8.72 zur Heaperzeugung auf A[0, 7):
Das Eingabefeld aufgefaßt als Baum ist noch kein Heap. Dieser muß erst aufgebaut werden.
Die darauf folgenden Schritte 8.7, 8.8, 8.9, 8.10 und 8.11 — sie zeigen den Zustand
des Heaps in der solange-Schleife von Algorithmus 8.76 unmittelbar vor und unmittelbar

88
0 4 Vertauschung

4 3 2 3

2 1 5 6 0 1 5 6

heapsize heapsize

4 Vergleiche

Abbildung 8.8: Die Auswahlphasephase: Schritt 2

Vertauschung
1 3

2 3 2 1

0 4 5 6 0 4 5 6

heapsize heapsize

2 Vergleiche

Abbildung 8.9: Die Auswahlphase: Schritt 3

nach der Ausführung der Instruktion reheap(0, heapsize) — verlaufen alle nach demsel-
ben Muster: Die Schlüssel A[0] und A[heapsize − 1] tauschen die Plätze. Anschließend ist
der Heap an der Wurzel gestört. Die Methode reheap (Algorithmus 8.68) stellt die Heapei-
genschaft wieder her. Das geschieht auf die folgende Weise: Der Schlüssel an der Wurzel ist
möglicherweise zu klein. Man läßt ihn an seinen Platz sickern“. Dazu wird das Maximum

der Schlüssel der beiden Söhne der Wurzel mit dem Schlüssel verglichen, den diese trägt.
Ist der maximale Sohn“ größer als der Vater, so tauschen beide die Schlüssel und der Pro-

zeß iteriert mit dem maximalen Sohn als neuem Vater. Anderfalls ist die Heapeigenschaft
wiederhergestellt.
Um auch etwas formaler noch vernünftig arbeiten zu können, sei zunächst A[0, ℓ) (ℓ ≤ n)
derjenige Teilbaum von A[0, n), aus dem wir alle Knoten i ≥ ℓ entfernt haben. Sei k ein
Knoten von A[0, ℓ). Die Bedingung HB(k, ℓ) hat zwei Klauseln:

Klausel 1. Wenn 2k + 1 < ℓ ist, so ist A[k] ≥ A[2k + 1].

Klausel 2. Wenn 2k + 2 < ℓ ist, so ist A[k] ≥ A[2k + 2].

Die Notation  HB(k, ℓ) bedeutet, daß beide Klauseln von HB(k, ℓ) wahr sind: Am
Knoten k von A[0, ℓ) ist die Heapbedingung (8.61) erfüllt.

89
Vertauschung
0 2

2 1 0 1

3 4 5 6 3 4 5 6

2 Vergleiche

Abbildung 8.10: Die Auswahlphase: Schritt 4

1 0

0 2 1 2

3 4 5 6 3 4 5 6

1 Vergleich

Abbildung 8.11: Die Auswahlphase: Schritt 5

Folglich ist A[0, ℓ) genau dann ein Heap, wenn für alle Knoten gilt: k < ℓ  HB(k, ℓ).

Algorithmus 8.68 (Wiederherstellung des Heaps)


Methodenkopf:
reheap(k, ℓ)
Vorbedingung:
Für alle Nachfahren k ′ von k in A[0, ℓ) gilt  HB(k ′ , ℓ).
Nachbedingung:
Ist k ′ gleich k oder ein Nachfahre von k in A[0, ℓ), so ist  HB(k ′ , ℓ).
Großschritt 1. [Basis.]
1.1. [Knoten k ist das Sickerziel“, weil er keinen Sohn in A[0, ℓ) hat.]

Falls (2k + 1) ≥ ℓ, so
return
1.2. [Berechnung des maximalen Sohns von k in A[0, ℓ).]
Falls (2k + 1) = ℓ − 1, so führe aus:
maxson ← 2k + 1
Andernfalls führe aus:
Falls A[2k + 1] > A[2k + 2], so maxson ← 2k + 1
Andernfalls maxson ← 2k + 2

90
1.3. [Knoten k ist das Sickerziel“, obwohl er einen Sohn in A[0, ℓ) hat.]

Falls A[k] ≥ A[maxson], so
return
Großschritt 2. [Rekursion]
A[k] ⇄ A[maxson]
reheap(maxson, ℓ)

Bemerkung. Wir haben Algorithmus 8.68 der größeren Übersichtlichkeit halber rekur-
siv aufgeschrieben. Wenn es wirklich darauf ankommt, koste es, was es wolle, mit jedem
Maschinenbefehl zu sparen, sollte man der iterativen Variante, bei der die Folge der rekur-
siven Aufrufe durch eine Schleife ersetzt wird, den Vorzug geben.
Der Beweis des folgenden Lemmas durch vollständige Induktion über die Anzahl der
Knoten von A[0, ℓ) ist eine leichte Übungsaufgabe.

Lemma 8.69 Algorithmus 8.68 arbeitet korrekt.

Wir kommen zur Anzahl der Schlüsselvergleiche und damit zur Laufzeit von Algorith-
mus 8.68.
 ℓ

Lemma 8.70 Es ist keyComp(reheap(k, ℓ)) = 2 · log2 k+1 .

Beweis. Die Inspektion des Pseudocodes von Algorithmus 8.68 zeigt für den Aufruf
von reheap(k, ℓ) folgendes. Sickert“ der Schlüssel A[k] bis zu einem Blatt durch, wobei

stets der linke Sohn maximal ist, liegt ein schlechtester Fall vor. Der Abbruch erfolgt in
Großschritt 1.1. Bei diesem letzten Aufruf kommt es zu keinem Schlüsselvergleich mehr.
Wie groß ist in diesem schlechtesten Fall die Anzahl r0 der rekursiven Aufrufe ein-
schließlich des ersten, bei denen es zu Schlüsselvergleichen kommt? Es sind genau jene, bei
denen Großschritt 1.1 nicht zum Abbruch führt. Folglich gilt:
 
 
r r−1 r−2
r0 = max r | 2 k + |2 + 2 {z + . . . + 1} ≤ ℓ − 1
 
2r −1
 
r r ℓ
= max {r | 2 (k + 1) ≤ ℓ} = max r | 2 ≤
k+1

Aus der Gleichung 1.3 erhalten wir


 

r0 = log2 .
k+1
Da es in jedem dieser r0 Aufrufe zu höchstens zwei Vergleichen kommt, folgt die Behaup-
tung. 



Korollar 8.71 Es ist time(reheap(k, ℓ)) = O log k+1 .

91
Nun ist klar, wie man Algorithmus 8.68 einsetzen kann, um den Heap am Anfang
aufzubauen: Man wendet ihn in reverser Level-Ordnung, beginnend mit dem vorletzten
Tiefenniveau, von unten nach oben an. (Der erste Knoten, für den etwas zu tun ist, ist
dann der Vater ⌊(n − 2)/2⌋ = ⌊n/2⌋ − 1 des letzten Knotens n − 1.) Das sichert stets die
Gültigkeit der Vorbedingung.

Algorithmus 8.72 (Aufbau des Heaps)


Methodenkopf:
buildheap()
Rumpf:
Für j = ⌊n/2⌋ − 1, . . . , 0 führe aus.
reheap(j, n)
Über den Beweis von Lemma 8.73 läßt sich dasselbe sagen wie über den Beweis von
Lemma 8.69.
Lemma 8.73 Algorithmus 8.72 arbeitet korrekt.
Wir kommen zur Anzahl der Schlüsselvergleiche und damit zur Laufzeit von Algorith-
mus 8.72.
Lemma 8.74 Es ist keyComp(buildheap()) < 2 · n.

Beweis. Nach Lemma 8.70 läßt sich die Anzahl der Schlüsselvergleiche von buildheap()
wie folgt nach oben beschränken.
⌊n/2⌋−1   ⌊n/2⌋ j
X n X nk
keyComp(buildheap()) ≤ 2 log2 =2 log2
k+1 k
k=0 k=1
⌊log2 n⌋ n j
X n ko
=2 d · card k | d = log2
d=1
k
⌊log2 n⌋ n o
X n
=2 d · card k | d ≤ log2 < d + 1
d=1
k
⌊log2 n⌋ n
X no
=2 card k | d ≤ log2
d=1
k
⌊log2 n⌋
X 
=2 card k | 2d k ≤ n
d=1
| {z }
≤n/2d
⌊log2 n⌋
X
≤2 n/2d < 2 · n.
d=1


92
Korollar 8.75 Es ist time(buildheap()) = O (n) .

Nun sind wir in der Lage, den Standard-Heapsortalgorithmus aufzuschreiben.

Algorithmus 8.76 (Standard-Heapsort)

Methodenkopf:
heapsort()
Nachbedingung:
Das Eingabefeld A[0, n) ist aufsteigend sortiert.
Großschritt 0.
Falls n ≤ 1, so return.
Großschritt 1. [Aufbauphase des Heaps.]
buildheap()
Großschritt 2. [Auswahlphase.]
heapsize ← n
Solange heapsize ≥ 2 führe aus.
A[0] ⇄ A[heapsize − 1]
heapsize ← heapsize − 1
reheap(0, heapsize)

Aus Lemma 8.69 und Lemma 8.73 folgt sofort die Korrektheit von Algorithmus 8.76.
Lemma 8.70, Lemma 8.74, Korollar 8.71 und Korollar 8.75 führen unmittelbar zu dem
folgenden Satz über die Laufzeit von Standard-Heapsort.

Satz 8.77 Die Anzahl der Schlüsselvergleiche von Algorithmus 8.76 auf jede Eingabe der
Länge n ist durch 2n (⌊log2 n⌋ + 1) nach oben beschränkt.
Die Laufzeit ist ein O (n log n) .

8.4.3 Bottom-Up Heapsort


Selbst die einfachste Variante von Quicksort schlägt Algorithmus 8.76 für eine zufällige
Eingabe mit hoher Wahrscheinlichkeit. Der Grund dafür liegt darin, daß die Konstante
des führenden Terms der Schlüsselvergleichsanalyse für Quicksort (siehe Satz 8.65) mit
1, 386 . . . deutlich niedriger ist als bei Standard-Heapsort (siehe Satz 8.77). Will man Ab-
hilfe schaffen, kommt nur eine Verbesserung der Methode reheap für die Anwendung in
Großschritt 2 von Algorithmus 8.76 in Betracht. (Der Einsatz der verbesserten Variante in
Großschritt 1 lohnt sich kaum.) Es kommt darauf an, die Anzahl der Vergleiche je Sicker-

schritt“ zu veringern. Wir können uns auf den Fall beschränken, daß in A[0, heapsize) die
Heapbedingung nur an der Wurzel gestört ist. Die folgenden zwei Beobachtungen sind für
unsere Verbesserungsbemühungen maßgebend:

93
1. Eine gründliche Analyse dessen, was in Algorithmus 8.76 geschieht, zeigt, daß der
Pfad, längs dessen ein zu kleines Element von der Wurzel aus versickert, von diesem
unabhängig ist und vorab berechnet werden kann: Man muß einen Pfad maximaler
Söhne nehmen.

2. Wir wissen, daß mehr als die Hälfte der Knoten eines vollständigen binären Baumes
Blätter sind, mehr als 75% in den untersten beiden Niveaus liegen usw. Deshalb
wird es sehr häufig vorkommen, daß der Schlüssel der Wurzel bis fast ganz nach
unten versickern wird. Dann aber ist es vernünftig, den Wurzelschlüssel von unten
an seinen Platz aufsteigen zu lassen.

Definition 8.78 Sei A[0, m) ein höchstens an der Wurzel gestörter Heap.
Eine Folge (b0 , b1 , b2 , . . . , bλ ) von Knoten aus A heißt Pfad maximaler Söhne in A, wenn
1. Knoten b0 gleich der Wurzel 0 ist;

2. für jedes j = 1, 2, . . . , λ der Knoten bj ein maximaler Sohn des Knotens bj−1 ist:
(
argmax{A[2bj−1 + 1], A[2bj−1 + 2]} falls 2bj−1 + 2 < m;
bj =
2bj−1 + 1 sonst;

wobei argmax in Abschitt 1.7, Gleichung 1.49 definiert ist;

3. Knoten bλ ein Blatt ist.

Bemerkung. Ist

(b0 , b1 , b2 , . . . , bλ )

ein Pfad maximaler Söhne, so gilt für die zugehörigen Schlüssel sj := A[bj ] (j = 1, 2, . . . , λ)

s1 ≥ s2 ≥ . . . ≥ sλ

Aus Bequemlichkeit setzen wir sλ+1 := −∞.

Algorithmus 8.79 (Generische Wiederherstellung des Heaps an der Spitze)


Vorbedingung:
Für alle Nachfahren k von 0 in A[0, heapsize) gilt  HB(k, heapsize)).
Nachbedingung:
Für alle k in A[0, heapsize) gilt  HB(k, heapsize)).
Großschritt 1. [Berechnung eines Pfades maximaler Söhne]
Mache einen Pfad maximaler Söhne
p := (b0 , b1 , b2 , . . . , bλ )
verfügbar.

94
Großschritt 2. [Verschiebung der Schlüssel längs des Pfades maximaler Söhne.]
2.1.[Auffinden des Verschiebeziel.]
Ist s1 ≥ s2 ≥ . . . ≥ sλ die Schlüsselfolge auf p ohne die Wurzel, so
bestimme einen Index i ∈ {0, 1, . . . , λ} mit si ≥ s0 ≥ si+1 .
2.2.[Verschiebung der Schlüssel zum Verschiebeziel.]
Falls i = 0, so
return
Für j = 0, 1, . . . , i − 1 führe aus.
A[bj ] ← sj+1
A[bi ] ← s0
Bemerkung. Wir beobachten, daß auch reheap(1, heapsize) (spezieller Aufruf von
Algorithmus 8.68) eine konkrete Ausprägung des generischen Algorithmus 8.79 ist: Die
Verfügbarmachung des Pfades maximaler Söhne heißt natürlich nicht die Vorabberechnung
aller seiner Knoten. Deshalb ist die Korrektheit von Algorithmus 8.79 genauso zu beweisen
wie die Korrektheit von Algorithmus 8.68.
Folglich ist jede andere konkrete Implementation von Algorithmus 8.79 korrekt.
Wir spezialisieren Algorithmus 8.79 zu dem folgenden generischen Bottom-Up-Reheap-
Algorithmus.

Algorithmus 8.80 (Generisches Bottom-Up-Reheap)


Vorbedingung:
Für alle Nachfahren k von 0 in A[0, heapsize) gilt  HB(k, heapsize)).
Nachbedingung:
Für alle k in A[0, heapsize) gilt  HB(k, heapsize)).
Großschritt 1. [Ablage eines Pfades der maximalen Söhne in einem Feld]
B[0] ← 0
λ ← ⌊log2 heapsize⌋
Für j = 1, 2, . . . , λ führe aus.
Berechne maxson von B[j − 1] wie in Algorithmus 8.68.
B[j] ← maxson
Großschritt 2. [Verschiebung der Schlüssel längs des Pfades maximaler Söhne.]
2.1.[Auffinden des Verschiebeziels (generischer Teilschritt).]
Bestimme einen Index i ∈ {0, 1, . . . , λ} mit
A[B[i]] ≥ A[0] ≥ A[B[i + 1]].
2.2.[Verschiebung der Schlüssel zum Verschiebeziel.]
Falls i = 0, so
return
S ← A[0]
Für j = 0, 1, . . . , i − 1 führe aus.
A[B[j]] ← A[B[j + 1]]
A[B[i]] ← S

95
Bemerkung. Man kann die Ablage eines Pfades der maximalen Söhne in einem Feld ver-
meiden. Bei unserer Definition eines in-situ Algorithmus ist sie jedoch unschädlich.
Nun liegen zwei konkrete Implementationen von Algorithmus 8.80, genauer gesagt, von
dessen Großschritt 2.1, auf der Hand.

Lineares Sondieren von unten.

i←λ
Solange A[0] > A[B[i]]
i ← i − 1.

Binäre Suche. Ermittle das Verschiebeziel i durch binäre Suche.

Satz 8.81 Die Anzahl der Schlüsselvergleiche von Bottom-Up Heapsort mit binärer Suche
auf jede Eingabe der Länge n ist durch n log2 n+n log2 log2 n+O (n) nach oben beschränkt.
Die Laufzeit ist ein O (n log n) .

Beweis. Ist der Logarithmus zur Basis zwei der aktuellen Größe des Heaps gleich λ, so
kostet ein Aufruf von Algorithmus 8.80 mit binärer Suche für Großschritt 2.1 λ + log2 λ
Schlüsselvergleiche: λ Vergleiche für die Berechnung des Feldes der maximalen Söhne und
log2 λ Vergleiche für die binäre Suche. 

Bemerkung. Obwohl Bottom-Up Heapsort mit binärer Suche das, was die Schlüssel-
vergleiche angeht, asymptotisch beste Verfahren ist, zeigen Experimente, daß es nur eine
geringe praktische Bedeutung hat: Es ist besser als das einfache Quicksort, falls n ≥ 400
und besser als die verfeinerte Variante von Quicksort, bei der drei Pivotkandidaten ins
Auge gefaßt und dann der Median ausgewählt wird, falls n ≥ 16.000 ist.
Ohne Beweis nehmen wir zur Kenntnis.

Satz 8.82 Die Anzahl der Schlüsselvergleiche von Bottom-Up Heapsort mit linearer Suche
von unten auf jede Eingabe der Länge n ist durch 32 n log2 n + O (n) nach oben beschränkt.
Die Laufzeit ist ein O (n log n) .

8.4.4 Der Heap als Datenstruktur


Wir verwenden nun den Heap, um die Prioritätswarteschlange zu implementieren: Es wer-
den Mengen von Datenobjekten eines in unserem Falle natürlich generischen Typs GT
verwaltet, der ein ganzzahliges Datenfeld priority hat. Je größer der Wert dieses Da-
tenfeldes, desto größer die Priorität des Objektes. Das kann man dadurch erreichen, daß
in jedem stabilen Zustand der Prioritätswarteschlange das Element, das an der Reihe ist,
unter allen, die warten, höchste Priorität hat.
Die folgenden Operationen werden von der Klasse MaxPriorityQueue unterstützt:

96
create(Integer n). Erzeugt wird die leere Prioritätswarteschlange mit einer Aufnahme-
kapazität für n Objekte.

empty() returns boolean. Es wird überprüft, ob die aktuelle Warteschlange leer ist.

top() returns GT. Diese Operation verändert die Warteschlange nicht. Sie gibt einen
Zeiger auf ein Objekt höchster Priorität unter den gespeicherten Objekten zurück,
sofern die Warteschlange nichtleer ist. Andernfalls erfolgt eine Fehlermeldung.

add(GT g). Das Objekt g wird der aktuellen Warteschlange hinzugefügt, sofern diese noch
nicht voll ist.

remove(). Falls die aktuelle Warteschlage nichtleer ist, wird das Objekt aus der Warte-
schlange entfernt, das top() zurückgibt.

siftup(GT g, Integer newpriority). Versieht das in der aktuellen Warteschlange ge-


speichertes Objekt g mit der neuen Priorität newpriority, sofern diese größer ist als
die alte. Ist das Objekt g nicht in der aktuellen Warteschlange gespeichert oder aber
die neue Priorität kleiner als die alte, so wird mit einer Fehlermeldung abgebrochen.

Bemerkung. In völlig analoger Weise läßt sich die Klasse MinPriorityQueue spezifizie-
ren (und implementieren), bei der kleinere Werte des Datenfeldes priority den Vorrang
anzeigen.
Wie sieht unsere konkrete Implementation der Klasse MaxPriorityQueue aus? Wir
setzen einen Heap

C[0, n) of GT (8.63)

als die eigentliche Warteschlange ein. Natürlich gibt es auch das Datenfeld heapsize, das
die Größe der aktuellen Warteschlange unterhalb der maximalen Größe n hält.
Wir sind im weiteren an Algorithmus 8.68 interessiert. Er bleibt verwendbar, wobei nun
die Priorität C[i].priority() die Rolle der Schlüsselwerte A[i] spielt.
Ist ein Objekt g des Typs GT in einer Warteschlange gespeichert, die das Feld C aus
(8.63) zur Grundlage hat, würden wir viel an Effizienz aufgeben, wenn wir für g nicht
seinen Trägerindex“ in C verfügbar machten. Dazu nehmen wir an, daß die Klasse GT

ein Datenfeld carrier hat, so daß für jedes Objekt g des Typs GT zur Laufzeit stets
gilt: Es ist genau dann g.carrier() = i ≥ 0, wenn C[i] = g ist. (Um das zu sichern,
muß Algorithmus 8.68 an geeigneter Stelle um Instruktionen angereichert werden, die das
carrier-Datenfeld aktualisieren.) Bei diesem Ansatz kann zur Laufzeit kein Objekt g in
zwei Prioritätswarteschlangen gleichzeitig sein. In unseren Anwendungen kommt letzteres
jedoch nicht vor.
Wir müssen Algorithmus 8.68 um die Pflege der Trägerindizes anreichern: Immer dann,
wenn zwei Objekte die Plätze tauschen, müssen auch die Werte ihrer carrier-Datenfelder
vertauscht werden.

97
Die Implementation mit Hilfe eines Feldes setzt dem Einfügen Grenzen. Natürlich könn-
te man, wie beim Hashing, eine Verdopplungsstrategie vorsehen. Aber das ist für unsere
Anwendungen nicht notwendig.
Wir kommen zu den Algorithmen für die Operationen. Wir beschränken uns auf add,
remove und siftup. (Der Rest ist algorithmisch uninteressant.) Unsere Kenntnisse über
die Heapsortalgorithmen machen deren Korrektheitsbeweise und Laufzeitanalysen zu einer
leichten Übungsaufgabe.

Algorithmus 8.83 (Entfernen)


Methodenkopf:
remove()
Großschritt 1. [Abbruch, falls Schlange leer.]
Falls heapsize = 0
Fehlermeldung, beispielsweise: Schlange leer, oh Du Säule der Informatik.“

return
Großschritt 2.
C[0] ← C[heapsize − 1]
C[0].carrier ← 0
heapsize ← heapsize − 1
reheap(0, heapsize)
Bei dem nun folgenden Beförderungsalgorithmus wird das Objekt, dessen Priorität
erhöht worden ist, längs des eindeutig bestimmten Weges von der Wurzel zu seinem
Trägerindex nach oben geschoben.

Algorithmus 8.84 (Beförderung)


Methodenkopf:
siftup(g, newpriority)
Großschritt 1. [Abbruch bei falscher Eingabe.]
i ← g.carrier()
Falls (i 6∈ [0, heapsize)) oder (i ∈ [0, heapsize) und C[i] 6= g)
Fehlermeldung
return
Falls C[i].priority() > newpriority
Fehlermeldung: Du willst mich degradieren? Nichtsda!“

return
Großschritt 2.
C[i].priority ← newpriority
Solange (i > 0 und C[i].priority() > C[(i − 1)/2].priority()) führe aus.
C[i] ⇄ C[(i − 1)/2]
C[i].carrier ⇄ C[(i − 1)/2].carrier

98
i ← ⌊(i − 1)/2⌋

Hinzugefügt wird, indem das in Rede stehende Objekt an die letzte Position mit der
kleinstmöglichen Priorität eingefügt und dann zu seiner eigentlichen Priorität befördert
wird.

Algorithmus 8.85 (Hinzufügen)

Methodenkopf:
add(g)
Großschritt 1. [Abbruch, wenn Schlange voll.]
Falls heapsize = n
Fehlermeldung
return
Großschritt 2.
heapsize ← heapsize + 1
C[heapsize − 1] ← g
g.carrier ← heapsize − 1
priority ← g.priority()
g.priority ← −∞
siftup(g, priority)

Satz 8.86 Hat die zu Grunde liegende Prioritätswarteschlange die Größe m, so ist die
Laufzeit der Algorithmen 8.83, 8.84 und 8.85 ein O (log2 m).

Die bisher betrachteten Heaps nennt man auch 2-Heaps. In völlig analoger Weise kann
man d-Heaps einführen, wobei d eine natürliche Zahl größer als zwei ist. Werden d-Heaps
zur Implementation von Prioritätswarteschlangen eingesetzt, ist die Zahl d in der Regel
keine Konstante, sondern hängt von der Kapazität n der Warteschlange ab (siehe Abschnitt
10.3.2). Bei einem d-Heap haben alle Knoten bis auf die aus den beiden tiefsten Niveaus
genau d Söhne. Alle Algorithmen aus diesem und den Abschnitten 8.4.2 und 8.4.3 lassen
sich in kanonischer Weise übertragen. Der Beweis des folgenden Satzes ist offensichtlich.

Satz 8.87 Hat die zu Grunde liegende Prioritätswarteschlange die Größe m, so ist für
d-Heaps die Laufzeit von Algorithmus 8.83 ein O (d · logd m), die Laufzeit der Algorithmen
8.84 und 8.85 ein O (logd m).

8.4.5 Internes Mergesort


Die Grundidee ist einfach. Sie verwirklicht das Teile–und–Herrsche–Prinzip:

99
Teile die Eingabefolge A[v, v +l) — am Anfang ist natürlich v = 0 und l = n — der Länge
l in die Folge A[v, v+⌊l/2⌋) der Länge ⌊l/2⌋ und die Folge A[v+⌊l/2⌋, v+l) der Länge
⌈l/2⌉. (Es ist offensichtlich, daß für jede ganze Zahl z die Gleichung z = ⌊z/2⌋+⌈z/2⌉
erfüllt ist.)

Beherrsche die Teilfolgen durch rekursiven Aufruf.

Kombiniere die sortierten Teilfolgen durch Mischen zu einer sortierten Gesamtfolge.


Wie sieht das Mischen monoton sortierter Teilfolgen zu einer sortierten Gesamtfolge
aus? Wir nehmen ab jetzt an, daß unser Algorithmus neben dem Eingabefeld A, auf das
er lesend und schreibend zugreifen kann, noch über ein zweites Hilfsfeld B gleicher Länge
verfügt. Zunächst spezifizieren wir unseren Algorithmus merge(v, l), der zwei Teilfelder
von A mischen soll. Ihm werden zwei Indizes v und l übergeben, wobei v der Anfangsindex
unseres Teilfeldes und l dessen Länge ist.
Vorbedingung: Die Teilfelder A[v, v + ⌊l/2⌋) und A[v + ⌊l/2⌋, v + l) sind aufsteigend
sortiert.

Nachbedingung: Das Teilfeld A[v, v + l) ist aufsteigend sortiert.


Nun geht es zur Sache.

Algorithmus 8.88 (Mischen zweier sortierter Teilfelder)


Methodenkopf:
merge(v, l)
Rumpf:
Großschritt 1.
Kopiere A[v, v + l) auf B[v, v + l).
Großschritt 2.
Initialisiere i, j und k mit den Werten v, v + ⌊l/2⌋ und v.
Führe aus:
min ← min{B[i], B[j]}
A[k] ← min
k ← k + 1.
Falls min = B[i] ist,
i ← i + 1.
Andernfalls
j ← j + 1.
bis daß i = v + ⌊l/2⌋ oder j = v + l.
Großschritt 3.
Falls i = v + ⌊l/2⌋ führe aus:
Führe aus:
A[k] ← B[j]

100
j ← j + 1, k ← k + 1
bis daß j = v + l.
Andernfalls führe aus.
Führe aus:
A[k] ← B[i]
i ← i + 1, k ← k + 1
bis daß i = v + ⌊l/2⌋.
Die Korrektheit von Algorithmus 8.88 ist offensichtlich. Was ist mit seiner Laufzeit?
Lemma 8.89 gibt Antwort.

Lemma 8.89 1. Die Anzahl der Schlüsselvergleiche für das Mischen eines Arrays A
der Länge l vermöge des vorstehenden Algorithmus ist durch l − 1 nach oben und
durch ⌊l/2⌋ nach unten beschränkt.

2. Jeder vergleichsorientierte Algorithmus A, der ein Array der Länge l in der vor-
stehend spezifizierten Weise mischt, benötigt im schlechtesten Fall l − 1 Vergleiche
zwischen Schlüsselelementen.

Beweis. Behauptung 1. Nach jedem Schlüsselvergleich wird ein Schlüssel vom Hilfsfeld
auf das Hauptfeld umkopiert. Dies geschieht solange, bis die eine Hälfte des Hilfsfeldes
leer ist. Im besten Falle reichen ⌊l/2⌋ Vergleiche. Dieser tritt ein, wenn jeder Schlüssel
aus der linken Hälfte kleiner oder gleich jedem Schlüssel aus der rechten Hälfte ist. Im
schlechtesten Fall sind die Schlüssel der beiden Hälften wie im Falle des Beweises von
Behauptung 2 verschränkt. Dann bleibt nach dem letzten Vergleich in der rechten Hälfte
des Hilfsfeldes genau ein Schlüssel stehen, der ohne Vergleich umgespeichert werden kann.
Behauptung 2. Wir führen den Beweis indirekt und nehmen das Gegenteil an. Sei A
ein Algorithmus, der mit weniger als l − 1 Vergleichen im schlechtesten Fall mischt. Wir
nehmen an, daß der Inhalt des Feldes A[0, l) die folgende Eigenschaft hat:

A[0] < A[⌊l/2⌋] < A[1] < A[⌊l/2⌋ + 1] < A[2] < . . . < A[⌊l/2⌋ − 1] < A[l − 1].

Dann kann wenigstens eines der Paare A[ν], A[ν + ⌊l/2⌋] oder A[ν + ⌊l/2⌋], A[ν + 1] von A
nicht miteinander verglichen worden sein. Folglich würde der Algorithmus A die Eingabe
in derselben Weise permutieren, wenn man die Größenverhältnisse genau dieses Paares
umdrehte. Widerspruch. 

Aus Lemma 8.89 folgt insbesondere, daß die Laufzeit des Algorithmus merge(v, l) – sie ist
proportional zur Anzahl der Schlüsselvergleiche – ein O (ℓ) ist.
Wir kommen nun zum Algorithmus mergesort(v, l), dessen Aufgabe darin besteht, das
Eingabeteilfeld A[v, v + l) aufsteigend zu sortieren und dabei die anderen Feldelemente
unberührt zu lassen.

Algorithmus 8.90 (Mergesort)

101
Methodenkopf:
mergesort(v, l)
Rumpf:
Großschritt 1. [Basis.]
Falls l ≤ 1 ist, so return.
Falls l = 2 ist, so führe aus:
Sortiere das Teilfeld durch Vergleich der Schlüssel A[v] und A[v + 1]
return
Großschritt 2. [Rekursion.]
mergesort(v, ⌊l/2⌋)
mergesort(v + ⌊l/2⌋, ⌈l/2⌉)
merge(v, l)

Satz 8.91 1. Algorithmus 8.90 arbeitet korrekt.

2. Die Anzahl der Schlüsselvergleiche ist kleiner oder gleich n · ⌈log2 n⌉ − 2⌈log2 n⌉ + 1,
wobei n die Länge der übergebenen Sequenz ist.

3. Die Laufzeit ist O (n log n).

Beweis. Die Korrektheit von Algorithmus 8.90 auf der Grundlage der Korrektheit von
Algorithmus 8.88 ist sofort einzusehen: Ein einfacher Induktionsbeweis über die Länge des
Arrays führt zum Ziel.
Für die Abschätzung der Anzahl der Schlüsselvergleiche werden wir uns, um uns das
Leben zu erleichtern, auf den Fall beschränken, daß die Eingabenlänge eine Potenz der Zahl
2 ist: n = 2k . Die Anzahl der Vergleiche V (n) genügt der folgenden einfachen Rekursion:

V (1) = 0
V (n) ≤ n − 1 + 2 · V (n/2)

Die Auflösung dieser Rekursion ist kein Problem. Für alle l ≤ log2 n = k gilt:

V (n) ≤ (n − 1) + (n − 2) + . . . + (n − 2l−1 ) + 2l V (n/2l )

Wegen V (n/2k ) = V (1) = 0 ist


k−1
X
V (n) ≤ k · n − 2l ≤ k · n − n + 1.
l=0

Die Aussage über die Gesamtlaufzeit ist eine leichte Übungsaufgabe. 

102
8.4.6 Untere Schranken für das vergleichsorientierte Sortieren
Wir haben bisher vergleichsorientierte Sortieralgorithmen studiert. Die mittlere Anzahl
von Schlüsselvergleichen bei deterministischen bzw. die erwartete Anzahl von Schlüssel-
vergleichen bei randomisierten Algorithmen war stets ein Ω(n log2 n). Lag das an unserem
Unvermögen, bessere Algorithmen zu entwerfen, oder geht es wirklich nicht besser? In
diesem Abschnitt geben wir auf diese Frage eine Antwort.
Für die Zwecke dieses Abschnitt schreiben wir die Spezifikation eines vergleichsorien-
tierten Sortieralgorithmus nochmals auf. Dazu sei S = Sn eine total geordnete n-Menge
von Schlüsseln.
Eingabe. Eine Anordnung s1 , s2 , . . . sn aller Schlüssel aus S. Wir fassen dabei die Elemente
si als Variable über der Schlüsselmenge S auf.
Ausgabe. Eine Umordnung sπ(1) , sπ(2) , . . . , sπ(n) (π ∈ Sn ) der Eingabe, so daß sπ(1) <
sπ(2) < . . . < sπ(n) ist.
Wir interessieren uns zunächst für deterministische vergleichsorientierte Sortieralgorith-
men A und deren mittlere Anzahl

keyCompaverage
A (n)

von Schlüsselvergleichen auf Eingaben der Länge n. Die Haupteigenschaft solcher Algorith-
men ist, daß im Laufe jeder Rechnung die einzigen Verzweigungspunkte Schlüsselverglei-
che si > sj ? sind. An diesen und nur an diesen Stelle erfolgt der Zugriff auf die Eingabe.
Folglich kann man alle Rechnungen auf Eingaben der Länge n durch einen sogenannten
Entscheidungsbaum protokollieren. Ein Beispiel sieht man in Abbildung 8.12.

s 2 > s3 1 - wahr
0 1 0 - falsch

s 1 > s2 s 1 > s3

0 1 0 1

s 1 < s2 < s3 s 1 > s3 s 1 < s3 < s2 s 1 > s2


0 1 0 1

s 2 < s1 < s3 s 2 < s3 < s1 s 3 < s1 < s2 s 3 < s2 < s1

Abbildung 8.12: Ein Entscheidungsbaum zum Sortieren von drei Schlüsseln

Definition 8.92 [Entscheidungsbaum]

103
Syntax. Ein Entscheidungsbaum Bn für das vergleichorientierte Sortieren von Problem-
stellungen der Größe n ist ein voller geordneter binärer Wurzelbaum, dessen innere
Knoten einschließlich der Wurzel Markierungen der Art si > sj“ für 1 ≤ i 6= j ≤ n

tragen. Die ausgehenden Kanten sind mit 0 oder mit 1 markiert. Die n! Blätter sind
mit allen Permutationen π ∈ Sn markiert. (Wir schreiben für die Markierungen der
Blätter sπ(1) < sπ(2) < . . . < sπ(n)“ und vermischen damit Syntax und Semantik

etwas. Dafür ist sofort klar, worauf es ankommt.)

Semantik. Für jede Eingabe s1 , s2 , . . . , sn gibt es einen eindeutig bestimmten Pfad von
der Wurzel zu einem Blatt, der in jedem Verzweigungspunkt mit Markierung si > sj“

die ausgehende 1-Kante auswählt, wenn dies für die Eingabe wahr ist. Andernfalls
wird die ausgehende 0-Kante ausgewählt.

Korrektheit. Für jede Eingabe s1 , s2 , . . . , sn trägt das Blatt, zu dem der Berechnungspfad
der Eingabe in Bn führt, die korrekte Markierung.

Bemerkungen.
• Ist A ein vergleichsorientierter Sortieralgorithmus, so kann man A eine Folge von
Entscheidungsbäumen Bn (A) n∈N zuordnen, die für jedes n alle Problemstellungen
der Größe n korrekt sortieren. Für jedes n und jede Eingabe s1 , s2 , . . . , sn ist

keyCompA (s1 , s2 , . . . , sn ) = depthBn (A) (v),

wobei v das Blatt ist, zu dem der Berechnungspfad in Bn (A) unter der Eingabe
s1 , s2 , . . . , sn führt.
• Für jedes n definiert der Entscheidungsbaum Bn (A) auf kanonische Weise einen
präfixfreien Code

cA ∗
n : Sn → {0, 1} , (8.64)

für dessen erwartete Wortlänge unter der Voraussetzung, daß Π ein gleichverteiltes
Zufallselement aus der Menge Sn der Permutationen von {1, 2, . . . , n} ist, gilt:

1 X
E LcAn (Π) = depthBn (A) v = keyCompaverage
A (n). (8.65)
n!
v ist Blatt
von Bn (A)

• Aus der elementaren Analysis kennen wir die Stirlingsche Formel:


√ nn Θn
n! = 2πn · · e 12n (Θn ∈ (0, 1))
en
Es folgt:

log2 n! = n log2 n − 1, 44n + Θ(log2 n).

104
Da log2 n! die Entropie der Gleichverteilung auf Sn ist, erhalten wir aus dem Quellen-
codierungssatz (Satz 3.17) den folgenden Satz.
Satz 8.93 Ist A ein beliebiger deterministischer vergleichsorientierter Sortieralgorithmus,
so ist für jede Problemgröße n
log2 n! ≤ keyCompaverage
A (n). (8.66)

Zum Abschluß kommen wir zu einer unteren Schranke für randomisierte vergleichsori-
entierte Sortieralgorithmen. Wir bemerken, daß wir uns randomisierte Algorithmen auf
eine Eingabe der Größe n in der Weise normalisiert denken können, daß
– zuerst eine Folge zufälliger Bits
Υ0 , Υ1 , . . . , Υρ(n)
(siehe dazu Abschnitt 8.3.6) erzeugt wird, und dann
– die weitere Rechnung in Abhängigkeit von der Eingabe und den Werten, welche die
Zufallsbits angenommen haben, deterministisch erfolgt.
Unser randomisiertes Quicksort ist ein sogenannter Las-Vegas-Algorithmus: Gleich-
gültig, welche Werte die Zufallsbits angenommen haben, berechnet der Algorithmus im
weiteren stets das richtige Ergebnis. Die Werte der Zufallsbits beeinflussen lediglich die
Laufzeit. Monte-Carlo-Algorithmen dagegen berechnen nicht immer das richtige Ergebnis.
Natürlich muß letzteres mit hoher Wahrscheinlichkeit der Fall sein, damit der Algorithmus
einen praktischen Wert haben soll.
Satz 8.94 Ist A ein beliebiger vergleichsorientierter Las-Vegas-Sortieralgorithmus, so gibt
es für jede Problemgröße n eine Eingabe s = (s1 , s2 , . . . , sn ) derart, daß
log2 n! ≤ E keyCompA (s1 , s2 , . . . , sn ). (8.67)

Beweis. Sei n eine beliebige, aber feste Problemgröße. Dann können wir dem Las-Vegas-
Algorithmus A eine Folge von 2ρ(n) Entscheidungsbäumen Bn,j (j = 0, 1, . . . , 2ρ(n) − 1)
zuordnen, die alle das Sortierproblem für Eingaben der Länge n lösen. Die Wirkung von
A auf solche Eingaben besteht darin, daß mit Wahrscheinlichkeit 2−ρ(n) einer dieser Ent-
scheidungsbäume ausgewürfelt“ wird, mit Hilfe dessen dann sortiert wird.

Wir führen den Beweis indirekt. Angenommen, für alle Eingaben s = (s1 , s2 , . . . , sn )
gilt
log2 n! > E keyCompA (s).

Das ist gleichbedeutend mit


2ρ(n)
X−1
1
log2 n! > · depthBn,j vn,j (s)
2ρ(n) j=0

105
für alle s, wobei vn,j (s) das Blatt ist, zu dem der Berechnungspfad zu der Eingabe s im
Entscheidungsbaum Bn,j führt. Indem wir über alle n! Eingaben der Länge n mitteln und
die Summationsindizes vertauschen, erhalten wir
 
2ρ(n)
X−1
1 1 X 
log2 n! > ·  depthBn,j v  .
2ρ(n) j=0
n! v ist Blatt
in Bn,j

Dann muß es aber einen Index j ∈ {0, 1, . . . , 2ρ(n) − 1} mit

1 X
log2 n! > depthBn,j v
n! v ist Blatt
in Bn,j

geben. Das steht im Widerspruch zu Satz 8.93. 

8.5 Sortieren reeller Zahlen durch Fachverteilung


In diesem Abschnitt werden wir sehen, daß man den Sortiervorgang deutlich beschleunigen
kann, wenn man aus den Schlüsseln nicht nur durch Vergleich untereinander Informationen
gewinnt.
Die Situation. Uns ist ein Feld

A[0, n) of Real (8.68)

gegeben, das aufsteigend sortiert werden soll. Vorausgesetzt wird, daß die Folge

A[0], A[1], . . . , A[n − 1] (8.69)

unabhängige, gleichverteilte Zufallsvariablen aus [0, 1) sind.


Die Grundidee von Algorithmus 8.95 knüpft an unsere Vorstellungen über die gleichmäßi-
ge Verteilung der Schlüssel beim offenen Hashing
 j j+1  (siehe Abschnitt 8.3.2) an. Das Intervall
[0, 1) wird in n gleichlange Teilintervalle n , n (j = 0, 1, . . . , n − 1) partitioniert, denen
Buckets q[j] zugeordnet werden. Für jedes i = 0, 1, . . . , n − 1 verfahren wir wie folgt. Wir
legen den Schlüssel A[i] genau dann in das Bucket q[j], wenn
j j+1
≤ A[i] < ⇐⇒ j = ⌊n · A[i]⌋ (8.70)
n n
ist. Anschließend werden die Buckets z.B. mit Heapsort sortiert und abschließend unter
Wahrung der nun bereits ermittelten Ordnung auf A zurückgespeichert. Wir erhalten:

Algorithmus 8.95 (Sortieren durch Fachverteilung)

106
Methodenkopf:
hybridsort()
Großschritt 1. [Erzeugen eines Feldes von Buckets.]
Erzeuge ein Feld q[0, n) von leeren Warteschlangen.
Großschritt 2. [Verteilen der Schlüssel auf die Buckets.]
Für i = 0, 1, . . . , n − 1führe aus.
q [⌊nA[i]⌋] .add A[i]
Großschritt 3 [Sortieren der Buckets durch vergleichsorientiertes Sortieren.]
Für j = 0, 1, . . . , n − 1 führe aus.
Sortiere q[j] mit Heapsort.
Großschritt 4.[Rückspeicherung auf A.]
Für j = 0, 1, . . . , n − 1 hführe aus. 
Pj−1 Pj
Speichere q[j] auf A i=0 λi , i=0 λi unter Wahrung der Reihenfolge um,
wobei λi die Länge von q[i] ist.

Satz 8.96 1. Algorithmus 8.95 ist korrekt.

2. Im schlechtesten Fall hat Algorithmus 8.95 eine Laufzeit von O (n ln n).

3. Die erwartete Laufzeit von Algorithmus 8.95 auf eine zufällige Eingabe A der Länge
n ist O (n).
Die erwartete Anzahl der Schlüsselvergleiche auf eine zufällige Eingabe der Länge n
läßt sich nach oben durch 2n − 1 abschätzen.
1
4. Mit Wahrscheinlichkeit größer oder gleich 1 − n2
ist die Laufzeit von Algorithmus
8.95 ein O (n ln ln n).

Beweis. Behauptung 1 ist eine unmittelbare Folgerung von (8.70).


Behauptung 2 ist klar: Im schlechtesten Fall fallen alle Eingaben in dasselbe Bucket.
Behauptung 3. Da für jedes i = 0, 1, . . . , n − 1 die Zufallsvariable A[i] auf [0, 1) gleich-
verteilt ist, gilt nach Definition für jedes j = 0, 1, . . . , n − 1

  
j j+1 1
P A[i] ∈ , = .
n n n

Für jedes j = 0, 1, . . . , n − 1 ist die Zufallsvariable


  
j j+1
card i A[i] ∈
, =:Lj
n n

107

n, n1 -binomialverteilt, da alle Zufallsvariablen aus (8.69) auch unabhängig sind. Die Va-
riable Lj ist die zufällige Länge des j-ten Buckets. Es ist klar, daß sich die erwartete
Laufzeit für eine zufällige Eingabe bis auf eine multiplikative Konstante nach oben durch
n−1
! n−1
X X
E Lj log2 Lj ≤ E L2j = n · E L21
j=0 j=0

abschätzen läßt, wobei wegen 2n log2 n ≤ n2 für alle natürlichen Zahlen n ≥ 1 die rechte Sei-
te der vorstehenden Gleichung die erwartete Anzahl der Schlüsselvergleiche in Großschritt
3 nach oben abschätzt. Es genügt also, E L21 zu berechnen. Wir wissen, daß E L1 = 1 ist,
und daß auf die Varianz von L1 einerseits Gleichung 3.30 und andererseits Gleichung 3.27
anwendbar sind. Wir erhalten
1
Var L1 = E L21 − (E L1 )2 = 1 − .
n
Es folgt
1
E L21 = 2 − .
n
Damit haben wir auch bewiesen, daß die erwartete Anzahl der Schlüsselvergleiche in Al-
gorithmus 8.95 nach oben durch 2n − 1 abgeschätzt werden kann.
Zu Behauptung 4. Wir stellen fest, daß wir in der gleichen Situation sind wie beim
Beweis von Satz 8.44. Uns interessiert die Länge des längsten Buckets nach Großschritt 2:
L := max {Lj | j = 0, 1, . . . , n − 1} .

Nach Satz 8.44 wissen wir, daß


1
P (L > 3 · λ(n)) <
n2
ist, wobei
ln n
λ(n) := min{r | r! ≥ n} ∼
ln ln n
ist. Wir wissen, daß wir in Großschritt 3 eine Laufzeit
n−1
!
X
T3 := O Li ln Li
i=0

haben, die überdies die Laufzeit des restlichen Algorithmus majorisiert. Wir betrachten
nun da Ereignis

E := {L ≤ 3 · λ(n)},

108
von dem wir wissen, daß es eine Wahrscheinlichkeit ≥ 1 − 1/n2 hat. Da die Funktion x ln x
∪-konvex ist, steht, sofern E gegeben ist, der für die Laufzeit schlimmste Fall ins Haus,
wenn soviele Buckets wie nur möglich die Größe 3 · λ(n) haben. Wir überschätzen diese
Zahl und damit die Laufzeit, wenn wir sie
   
n n ln ln n
b := =Θ
3 · λ(n) ln n

setzen. Wir erhalten


 
ln n
T3 = O b · (ln ln n − ln ln ln n) = O (b · ln n)
ln ln n
= O (n ln ln n) .

109
Literaturverzeichnis

[CLRS01] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein. Introduction to


Algorithms. MIT Press, 2001.

[MR95] R. Motwani and P. Raghavan. Randomized Algorithms. Cambridge University


Press, 1995.

110
Kapitel 9

Externes Suchen und Sortieren

Bisher galt, daß sich alle Datenstrukturen komplett im Hauptspeicher befunden haben.
Für manche Anwendungen trifft das nicht zu. Einerseits kann es erforderlich sein, daß
die Datenstrukturen dauerhaft sein sollen. Andererseits besteht die Möglichkeit, daß die
vorhandenen Datenmengen einfach zu groß sind.
Als Hintergrundspeicher sind heute Magnetplatten gebräuchlich. Um unsere Strukturen
und Algorithmen analysieren zu können, benötigen wir ein angemessenes Modell. Im Ar-
beitsspeicher kostet die Verarbeitung von k Bytes k mal soviel wie die Verarbeitung eines
Bytes. Auf Plattenspeichern werden sogenannte Blöcke mit einer typischen Größe von 12
bis 8 K gemeinsam geschrieben oder gelesen, so daß man kaum sparen kann, wenn man
beispielsweise nur ein Byte lesen will. Aus der Sicht des Systems heißen die Blöcke Seiten.
Der Zeitbedarf für einen Seitenzugriff ist relativ hoch. Die CPU kann in dieser Zeit in der
Regel mehrere tausend Instruktionen ausführen. Deshalb legen wir folgendes fest:
– Bei der Analyse betrachten wir vor allem die Anzahl der Seitenzugriffe. Dazu ha-
ben wir Operationen disk-read und disk-write. Die CPU-Zeit ist dagegen von
geringerer Bedeutung.

– Der Platzbedarf einer externen Speicherstruktur wird in der Anzahl der belegten Sei-
ten gemessen.
Die Ausnutzung der benutzten Seiten kann darüber hinaus von Interesse sein. Sie wird
gewöhnlich in Prozent angeben und ist als
Anzahl der benutzten Bytes
(9.1)
Anzahl der benutzten Seiten · Anzahl der möglichen Bytes je Seite
definiert.
Eine externe Datenstruktur ist vollständig im Hintergrundspeicher dargestellt; wir spre-
chen deshalb auch von einer Speicherstruktur. Für die Verarbeitung einer Speicherstruktur
mit Größenparameter n wird nur O (1) Platz im Arbeitsspeicher benötigt: Eine konstante
Anzahl von Seiten wird im Hauptspeicher gehalten und verarbeitet. Dann wird rückgespei-
chert und ggf. neue Seiten geladen.

111
Wir werden uns relativ kurz fassen. Wer zu diesem Thema mehr wissen will, der sei
auf die gute Darstellung in [GD03] ab Seite 295 verwiesen, auf die wir uns auch stützen
werden.
Wie ist die Situation? Rein mathematisch gesehen, gleicht sie der zu Beginn von Kapitel
8 dargestellten: Wir haben n Schlüssel aus einer total geordneten Menge. (Über Satelliten-
daten reden wir in diesem Kapitel nicht mehr.)
Die n Datensätze

k0 , k1 , . . . , kn−1 (9.2)

befinden sich im Hintergrundspeicher. Wir werden in diesem Kapitel Algorithmen und


Datenstrukturen besprechen, die es uns ermöglichen, die Daten aus (9.2)
1. als Wörterbuch (Spezifikation siehe Abschnitt 8.1) zu verwalten (siehe Abschnitt 9.1);

2. zu sortieren (siehe Abschnitt 9.2).

9.1 Externes Suchen: B-Bäume


Die Datenstruktur, um die es uns im folgenden geht, sind Bayer-Bäume, abgekürzt B-
Bäume, eine Datenstruktur im Hintergrundspeicher. Die Natur der Schlüssel ist für uns
nicht interessant, weil sie algorithmisch ohne Bedeutung ist. Wir denken sie uns deshalb
als ganze Zahlen. (Tatsächlich handelt es sich um Schlüssel, die bei der Speicherverwaltung
eine Bedeutung haben.) Ein Beispiel ist in Abbildung 9.1 dargestellt. Mit n bezeichnen wir
die Anzahl der in einem B-Baum gespeicherten Schlüssel.

10

3 7 13 16

1 2 4 5 8 9 11 12 14 15 17 18

Abbildung 9.1: Beispiel eines B-Baums

Definition 9.1 Ein B-Baum T ist ein gerichteter geordneter Wurzelbaum mit den folgen-
den Eigenschaften:
1. Jeder Knoten x von T hat als Attribute

– eine geordnete Folge von Schlüsseln k1 < k2 < . . . < kb

112
– eine Folge Succ(1), Succ(2), . . ., Succ(b + 1) von Nachfolgerknoten, die entwe-
der alle vorhanden oder aber – genau dann, wenn x ein Blatt ist – alle nicht
vorhanden sind.

Die Zahl b ist abhängig von x. Abbildung 9.2 zeigt, wie man sich den Knoten x
vorstellen kann.

k1 k2 ... ... kb

Succ(1) Succ(2) Succ(3) Succ(b) Succ(b + 1)

Abbildung 9.2: Knoten eines B-Baums mit b Schlüsseln

2. Alle Blätter von T haben dieselbe Tiefe.

3. Es gibt eine natürliche Zahl t ≥ 2, den Verzweigungsfaktor oder Branching Factor,


so daß für die Anzahl der Schlüssel b, die jeder Knoten x von T trägt, folgendes gilt.

(a) b ≤ 2t.
(b) Ist x von der Wurzel verschieden, so ist b ≥ t.
(c) Ist T vom leeren Baum verschieden, und ist x die Wurzel, so ist b ≥ 1.

4. Es gilt die folgende verallgemeinerte Suchbaumeigenschaft: Ist x ein beliebiger Knoten


von T , der b Schlüssel k1 < k2 < . . . < kb trägt und kein Blatt ist, und sind κ1 , κ2 , . . .,
κb+1 beliebige Schlüssel der Nachfolgerknoten Succ(1), Succ(2), . . ., bzw. Succ(b + 1),
so ist

κ1 < k1 < κ2 < k2 < . . . < κb < kb < κb+1 .

Bemerkungen.
• Die Knoten eines B-Baumes werden auch Seiten genannt. Damit wird angezeigt, daß
sie jeweils nicht nur einen Schlüssel, sondern eine Anzahl b von Schlüsseln tragen,
wobei b im Normalfall in dem Intervall [t, 2t] liegt. Warum geben wir keine feste
Zahl an? In Definition 9.1 fordern wir, daß jedes Blatt eines B-Baumes dieselbe Tiefe
hat. Diese Forderung ist mit der Festlegung, jeder Knoten solle gleichviele Schlüssel
tragen, unverträglich.
• Der Verzweigungsfaktor ist so bemessen, daß der Speicherplatzbedarf eines Knotens
die zu Beginn dieses Kapitels beschriebene Seitengröße nicht überschreitet.
• Für die Algorithmen dieses Abschnitts vereinbaren wir folgendes:

113
1. Das Original jeder Seite befindet sich im Hintergrundspeicher. Die Wurzel ist
aber stets geladen“. Wir legen fest, daß jeder Zugriff auf eine Seite zunächst

mit einem disk-read und nach erfolgter Modifikation mit einem disk-write
verbunden ist. Diese klare Regelung ermöglicht es, die Seitenzugriffsbefehle in
unserem Pseudocode zu unterdrücken.
Stets werden nur konstant viele Knoten gleichzeitig einer Veränderung unter-
worfen. Versionen dieser Knoten befinden sich im Hauptspeicher. Diese Arbeits-
versionen heißen interne Knoten. Alle anderen Knoten nennen wir auch extern.
2. Bei der Analyse werten wir sowohl die CPU- als auch die Plattenzugriffszeit aus.
Der Verzweigungsfaktor t – man mag an t ≥ 50 denken – wird dabei nicht in
der O-Notation verborgen.
3. Wir betrachten keine Garbage-Kollektion.
• Ist x ein interner Knoten, ist x also gerade einem Veränderungsprozeß unterworfen,
so können die Bedingungen aus Definition 9.1 über die Anzahl b der von x gehaltenen
Knoten vorübergehend verletzt sein:
(
[t − 1, 2t + 1] falls x verschieden von der Wurzel ist;
b∈
[0, 2t + 1] falls x gleich der Wurzel ist.

Ein interner Knoten x von T , der von der Wurzel verschieden ist, heißt unterfüllt
(überfüllt), wenn b = t − 1 (b = 2t + 1) ist. Die Wurzel kann überfüllt aber nicht
unterfüllt sein.
• Knoten können wir uns als Instanzen einer Klasse BNode implementiert denken.
Hauptdatenfelder sind
– ein Feld
(
Array[1, 2t] of Key falls der Knoten extern ist;
key :
Array[0, 2t + 2] of Key falls der Knoten intern ist;

wobei in unserer Darstellung aus technischen Gründen – Randbedingungen ver-


einfachen sich – für interne Knoten stets key(0) = −∞ und key(2t + 2) = +∞
gilt;
– eine ganzzahlige Variable used, welche die Anzahl der Schlüssel hält, die in dem
Knoten gespeichert sind;
– ein Feld
(
Array[1, 2t + 1] of BNode falls der Knoten extern ist;
succ :
Array[1, 2t + 2] of BNode falls der Knoten intern ist;

– eine Variable father of BNode.

114
Wir wollen annehmen, daß wenn used = b ist, so ist key(b + 1) = . . . = key(2t + 2) =
+∞.
Da interne und externe Knoten eine geringfügig abweichende Darstellung haben,
muß jeder Plattenzugriff von einem (sehr einfachen) Konversionalgorithmus flankiert
werden, der O (t) CPU-Zeit erfordert.
• Die Datenfelder key und succ sind von expandiertem Typ. Das soll heißen, sie ent-
halten nicht nur die Referenz auf das entsprechende Feld, sondern vielmehr das Feld
selbst.
• Für ein internes BNode-Objekt x gilt:
– x ist genau dann die Wurzel, wenn x.father = ↑ ist.
– x ist genau dann ein Blatt, wenn succ(1) = . . . = succ(2t + 2) = ↑ ist.
• Für externe Knoten kann man analoge Bedingungen wie die vorstehenden formulie-
ren. Aber sie sind für uns uninteressant, da unsere Algorithmen definitionsgemäß nur
auf interne Knoten als Aktualparameter zugreifen.
• B-Bäume insgesamt denken wir uns als Instanzen einer Klasse BTree mit dem Haupt-
datenfeld root vom Typ BNode und allen Algorithmen, die in diesem Abschnitt be-
sprochen werden.
Der Zustand eines B-Baumes zwischen zwei Aufrufen öffentlicher Methoden heißt
stabil. In einem stabilen Zustand sind alle Bedingungen aus Definition 9.1 erfüllt.

Wie steht es um die Tiefe eines B-Baumes T mit Verzweigungsfaktor t, der n Schlüssel
trägt?

Satz 9.2 Es ist


 
n+1
log2t+1 (n + 1) − 1 ≤ depth(T ) ≤ logt+1 .
2

Beweis. Sei d die Tiefe des in Rede stehenden B-Baumes.


Ein Suchbaum Td,min mit der Tiefe d mit minimal vielen Schlüsseln sieht so aus.

1 Schlüssel

t Schlüssel t Schlüssel
· . . . (t + 1) . . . · · . . . (t + 1) . . . ·
... ......... ............ ...
t Schlüssel t Schlüssel

. . . t Schlüssel . . . (t + 1) . . . t Schlüssel t Schlüssel . . . (t + 1) . . . t Schlüssel . . .

115
Aus der vorstehenden Abbildung erkennen wir leicht, daß im linken und im rechten Teil-
baum von Td,min jeweils
d−1
!
X (t + 1)d − 1
t· (t + 1)i =t· = (t + 1)d − 1
i=0
t

Schlüssel gespeichert sind. Folglich enthält Td,min insgesamt 2(t + 1)d − 1 Schlüssel. Wegen
der Minimalität von Td,min folgt

2(t + 1)d − 1 ≤ n,

woraus wir

n+1
d ≤ logt+1
2
erhalten.
Ein Suchbaum Td,max mit der Tiefe d mit maximal vielen Schlüsseln sieht so aus.

2t Schlüssel

2t Schlüssel . . . (2t + 1) . . . 2t Schlüssel


· . . . (2t + 1) . . . · · . . . (2t + 1) . . . ·
... ......... ............ ...
2t Schlüssel 2t Schlüssel

. . . 2t Schlüssel . . . (2t + 1) . . . 2t Schlüssel 2t Schlüssel . . . (2t + 1) . . . 2t Schlüssel . . .

Die vorstehende Abbildung macht deutlich, daß in Td,max


d
!
X (2t + 1)d+1 − 1
2t · (2t + 1)i = 2t · = (2t + 1)d+1 − 1
i=0
2t

Schlüssel gespeichert sind. Wegen der Maximalität von Td,max folgt

n ≤ (2t + 1)d+1 − 1,

woraus wir

log2t+1 (n + 1) − 1 ≤ d

erhalten. 

116
Korollar 9.3 Es ist

depth(T ) = Θ (logt n) .

Viele Begriffe und Aussagen über binäre Suchbäume aus dem Abschnitt 8.2 können sehr
leicht auf B-Bäume übertragen werden. Mehr noch, die Verhältnisse sind bei B-Bäumen
einfacher, da jeder Knoten eines B-Baumes, der b Schlüssel hält, entweder b + 1 oder keinen
Nachfolger hat. Der Beweis des folgenden Lemmas ist deshalb eine leichte Übungsaufgabe.

Lemma 9.4 Sei T ein B-Baum und v ein innerer Knoten von T . Ist k ein Schlüssel auf
v, so liegt der unmittelbare Vorgänger und der unmittelbare Nachfolger von k auf einem
Blatt.

Besonders einfach ist die Übertragung des Begriffes des Suchpfades aus Definition 8.17.
Es ist offensichtlich, daß der Endknoten des Suchpfades nach jedem Schlüssel, der nicht zu
dem aktuellen B-Baum gehört, ein Blatt ist. Der folgende Algorithmus ist dem Algorithmus
8.19 aus dem Abschnitt 8.2.5 sehr ähnlich.

Algorithmus 9.5 (Berechnung des Blattes eines Suchpfades)


Methodenkopf: 
searchPath Key k, BNode v returns BNode
Vorbedingung:
Der übergebene Knoten v gehört zum aktuellen B-Baum T .
Nachbedingung:
Rückgabe des Endknotens des Suchpfades nach k in Tv .
Großschritt 1. [Basis]
Berechne den eindeutig bestimmten Index i mit
v.key(i) ≤ k < v.key(i + 1).
Führe
return v
aus, falls eine der folgenden zwei Bedingungen erfüllt ist:
- v.key(i) = k
- v.key(i) 6= k und v ist Blatt.
Großschritt 2. [Rekursion] 
return searchPath k, v.succ(i + 1)

Wie alle Laufzeitanalysen dieses Abschnitts, ist der Beweis des folgenden Lemmas auf
der Grundlage von Korollar 9.3 eine leichte Übungsaufgabe.

Lemma 9.6 Algorithmus 9.5 hat O (logt n) Plattenzugriffs- und O (t · logt n) CPU-Zeit.

117
Der folgende Algorithmus ist eine nichtöffentliche Methode, die auf B-Bäume Anwen-
dung findet, die sich durch Einfügen eines Schlüssels in einen Knoten vorübergehend nicht
mehr in einem stabilen Zustand befinden.

Algorithmus 9.7 (Hilfsalgorithmus zum Aufspalten eines Knotens)


Methodenkopf: 
split BNode v
Vorbedingung:
Der übergebene Knoten v des aktuellen B-Baumes T ist als einziger überfüllt.
Nachbedingung:
Kein Knoten des aktuellen B-Baums T ist mehr überfüllt.
Großschritt 1. [Basis]
Falls v die Wurzel ist, so
verfahre gemäß Abbildung 9.3.
return
Großschritt 2. [Rekursion]
Verfahre gemäß Abbildung 9.4.
Falls der Vater von Knoten v nun überfüllt ist, führe
split(v.father())
aus.
Wir bemerken, daß sowohl in Abbildung 9.3 als auch in Abbildung 9.4 die beiden
Knoten, die durch Aufspaltung neu entstehen, genau t Schlüssel enthalten.

Lemma 9.8 Algorithmus 9.7 hat O (logt n) Plattenzugriffs- und O (t · logt n) CPU-Zeit.

Mit Hilfe von Algorithmus 9.7 ist die Einfüge-Operation leicht ins Werk zu setzen.

Algorithmus 9.9 (Algorithmus zum Einfügen eines Schlüssels)


Methodenkopf: 
insert Key k
Großschritt 1.
Falls die gespeicherte Menge leer ist, so führe aus.
Speichere Schlüssel k als einzigen Schlüssel in der Wurzel.
Führe return aus.
Großschritt 2.
v ← searchPath(k, root).
Falls v den Schlüssel k trägt, so führe return aus.
Großschritt 3.
Füge den Schlüssel k an der richtigen Stelle der Schlüsselfeldes des Knotens v ein.
Falls v nun überfüllt ist, so führe split(v) aus.

118
k1 ... kt kt+1 kt+2 ... k2t+1

R1 R2 R3 R4

kt+1

k1 ... kt kt+2 ... k2t+1

R1 R2 R3 R4

Abbildung 9.3: Aufspalten eines überfüllten Knotens: Geburt“ einer neuen Wurzel

Satz 9.10 Algorithmus 9.9 hat O (logt n) Plattenzugriffs- und O (t · logt n) CPU-Zeit.

Der Algorithmus des Streichens ist zu dem des Einfügens dual. Zunächst entwerfen
wir eine nichtöffentliche Methode, die Anwendung findet, wenn sich durch Streichen ei-
nes Schlüssels der aktuelle B-Baum vorübergehend nicht mehr in einem stabilen Zustand
befindet, weil ein Knoten u nunmehr unterfüllt ist.
Da ein B-Baum geordnet ist, kann man für jeden Knoten von einem rechten oder
linken Nachbarn sprechen. Diese können auch fehlen. Ein unterfüllter Knoten hat stets
einen Nachbarn, denn er ist definitionsgemäß verschieden von der Wurzel.
Ein Nachbar des unterfüllten Knotens u heißt reich, wenn er mehr als t Schlüssel hält.
Ein solcher Nachbar kann einen Schlüssel abgeben. Ein Nachbar heißt arm, wenn er genau
t Schlüssel hält. Ein solcher Nachbar kann nichts abgeben. Das Wenige, was er hat, braucht
er für sich.
In dem nun folgenden Algorithmus wird zunächst geprüft, ob u einen reichen Nachbarn
hat. Wenn ja, so wird einer ausgewählt, der einen Schlüssel gemäß Abbildung 9.5 an u
abgibt. Das geht nur deshalb so einfach, weil die Schlüsselliste, die ein Knoten hält, geordnet
ist.
Hat der unterfüllte Knoten nur arme Nachbarn, fusioniert er mit einem von ihnen gemäß
Abbildung 9.6 zu einem neuen Knoten. Dabei bedienen sich beide aus der Substanz ihres
gemeinsamen Vaters mit einem Schlüssel. Der so entstandene neue Knoten hält genau 2t

119
λ κ

k1 ... kt kt+1 kt+2 ... k2t+1

R1 R2 R3 R4

λ kt+1 κ

k1 ... kt kt+2 ... k2t+1

R1 R2 R3 R4

Abbildung 9.4: Aufspalten eines überfüllten Knoten: Vergrößerung des Vaters

Schlüssel. Nun kann es sein, daß der Vater unterfüllt ist, sofern er überhaupt überlebt
hat. War der Vater (vorher) die Wurzel, sind wir fertig, da die Wurzel unterfüllt sein darf.
Anderfalls kommt es zu einem rekursiven Aufruf mit dem Vater als Aktualparameter.

Algorithmus 9.11 (Hilfsalgorithmus zum Zusammenlegen von Knoten)


Methodenkopf: 
contract BNode v
Vorbedingung:
Der übergebene Knoten v von T ist als einziger unterfüllt.
(Erinnerung: Der Knoten v ist dann verschieden von der Wurzel.)
Nachbedingung:
Kein Knoten des aktuellen B-Baums T ist mehr unterfüllt.
Großschritt 1. [Basis]

120
Falls v einen reichen Nachbarn v ′ hat, so
verfahre gemäß Abbildung 9.5 und führe dann return aus.
Falls v die Wurzel als Vater hat, so
verfahre gemäß Abbildung 9.6 und führe dann return aus.
Großschritt 2. [Rekursion]
Verfahre gemäß Abbildung 9.6.
Falls der Vater von Knoten v nun unterfüllt ist, führe
contract(v.father())
aus.

Vater von v

... k ...

Unterfüllter Knoten v Reicher“ Nachbar v ′ von v



... ... k′ k ′′ ℓ ...

R1 R2 R3 R4

... k ′′ ...

... k′ k ℓ ... ...

R1 R2 R3 R4

Abbildung 9.5: Nachbarschaftlicher Vermögensausgleich“


Lemma 9.12 Algorithmus 9.11 hat O (logt n) Plattenzugriffs- und O (t · logt n) CPU-Zeit.
Mit Hilfe von Algorithmus 9.11 ist das Streichen leicht möglich. Es ist einfacher als
im Falle von Suchbäumen: Der Fall des Großschritts 3 aus Algorithmus 8.25 kann nicht
auftreten.

121
Vater von v

... k ...

Unterfüllter Knoten v Armer“ Nachbar von v



... ... k′ k ′′ ... ...

R1 R2 R3 R4

... ...

... ... k′ k k ′′ ... ...

R1 R2 R3 R4

Abbildung 9.6: Der Staubsaugereffekt“


Der Fall, daß der zu streichende Schlüssel auf einem Blatt liegt, ist kanonisch zu behan-
deln. Schlüssel k wird aus der Schlüsselliste des Blattes gestrichen und Algorithmus 9.11
mit diesem Blatt als Aktualparameter aufgerufen.
Liegt der zu streichende Schlüssel auf einem Knoten u, der nicht Blatt ist, so verfährt
man wegen Lemma 9.4 analog zu Großschritt 4 aus Algorithmus 8.25. Man ermittelt das
Blatt u′ , auf dem der Vorgänger k ′ von Schlüssel k liegt, vertauscht k und k ′ mit einander
und löscht schließlich k auf dem Blatt v ′ wie oben beschrieben.

Algorithmus 9.13 (Algorithmus zum Streichen eines Schlüssels)


Methodenkopf: 
delete Key k
Großschritt 1.
Falls die gespeicherte Menge leer ist, so führe return aus.

122
u ← searchPath(k, root).
Falls u den Schlüssel k nicht trägt, so führe return aus.
Großschritt 2 [u ist kein Blatt].
Falls u kein Blatt ist, so führe aus.
Berechne das Blatt u′ , das den Vorgänger k ′ von k trägt.
Tausche die Positionen der Schlüssel k und k ′ .
u ← u′
Großschritt 3 [u ist Blatt].
Streiche den Schlüssel k aus dem Schlüsselfeld des Knotens u.
Falls u nun unterfüllt ist, so führe contract(u) aus.

Satz 9.14 Algorithmus 9.13 hat O (logt n) Plattenzugriffs- und O (t · logt n) CPU-Zeit.

Zum Abschluß betrachten wir als Beispiel für Algorithmus 9.13 den B-Baum aus Abbil-
dung 9.1, aus dem wir den Schlüssel 7 streichen wollen. (Der Verzweigungsfaktor sei t = 2.
Jeder Knoten, der von der Wurzel verschieden ist, darf zwischen 2 und 4 Schlüssel halten.)
Zunächst tauscht der Schlüssel 7 mit seinem Vorgänger, dem Schlüssel 5, die Plätze. An-
schließend wird Schlüssel 7 aus dem Blatt, auf dem er sich nunmehr befindet, entfernt. Das
Ergebnis ist in Abbildung 9.7 dargestellt: Das Blatt ist nach dem Streichen des Schlüssels
7 unterfüllt. Ein typischer Fall für die Methode contract mit diesem Blatt als Aktualpa-
rameter. Da beide Nachbarn arm sind, fusioniert dieses Blatt (natürlich unter Gleichen)
mit seinem rechten Nachbarn, nicht ohne vorher den gemeinsamen Vater zur Kasse gebe-
ten zu haben (siehe Abbildung 9.8). Dieser wiederum ist nunmehr unterfüllt und muß sich
seinerseits mit seinem einzigen, armen Nachbarn zusammentun. Ihr gemeinsamer Vater ist
die Wurzel, die dabei aufgesogen wird, wie aus Abbildung 9.9 ersichtlich ist.

10

3 5 13 16

1 2 4 8 9 11 12 14 15 17 18

unterfüllt

Abbildung 9.7: Das Blatt, das Schlüssel 4 trägt, ist unterfüllt.

123
10

3 unterfüllt 13 16

1 2 4 5 8 9 11 12 14 15 17 18

Abbildung 9.8: Der Knoten, der Schlüssel 3 trägt, ist unterfüllt.

3 10 13 16

1 2 4 5 8 9 11 12 14 15 17 18

Abbildung 9.9: Die Wurzel aus Abbildung 9.8 wurde aufgesaugt“.


9.2 Externes Sortieren: Mergesort


Das zweite zentrale Problem der Behandlung großer Datensätze ist das Sortieren. Dazu
stehen die Daten aus (9.2) in einer vom Betriebssystem verwalteten Datei, deren Darstel-
lung auf Magnetspeicherplatten wir uns als Seitenfolge vorstellen wollen. Bequem, wie wir
sind, nehmen wir an, daß jede Seite (eventuell bis auf die letzte) genau b Schlüssel enthält:

k0 , k1 , . . . , kb−1 kb , kb+1 , . . . , k2b−1 . . . k(k−1)b , k(k−1)b+1 , . . . , kn−1 (9.3)


| {z }| {z } | {z }
Seite 0 Seite 1 Seite k − 1

Von zentraler Bedeutung für die Analyse der Anzahl der Plattenzugriffe in diesem
Abschnitt sind die folgenden Bemerkungen.
Bemerkungen.
 
• Wollen wir alle Daten lesen, so kann das mit k = nb Seitenzugriffen geschehen, wenn
wir die kanonische Reihenfolge aus (9.3) einhalten.
• Man braucht kein Genie zu sein, um sich vorstellen zu können, daß man bei einem
Zugriff in einer Reihenfolge, die stark von der in (9.3) gegebenen abweicht, n Zugriffe

124
benötigt, denn der Platz, den man im Arbeitsspeicher benutzen darf, ist ja durch
eine konstante Anzahl von Seiten beschränkt.
Da die Zahl b in der Regel größer oder gleich 50 ist, und Seitenzugriffe teuer sind,
brauchen wir ein Sortierverfahren, das die Anordnung der Schlüssel der verwendeten Da-
teien einhält. Letzteres wird zur unabweislichen Notwendigkeit, wenn Magnetbänder zum
Einsatz kommen: Hier ist der Zeitaufwand extrem hoch, einen Schlüssel außerhalb der
Speicherreihenfolge zu lesen. Der Algorithmus unserer Wahl ist die iterative Variante des
in Abschnitt 8.4.5 dargestellten Mergesort. Wir machen uns den Algorithmus zunächst
anhand eines Beispiels klar. Wir verwenden vier Dateien f1 , f2 , g1 und g2 . Am Anfang
befinden sich alle Schlüssel in der Datei g1 .

g1 :48|99|30|15|9|72|38|2|79|61|69|12|16
g2 :∅
f1 :∅
f2 :∅

Während des Preprocessings, der Methode initialRuns(g1 , f1 , f2 ) mit der Vorbedingung


g2 = f1 = f2 = ∅, werden die Schlüssel von g1 auf f1 und f2 in kanonischer Weise abwech-
selnd verteilt:

g1 :∅
g2 :∅
f1 :48|30|9|38|79|69|16
f2 :99|15|72|2|61|12

Das Symbol |“ ist das Trennzeichen für die sogenannte Läufe oder Runs. Ein Lauf ist eine

aufsteigend sortierte Teilfolge.

In der nun folgenden Mischphase ist die Situation wie folgt. Die zu sortierende Folge
befindet sich, aufgeteilt in Läufe, gleichmäßig verteilt entweder auf f1 und f2 oder aber auf
g1 und g2 . Wir nehmen an, daß das erstere der Fall ist. Dann werden die Läufe von f1 und
f2 von links nach rechts paarweise, wie in Algorithmus 8.88 dargestellt, gemischt und die
neuen längeren Runs abwechselnd auf g1 und g2 gespeichert.

Mischphase 1.

g1 :48, 99|9, 72|61, 79|16


g2 :15, 30|2, 38|12, 69|
f1 :∅
f2 :∅

125
Mischphase 2.

g1 :∅
g2 :∅
f1 :15, 30, 48, 99|12, 61, 69, 79|
f2 :2, 9, 38, 72|16

Mischphase 3.

g1 :2, 9, 15, 30, 38, 48, 72, 99|


g2 :12, 16, 61, 69, 79|
f1 :∅
f2 :∅

Mischphase 4.

g1 :∅
g2 :∅
f1 :2, 9, 12, 15, 16, 30, 38, 48, 61, 69, 72, 79, 99|
f2 :∅

Wir wenden uns dem allgemeinen Fall zu. Wir brauchen für unsere Rahmenklasse vier
Hauptdatenfelder f1 , f2 , g1 , g2 vom Typ Datei“. Hinzu kommt ein Datenfeld runs vom

Typ Integer. Die Eingabe erfolgt auf g1 , die Ausgabe auf f1 oder g1 . Das Datenfeld runs
hält (zwischen den Mischphasen) die Anzahl der Läufe in allen Dateien.

Algorithmus 9.15 (Initialisierung)


Methodenkopf:
InitialRuns(Datei k, ℓ1 , ℓ2 )
Vorbedingungen:
1.) Die aus n Schlüsseln bestehende Eingabe befindet sich in der Datei k.
2.) Alle anderen Dateien sind leer.
Nachbedingungen:
1.) In ℓ1 sind ⌈n/2⌉ viele Läufe der Länge eins.
2.) In ℓ2 sind ⌊n/2⌋ viele Läufe der Länge eins.
3.) Alle anderen Dateien sind leer.
4.) runs = n.
Rumpf:
runs ← Anzahl der Schlüssel in g1
Verteile die Schlüssel aus k abwechselnd zu Läufen der Länge eins auf ℓ1 und ℓ2 .

126
Aufgrund der zu Eingang dieses Abschnittes gemachten Bemerkung beobachten wir,
daß die Anzahl der Plattenzugriffe von Algorithmus 9.15 ein O (n/b) ist. Das trifft aus
demselben Grund auch für den folgenden Algorithmus 9.16 zu.

Algorithmus 9.16 (Mischen)


Methodenkopf:
merge(Datei k1 , k2, l1 , l2 )
Vorbedingungen:
1.) runs > 1
2.) In k1 und k2 befindet sich eine Schlüsselmenge S in runs vielen Läufen.
3.) In k1 sind ⌈runs/2⌉ viele Läufe.
4.) In k2 sind ⌊runs/2⌋ viele Läufe.
5.) Die Dateien l1 und l2 sind leer.
Nachbedingungen:
1.) In l1 und l2 befindet sich dieselbe Menge S in r := ⌈runs/2⌉ vielen Läufen.
2.) In l1 sind ⌈r/2⌉ viele Läufe.
3.) In l2 sind ⌊r/2⌋ viele Läufe.
4.) Die Dateien k1 und k2 sind leer.
Rumpf:
Mische die Läufe aus k1 und k2 paarweise von links nach rechts,
und speichere sie abwechseln in l1 und l2 mit l1 beginnend ab.
runs ← ⌈runs/2⌉
Nun sind wir auf das externe Mergesort vorbereitet.

Algorithmus 9.17 (Externes Mergesort)


Methodenkopf:
externalMergesort()
Vorbedingung:
1.) Die aus n Schlüsseln bestehende Eingabe befindet sich in der Datei g1 .
2.) Alle anderen Dateien sind leer.
Nachbedingungen:
Die Datei f1 oder die Datei g1 enthält die sortierte Folge.
Rumpf:
Großschritt 1.
Führe InitialRuns(g1 , f1 , f2 ) aus.
even ← true.
Großschritt 2.
Führe aus
Falls even, so führe merge(f1 , f2 , g1 , g2 ) aus.
Andernfalls führe merge(g1 , g2 , f1 , f2 ) aus.

127
even ← ¬(even)
bis daß runs = 1.

Wir kommen zur Analyse von Algorithmus 9.17. Dazu nennen wir jeden Durchlauf der
Führe-aus-bis-daß-Schleife eine Phase. Wir erinnern uns, daß wir b Schlüssel je Seite haben.
Wegen der Halbierung des Datenfeldes runs je Phase gibt es ⌈log2 n⌉ Phasen. In jeder Phase
wird jeder Datensatz genau einmal gelesen, und folglich gibt es O (n/b) Seitenzugriffe.
Die Anzahl der Seitenzugriffe insgesamt ist ein O (n/b · log2 n). Die CPU-Zeit ist, wie bei
Algorithmus 8.90, um dessen iterative Variante es sich handelt, ein O (n log2 n).

128
Literaturverzeichnis

[GD03] R. H. Güting and St. Dieker. Datenstrukturen und Algorithmen. Leitfäden der
Informatik. Teubner Verlag, Stuttgart, Leipzip, Wiesbaden, 2003.

129
Kapitel 10

Grundlegende Algorithmen für


ungerichtete Graphen

Dieses Kapitel setzt den Inhalt des Abschnitts 1.5 des Kapitels 1 voraus. Als ergänzende
Lektüre ist [CLRS01] besonders gut geeignet. Die Darstellung der MST-Algorithmen folgt
[Tar83].

10.1 Datenstrukturen für Graphen


Adjazenzmatrizen. Für einen Graphen G = (V, E) mit n Knoten gehen wir davon aus,
daß die Knoten von 0 bis n − 1 durchnumeriert sind. Anders ausgedrückt, denken wir die
Knoten als Folge v0 , v1 , . . . , vn−1 aufgelistet. (Betrachten wir beispielsweise den Graphen
aus Abbildung 1.1, so ist die natürliche Numerierung die folgende: v0 = A, v1 = B,
. . ., v6 = G.) Nun ordnen wir G die folgende n × n Matrix A(G) zu: Der Eintrag von
A(G) in der i-ten Zeile und der j-ten Spalte ist genau dann 1, wenn (vi , vj ) eine Kante
in G ist. Andernfalls ist der Eintrag 0. Da wir ungerichtete Graphen betrachten, ist die
Adjazenzmatrix symmetrisch.
Der Graph aus Abbildung 1.1 hat dann die Adjazenzmatrix
 
0 1 1 0 0 1 1
1 0 0 0 0 0 0
 
1 0 0 0 0 0 0
 
0 0 0 0 1 1 0
 
0 0 0 1 0 1 1
 
1 0 0 1 1 0 0
1 0 0 0 1 0 0

Adjazenzmatrizen sind für dicke Graphen (d.h. mit Ω(n2 ) vielen Kanten), bei denen sich
die Menge der Knoten über die gesamte Anwendung nicht verändert, besonders speicher-
und zeiteffizient.

131
Adjazenzlisten. Hier wird für jeden Knoten vi des Graphen eine Liste vi : vj1 , . . . , vjki
seiner Nachfolger gehalten.
Die Adjazenzlisten des Graphen aus Abbildung 1.1 sehen so aus:

X Liste der Nachbarn von X


A F, C, B, G
B A
C A
D E, F
E F, G, D
F A, D, E
G A, E

Adjazenzlisten bieten sich bei dünnen Graphen – Graphen mit relativ wenigen Kanten
– an, oder bei Graphen, bei denen die Knotenmenge während der Anwendung variabel ist.
Kantenlisten sind eine Erweiterung von Adjazenzlisten. Man hält für jeden Knoten des
Graphen die Liste der zu ihm inzidenten Kanten. Man wird Kantenlisten einsetzen, wenn
die Bedingungen für den Einsatz von Adjazenzlisten gegeben sind, und man Kantenobjekte
braucht, um die Algorithmen transparent entwerfen und implementieren zu können.
Die Kantenlisten des Graphen aus Abbildung 1.1 sehen so aus:

X Liste der zu X inzidenten Kanten


A (A,F), (A,C), (A,B), (A,G)
B (B,A)
C (C,A)
D (D,E), (D,F)
E (E,D), (E,F), (E,G)
F (F,A), (F,D), (F,E)
G (G,A), (G,E)

Wir entscheiden uns für Kantenlisten. Jeder Knoten und jede Kante bekommt ihr ei-
genes Objekt. Das ist für uns bequem, da wir unsere Algorithmen sowohl Knoten als auch
Kanten markieren lassen werden.
Wenden wir uns zuerst unserer Kantenklasse Edge zu. Die Schlüsselinformation für ei-
ne Kante ist für uns die Zweimenge ihrer Endknoten. (Das heißt, zwei Kantenobjekte sind
genau dann gleich, wenn ihre Endknotenmengen gleich sind. Dies findet seine Rechtferti-
gung darin, daß wir im Abschnitt Abschnitt 1.5 Mehrfachkanten ausgeschlossen haben.)
Für diese sind zwei Datenfelder vorgesehen.
Die Knotenklasse Vertex hat als Hauptdatenfeld die Liste edges der zu dem aktuellen
Knoten inzidenten Kantenobjekte.
Die Graphen selbst sind Inkarnationen einer Klasse UGraph, deren Hauptdatenfeld die
Liste vertices der zu dem Graphen gehörigen Knoten ist.

132
10.2 Berechnung eines aufspannenden Baumes für un-
gerichtete Graphen
Um Kanten danach klassifizieren zu können, ob sie zu dem aufzubauenden aufspannenden
Wald gehören, gibt es ein Datenfeld classification, das die Werte unclassifiedEdge,
treeEdge und otherEdge enthalten kann.
Die Algorithmen dieses Abschnitts verwenden eine Warteschlange oder einen Stapel
von Knoten. Jeder Knoten muß da genau einmal durch. Um einem Knoten diesen Status
sofort ansehen zu können, hat die Klasse Vertex ein Datenfeld color, das die Werte white,
grey und black annehmen kann: Die Farbe white bedeutet, daß der Knoten noch nicht
in der Warteschlange bzw. auf dem Stapel war. Die Farbe grey zeigt an, daß der Knoten
gerade dort ist. Die Farbe black schließlich verkündet, er wurde bereits von dort entfernt.
Darüber hinaus ist ein Datenfeld n components mit Werten in den natürlichen Zahlen
hilfreich, das nach Abschluß unserer Algorithmen die Anzahl der Zusammenhangskompo-
nenten des aktuellen Graphen hält.
Wir beginnen mit den Hilfsalgorithmen Breitensuche (engl. breadth–first search, ab-
gekürzt bfs) und Tiefensuche (engl. depth–first search, abgekürzt dfs), denen jeweils ein
sogenannter Wurzelknoten v0 , an dem die Suche beginnt, übergeben wird. Deren Spezifi-
kation sieht so aus:

Vorbedingung: (i) Der übergebene Knoten v0 gehört zur Knotenmenge V des aktuellen
Graphobjekts G = (V, E).
(ii) Das aktuelle Graphobjekt G = (V, E) ist zusammenhängend.
(iii) Alle Knoten von G haben die Farbe white, alle Kanten tragen das Klassifizie-
rungsmerkmal unclassifiedEdge.

Nachbedingung: (i) Alle Knoten von G tragen die Farbe black.


(ii) Alle Kanten von G sind entweder als treeEdge oder als otherEdge klassifiziert.
(iii) Der Teilgraph T = (V, {e ∈ E | e ist treeEdge}) ist ein aufspannender Baum
von G.

Wir beginnen mit dem Algorithmus bfs(v0 ).

Algorithmus 10.1 (Breitensuche von einem Knoten aus)


Methodenkopf:
bfs(Vertex v0 )
Rumpf:
Großschritt 1.
Erzeuge eine (leere) Warteschlange x für Knoten.
Großschritt 2. [Berühre die Wurzel]
2.1. Färbe die Wurzel v0 mit der Farbe grey ein.
2.2. Füge v0 in die Warteschlange x ein.

133
Großschritt 3. [Besuch beim Spitzenknoten der Warteschlange]
Führe aus
3.1.[Beginne den Besuch]
Binde das Spitzenelement von x an Variable v.
Entferne das Spitzenelement aus der Warteschlange.
3.2.[Berühre alle noch unberührten Nachbarn des Knotens v]
Für alle zu v inzidenten, bisher unklassifizierten Kanten e führe aus:
Binde den von v verschiedenen Endknoten von e an u.
Falls die Farbe von u white ist, so führe aus:
Versehe e mit der Klassifikation treeEdge.
Berühre u: Färbe u grey und füge ihn in die Schlange x ein.
Andernfalls klassifiziere e als otherEdge.
3.3.[Beende den Besuch]
Färbe v mit der Farbe black.
solange die Warteschlange x nicht leer ist.

Machen wir uns zunächst anhand des Graphen aus Abbildung 1.1 klar, wie der Al-
gorithmus arbeitet. Ist der dort dargestellte Graph das aktuelle Objekt und wird dieses
gebeten, bfs(A) auszuführen, so hängt das Ergebnis sicherlich von der Reihenfolge ab, in
der die zu dem jeweiligen Spitzenknoten inzidenten Kanten aufgezählt werden. Wir wol-
len annehmen, daß die zu einem Knoten inzidenten Kanten in lexikografischer Ordnung
bezüglich des Schlüssels des anderen“ Knotens der Kante aufgezählt werden:

v Liste der zu v inzidenten Kanten e


A (A,B), (A,C), (A,F), (A,G)
B (B,A)
C (C,A)
D (D,E), (D,F)
E (E,D), (E,F), (E,G)
F (F,A), (F,D), (F,E)
G (G,A), (G,E)

(In der Terminologie des Algorithmus variieren die Kanten der Tabelle wie das Varia-
blenpaar (v,u).)
Wir notieren nun für jedes i von 1 bis 7 – gilt es doch 7 Knoten zu besuchen – den
Inhalt der Warteschlange vor dem i–ten Durchlauf der Schleife als Folge von Knoten, wobei
links stets das im i–ten Durchlauf zu besuchende Spitzenelement steht. Zusätzlich schrei-
ben wir uns auf, welche Kanten im i–ten Durchlauf das Prädikat treeEdge und welche das
Prädikat otherEdge bekommen. Die folgende Tabelle leistet das Gewünschte:

134
Durchlauf Inhalt von x neue treeEdge–Kanten neue otherEdge–Kanten
1 A (A,B), (A,C), (A,F), (A,G) keine
2 B, C, F, G keine keine
3 C,F,G keine keine
4 F,G (F,D), (F,E) keine
5 G,D,E keine (G,E)
6 D,E keine (D,E)
7 E keine keine
Im Ergebnis unserer Rechnung sind die Kanten des Graphen wie in Abbildung 10.1
angegeben klassifiziert.

treeEdge treeEdge treeEdge treeEdge

F C B G

treeEdge treeEdge

E D
otherEdge

otherEdge

Abbildung 10.1: Ein Beispiel zur Breitensuche

Bevor wir uns um die Korrektheit und die Laufzeit unseres Algorithmus kümmern,
untersuchen wir, was passiert, wenn wir zur Aufbewahrung“ von noch zu besuchenden

Knoten statt einer Schlange einen Stapel verwenden. Heraus kommt die Tiefensuche, die
sich nur in der Reihenfolge des Besuchs der entdeckten Knoten von der Breitensuche un-
terscheidet. Die Namen rühren daher, daß mit einer Schlange zuerst in die Breite gesucht
wird und mit einem Stapel zuerst in die Tiefe.

Algorithmus 10.2 (Tiefensuche von einem Knoten aus)


Methodenkopf:
dfs(Vertex v0 )

135
Rumpf:
Großschritt 1.
Erzeuge einen (leeren) Stapel x für Knoten.
Großschritt 2. [Berühre die Wurzel]
2.1. Färbe die Wurzel v0 mit der Farbe grey ein.
2.2. Lege v0 auf den Stapel x.
Großschritt 3. [Besuch beim Spitzenknoten des Stapels]
Führe aus
3.1.[Beginne den Besuch]
Binde das Spitzenelement von x an Variable v.
Entferne das Spitzenelement vom Stapel.
3.2.[Berühre alle noch unberührten Nachbarn des Knotens v]
Für alle zu v inzidenten, bisher unklassifizierten Kanten e führe aus:
Binde den von v verschiedenen Endknoten von e an u.
Falls die Farbe von u white ist, so führe aus:
Versehe e mit der Klassifikation treeEdge.
Berühre u: Färbe u grey und lege ihn auf den Stapel x.
Andernfalls klassifiziere e als otherEdge.
3.3.[Beende den Besuch]
Färbe v mit der Farbe black.
solange der Stapel x nicht leer ist.

Verdeutlichen wir uns den Unterschied von Algorithmus 10.2 zu Algorithmus 10.1,
indem wir den Graphen aus Abbildung 1.1 diesem Algorithmus unterwerfen.
Als Protokoll der Rechnung“ erhalten wir die folgende Tabelle, wobei das Spitzenele-

ment des Stapels jeweils links steht:
Durchlauf Inhalt von x neue treeEdge–Kanten neue otherEdge–Kanten
1 A (A,B), (A,C), (A,F), (A,G) keine
2 G, F, C, B (G,E) keine
3 E, F, C, B (E,D) (E,F)
4 D, F, C, B keine (D,F)
5 F, C, B keine keine
6 C, B keine keine
7 B keine keine

Im Ergebnis werden die Kanten wie in der Abbildung 10.2 dargestellt klassifiziert.
Als nächstes geht es darum, die Korrektheit unserer Algorithmen nachzuweisen.

Lemma 10.3 Die folgende Bedingung Inv ist eine Invariante der Schleife des Großschrit-
tes 3 der Algorithmen bfs(v0 ) und dfs(v0 ):
Klausel 1: Der Teilgraph T des Eingabegraphen, dessen Knotenmenge aus allen grauen
und schwarzen Knoten besteht, und dessen Kanten die treeEdge–Kanten sind, ist ein
Baum.

136
A

treeEdge treeEdge treeEdge treeEdge

F C B G

otherEdge

otherEdge

E D
treeEdge

treeEdge

Abbildung 10.2: Ein Beispiel zur Tiefensuche

Klausel 2: Die Warteschlange (Der Stapel) enthält genau die grauen Knoten. Die weißen
Knoten waren noch nie in der Schlange (dem Stapel), die schwarzen haben sie (ihn) bereits
wieder verlassen.

Beweis. Die Invarianz von Klausel 2 der Bedingung Inv ist offensichtlich: Jeder weiße
Knoten, der grau gefärbt wird, muß sich sofort in die Warteschlange bzw. in den Stapel
einreihen. Ein Knoten, der aus der Schlange (dem Stapel) entfernt wurde, bekommt nach
dem Besuch die Farbe schwarz.
Die Invarianz von Klausel 1 beweisen wir durch vollständige Induktion über die Anzahl
der Schleifendurchläufe λ.
Induktionsanfang λ = 0. Vor dem ersten Durchlauf befindet sich nur der an die Methode
übergebene Wurzelknoten in der Schlange bzw. dem Stapel, ist also grau. Schwarze Knoten
und Tree-Kanten gibt es noch nicht.
Induktionsschritt von λ ≥ 0 auf λ + 1. Sei Tλ der in Klausel 1 angegebene Teil-
graph nach dem λ–ten Schleifendurchlauf. Nach Induktionsvoraussetzung handelt es sich
um einen Baum. Im λ + 1–ten Durchlauf kommen alle zum Spitzenknoten der Schlange
(des Stapels), der ja als grauer Knoten bereits zu Tλ gehört, adjazenten weißen Knoten
hinzu. Die jeweiligen Verbindungskanten werden Tree–Kanten. Damit ist klar, daß der
Graph Tλ+1 , der Graph aus Klausel 1 nach dem λ + 1–ten Durchlauf der Schleife, zusam-
menhängend ist. Warum ist Tλ+1 ebenfalls ein Baum? Wir verwenden die Bedingung 4 aus
Satz 1.35. Nach Induktionsvoraussetzung ist Tλ Baum und es gilt: E (Tλ ) = V (Tλ ) − 1. Im
λ + 1–ten Durchlauf der Schleife kommen genauso viele Knoten wie Kanten hinzu. Um-

137
klassifizierungen von Tree-Kanten und Other–Kanten finden nicht statt. Folglich gilt auch
E (Tλ+1 ) = V (Tλ+1 ) − 1. 

Satz 10.4 Sei G = (V, E) das aktuelle Graphobjekt, das die Vorbedingung erfüllt, und sei
v0 ∈ V ein beliebiger Wurzelknoten. Dann gilt:
Die Methoden bfs(v0 ) und dfs(v0 ) arbeiten korrekt und haben Laufzeit Θ(|E| + |V |).

Beweis. Die Korrektheit folgt unmittelbar aus Lemma 10.3: die Schleife in Großschritt
3 wird erst verlassen, wenn keine grauen Knoten mehr vorhanden sind, denn diese sind
nach Klausel 2 die Knoten in der Warteschlange bzw. dem Stapel. Daß alle Knoten ent-

deckt“, also grau gefärbt werden, liegt daran, daß der Graph (nach der Vorbedingung)
zusammenhängend ist.
Die entscheidende Beobachtung in Bezug auf die Laufzeit ist, daß nach Klausel 2 der
Schleifeninvarianten jeder Knoten genau einmal in die Schlange bzw. den Stapel einge-
reiht wird. Folglich gibt es genau |V | Schleifendurchläufe, von denen jeder offensichtlich
Θ(1 + degree(v)) Kosten verursacht, wobei v derjenige Knoten ist, der während des Durch-
laufs gerade besucht wird, und degree(v) den Grad des Knotens v bezeichnet. Nach Aussage
1.32 folgt nun die Behauptung. 

Zum Abschluß dieses Abschnittes müssen wir uns der Tatsache stellen, daß wir einem
sehr großen Graphen, wenn er uns durch Kantenlisten gegeben ist, nicht problemlos ansehen
können, ob er zusammenhängend ist. Dazu sehen wir uns die Algorithmen 10.1 und 10.2
nochmals an und stellen fest, daß sie auch den folgenden Vertrag erfüllen:

Vorbedingung: (i) Knoten v0 gehört zur Knotenmenge V des aktuellen Graphen G.


(ii) Ist Gv0 die Zusammenhangskomponente von G, in welcher der Knoten v0 liegt,
so sind alle Knoten von Gv0 weiß; alle Kanten von Gv0 tragen das Klassifizierungs-
merkmal unclassifiedEdge.

Nachbedingung: (i) Alle Knoten von Gv0 tragen die Farbe black.
(ii) Alle Kanten von Gv0 sind entweder als treeEdge oder als otherEdge klassifiziert.
(iii) Der Teilgraph Tv0 = (V (Gv0 ) , {e ∈ E(Gv0 ) | e ist treeEdge} ist ein aufspannen-
der Baum von Gv0 .

Wir betrachten die folgenden Algorithmen:

Algorithmus 10.5 (Breiten/Tiefensuche)


Methodenkopf:
breadthFirstSearch() bzw. depthFirstSearch()
Rumpf:
Großschritt 1.
Setze das Datenfeld n components des aktuellen Graphen auf 0.

138
Großschritt 2.
Durchlaufe mit dem Platzhalter v alle Knoten von G und führe aus:
Falls v noch weiß ist, so führe aus:
Erhöhe das Datenfeld n components um 1.
Führe bfs(v) bzw. dfs(v) aus.
Die Analyse der beiden Algorithmen 10.5 ist einfach: Zunächst stellt man ohne Proble-
me fest, daß sie den folgenden Vertrag erfüllen:
Vorbedingung: (i) Alle Knoten des aktuellen Graphen G = (V, E) sind weiß.
(ii) Alle Kanten von G tragen das Klassifizierungsmerkmal unclassifiedEdge.
(iii) Das Datenfeld n components ist 0.

Nachbedingung: (i) Alle Knoten von G tragen die Farbe black.


(ii) Alle Kanten von G sind entweder als treeEdge oder als otherEdge klassifiziert.
(iii) Der W = (V, {e ∈ E | e ist treeEdge} ist ein aufspannender Wald von G.
(iv) Das Datenfeld n components hält die Anzahl der Zusammenhangskomponenten
von G
Die Laufzeit der beiden Algorithmen, angesetzt auf den Graphen G, ist ebenfalls
Θ(|E(G)| + |V (G)|): Da nichts passiert, wenn der Läufer v in eine Zusammenhangskom-
ponente des aktuellen Graphen G fällt, deren aufspannender Baum bereits durch einen
früheren Aufruf von bfs bzw. dfs berechnet worden ist, gilt für die Laufzeit die folgende
Gleichung:
k
!
X
Θ |V (G)| + (|E(Gi )| + |V (Gi )|) = Θ(|E(G)| + |V (G)|),
i=1

wobei G1 , . . . , Gk die Zusammenhangskomponenten des Eingabegraphen G sind.

10.3 Minimale aufspannende Bäume


10.3.1 Begriffe. Ein allgemeiner generischer Algorithmus
Ein ungerichteter Graph G = (V, E) heißt ungerichtetes Netzwerk, wenn er zusätzlich mit
einer Kantenmarkierungsfunktion, einer Kostenfunktion

cost : E → N,

ausgestattet ist. (Als Kantengewichte kommen nicht nur natürliche, sondern auch ganze
oder reelle Zahlen in Betracht.)
Die Kosten einer Kantenmenge sind die Summe der Kosten ihrer Elemente. Die Kosten
eines Teilgraphen sind die Kosten seiner Kantenmenge. Ein aufspannender Baum eines
Netzwerks G heißt minimal, wenn er unter allen aufspannenden Bäumen von G minimale
Kosten hat.

139
Beobachtung 10.6 Die Existenz minimaler aufspannender Bäume (Abkürzung: MST )
ist wegen Beobachtung 1.36 und der Tatsache, daß es für einen endlichen Graphen nur
endlich viele aufspannende Bäume gibt, gesichert.

Im weiteren sei N = G = (V, E), cost ein zusammenhängendes ungerichtetes Netz-
werk. Wir fragen uns, unter welchen Bedingungen wir eine bestimmte Kante in einen
vorliegenden MST einwechseln können. Folgendes ist offensichtlich.

Beobachtung 10.7 (Einwechseln einer Kante in einen MST) Sei T ein MST von
N, und sei e ∈ E eine Kante, die nicht zu T gehört. Dann schließt die Hinzunahme von e
zu T einen Kreis in T ∪ {e}. Sind e1 , e2 , . . ., em und e genau die Kanten dieses Kreises,
so bezeichnen wir für jede Kante e′ ∈ {e1 , e2 , . . . , em } mit Te′ ⇄e denjenigen aufspannenden
Baum, den man aus T durch Austausch der Kante e′ gegen die Kante e erhält.
Dann ist

1. cost e ≥ cost e′ ;

2. der Baum Te′ ⇄e genau dann ein MST, wenn cost e = cost e′ ist.

Für die umgekehrte Frage benötigen wir den folgenden Begriff.

Definition 10.8 Ein Schnitt in G = (V, E) ist eine nichttriviale Partition (X, V \ X) der
Knotenmenge V von G.
Eine Kante e ∈ E kreuzt den Schnitt (X, V \ X), wenn der eine Endknoten von e zu
X, der andere Endknoten zu V \ X gehört.

Natürlich gilt.

Beobachtung 10.9 Sei (X, V \ X) ein Schnitt von G = (V, E), und sei e eine Kante, die
den Schnitt (X, V \ X) kreuzt.
Jeder Kreis C in G, der die Kante e enthält, enthält mindestens noch eine weitere den
Schnitt (X, V \ X) kreuzende Kante e′ 6= e.
Ist darüber hinaus T ein aufspannender Baum von G = (V, E), der die Kante e enthält,
und zerfällt T durch Entfernen der Kante e in die Zusammenhangskomponenten X und
V \ X, so gehört e′ nicht zu T .

Beobachtung 10.10 (Auswechseln einer Kante aus einem MST) Sei T ein MST
von N, und sei e ∈ E eine Kante, die zu T gehört. Seien ferner X und V \ X die Zu-
sammenhangskomponenten, in die T durch Entfernen der Kante e zerfällt.
Sind e, und e1 , e2 , . . ., em genau die Kanten aus E, die den Schnitt (X, V \ X) kreuzen,
und ist für jede Kante e′ ∈ {e1 , e2 , . . . , em } Te⇄e′ der aufspannende Baum, den man aus T
durch Austausch der Kante e gegen die Kante e′ erhält.
Dann ist

1. cost e ≤ cost e′ ;

140
2. der Baum Te⇄e′ genau dann ein MST, wenn cost e = cost e′ ist.

Unsere Algorithmen berechnen einen MST, indem sie die Kanten färben. Ist eine Kante
grün, so gehört sie zum MST, ist sie rot, so gehört sie nicht dazu. Wir brauchen Regeln, die
es erlauben festzustellen, ob zu einem Zeitpunkt während der Laufzeit eine noch ungefärbte
Kante grün- oder rotfärbbar ist.

Definition 10.11 1. Eine bisher ungefärbte Kante e ∈ E heißt genau dann grünfärb-
bar, wenn es einen Schnitt (X, V \ X) mit den folgenden Eigenschaften gibt:

– Die Kante e kreuzt den Schnitt (X, V \ X).


– Keine grüne Kante kreuzt den Schnitt (X, V \ X).
– Unter allen ungefärbten Kanten, die den Schnitt (X, V \ X) kreuzen, ist die
Kante e von minimalem Gewicht.

2. Eine bisher ungefärbte Kante e heißt genau dann rotfärbbar, wenn einen Kreis C in
G mit den folgenden Eigenschaften gibt:

– Der Kreis C enthält die Kante e.


– Der Kreis C enthält keine rote Kante.
– Unter allen ungefärbten Kanten, die auf dem Kreis C liegen, ist die Kante e von
maximalem Gewicht.

3. Eine bisher ungefärbte Kante heißt genau dann färbbar, wenn sie grün- oder rotfärb-
bar ist.

Alle Algorithmen, die wir in diesem Abschnitt studieren werden, sind Konkretisierungen
des folgenden allgemeinen generischen Algorithmus.

Algorithmus 10.12 (Allgemeiner generischer MST-Algorithmus)


Vorbedingung.
N = (G = (V, E), cost) ist ein zusammenhängendes Netzwerk mit |E| ≥ 1.
Alle Kanten aus E sind ungefärbt.
Nachbedingung.
Alle Kanten sind gefärbt.
Die grüngefärbten Kanten zusammen mit V bilden einen MST von N.
Rumpf.
Führe aus
Wähle eine färbbare Kante aus und färbe sie entsprechend ein.“

bis daß keine Kante mehr färbbar ist.

Lemma 10.13 Algorithmus 10.12 ist korrekt.

141
Beweis. Zum Beweise der Korrektheit von Algorithmus 10.12 genügt es offenbar zu
zeigen, daß die folgende Bedingung eine Invariante von dessen Führe-aus-Schleife ist. Dazu
sei t die Anzahl der bisherigen Iterationen dieser Schleife, und seien
(red) (green)
Et , Et ⊆E

die Menge der bis zu diesem Zeitpunkt rot bzw. grüngefärbten Kanten.

Klausel 1. Es existiert ein MST Tt mit


(green)
Et ⊆ E (Tt ) (10.1)
(red)
Et ∩ E (Tt ) = ∅ (10.2)

Klausel 2. Falls

(red)
Et < |E| − |V | + 1

ist, so gibt es eine rotfärbbare Kante.

Klausel 3. Falls

(green)
Et < |V | − 1

ist, so gibt es eine grünfärbbare Kante.

Invarianz von Klausel 1. Wir führen den Beweis durch vollständige Induktion über die
Anzahl t der bisherigen Iterationen der Schleife des Rumpfes von Algorithmus 10.12.
Der Induktionsanfang t = 0 ist eine unmittelbare Folge von Beobachtung 10.6.
Induktionsanfang t ր t + 1. Sei e diejenige Kante, die im (t + 1)-ten Durchlauf der
Schleife gefärbt wird.
Fall 1. Die Kante e wird grüngefärbt und e ∈ E (Tt ) oder die Kante e wird rotgefärbt
und e 6∈ E (Tt ). Dann kann man Tt+1 als Tt wählen.
Fall 2: Die Kante e wird grüngefärbt und e 6∈ E (Tt ). Wir erhalten Tt+1 aus Tt durch
Einwechseln der Kante e unter Verwendung von Beobachtung 10.7 und Definition 10.11
(1): Sei (X, V \ X) der Schnitt, vermöge dessen die Kante e im (t + 1)-ten Durchlauf der
Schleife grüngefärbt wird. Wir betrachten den Teilgraphen Tt ∪ {e}, in dem durch e ein
Kreis C geschlossen wird. Da die Kante e den Schnitt (X, V \ X) kreuzt, gibt es nach
Beobachtung 10.9 eine zweite Kante e′ 6= e auf C, die folglich sogar in Tt liegt und den
Schnitt (X, V \ X) ebenfalls kreuzt.
Die Kante e′ ist unmittelbar nach dem Einfärben von e noch ungefärbt, denn wäre sie
schon
– grün, so könnte nach Definition 10.11 (1) (X, V \ X) nicht der Schnitt sein, längs
dessen e grüngefärbt wird;

142
– rot, so widerspräche das Klausel 1 (10.2) zum Zeitpunkt t und damit der Induktions-
voraussetzung, da ja e′ ∈ Tt ist.

Wegen e′ ∈ Tt folgt unter Verwendung von Beobachtung 10.7 (1)

cost(e) ≥ cost(e′ ).

Wegen Definition 10.11 (1) folgt

cost(e) ≤ cost(e′ )

und somit

cost(e) = cost(e′ ).

Nun können wir Beobachtung 10.7 (2) anwenden und erhalten, daß Tt+1 := (Tt )e′ ⇄e ein
MST ist.
Fall 3: Die Kante e wird rotgefärbt und e ∈ E (Tt ). Wir erhalten Tt+1 aus Tt durch
Auswechseln der Kante e unter Verwendung von Beobachtung 10.10 und Definition 10.11
(2): Sei C der Kreis, längs dessen die Kante e im (t + 1)-ten Durchlauf der Schleife rot-
gefärbt wird. Wir betrachten den Teilgraphen Tt \ {e}, der durch Entfernen von e in zwei
Zusammenhangskomponenten X und V \ X zerfällt, die einen Schnitt (X, V \ X) bilden.
Da die Kante e diesen Schnitt kreuzt, gibt es nach Beobachtung 10.9 eine zweite Kante
e′ 6= e auf C, die den Schnitt (X, V \ X) ebenfalls kreuzt und die nicht zu Tt gehört.
Die Kante e′ ist unmittelbar nach dem Einfärben von e noch ungefärbt, denn wäre sie
schon

– rot, so könnte nach Definition 10.11 (2) C nicht der Kreis sein, längs dessen e rot-
gefärbt wird;

– grün, so folgte aus Klausel 1 (10.1) zum Zeitpunkt t und damit aus der Induktions-
voraussetzung, daß e′ ∈ Tt wäre. Widerspruch!

Wegen Definition 10.11 (2) folgt

cost(e) ≥ cost(e′ ).

Wegen e ∈ Tt folgt aus Beobachtung 10.10 (1)

cost(e) ≤ cost(e′ )

und somit

cost(e) = cost(e′ ).

143
Nun können wir Beobachtung 10.10 (2) anwenden und erhalten, daß Tt+1 := (Tt )e⇄e′ ein
MST ist.
Invarianz von Klausel 2. Angenommen, es ist

(red)
t+1 < |E| − |V | + 1.
E (10.3)
Wir müssen zeigen, daß es eine rotfärbbare Kante gibt. Dazu betrachten wir den MST
Tt+1 , dessen Existenz wir zum Nachweis der Invarianz von Klausel 1 bewiesen haben.
Wegen (10.3) und der Tatsache, daß Tt+1 genau |V | − 1 Kanten enthält, gibt es eine Kante
(red)
e, die nicht in Et+1 und nicht in Tt+1 liegt. Folglich ist sie noch ungefärbt. Wir nehmen
sie zu Tt+1 hinzu, und wissen, daß sie in Tt+1 ∪ {e} einen Kreis geschlossen hat. Auf diesem
Kreis befindet sich keine rote Kante, da Tt+1 keine roten Kanten enthält. Folglich ist die
Kante e rotfärbbar.
Invarianz von Klausel 3. Angenommen, es ist

(green)
Et+1 < |V | − 1. (10.4)
Wir müssen zeigen, daß es eine grünfärbbare Kante gibt. Dazu betrachten wir den MST
Tt+1 , dessen Existenz wir zum Nachweis der Invarianz von Klausel 1 bewiesen haben.
Wegen (10.4) und der Tatsache, daß Tt+1 genau |V | − 1 Kanten enthält, gibt es eine Kante
(green)
e ∈ Tt+1 , die nicht in Et+1 liegt. Da Tt+1 keine roten Kanten enthält, ist e noch ungefärbt.
Entfernt man e aus Tt+1 , so erhält man aus den Zusammenhangskomponenten, in die Tt+1
dann zerfällt, einen Schnitt, längs dessen man e grünfärben kann. 

Bemerkungen.
• Klausel drei der Schleifeninvariante aus Lemma 10.13 sichert, daß man für einen
konkreten Algorithmus nur eine grüne Regel benötigt. Man färbt entsprechend dieser
Regeln |V | − 1 Kanten grün. Der Rest gehört dann nicht zum MST. (Etwas analoges
gilt für Klausel zwei und die rote Regel.)
• In den Abschnitten 10.3.2 und 10.3.3 werden wir Algorithmen angeben, die weder
Schnitte noch Kreise inspizieren. Es ist unsere Aufgabe, die dort angegeben Färbungs-
regeln auf die Regeln aus Definition 10.11 zurückzuführen.

10.3.2 Der Algorithmus von Prim


Sei N = (G, cost), wobei G = (V, E) ein ungerichteter Graph ist, ein zusammenhängendes
Netzwerk mit |E| ≥ 1. Im Algorithmus von Prim wird, von einem beliebigen, aber festen
Knoten u ∈ V ausgehend, ein MST aufgebaut. Es gibt nur eine grüne Färbungsregel.
Nachdem man |V | − 1 Kanten grüngefärbt hat, werden die restlichen Kanten rotgefärbt.
Der Algorithmus von Prim läßt sich besonders gut verstehen, wenn man auch für ihn
einen generischen Rahmen hat.

Algorithmus 10.14 (Prims generischer MST-Algorithmus)

144
Methodenkopf.
genericPrim(Vertex u)
Vorbedingung.
N = (G, cost) ist ein zusammenhängendes Netzwerk mit |E| ≥ 1.
Der Knoten u gehört zu V .
Alle Kanten aus E sind ungefärbt.
Nachbedingung.
Alle Kanten sind gefärbt.
Die grüngefärbten Kanten zusammen mit V bilden einen MST von N.
Großschritt 1.
Führe aus
Wähle eine Prim-grünfärbbare Kante aus und färbe sie grün.“

bis daß die Anzahl der grünen Kanten gleich |V | − 1 ist.
Großschritt 2.
Färbe alle noch ungefärbten Kanten rot.

Wir fixieren ein beliebige u ∈ V . Wann ist eine Kante Prim-grünfärbbar ? Dazu be-
trachten wir den t + 1-ten Durchlauf der Schleife in Großschritt 1 von Algorithmus 10.14.
Sei

Wt := (V, {e | e ist nach t Durchläufen grüngefärbt}) (10.5)

der grüne Wald zu diesem Zeitpunkt. (Daß es sich dabei tatsächlich um einen Wald handelt,
folgt unmittelbar aus Definition 10.15.) Sei ferner Ut diejenige Zusammenhangskomponente
von Wt , zu welcher der Knoten u gehört:

u ∈ Ut . (10.6)

Vor dem ersten Duchlauf ist

W0 = (V, ∅) (10.7)

und

U0 = ({u}, ∅) . (10.8)

Definition 10.15 Eine bisher ungefärbte Kante e ∈ E ist im (t + 1)-ten Durchlauf der
Schleife aus Großschritt 1 von Algorithmus 10.14 genau dann Prim-grünfärbbar, wenn sie

– die Zusammenhangskomponente Ut mit einer anderen Zusammenhangskomponente


von Wt verbindet;

– unter allen Kanten mit dieser Eigenschaft von minimalem Gewicht ist.

145
Bemerkung. Offenbar bestehen zu jedem Zeitpunkt t die von Ut verschiedenen Zusam-
menhangskomponenten von Wt aus genau einem Knoten.
Um zu zeigen, daß Algorithmus 10.14 korrekt ist, genügt es, die Prim-Grünfärbbarkeit
auf die Grünfärbbarkeit aus Definition 10.11 zurückzuführen.
Lemma 10.16 Die Prim-Grünfärbbarkeit ist eine Spezialisierung der Grünfärbbarkeit aus
Definition 10.11.

Beweis. Man muß lediglich den Schnitt angeben, längs dessen eine Kante im (t + 1)-ten
Durchlauf der Schleife aus Großschritt 1 von Algorithmus 10.14 grüngefärbt wird. Das ist
(Ut , V \ Ut ) . (10.9)


Wir kommen zur effizienten Implementation von Algorithmus 10.14. Das Hauptproblem
besteht darin, eine Kante minimalen Gewichts zu finden, die Ut mit einer anderen Zusam-
menhangskomponente von Wt verbindet. Dazu benötigen wir den Begriff des Randknotens
und der (zum Zeitpunkt t) hellgrünen Kante.
Definition 10.17 Ein Knoten v aus der Knotenmenge V heißt Randknoten von Ut , wenn
1. er nicht zu Ut gehört;
2. es eine Kante gibt, die v mit einem Knoten aus Ut verbindet.
Eine Kante e heißt zum Zeitpunkt t hellgrün, wenn es einen Randknoten v von Ut gibt,
so daß die Kante e
1. den Knoten v mit Ut verbindet;
2. unter allen Kanten, die ebenfalls den Knoten v mit Ut verbinden, von minimalem
Gewicht ist.
Offensichtlich gilt.
Beobachtung 10.18 (Charakterisierung der Prim-Grünfärbbarkeit) Eine Kante ist
genau dann Prim-grünfärbbar, wenn sie unter allen hellgrünen Kanten von minimalem Ge-
wicht ist.
Alles, worauf es nach Beobachtung 10.18 nun ankommt, ist, eine hellgrüne Kante von
minimalem Gewicht schnell aufzufinden.
Die Idee. Wir halten alle Randknoten von Ut in einer Prioritätswarteschlange q, wobei
die Priorität eines Randknotens das Gewicht einer seiner hellgrünen Kanten ist. Im Gegen-
satz zu der Prioritätswarteschlange aus Abschnitt 8.4.4 gilt hier: Je kleiner der Wert des
Datenfeldes priority, desto größer die Priorität. Dann gilt aber für das Spitzenelement
vt+1 der Warteschlange, daß man seine hellgrüne Kante grünfärben kann: Ut+1 = Ut ∪{vt+1 }.
Nun muß die Warteschlange, die den Rand von Ut+1 halten soll, aktualisiert werden:

146
1. Der Spitzenknoten vt+1 wird aus der Warteschlange entfernt.

2. Alle zu vt+1 adjazenten Knoten w müssen besucht werden:

(a) Gehört der Knoten w nicht zum Rand von Ut , so muß er mit dem Gewicht der
Kante (vt+1 , w), die offenbar zu diesem Zeitpunkt seine hellgrüne Kante ist, in
die Proritätswarteschlange q aufgenommen werden.
(b) Gehört w schon zum Rand von Ut , so kann durch das Hinzukommen von vt+1
beim Übergang von Ut zu Ut+1 eine Veränderung in der Priorität von w ein-
getreten sein: Man muß das Gewicht der Kante (vt+1 , w) mit dem Gewicht der
bisherigen hellgrünen Kante von w vergleichen. Hat jene ein geringeres Gewicht,
ist sie nun die hellgrüne Kante von w. Der Knoten w muß dann in der Prio-
ritätswarteschlange q befördert werden (siehe Algorithmus 8.84).

Wie sehen die zusätzlichen Hauptdatenfelder der Klasse Vertex aus? Die Prioritäts-
warteschlange q spielt für Prims Algorithmus eine ähnliche Rolle wie die Warteschlange
bzw. der Stapel für die Algorithmen aus Abschnitt 10.2: Jeder Knoten muß genau einmal
hinein und genau einmal heraus, wobei wir stets für jeden Knoten darüber im Bilde sein
müssen, in welcher der drei möglichen Phasen er gerade ist. Dazu verwalten wir ein Da-
tenfeld color mit den möglichen Werten white, grey und black: Die Farbe white steht
dafür, daß der Knoten noch nicht in der Warteschlange war. Die Farbe grey zeigt seine
Zugehörigkeit zur Warteschlange und damit zum Rand von U — wir lassen ab jetzt den
Index t“ meist weg — an. Die Farbe black schließlich weist aus, daß der Knoten seinen

Platz in U eingenommen hat.
Die folgenden beiden Datenfelder der Klasse Vertex sind nur dann von Bedeutung,
wenn der aktuelle Knoten Randknoten von U ist: Das Datenfeld lightgreen zeigt auf
eine hellgrüne Kante des aktuellen Knotens. Das Datenfeld priority trägt die Kosten der
hellgrünen Kante.
Schließlich brauchen wir ein carrier-Datenfeld, um die Prioritätswarteschlange, wie in
Abschnitt 8.4.4 dargestellt, effizient verwalten zu können.
Die Klasse Edge muß um ein Datenfeld color mit den möglichen Werten uncolored,
green und red ergänzt werden.
Nun zu dem Algorithmus, der uns den Rumpf der Schleife in Großschritt 2 von Algo-
rithmus 10.14 implementiert.

Algorithmus 10.19 (Prims Grünfärberegel)


Methodenkopf.
prim-greencoloringrule(Vertex u)
Vorbedingung.
Ist U die Zusammenhangskomponente von W , zu der u gehört, so
ist |U| < |V | und die Prioritätswarteschlange q enthält alle Randknoten von U;

147
sind alle Randknoten von U grau;
sind alle Knoten aus U schwarz;
sind alle anderen Knoten weiß.
Nachbedingung.
Die Vorbedingung bleibt erhalten.
Die Menge U ist um ein Element vergrößert.
Großschritt 2.1.
v ← q.top(), q.remove(), v.color ← black
e ← v.lightgreen(), e.color ← green
Großschritt 2.2.
Durchlaufe mit w alle zu v adjazenten Knoten und führe aus.
2.2.1. Falls w.color() = white, so führe aus:
w.lightgreen ← (w, v), w.priority ← cost(w, v), w.color ← grey
q.add(w)
2.2.2. Falls w.color() = grey, so führe aus:
e ← (w, v)
Falls cost e < w.priority(), so führe aus.
w.lightgreen ← e
q.siftup(w, cost e)

Nun ist die Zeit reif für die finale Version von Prims Algorithmus.

Algorithmus 10.20 (Prims MST-Algorithmus)


Methodenkopf.
mstPrim(Vertex u)
Vorbedingung.
N = (G, cost) ist ein zusammenhängendes Netzwerk mit |E| ≥ 1.
Der Knoten u gehört zu V .
Alle Kanten aus E sind ungefärbt.
Nachbedingung.
Alle Kanten sind gefärbt.
Die grüngefärbten Kanten zusammen mit V bilden einen MST von N.
Großschritt 1.
Richte leere Prioritätswarteschlange q mit der Kapazität |V | − 1 ein.
Färbe u schwarz.
Durchlaufe mit w alle zu u adjazenten Knoten und führe aus.
w.lightgreen ← (u, w)
w.priority ← cost(u, w)
w.color ← grey
q.add(w)
Großschritt 2.
Führe aus

148
prim-greencoloringrule(u)
bis daß die Anzahl der grünen Kanten gleich |V | − 1 ist.
Großschritt 3.
Färbe alle noch ungefärbten Kanten rot.

Wir bemerken, daß die Schleife aus Großschritt 2 von Algorithmus 10.20 genau dann
abgebrochen wird, wenn die Prioritätswarteschlange leer ist.
Wie in Abschnitt 8.4.4 dargestellt, verwenden wir zur Implementation der Prioritäts-
warteschlage einen d-Heap. Dann gilt.

Satz 10.21 Die Laufzeit von Algorithmus 10.20 ist ein

O (d · |V | · logd |V | + |E| · logd |V |) . (10.10)

Beweis. Wir beobachten.

– Die Verwaltung der Prioritätswarteschlange ist, was die Laufzeit angeht, in Algorith-
mus 10.20 dominant. Für die Laufzeit von deren Operationen können wir Satz 8.87
anwenden.

– Jeder Knoten wird genau einmal in die Prioritätswarteschlange eingefügt und genau
einmal herausgenommen. Der Laufzeitbedarf dafür ist

O (d · |V | · logd |V |) .

– Es gibt höchstens soviele siftup-Operationen wie Kanten. Der Beitrag dieser Ope-
rationen zur Laufzeit ist ein

O (|E| · logd |V |) .

Korollar 10.22 Falls


 
|E|
d= 2+ (10.11)
|V |
| {z }
>2

ist, so ist die Laufzeit von Algorithmus 10.20 ein

O (|E| · logd |V |) . (10.12)

149
Beweis. Wegen (10.10) genügt es zu zeigen, daß
d · |V | = O (|E|)

ist. Da G zusammenhängend ist, gilt |E| ≥ |V | − 1. Wir erhalten


 
|E|
d · |V | = 2 + |V |
|V |
 
|E|
≤ 3+ |V |
|V |
= O (|E|) .


Korollar 10.23 Falls |E| = Ω (|V |1+ǫ ) für eine Konstante ǫ ∈ (0, 1) ist, so ist die Laufzeit
von Algorithmus 10.20 ein
 
1
O · |E| , (10.13)
ǫ
sofern d gemäß Gleichung 10.11 gewählt wird.

Beweis. Sei
|E| ≥ γ · |V |1+ǫ

für eine positive reelle Konstante γ. Wegen (10.12) genügt es,


 
1
logd |V | = O
ǫ
für
 
|E|
d= 2+ ≥ 2 + γ · |V |ǫ
|V |
zu zeigen. Wir erhalten

logd |V | ≤ log(2+γ·|V |ǫ ) |V |
ln |V |
=
ln (2 + γ · |V |ǫ )
ln |V |
=  
2
γǫ ln |V | + ln +1
γ|V |ǫ
| {z }
>0
1
< .
γǫ

150


Wir bemerken abschließend, daß Korollar 10.23 besagt, daß Algorithmus 10.20 für so-
genannte dicke Graphen in linearer Zeit arbeitet.

10.3.3 Der Algorithmus von Kruskal


Kruskals generischer Algorithmus
Wie beim Algorithmus von Prim, erleichtert auch im vorliegenden Falle ein generischer
Algorithmus das Verständnis.

Algorithmus 10.24 (Kruskals generischer MST-Algorithmus)


Methodenkopf.
genericKruskal()
Vorbedingung.
N = (G, cost) ist ein zusammenhängendes Netzwerk
mit n = |V | Knoten und m = |E| ≥ n − 1 Kanten.
Alle Kanten aus E sind ungefärbt.
Nachbedingung.
Alle Kanten sind gefärbt.
Die grüngefärbten Kanten zusammen mit V bilden einen MST von N.
Großschritt 1.
Sortiere die Kanten aus E mit aufsteigendem Gewicht:
e1 , e2 , . . . , em
Großschritt 2.
Für t = 0, 1, . . . , m − 1 führe aus:
Wende Kruskals Färberegel auf die Kante et+1 an.

Wie sieht Kruskals Färberegel für die Kante et+1 aus Großschritt 2 von Algorithmus
10.24 aus? Sei für t = 0, 1, . . . , m − 1

Wt := (V, {e | e ist nach t Durchläufen grüngefärbt}) (10.14)

der grüne Wald zu diesem Zeitpunkt. (Daß es sich dabei tatsächlich um einen Wald handelt,
müßte strenggenommen gezeigt werden. Diese Eigenschaft ergibt sich aus dem Folgenden
unmittelbar durch vollständige Induktion über t.)

Definition 10.25 Die Kante et+1 aus Großschritt 2 von Algorithmus 10.24 wird genau
dann grüngefärbt, wenn sie zwei Zusammenhangskompenten von Wt verbindet. Anderfalls
wird sie rotgefärbt.

Um zu zeigen, daß Algorithmus 10.24 korrekt ist, genügt es, die Kruskal-Färbbarkeit
auf die Färbbarkeit aus Definition 10.11 zurückzuführen.

151
Lemma 10.26 Die Kruskal-Grünfärbbarkeit ist eine Spezialisierung der Grünfärbbarkeit
aus Definition 10.11, die Kruskal-Rotfärbbarkeit eine Spezialisierung der Rotfärbbarkeit.

Beweis. Seien T1 , T2 , . . ., Tk die Zusammenhangskomponenten von Wt aus Gleichung


10.14, und sei et+1 = (a, b) die Kante aus Großschritt 2 von Algorithmus 10.24.
Fall 1. Die Knoten a und b gehören beide zu Ti für ein i = 1, 2, . . . , k. Dann schließt die
Kante et+1 in Ti ∪ {et+1 } einen Kreis, längs dessen sie gemäß Definition 10.11 rotgefärbt
werden kann.
Fall 2. Die Knoten a und b gehören zu unterschiedlichen
 Bäumen Ti bzw.
S  Tj (i 6= j) des
Waldes Wt . Dann kreuzt die Kante et+1 den Schnitt V (Ti ), k6=i V (Tk ) , längs dessen sie
gemäß Definition 10.11 grüngefärbt werden kann. 

Zur algorithmischen Umsetzung der Kruskalsche Färberegeln aus Definition 10.25 ver-
wenden wir eine Union-Find-Datenstruktur.

Spezifikation der Union-Find-Datenstruktur


Wir nehmen an, daß die Menge V gleich {1, 2, . . . , n} ist, wobei n zu Beginn der Anwendung
festgelegt und dann nicht mehr verändert wird. Wir wollen Partitionen einer Teilmenge
von V verwalten. Die nichtleeren Teilmengen, die eine solche Partition ausmachen, heißen
Blöcke. Jeder Block bekommt als Schlüsselinformation einen Repräsentanten aus seiner
Mitte zugeordnet.
Als Beispiel betrachten wir eine Partition der Teilmenge {1, 2, . . . , 10} der Menge

V = {1, 2, . . . , 11, 12} mit den Blöcken V1 = {5, 4, 1, 9} V2 = {8, 2, 3, 6} V3 = {7, 10}

wobei 5 der Repräsentant von V1 , 8 der Repräsentant von V2 und 7 der Repräsentant von
V3 sei. Die Elemente 11 und 12 gehören zu keinem Block.
Der Zustand unserer Datenstruktur ist eine Partition

W = {V1 , V2 , . . . , Vk }

einer Teilmenge der Menge V . Zusätzlich ist eine Funktion

k
[
rep : W → Vi
i=1

mit

rep(Vi ) ∈ Vi (∀ i = 1, 2, . . . , k),

definiert, für die es darüber hinaus keine weiteren Anforderungen gibt. Die folgenden Ope-
rationen werden unterstützt.

152
empty(Integer n). Erzeugt wird die leere Union-Find-Datenstruktur – der Zustand ist
W = ∅ –, die zur Verwaltung von Partionen von Teilmengen der Menge {1, 2, . . . , n}
geeignet ist.

makeSet(Integer x). Diese Operation hat zur Vorbedingung, daß das Element x mit
1 ≤ x ≤ n in keinem der Blöcke der aktuellen Partition enthalten ist. Sie garantiert,
daß ein Einer-Block {x} erzeugt und der Partition hinzugefügt wird.

find(Integer x) returns Integer liefert den Repräsentanten rep Vi des Blockes Vi , zu


dem das übergebene Element x gehört. Vorbedingung ist natürlich, daß es einen
solchen Block gibt.

union(Integer x, y) hat als Vorbedingung, daß die übergebenen Elemente x und y von-
einander verschiedene Repräsentanten von Blöcken des aktuellen Zustands der Da-
tenstruktur sind. Die Operation vereinigt diese beiden Blöcke zu einem neuen Block
und bestimmt einen Repräsentanten aus dessen Mitte.

Bemerkungen.
• Ausgehend von n Einerblöcken benötigt man genau n − 1 union-Operationen, um
alle Elemente in einem Block vereinigen zu können.
• Natürlich kann man auch Union-Find-Datenstrukturen über Mengen von Objekten
eines beliebigen Typs definieren.

Kruskals Algorithmus
Nun können wir unter Verwendung der Union-Find-Datenstruktur sehr leicht Algorithmus
10.24 durch den folgenden Algorithmus implementieren.

Algorithmus 10.27 (Kruskals MST-Algorithmus)


Methodenkopf.
mstKruskal()
Vorbedingung.
N = (G, cost) ist ein zusammenhängendes Netzwerk
mit n = |V | Knoten und m = |E| ≥ 1 Kanten.
Alle Kanten aus E sind ungefärbt.
Nachbedingung.
Alle Kanten sind gefärbt.
Die grüngefärbten Kanten zusammen mit V bilden einen MST von N.
Großschritt 1.
Sortiere die Kanten aus E mit aufsteigendem Gewicht:
e1 , e2 , . . . , em
Großschritt 2.

153
2.1. Erzeuge eine Union-Find-Datenstruktur greenForest:
greenForest.empty(n)
2.2. Für x = 1, 2, . . . , n führe aus.
greenForest.makeSet(x)
2.3. Für t = 0, 1, . . . , m − 1 führe aus:
Weise x und y die Endpunkte der Kante et+1 zu.
x ← greenForest.find(x)
y ← greenForest.find(y)
Falls x 6= y, so führe aus.
greenForest.union(x, y)
Färbe die Kante et+1 grün.
Andernfalls
färbe die Kante et+1 rot.
Es ist auf Grund der Spezifikation der Union-Find-Datenstruktur offensichtlich, daß
Algorithmus 10.27 korrekt arbeitet. Aber wie steht es um seine Laufzeit? Alles hängt
von der Implementation der Union-Find-Datenstruktur ab. Eine sehr effiziente Variante
besprechen wir im folgenden Abschnitt. Das Ergebnis nehmen wir schon an dieser Stelle
vorweg.
Dazu wiederholen wir die Definition des iterierten Logarithmus aus Kapitel 1.

g(0) = 1
g(j) = 2g(j−1) (j ≥ 1)

Das größte monoton wachsende zahlentheoretische Linksinverse der durch die vorste-
henden Rekursionsgleichungen definierten Funktion heißt iterierte Logarithmusfunktion zur
Basis 2. (Bezeichnungen: log∗2 ).
Die Bezeichnung iterierter Logarithmus ist durch Gleichung 1.22 aus dem Kapitel 1
motiviert:
(j)
log∗2 n := min{j ∈ N | ⌈log2 n⌉ = 1}

Gleichung 1.7 besagt in diesem Falle


(
j falls j − 1 ≥ 0 und g(j − 1) < n ≤ g(j);
log∗2 n =
0 falls n ≤ 1.

Wir ersehen aus den vorstehenden Gleichungen insbesondere, daß die Zahl log∗2 n angibt,
wie oft man die Zahl n zur Basis zwei logarithmieren muß, um eine Zahl ≤ 1 zu erhalten.
Zum Abschluß dieses Abschnitts wiederholen wir die Aussage über das Wachstum der
Funktion log∗2 n aus dem Kapitel 1 (siehe dort Gleichung 1.27).

g(0) = 1 g(1) = 2 g(2) = 4 g(3) = 16 g(4) = 216 = 65536 g(5) = 265536

154
Wir erhalten:

[0, 1] → 0 (1, 2] → 1 (2, 4] → 2 (4, 16] → 3 (16, 65536] → 4 (65536, 265536 ] → 5

Die Aussage, die Funktion log∗2 sei in dieser Welt kleiner oder gleich fünf, ist zwar
unmathematisch aber dennoch nicht unbegründet.

Satz 10.28 Unter Verwendung der Implementation der Union-Find-Datenstruktur aus


Abschnitt 10.4 ist die Laufzeit von Algorithmus 10.27 ein

O (|E| · log2 |V |) . (10.15)

Beweis. Die Laufzeit von Großschritt 1 ist ein O (|E| · log2 |E|) = O (|E| · log2 |V |).
In Großschritt 2 werden zuerst |V | makeSet-Operationen und dann |V | − 1 union- und
2|E| find-Operationen durchgeführt. Die Implementation der Union-Find-Datenstruktur
aus Abschnitt 10.4 sichert, daß dies in Zeit O (|E| · log∗2 |V |) geschehen kann. 

10.4 Eine effiziente Implementation der Union-Find


Datenstruktur
Die Union-Find-Datenstruktur haben wir in Abschnitt 10.3.3 spezifiziert. Nun wollen wir
eine besonders effiziente Implementation kennenlernen. Die Elemente der Menge V =
{1, 2, . . . , n} werden als Knoten eines zu den Wurzeln der Bäume gerichteten Waldes darge-
stellt, wobei ein Block einem Baum entspricht. Die Knoten des Baumes sind die Elemente,
die Wurzel der Repräsentant des Blockes. Der Block wird durch seine Wurzel repräsen-
tiert. Besonders bequem läßt sich ein solcher Wald durch ein Feld father : array[1 . . . n]
mit Werten in den ganzen Zahlen repräsentieren, wobei father[i] genau dann gleich j > 0
ist, wenn der Knoten j der Vater des Knotens i ist. Ein Knoten i gehört genau dann noch
nicht zur Union-Find-Datenstruktur, wenn father[i] = 0 ist.
Sei W ein Wald über V , sei T ein Baum dieses Waldes, und sei schließlich v ein Knoten
aus T . Wir bezeichnen mit SIZE v die Anzahl der Knoten des in v wurzelnden Teilbaumes
Tv des Baumes T .
Wie lautet der Eintrag im father-Feld für eine Wurzel? Will man die Operation
union(x, y) durchführen, wobei x und y Wurzeln sind, so ist es naheliegend, entweder
den Knoten x zum Vater des Knotens y zu machen oder umgekehrt. Es ist vernünftig
(siehe Lemma 10.34), die Wurzel des kleineren Baumes zum Sohn der Wurzel des größeren
Baumes zu machen. Um das umsetzen zu können, muß man für jede der aktuellen Wurzeln
deren Größe in der Datenstruktur halten. Da der father-Eintrag einer Wurzel v nicht dazu
gebraucht wird, auf den Vater zu verweisen, kann hier die Größe des zugehörigen Baumes
eingetragen werden: Für jede Wurzel v des Waldes W ist father[v] = − SIZE v. Folglich
gilt
father : array[1 . . . n] of {0, ±1, . . . , ±n}.

155
Wir betrachten ein Beispiel für den Zustand einer Union-Find-Datenstruktur für die
Menge V = {1, 2, . . . , 13}.

4 12 10 (10.16)

3 11 6

2 5 9 1 7 8

Die Wurzeln 4, 12 und 10 sind die Repräsentanten ihrer Blöcke. Das father-Feld sieht wie
folgt aus:

i 1 2 3 4 5 6 7 8 9 10 11 12 13
(10.17)
father[i] 6 11 4 −6 11 12 6 6 11 −1 4 −5 0

Nun sind wir in der Lage, die empty-, die makeSet- und die union-Operation zu imple-
mentieren.

Algorithmus 10.29 (empty-Operation)

Methodenkopf.
empty(Integer n)
Erzeuge ein Feld father vom Typ Integer mit dem Indexbereich {1, 2, . . . , n}.
Initialisiere jede Komponente dieses Feldes mit 0.

Algorithmus 10.30 (makeSet-Operation)

Methodenkopf.
makeSet(Integer x)
Vorbedingung.
x gehört nicht zum aktuellen Wald: father[x] = 0.
father[x] ← −1

Algorithmus 10.31 (union-Operation)

156
Methodenkopf.
union(Integer x, y)
Vorbedingung.
x 6= y sind Repräsentanten.
Falls |father[x]| ≤ |father[y]|, so führe aus:
father[y] ← father[y] + father[x]
father[x] ← y
return
father[x] ← father[x] + father[y]
father[y] ← x
Wir erkennen leicht, daß die Union-Find-Datenstruktur aus (10.16) nicht vermöge des
Algorithmus 10.31 zustande gekommen sein kann: Zum Zeitpunkt der Vereinigung von dem
in 4 wurzelnden Baum mit dem in 11 wurzelnden Baum war der in 11 wurzelnde größer.
Das gleiche trifft für die in 12 und in 6 wurzelnden Bäume zum Vereinigungszeitpunkt zu.
Ein mit Algorithmus 10.31 verträglicher Zustand sieht so aus:

11 6 10 (10.18)

2 5 9 4 1 7 8 12

3
Wenden wir union(11, 6) auf (10.18) an, so erhalten wir

11 10 (10.19)

2 5 9 4 6

3 1 7 8 12

Auch an einer einfachen Implementation der find-Operation soll es an dieser Stelle


nicht fehlen.

Algorithmus 10.32 (Einfache find-Operation)

157
Methodenkopf.
simpleFind(Integer x) returns Integer
Vorbedingung.
x gehört zum aktuellen Wald.
Rumpf.
Falls father[x] < 0, so return x.
return simpleFind(father[x])
Um die Vorbedingung einer union-Operation zu erfüllen, müssen insbesondere vorher
entsprechende makeSet-Operationen angewandt worden sein. Eine Folge von makeSet- und
union-Operationen, deren Vorbedingungen erfüllt und die damit ausführbar sind, heißt
zulässig.
Definition 10.33 Ein Wald über der Menge V = {1, 2, . . . , n}, der durch eine Folge
zulässiger makeSet- und union-Operationen auf den anfänglichen Aufruf von empty(n)
erzeugt wurde, heißt unkomprimiert.
In Abschnitt 1.5.2 aus Kapitel 1 haben wir für einen Knoten v eines Wurzelbaumes T
dessen Höhe heightT v eingeführt. Dabei handelte es sich um die Länge des längsten Weges
von einem Blatt von T zum Knoten v. (In (10.16) hat der Knoten 4 die Höhe 2, der Knoten
11 die Höhe 1.)
Lemma 10.34 Sei W ein unkomprimierter Wald gemäß Definition 10.33, und sei x ein
Knoten aus W . Dann ist
SIZE x ≥ 2height x .

Beweis. Der Beweis wird durch vollständige Induktion über die Höhe h = height x
geführt.
Induktionsanfang. Ist h = 0, so ist der Knoten x ein Blatt, und deshalb SIZE x =
#Tx = 1.
Induktionsschritt h − 1 ր h. Ist h > 0, so hat der Knoten x einen Sohn y mit der Höhe
h − 1. Nach Induktionsvorausetzung ist SIZE y ≥ 2h−1 . Wir betrachten zusätzlich zum
aktuellen Zeitpunkt denjenigen Zeitpunkt, zu dem der Knoten y vermöge der Operation
union(x, y) (oder der Operation union(y, x)) Sohn des Knotens x wurde (Vereinigungs-
zeitpunkt):
x

158
Es galt SIZE x ≥ SIZE y. Seitdem hat sich die Größe des in y wurzelnden Teilbaumes
Ty nicht mehr verändert, der Teilbaum, der unmittelbar vor dem Vereinigungszeitpunkt
gleich Tx war, kann sich bis zum aktuellen Zeitpunkt nur größert haben. Wir erhalten
SIZE x ≥ 2 · 2h−1 = 2h . 

Mit Hilfe von Lemma 10.34 erkennen wir, warum in Algorithmus 10.31 die Wurzel
mit der kleineren Größe zum Sohn und die mit der größeren Größe zum Vater gemacht
wird: Der Baum wird buschig, lange Pfade können bis zu einem gewissen Grade vermieden
werden.

Korollar 10.35 Alle Bäume eines unkomprimierten Waldes sind in ihrer Tiefe durch
O (log2 n) beschränkt.

Wir erhalten.

Satz 10.36 Implementiert man die Union-Find-Datenstruktur mit Hilfe der Algorithmen
10.30, 10.31, 10.32, so haben die makeSet- und die union-Operation konstante Laufzeit.
Die Laufzeit der Operation simpleFind(x) ist ein O (log2 n).

Bemerkung. Satz 10.36 komplettiert bereits den Beweis von Satz 10.28, denn die 2|E|
find-Operationen kosten O (|E| log2 |V |).

Aber es geht, was die find-Operation angeht, noch besser. Die Wälder aus Definition
10.33 heißen nicht ohne Grund unkomprimiert. Algorithmus 10.32 hat Laufzeit O (depth x),
wobei x der übergebene Knotren ist. Diese vergeht beim Marsch“ von Knoten x in Rich-

tung Wurzel. Man kann Algorithmus 10.32 ohne großen Zusatzaufwand so anreichern, daß
zukünfige find-Operationen schneller ablaufen können. Die Idee dazu heißt Pfadkompres-
sion (engl. path compresssion). Auf dem Weg zur Wurzel werden alle Knoten, auf die man
dabei stößt, zu Söhnen der Wurzel gemacht:

159
x4 ⇒ x4

x3 x3 x2 x1 x

x2

x1

(10.20)

Die rekursive Umsetzung der Idee der Pfadkompression gemäß (10.20) liefert den fol-
genden Algorithmus.

Algorithmus 10.37 (find-Operation mit Pfadkompression)


Methodenkopf.
find(Integer x) returns Integer
Vorbedingung.
x gehört zum aktuellen Wald: father[x] 6= 0.
Falls father[x] < 0, so return x.
root ← find(father[x])
father[x] ← root
return root

Das Szenario für die Laufzeitanalyse der Union-Find-Datenstruktur unter Verwendung


von Algorithmus 10.37 sieht in Anbetracht von Algorithmus 10.27 so aus: Wir nehmen an,
daß n Objekte zu verwalten sind, die nach der Ausführung von n makeSet-Operationen,
die ja zusammen in O (n) Zeit ausführbar sind, als Einerblöcke vorliegen. Nun werden 2m
find- und n − 1 union-Operationen in beliebiger Reihenfolge ausgeführt, wobei m ≥ n − 1

160
ist. Das ist ein typischer Anwendungsfall für die Tilgungskostenanalyse. Deshalb beweisen
wir den folgenden Satz im Abschnitt 11.2 des Kapitels 11.

Satz 10.38 Implementiert man die Union-Find-Datenstruktur mit Hilfe der Algorithmen
10.30, 10.31, 10.37, so ist nach der Ausführung der n makeSet-Operationen die Laufzeit
der 2m find-Operationen und der n − 1 union-Operationen zusammen ein O (m · log∗2 n).

161
Literaturverzeichnis

[CLRS01] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein. Introduction to


Algorithms. MIT Press, 2001.

[Tar83] R. E. Tarjan. Data Structures and Network Algorithms. SIAM, 1983.

162
Kapitel 11

Fortgeschrittene Analyse- und


Entwurfstechniken

Dieses Kapitel ist stark an [CLRS01] angelehnt.

11.1 Rekursive Algorithmen


11.1.1 Das Mastertheorem zur Analyse rekursiver Algorithmen
Rekursive Algorithmen von der Art, wie wir sie in diesem Abschnitt studieren wollen,
sind beispielsweise Algorithmus 6.3 aus Abschnitt 6.1 oder Algorithmus 8.90 (rekursives
Mergesort) aus Abschnitt 8.4.5.
Seien c : N → N eine monoton wachsende zahlentheoretische Funktion, a ≥ 1 eine
konstante natürliche, b > 1 eine konstante rationale und n0 ≥ b wiederum eine konstante
natürliche Zahl. Wir analysieren den folgenden generischen Algorithmus bzgl. seines Ver-
brauchs einer Ressource A in Abhängigkeit vom Problemgrößeparameter n: A(n) ∈ N ist
der Ressourcenverbrauch auf Problemstellungen I der Größe n.

Algorithmus 11.1 (Generischer rekursiver Algorithmus)

Eingabe: Problemstellung I der Größe n.


Großschritt 1.
Falls n ≤ n0 ist, so löse I unter konstantem Ressourcenverbrauch geeignet.
Großschritt 2.
Teile“ die Eingabe I der Größe n in a Teilprobleme der Größe ⌈n/b⌉.

Großschritt 3.
Beherrsche“ die Eingabe I wie folgt.

3.1. Löse die a Teilaufgaben der Größe ⌈n/b⌉ rekursiv.
3.2. Setze die Lösungen der Teilprobleme zu einer Lösung von I zusammen.

163
Ist c(n) der Ressourcenverbrauch in den Großschritten 2 und 3.2 beim Teilen und
beim Zusammensetzen, so gilt für den Gesamtressourcenverbrauch des Algorithmus 11.1
offensichtlich die folgende Rekursion.

(
a · A (⌈n/b⌉) + c(n) falls n > n0 ist;
A(n) = (11.1)
Θ (1) andernfalls.

Das folgende Mastertheorem löst die Rekursion (11.1) für viele interessante Fälle.

Satz 11.2 Es gilt.



1. Gibt es eine Konstante ǫ > 0, so daß c(n) = O nlogb a−ǫ ist, so ist

A(n) = O nlogb a . (11.2)


2. Ist c(n) = Θ nlogb a , so ist

A(n) = Θ nlogb a log2 n . (11.3)

3. Es ist

A(n) = Θ (c(n)) , (11.4)

sofern die Funktion c bezüglich der Konstanten a und b die folgende Regularitätsbe-
dingung erfüllt: Es existiert eine Konstante γ ∈ (0, 1), so daß für alle natürlichen
n>b

a · c (⌈n/b⌉) ≤ γ · c(n) (11.5)

gilt.

Beweis.
Schritt 1. Zunächst wiederholen wir Gleichung 1.5 aus Abschnitt 1.2:
 
n
logb = min{j | ⌈n/bj ⌉ ≤ n0 }.
n0

Schritt 2. Wir zeigen, daß aus der Regularitätsbedingung für die Funktion c bezüglich
der Konstanten a und b folgt, daß es eine Konstante ǫ > 0 so gibt, daß

c(n) = Ω nlogb a+ǫ (11.6)

164
l m
gilt. Es ist für k = logb nn0

 k
a 
c(n) ≥ c ⌈n/bk ⌉
γ

Wegen Gleichung 1.5 aus Abschnitt 1.2 und


 
n
k = logb = ⌈logb n − logb n0 ⌉ = logb n − Θ (1) (11.7)
n0

erhalten wir
 logb n !
a
c(n) = Ω
γ
 logb n !
1
= Ω alogb n ·
γ
 
logb a logb ( γ1 )·logb n
=Ω n ·b
 
= Ω nlogb a · nlogb ( γ ) .
1

Schritt 3. Wir beweisen in diesem Schritt, daß für n > n0

⌈logb (n/n0 )⌉−1


 X 
logb a
A(n) = Θ n + aj c ⌈n/bj ⌉ (11.8)
j=0
| {z }
=:B(n)

l m
ist. Sei k = logb nn0 . Durch k-faches Einsetzen der Rekursionsgleichung (11.1) in sich
selbst erhalten wir

k−1
X 
A(n) = aj c ⌈n/bj ⌉ + O (1) ak .
j=0

Unter Verwendung von Gleichung 1.5 aus Abschnitt 1.2 und Gleichung 11.7 folgt

ak = bk logb a = Θ nlogb a ,

woraus die Behauptung dieses Beweisschrittes folgt.

165
Schritt 4. Beweis von Behauptung 1. Es genügt zu zeigen, daß für B(n) aus Gleichung
11.8

B(n) = O nlogb a (11.9)

gilt. Unter den Voraussetzungen von Behauptung 1 erhalten wir


 
⌈logb (n/n0 )⌉−1
X
B(n) = O  aj ⌈n/bj ⌉logb a−ǫ  .
j=0

Wegen

⌈n/bj ⌉logb a−ǫ ≤ (2n/bj )logb a−ǫ = O (n/bj )logb a−ǫ

folgt
 
⌈logb (n/n0 )⌉−1  j
X a
B(n) = O nlogb a−ǫ
 
logb a−ǫ .
j=0 | b {z }
=bǫ
 

= O nlogb a−ǫ · |bǫ·⌈log{z


b (n/n0 )⌉  ,
}
=O(nǫ )

woraus Gleichung 11.9 folgt.


Schritt 5. Beweis von Behauptung 2. Es genügt zu zeigen, daß für B(n) aus Gleichung
11.8

B(n) = O nlogb a · log2 n (11.10)

gilt. Ein analoges Vorgehen wie beim Beweis von Behauptung 1 liefert die folgende
Gleichungskette.
 
⌈logb (n/n0 )⌉−1
X
B(n) = O  aj ⌈n/bj ⌉logb a 
j=0
 
⌈logb (n/n0 )⌉−1  
X a j
= O nlogb a 
j=0
blogb a

= O nlogb a · ⌈logb (n/n0 )⌉ ,

woraus unter Verwendung von Gleichung 11.7 Gleichung 11.8 und damit Behauptung
2 folgt.

166
Schritt 6. Beweis von Behauptung 3. Wegen Schritt 2 dieses Beweises und Gleichung 11.8
genügt es zu zeigen, daß
B(n) = O (C(n))
ist. Aus der Regularitätsbedingung 11.5 folgt für alle in Rede stehenden Indizes j

aj · c ⌈n/bj ⌉ ≤ γ j · c(n),
woraus wir
⌈logb (n/n0 )⌉−1
X 
B(n) = aj · c ⌈n/bj ⌉
j=0
⌈logb (n/n0 )⌉−1
X
≤ c(n) γj
j=0

X
< c(n) γj
j=0

= O (c(n))
erhalten. 

Bemerkungen.
• Die Algorithmen aus den Abschnitten 11.1.2 und 11.1.3 sind Beispiele für die Be-
hauptung 1 des Mastertheorems.
• Die Algorithmen 6.3 und 8.90 sind Beispiele für die Behauptung 2 des Mastertheo-
rems. In beiden Fällen ist a = b = 2 und c(n) = O (n).
• Behauptung 3 ist beispielsweise in dem folgenden Fall anwendbar: a = 3, b = 4 und
c(n) = n log n. Dann ist nlogb a = n0,793... . Wir überprüfen die Regularitätsbedingung:
3 · c(n/4) = 3 · (n/4) log(n/4)
3
≤ n log n .
4 | {z }
=c(n)

In diesem Falle wäre


A(n) = Θ (n · log n) .
• Die Behauptungen des Mastertheorems beruhen auf einem Vergleich der Funktio-
nen nlogb a und c(n). Die Fallunterscheidung des Mastertheorems ist jedoch nicht
vollständig. Ist beispielsweise a = b = 2 und c(n) = n log n, so ist nlogb a = n. Zwi-
schen nlogb a = n und c(n) = n log n ist jedoch kein polynomialer Abstand“. Es ist

für jedes ǫ > 0
c(n) log n
lim log a+ǫ = lim = 0.
n→∞ n b n→∞ nǫ

167
11.1.2 Die Multiplikationsmethode von Karatsuba und Ofman
Schon in der Schule macht man sich mit dem folgenden Problem vertraut.

Problem 1 (Multiplikation von zwei natürlichen Zahlen)

Zulässige Eingaben sind zwei Felder

A = (an−1 , an−2 , . . . , a0 ) B = (bn−1 , bn−2 , . . . , b0 ) (11.11)

über {0, 1}, wobei wir annehmen, daß die Länge n der Felder eine Zweierpotenz größer
oder gleich eins ist. (Wir identifizieren in diesem Abschnitt jede Bitfolge X mit der
durch sie kanonisch dargestellten natürlichen Zahl ν(X) (siehe Abschnitt 1.1).)

Komplexitätsmaß ist die Anzahl der Bitoperationen.

Ausgabe ist ein Feld

C = (cn , cn−1 , . . . , c0 ) = A ∗ B, (11.12)

wobei ∗“ in diesem Abschnitt die Multiplikation von Mehrbit-Zahlen bezeichnet.



Ist
 
A′ := an−1 , an−2 , . . . , an/2 A′′ := an/2−1 , an/2−2 , . . . , a0 (11.13)

und
 
B ′ := bn−1 , bn−2 , . . . , bn/2 B ′′ := bn/2−1 , bn/2−2 , . . . , b0 , (11.14)

so ist

A = A′ · 2n/2 + A′′ B = B ′ · 2n/2 + B ′′

und folglich

C = A′ ∗1 B ′ · 2n + [A′ ∗2 B ′′ + A′′ ∗3 B ′ ] · 2n/2 + A′′ ∗4 B ′′ (11.15)

Algorithmus 11.3 (Rekursive ganzzahlige Multiplikation)

168
Großschritt 1.
Wenn n = 1 ist, so führe aus.
Multipliziere die beiden Bits der Eingabe:
c0 ← a0 · b0
return
Großschritt 2.
Zerlege die Eingabefelder gemäß (11.13) und (11.14).
Großschritt 3.
Berechne das Produkt gemäß Gleichung 11.15.
Im Falle von Algorithmus 11.3 ist die Anzahl a der rekursiven Aufrufe gleich 4, die
Problemgröße wird halbiert (b = 2), und für das Zusammensetzen werden c(n) = O (n)
Bitoperationen für zwei Shifts um höchstens n Positionen, sofern man diese überhaupt
als Bitoperationen betrachtet, und drei Additionen von 2n-Bitzahlen benötigt.
 Satz 11.2,
logb a 2
Behauptung 1 ergibt als Abschätzung für die Bitoperationen O n = O (n ). Das ist
keine Verbesserung gegenüber der wohlbekannten Schulmethode, die ebenfalls O (n2 ) Bit-
operationen benötigt. Wir werden im folgenden aber zeigen, daß man zwei n-Bitzahlen A
und B mit weniger als 4 Multiplikationen von n/2-Bitzahlen und O (n) weiteren Bitope-
rationen multiplizieren kann.
Die Lösung ist überraschend einfach: Es ist
(A′ + A′′ ) ∗5 (B ′ + B ′′ ) = [A′ ∗2 B ′′ + A′′ ∗3 B ′ ] + A′ ∗1 B ′ + A′′ ∗4 B ′′ .

Wir erhalten

[A′ ∗2 B ′′ + A′′ ∗3 B ′ ] = (A′ + A′′ ) ∗5 (B ′ + B ′′ ) − A′ ∗1 B ′ − A′′ ∗4 B ′′ ,


woraus unter Verwendung von (11.15)
C = A′ ∗1 B ′ · 2n + [(A′ + A′′ ) ∗5 (B ′ + B ′′ ) − A′ ∗1 B ′ − A′′ ∗4 B ′′ ] · 2n/2 + A′′ ∗4 B ′′
(11.16)
folgt.

Algorithmus 11.4 (Ganzzahlige Multiplikation nach Karatsuba und Ofman)


Großschritt 1.
Wenn n = 1 ist, so führe aus.
Multipliziere die beiden Bits der Eingabe:
c0 ← a0 · b0
return
Großschritt 2.
Zerlege die Eingabefelder gemäß (11.13) und (11.14).
Großschritt 3.
Berechne das Produkt gemäß Gleichung 11.16.

169
Bemerkung. Man sieht, daß es in Großschritt 3 von Algorithmus 11.4 nur noch zu
drei rekursiven Aufrufen kommt. Die Terme A′ + A′′ und B ′ + B ′′ aus Gleichung 11.16
sind allerdings unter Umständen (n/2 + 1)- und keine n/2-Bitzahlen. Die Behebung dieser
(kleinen) Unverträglichkeit von Algorithmus 11.4 mit Algorithmus 11.1 ist eine leichte
Übungsaufgabe.
Mithilfe von Satz 11.2, Behauptung 1 erhält man den folgenden Satz.

Satz 11.5 Algorithmus 11.4 benötigt O nlog2 3 Bit-Operationen.

Bemerkungen.
• Offenbar ist log2 3 ≈ 1, 5 . . ..
• Die beste bekannte obere Schranke für die Multiplikation zweier n-Bitzahlen geht
auf Schönhage und Strassen zurück. Sie ist O (n · log n · log log n). Allerdings ist die
Konstante groß.

11.1.3 Die schnelle Matrixmultiplikation von Strassen


Ein weiteres sehr lehrreiches Beispiel für die Anwendung Satz 11.2, Behauptung 1 ist das
folgende Problem.

Problem 2 (Multiplikation von zwei quadratischen Matrizen)


Zulässige Eingaben sind zwei n × n Matrizen

A = (aij )ij=1,2,...,n B = (bij )ij=1,2,...,n (11.17)

über einem Ring R, wobei wir annehmen, daß der Grad n der Matrizen eine Zweier-
potenz größer oder gleich eins ist.

Komplexitätsmaß ist die Anzahl der arithmetischen Operationen (Additionen, Multi-


plikationen) in R.
Ausgabe ist eine Matrix

C = (cij )ij=1,2,...,n = A · B
n
X
cij = aik bkj (i, j = 1, 2, . . . , n) (11.18)
k=1

Es ist wohlbekannt, daß man für n = 2k mit k > 1 die Multiplikation der Matrizen aus
(11.17) durch Blockmatrixmultiplikation lösen kann. Ist
     
A11 A12 B11 B12 C11 C12
A= B= C= , (11.19)
A21 A22 B21 B22 C21 C22

170
n
wobei C = A · B (siehe Gleichung 11.18) ist, und die Aij ,Bij und Cij für i, j = 1, 2 2
× n2 -
Matrizen über R sind, so ist

Cij = Ai1 · B1j + Ai2 · B2j (i, j = 1, 2). (11.20)

Gleichung 11.20 besagt, daß man die Multiplikation zweier n × n-Matrizen über R auf
die Multiplikation von zwei 2 × 2-Matrizen über dem Ring der n2 × n2 -Matrizen über R
zurückführen kann. Dies gibt Anlaß zu dem folgenden rekursiven Algorithmus für die Ma-
trixmultiplikation.

Algorithmus 11.6 (Rekursive Matrixmultiplikation)

Großschritt 1.
Wenn n = 1 ist, so führe aus.
Multipliziere die beiden Skalare der Eingabe:
c11 ← a11 · b11 .
return
Großschritt 2.
Zerlege die Eingabematrizen gemäß Gleichung 11.19.
Großschritt 3.
Berechne das Produkt gemäß Gleichung 11.20.

Analysiert man Algorithmus 11.6 mithilfe von Satz 11.2, Behauptung 1 – im Falle
von Algorithmus 11.6 ist die Anzahl a der rekursiven Aufrufe gleich 8, die Problemgröße
2
wird halbiert (b = 2), und für das Zusammensetzen werden c(n) = 4 · n4 = n2 skalare
Additionen
 benötigt –, so erhält man als Abschätzung für die arithmetischen Operationen
O nlogb a = O (n3 ). Das ist nicht besser, als wenn man die Matrizen vermöge der Definition
aus Gleichung 11.18 multiplizierte. Aber man weiß nun, worauf es ankommt, wenn man eine
Verbesserung erzielen will: Man muß eine Regel finden, nach der man zwei 2 × 2-Matrizen
A und B mit weniger als 8 Skalarmultiplikationen multiplizieren kann.
Wir verwenden im folgenden für die Koeffizienten dieser Matrizen und der Produktma-
trix C die Notation aus Gleichung 11.19.

Lemma 11.7 (Strassen) Man kann zwei 2 × 2-Matrizen A und B mit

– 7 skalaren Multiplikationen,

– 6 skalaren Subtraktionen und

– 12 skalaren Additionen

171
multiplizieren: Ist

M1 := (A12 − A22 ) · (B21 + B22 )


M2 := (A11 + A22 ) · (B11 + B22 )
M3 := (A21 − A11 ) · (B11 + B12 )
M4 := (A11 + A12 ) · B22
M5 := A11 · (B12 − B22 )
M6 := A22 · (B21 − B11 )
M7 := (A21 + A22 ) · B11

so ist

C11 = M1 + M2 − M4 + M6
C12 = M4 + M5
C21 = M6 + M7
C22 = M2 + M3 + M5 − M7 .

Den Beweis von Lemma 11.7 kann man ganz einfach durch Einsetzen und Ausrechnen
führen. Das Problem war selbstverständlich, die Gleichungen aus Lemma 11.7 zu finden.
Lemma 11.7 ermöglicht den folgenden Algorithmus.

Algorithmus 11.8 (Rekursive Matrixmultiplikation nach Strassen)


Großschritt 1.
Wenn n = 1 ist, so führe aus.
Multipliziere die beiden Skalare der Eingabe:
c11 ← a11 · b11 .
return
Großschritt 2.
Zerlege die Eingabematrizen gemäß Gleichung 11.19.
Großschritt 3.
Berechne das Produkt gemäß Lemma 11.7.
Mithilfe von Satz 11.2, Behauptung 1 erhält man leicht den folgenden Satz.

Satz 11.9 Algorithmus 11.8 benötigt O nlog2 7 arithmetische Operationen.

Bemerkungen.
• Man beachte, daß log2 7 ≈ 2, 81 . . . ist.
• Die beste bekannte obere Schranke für die Matrixmultiplikation ist O (n2,376 ). Aller-
dings ist die Konstante groß.

172
11.2 Die Tilgungsmethode der Kostenanalyse
11.2.1 Zwei einführende Anwendungsbeispiele
Der modifizierte Stapel
Wir betrachten einen Stapel, bei dem neben den üblichen Methoden create() zur Erzeu-
gung eines leeren Stapels D0 , empty() zur Feststellung, ob der aktuelle Stapel leer ist, add
zum Hinzufügen eines Elements und remove() zum Entfernen des Spitzenelements noch
eine Methode multiremove(Integer k) existiert, die genau die ersten k Elemente des ak-
tuellen Stapels entfernt. Die Anwendung von remove() und multiremove(k) ist natürlich
nur dann zulässig, wenn noch mindestens ein bzw. k Elemente auf dem Stapel liegen.
Ist op1 := add, op2 := remove() und op3 := multiremove(k), so geht es uns um die
Laufzeitanalyse einer beliebigen zulässigen Folge

D0  opi1  opi2  . . .  opiℓ

von ℓ Operationen. Dazu ordnen wir jeder der drei Operationen sogenannte reale Kosten c
zu, die proportional zur Laufzeit sind: c(op1 ) = c(op2 ) = 1 und c(op3 ) =k. Die kanonische
Analyse ergibt

c(D0  opi1  opi2  . . .  opiℓ ) = O ℓ2 .

Unser Ziel ist der Nachweis, daß

c(D0  opi1  opi2  . . .  opiℓ ) = O (ℓ)

ist.

Die Hashtabelle
Bei der Laufzeitanalyse des offenen Hashings war die Behandlung der Verdopplungsstar-
tegie aus dem Abschnitt 8.3.4 offengeblieben. In diesem Kapitel werden wir diese Lücke
schließen. Im folgenden verwenden wir die Bezeichnungen aus den Abschnitten 8.3.2 und
8.3.4. Wir nehmen an, daß α0 := 14 und α1 := 1 ist. Nach der Erzeugung haben wir eine
leere Hashtabelle D0 mit einer fixierten Anzahl von Buckets m0 = 2k0 .
Ist op1 := insert, op2 := delete, so geht es uns um die mittlere Laufzeitanalyse einer
beliebigen zulässigen Folge

D0  opi1  opi2  . . .  opiℓ

von ℓ Operationen. Wiederum ordnen wir jeder der zwei Operationen auf eine Hashtabelle
mit m = 2k Buckets reale Kosten zu, die proportional zur mittleren Laufzeit sind:
1
– c(op1 ) = c(op2 ) = 1, falls nach dem Einfügen bzw. Streichen α < 1 bzw. 4
< α ist.

173
– c(op1 ) = m, falls nach dem Einfügen α = 1 ist.
m 1
– c(op2 ) = 2
, falls nach dem Streichen α = 4
ist.

Wie beim modifizierten Stapel auch schon, ergibt die kanonische Analyse

c(D0  opi1  opi2  . . .  opiℓ ) = O ℓ2 .

Wir streben den Beweis für

c(D0  opi1  opi2  . . .  opiℓ ) = O (ℓ)

an.

11.2.2 Eine allgemeine Beschreibung des Problems


Wir haben eine Datenstruktur von einem wohldefinierten Typ, die nach ihrer Erzeugung
im Zustand D0 ist. Zu dieser Datenstruktur gehören Operationen op1 , op2 , . . ., opr . (Zur
Erinnerung, der Zustand einer Datenstruktur wird durch die Werte ihrer Datenfelder be-
stimmt.) Wir messen die Kosten der Operationsausführung bezüglich eines Aufwandsma-
ßes A wie folgt: Jeder denkbaren Überführung der Datenstruktur von einem Zustand D
in einen Zustand D ′ vermöge einer Operation opi (i = 1, 2, . . . , r) ordnen wir sogenannte
reale ganzahlige Kosten c(D, opi ) so zu, daß

c(D, opi) = Θ (Aopi (D)) (11.21)

ist. Wir suchen für jede Operationsausführung


opi
D → D′

sogenannte Tilgungskosten

ĉ(D, opi) ∈ [0, ∞).

Für jede zulässige Folge von Operationen

adm = D0  opi1  opi2  . . .  opiℓ (11.22)

muß unter Verwendung der Bezeichnungen

Dj := D0  opi1  opi2  . . .  opij (j = 1, 2, . . . , ℓ) (11.23)


cj := c(Dj−1 , opij ) (j = 1, 2, . . . , ℓ) (11.24)
ĉj := ĉ(Dj−1 , opij ) (j = 1, 2, . . . , ℓ) (11.25)

174
die Tilgungsbedingung


X ℓ
X
cj ≤ ĉj (11.26)
j=1 j=1

erfüllt sein. Dabei heißt die Operationenfolge aus Gleichung 11.22 zulässig, wenn die Ope-
ration opi1 auf D0 ausführbar ist, und für j = 1, . . . , ℓ−1 die Nachbedingung von Operation
opij zusammen mit der Klasseninvarianten die Vorbedingung der Operation opij+1 garan-
tiert.
Die Ermittlung von Tilgungskosten je Operationsausführung war dann sinnvoll, wenn
diese wesentlich geringeren Schwankungen unterliegen, als es die realen Kosten tun. (For-
mal gesehen sind ja auch die realen Kosten Tilgungskosten.) In unseren Beispielen werden
die Tilgungskosten je Operationsausführung ein O (1) sein, so daß wegen Gleichung 11.21
und der Tilgungsbedingung gemäß Gleichung 11.26 der Aufwand für die Ausführung der
Operationenfolge aus Gleichung 11.22 ein O (ℓ) ist.
Um zu einer Verteilung der realen Kosten zu kommen, gibt es unter Verwendung der
Bezeichnungen aus den Gleichungen 11.23, 11.24 und 11.25 zwei Arten von Operationen:

– Die Operation opj heißt Ansparoperation, wenn

ĉj > cj

ist. Die höheren Tilgungkosten werden auf die hohe Kante“ gelegt.

– Die Operation opj heißt Konsumoperation, wenn

ĉj ≤ cj

ist. Die höheren realen Kosten werden aus den angesparten Überschüssen der voran-
gegangenen Ansparoperationen bezahlt.

Um die Überschüsse aus Ansparoperationen zu verwalten, ist ein Konto nicht schlecht,
für das aber wegen der allgegenwärtigen Tilgungsbedingung aus Gleichung 11.26 keinerlei
Überziehung gewährt werden kann, wenn eine Konsumoperation ansteht.

11.2.3 Die Kontomethode


Wir verwenden die Bezeichnungen aus den Gleichungen 11.22, 11.23, 11.24 und 11.25. Sei
K ein Konto, und sei

Kj := der Kontostand nach der Ausführung von opij in der Folge aus Gl. 11.22.

Über die gesamte Laufzeit der Ausführung jeder zulässigen Operationenfolge (Gleichung
11.22) müssen die folgenden Kontoklauseln erfüllt sein:

175
Kontoeröffnungsbedingung. Man bekommt nichts geschenkt: Es ist

K0 = 0. (11.27)

Kontobewegungbedingung. Es geht alles über das eine Konto, Schwarzgeldkonten sind nicht
erlaubt: Für jedes j = 1, 2, . . . , ℓ ist

Kj = Kj−1 + (ĉj − cj ) . (11.28)

Kontoabschlußbedingung. Man muß am Ende ein Plus gemacht haben: Es ist

Kℓ ≥ 0. (11.29)

Lemma 11.10 Für jede zulässige Operationenfolge (11.22) folgt aus der Gültigkeit der
Kontoklauseln die Gültigkeit der Tilgungsbedingung gemäß Gleichung 11.26.

Beweis. Aus

0 ≤ Kℓ = Kℓ−1 + (ĉℓ − cℓ )
= Kℓ−2 + (ĉℓ + ĉℓ−1 ) − (cℓ + cℓ−1 )
= ...

X ℓ
X
= K0 + ĉj − cj
|{z}
=0 j=1 j=1

folgt die Behauptung. 

Nun sind wir in der Lage, die Laufzeitanalyse für den modifizierten Stapel durch-
zuführen. Wir legen folgendes fest.

ĉadd := 2 ĉremove := ĉmultiremove := 0.

Die Kosten für das Entfernen, gleichgültig ob vermöge der Operation remove oder vermöge
der Operation multiremove, wird beim Hinzufügen bereits mitbezahlt und auf dem Konto
gebunkert“. Wir erhalten.

Satz 11.11 Für den modifizierten Stapel aus Abschnitt 11.2.1 ist der Zeitbedarf für jede
Folge zulässiger Operationen der Länge ℓ ein O (ℓ).

176
11.2.4 Die Potentialmethode
Wir verwenden wiederum die Bezeichnungen aus den Gleichungen 11.22, 11.23, 11.24 und
11.25.
Jeder Inkarnation D unserer Datenstruktur wird ein lokaler Kontostand, ein Potential

Φ(D) ∈ [0, ∞),

zugeordnet. Die Zahl Φ(D) ist für jede zulässige Folge von Operationen von D0 zu D eine
untere Schranke für die Überschüsse aus den Ansparoperationen gegenüber den Konsum-
operationen. Bei einer Konsumoperation müssen die Mehrkosten aus der Potentialdifferenz
bezahlt werden können. Natürlich wird einem am Anfang nichts geschenkt, und es gibt auch
keine negativen Potentiale, also keine Schulden. Etwas formaler aufgeschrieben, müssen die
folgenden Potentialklauseln erfüllt sein.

– Das Anfangspotential, das Potential von D0 , ist null. Alle Potentiale sind nichtnega-
tiv.

– Für jede zulässige Folge von Operationen (11.22) gilt:

– Ist opij eine Ansparoperation, so ist

ĉj − cj ≥ Φ(Dj ) − Φ(Dj−1 ). (11.30)

– Ist opij eine Konsumoperation, so ist

cj − ĉj ≤ Φ(Dj−1 ) − Φ(Dj ). (11.31)

Wir bemerken, daß die Bedingung aus Gleichung 11.30 die Bedingung aus Gleichung 11.31
miterfaßt. (Die Umkehrung ist ebenfalls richtig.) Für jede Operation muß folglich gelten,
daß die Differenz der Potentiale des Ziels und der Quelle die Differenz aus den Tilgungs-
und den realen Kosten nach unten abschätzt.

Lemma 11.12 Für jede zulässige Operationenfolge (11.22) folgt aus der Gültigkeit der
Potentialklauseln die Gültigkeit der Tilgungsbedingung gemäß Gleichung 11.26.

Beweis. Wir betrachten eine Folge zulässiger Operationen gemäß (11.22). Es ist

ĉℓ − cℓ ≥ Φ(Dℓ ) − Φ(Dℓ−1 )


ĉℓ−1 − cℓ−1 ≥ Φ(Dℓ−1 ) − Φ(Dℓ−2 )
... ...
ĉ1 − c1 ≥ Φ(D1 ) − Φ(D0 )

177
Indem wir die vorstehenden Gleichungen aufaddieren, erhalten wir

X ℓ
X
ĉj − cj = Φ(Dℓ ) − Φ(D0 )
| {z } | {z }
j=1 j=1 ≥0 =0

≥ 0.

Wir schließen die Laufzeitanalyse für das offene Hashing mit Hilfe der Potentialmethode
ab. Sei D eine Hashtabelle mit Bucketzahl m, Schlüsselzahl n und Auslastungsfaktor α.
Wir setzen

|2n − m| falls m > m0 ;

Φ(D) := 2n − m falls m = m0 und α ≥ 12 ;

falls m = m0 und α < 21 ;

0

und

ĉdelete := ĉinsert := 3.

Um zu zeigen, daß die vorstehende Potentiale und Tilgungskosten zusammen mit den
in Abschnitt 11.2.1 definierten realen Kosten die Potentialklauseln erfüllen, überlegen wir
uns folgendes. Wir beschränken uns auf den Fall m > m0 . Die Analyse für m = m0 ist
einfacher.
1. Ist α = 12 , also n = m
2
, so ist Φ(D) = 0.
2. Der unmittelbare Weg vom Auslastungsfaktor α = 21 zum Auslastungsfaktor α = 14
bei einer Bucketzahl m führt über genau m4 delete-Operationen. Folglich muß man,
um auf diesem Weg vom Potential null auf das Potential m2 zu kommen, auf die realen
Kosten für das Streichen einen Aufschlag von zwei erheben. Nun wird umgespeichert.
Das verursacht reale Kosten von m2 . Danach ist das Potential null. Die Umspeicherung
ist vollständig aus dem Potential bezahlt worden.
3. Um das Potential von m für α = 1 von null für α = 12 beginnend auf dem dafür
ungünstigsten Weg von m2 insert-Operationen ansparen“ zu können, muß man bei

den realen Kosten für das Einfügen einen Aufschlag von zwei erheben. Nun wird
umgespeichert. Das verursacht reale Kosten von m. Danach ist das Potential null.
Die Umspeicherung ist wiederum vollständig aus dem Potential bezahlt worden.
Wir haben bewiesen.

Satz 11.13 Für das offene Hashing aus Abschnitt 8.3.2 mit der Verdopplungsstrategie aus
Abschnitt 8.3.4 ist der Zeitbedarf für jede Folge zulässiger Operationen der Länge ℓ ein
O (ℓ).

178
Zum Abschluß dieses Abschnittes wollen wir analysieren, wieviele Bitoperationen das
Aufwärtszählen in Einerschritten von null auf ℓ kostet. (Die Anzahl der Bitoperationen für
das Abwartszählen ist die gleiche.)
Sei Dj (i = 1, 2, . . . , ℓ) der Zustand des binären Zählers (dessen Wert) nach der j-ten
Inkrementation. Der Anfangswert D0 ist gleich null.
Wir machen uns zunächst klar, wieviele Bitoperationen der Übergang von Dj−1 nach
Dj kostet. Sei tj−1 die Anzahl der 1 am Ende (niederwertige Bits) der Binärdarstellung
von Dj−1. Die Inkrementierung von Dj−1 bedarf cj = tj−1 + 1 Operationen: Die ersten tj−1
Bits müssen auf null, das (tj−1 + 1)-te Bit auf eins gesetzt werden. Damit haben wir auch
die realen Kosten festgelegt: Sie sind dieses Mal dem Aufwand gleich.
Sei für j = 1, 2, . . . , ℓ die Zahl bj die Häufigkeit des Vorkommens der Ziffer 1 in Dj . Wir
definieren Φ(Dj ) := bj . Da beim Übergang von Dj−1 nach Dj die ersten tj−1 Bits zu null
und das tj−1 + 1-te Bit zu eins wird, gilt:
bj − bj−1 ≤ 1 − tj−1 = 2 − (tj−1 + 1) .
| {z }
=cj

Setzen wir die Tilgungskosten cj für alle j = 1, 2, . . . , ℓ mit zwei an, so ist Ungleichung
11.30 erfült. Die Gültigkeit der anderen Potentialklausel ist offensichtlich.
Wir haben den folgenden Satz bewiesen.
Satz 11.14 Für das oben beschriebene binäre Aufwärtszählen von 0 bis ℓ benötigt man 2 · ℓ
Rechenschritte. Dasselbe gilt für das Abwärtszählen von ℓ bis 0.

11.2.5 Die Analyse der Union-Find-Datenstruktur


Nicht alle Probleme der Tilgungskostenanalyse lassen sich mit der Konto- oder mit der
Potentialmethode lösen. Ein Beispiel dafür ist die Laufzeitanalyse der effizienten Imple-
mentation der Union-Find-Datenstruktur aus Abschnitt 10.4. Im folgenden verwenden wir
die Bezeichnungen aus diesem Abschnitt.
Die Situation. Wir nehmen an, daß n Objekte zu verwalten sind, die nach der Ausführung
von n makeSet-Operationen als Einerblöcke vorliegen. Letzteres sei die Datenstruktur D0 .
Nun werden m find- und n − 1 union-Operationen in beliebiger Reihenfolge ausgeführt,
wobei m ≥ 2(n − 1) ist. Die Anzahl ℓ der Operationen ist in unserem Falle folglich gleich
m + n − 1.
Wir identifizieren den Zustand Dj (j = 1, 2, . . . , ℓ) unserer Union-Find-Datenstruktur
aus Gleichung 11.23 mit dem zu diesem Zeitpunkt vorliegenden Wald. Ferner sei Wj der
in Definition 10.33 definierte unkomprimierte Wald, den man hätte, wenn man anstelle
der find-Operation mit Pfadkompression nach Algorithmus 10.37 stets die einfache find-
Operation nach Algorithmus 10.32 genommen hätte. Wir nennen deshalb Wj den virtuellen
Wald zum Zeitpunkt j.
Aus Bequemlichkeit wiederholen wir hier nochmals die Definition des iterierten Loga-
rithmus aus Kapitel 1.
(j)
log∗2 n := min{j ∈ N | ⌈log2 n⌉ = 1} (11.32)

179
Ist die Funktion durch die beiden Rekursionsgleichungen

g(0) = 1 (11.33)
g(ρ−1)
g(ρ) = 2 (ρ ≥ 1) (11.34)

definiert, so ist
(
0 falls n ≤ 1;
log∗2 n = (11.35)
ρ falls ρ − 1 ≥ 0 und g(ρ − 1) < n ≤ g(ρ).

Definition 11.15 Zu jedem Zeitpunkt j = 1, 2, . . . , ℓ sei der Rang eines Elementes x ∈ V


die Höhe des Knotens x in Wj :

rankj x := heightWj x. (11.36)

Lemma 10.34 aus Kapitel 10 besagt dann für jeden Zeitpunkt j = 1, 2, . . . , ℓ, daß

SIZEj x ≥ 2rankj x (11.37)

ist, wobei die Größe SIZEj x des Knotens x zum Zeitpunkt j die Anzahl der Knoten des
in x wurzelnden Teilbaumes von Wj ist.
Bemerkung. Wir lassen den Index j bei der Bezeichnung des Ranges und der Größe
eines Knotens aus V in Zukunft gerne weg, wenn der betrachtete Zeitpunkt aus dem
Zusammenhang klar ist.

n
Lemma 11.16 Zu jedem Zeitpunkt j = 1, 2, . . . , ℓ gibt es höchstens 2r
viele Elemente aus
V mit dem Rang r.

Beweis. Sei Vj (r) ⊆ V die Menge derjenigen Elemente aus V , die zum Zeitpunkt j den
Rang r haben. Aus Ungleichung 11.37 folgt, daß es in allen Teilbäumen Tx von Wj mit
x ∈ Vj (r) zusammen mindestens
|Vj (r)| 2r
viele Knoten gibt. Da diese Zahl durch die Zahl n aller Knoten nach oben beschränkt ist,
folgt die Behauptung. 

Bemerkungen.
• Jeder Knoten durchläuft für j = 1, 2, . . . , ℓ eine Wurzelphase, in der er Wurzel eines
Teilbaumes in Dj und Wj ist, die (bis auf einen Knoten) von der Nachwurzelphase
(nach Anwendung einer entsprechenden union-Operation) gefolgt wird.
• In der Wurzelphase kann der Rang eines Elementes anwachsen. Mit Beginn der Nach-
wurzelphase bleibt er konstant.

180
Lemma 11.17 Zu jedem Zeitpunkt j = 1, 2, . . . , ℓ gilt: Ist Knoten x Sohn von Knoten y
in Dj , so ist rank y > rank x.

Beweis. Vorbemerkung. Eine analoge Aussage ist nach Definition des Ranges für den
virtuellen Wald Wj offensichtlich.
Für den Beweis der Aussage des Lemmas genügt es wegen der dem Lemma vorange-
gangenen Bemerkungen, den Zeitpunkt j0 ins Auge zu fassen, zu dem Knoten x zum Sohn
von Knoten y wird. Wir müssen zwei Fälle unterscheiden.
Fall 1. Knoten x wird zum Sohn von Knoten y durch eine union-Operation. Dann ist
aber Knoten x auch im virtuellen Wald Wj0 Sohn von Knoten y. Die Vorbemerkung ist
anwendbar.
Fall 2. Knoten x wird zum Sohn von Knoten y durch eine Pfadkompression. Dann ist
Knoten x im virtuellen Wald Wj0 Nachfahre von Knoten y. Die Vorbemerkung ist wiederum
anwendbar. 

Definition 11.18 Zu jedem Zeitpunkt j = 1, 2, . . . , ℓ gehört jeder Knoten x ∈ V zur


Ranggruppe log∗2 (rank x). Die Zahl log∗2 (rank x) heißt der Ranggruppenindex des Elements
x.

Lemma 11.19 Für jeden Zeitpunkt j = 1, 2, . . . , ℓ gilt:

1. Nur die Ranggruppen mit Indizes von 0 bis log∗2 n − 1 können nichtleer sein.

2. Für jeden Ranggruppenindex ρ ∈ [0, log∗2 n − 1] ist


n
N(ρ) := |{x | Knoten x gehört zur Ranggruppe mit dem Index ρ}| ≤ , (11.38)
g(ρ)

wobei die Funktion g durch die durch die Gleichungen 11.33 und 11.34 gegebene
Rekursion definiert ist.

Beweis. Wir fixieren einen Zeitpunkt j ∈ {1, 2, . . . , ℓ}.


Beweis von Behauptung 1. Zur Ranggruppe null gehören Elemente mit den Rängen null
und eins. Wegen Ungleichung 11.37 ist für jedes x ∈ V

log2 n ≥ rank x.

Folglich ist der Ranggruppenindex von Element x durch

log∗2 (log2 n) ≤ log∗2 n − 1

nach oben beschränkt (siehe Gleichung 11.32).


Beweis von Behauptung 2. Für den Ranggruppenindex ρ = 0 ist g(ρ) = 1 und Unglei-
chung 11.38 folglich trivial.

181
Für ρ > 0 haben nach Gleichung 11.35 die Ränge r mit

g(ρ − 1) + 1 ≤ r ≤ g(ρ)

den Ranggruppenindex ρ. Nach Lemma 11.16 haben höchstens 2nr Elemente den Rang r.
Pg(ρ)
Folglich gehören höchstens r=g(ρ−1)+1 2nr Elemente zur Ranggruppe ρ. Es ist

g(ρ) ∞
X n n X 1
r
< g(ρ−1)+1 ·
2 2 i=0
2i
r=g(ρ−1)+1
2·n n
= = .
2g(ρ−1)+1 g(ρ)


Nun kommen wir zum


Beweis von Satz 10.38. Es genügt, den Zeitbedarf der m find-Operationen mit Pfad-
kompression durch O (m · log∗2 n) abzuschätzen, da jede einzelne union-Operation offen-
sichtlich nur konstanten Zeitbedarf hat.
Wird find(x) zum Zeitpunkt j + 1 ausgeführt, so sei

x = x0 , x1 , . . . , xt−1 , xt (11.39)

der Weg von x zur Wurzel xt des Baumes aus Dj , zu dem x gehört. Wir setzen die realen
Kosten mit t + 1 an und ordnen jede Kosteneinheit einem Knoten des Weges von x zur
Wurzel xt fest zu. Nach Eigenschaften des Trägerknotens y klassifizieren wir die Kosten
wie folgt.
Grenzzoll. Es ist y ∈ {xt , xt−1 } oder y = xi für ein i < t − 1 so, daß die Ranggruppe des
Vaters von y größer ist als die Ranggruppe von y.

Wegezoll. Es ist y = xi für ein i < t − 1 so, daß die Ranggruppe des Vaters von y gleich
der Ranggruppe von y ist.
Zunächst schätzen wir den Grenzzoll für die in Rede stehende find(x)-Operation ab.
Es gibt nach Lemma 11.19, Behauptung 1 log∗2 n − 1 nichttriviale Ranggruppenindizes,
also auf dem Weg (11.39) höchstens log∗2 n − 2 Ranggruppenübergänge. Da für xt und xt−1
ebenfalls Grenzzoll fällig wird, ist dieser für die Operation find(x) durch log∗2 n nach oben
beschränkt.
Da es m find-Operationen gibt, ist der Gesamtgrenzzoll ein O (m · log∗2 n).
Wir schätzen den Gesamtwegezoll, der für einen festen Knoten y über die gesamte Lauf-
zeit fällig wird, nach oben ab. Wegezoll fällt für den Knoten y nach Definition nur in seiner
Nachwurzelzeit an. Folglich sind Rang r und Ranggruppe ρ von y zu jedem Zeitpunkt, zu
dem für y Wegezoll erhoben wird, gleich. Da der Knoten y unmittelbar vor einer Wegezoll-
Erhebung definitionsgemäß auch nicht Sohn einer Wurzel ist, wächst nach Lemma 11.17

182
nach jeder solchen Erhebung der Rang des Vaters von y an, da es sich dabei nunmehr um
die Wurzel handelt. Wie oft kann es zu einer Vergrößerung des Ranges des Vaters von y
kommen? Nur so oft, bis daß der Vater von Knoten y in einer höheren Ranggruppe liegt
als y selbst. (Danach kann für y nur noch Grenzzoll anfallen.) Nun haben nach Gleichung
11.35 höchstens g(ρ) von r verschiedene Ränge denselben Ranggruppenindex ρ wie der
Knoten y. Folglich können für den Knoten y über die gesamte Laufzeit nur höchstens g(ρ)
Wegezolleinheiten erhoben werden. Spätestens dann muß der Vater von y die Ranggruppe
von y verlassen haben.
Ist N(ρ) die Anzahl der Elemente der Ranggruppe ρ mit ρ ∈ [0, log∗2 n−1] zum Zeitpunkt
ℓ – spätestens dann hat jeder Knoten, für den jemals über die Gesamtlaufzeit Wegezoll
erhoben wird, die Ranggruppe seiner Nachwurzelzeit erreicht –, so ist die Gesamtzahl der
Plog∗2 n−1
Wegezolleinheiten durch ρ=0 N(ρ)g(ρ) nach oben beschränkt. Nach Lemma 11.19 ist
log∗2 n−1 log∗2 n−1
X X
N(ρ)g(ρ) ≤ n · 1
ρ=0 ρ=0

=n· log∗2 n.

Wegen m ≥ 2(n − 1) ist der Gesamtwegezoll ein O (m · log∗2 n).




11.3 Kombinatorische Optimierungsprobleme


Bereits im Abschnitt 6.1 haben wir das Rucksackoptimierungsproblem eingeführt. Wir
wiederholen seine Spezifikation hier.

Problem 3 (Maximierungsproblem MaxKNAPSACK)


Zulässige Eingaben sind Folgen natürlicher Zahlen

I := (w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , W ) ∈ N2n+1

in kanonischer binärer Darstellung. Die Problemgröße von I ist im


Einheitskostenmaß 2n + 1;
Pn Pn
logarithmischen Kostenmaß |I| := i=1 | bin ci | + i=1 | bin wi | + | bin W |.
Zulässige Lösungen sind alle Booleschen Vektoren β = (β1 , β2 , . . . , βn ) aus {0, 1}n , die
der Bedingung
n
X
βi wi ≤ W (11.40)
i=1

genügen.

183
Bewertungsfunktion:
n
X
Val(I, β) := βi ci (11.41)
i=1

Optimierungsziel: Suche eine zulässige Lösung β von I so, daß Val(I, β) maximiert wird.
Die lebenserfahrene Leserin weiß es: Es gibt Menschen, die der Tonnenideologie“

frönen. Für diese ist die folgende vereinfachende Variante von MaxKNAPSACK das rich-
tige.

Problem 4 (Maximierungsproblem MaxSimpleKNAPSACK)


Zulässige Eingaben sind Folgen natürlicher Zahlen

I := (w1 , w2 , . . . , wn , W ) ∈ Nn+1

in kanonischer binärer Darstellung.


Die Problemgröße von I ist im

Einheitskostenmaß n + 1;
Pn
logarithmischen Kostenmaß |I| := i=1 | bin wi | + | bin W |.

Zulässige Lösungen sind alle Booleschen Vektoren β = (β1 , β2 , . . . , βn ) aus {0, 1}n , die
der Bedingung
n
X
βi wi ≤ W
i=1

genügen.

Bewertungsfunktion:
n
X
Val(I, β) := βi wi
i=1

Optimierungsziel: Suche eine zulässige Lösung β von I so, daß Val(I, β) maximiert wird.
Bemerkung. Der Hauptunterschied des Einheitskostenmaßes zum logarithmischen Ko-
stenmaß bei der Bestimmung der Problemgröße besteht in der Annahme, daß Zahlen Ko-
sten eins verursachen (siehe Abschnitt 12.5.2). Dieser Annahme liegt die meist stillschwei-
gende Voraussetzung zu Grunde, daß ihre Darstellung innerhalb der Verarbeitungsbreite
liegt.
Allgemein besteht ein kombinatorisches Optimierungsproblem Π aus

184
– einer Menge von zulässigen Eingaben Input Π, die natürlich über dem Alphabet {0, 1}
codiert sind;

– zu jeder zulässigen Eingabe I aus einer endlichen Menge Sol I ⊆ {0, 1}+ zulässiger
Lösungen;

– einer Funktion Val(I, s), der Bewertungs- oder Zielfunktion, die jedem Paar bestehend
aus einer zulässigen Eingabe I ∈ Input Π und einer zulässigen Lösung s ∈ Sol I eine
natürliche Zahl zuordnet.
Die Laufzeit wird grundsätzlich in der binären Länge |I| der Eingabe gemessen.

Die Bestandteile müssen die folgenden Bedingungen erfüllen:

– Man muß in Polynomialzeit testen können, ob eine Zeichenkette über {0, 1}∗ eine
zulässige Eingabe verschlüsselt:

Input Π ∈ P.

– Die Relation

AdmRelΠ = {(I, s) | I ∈ Input Π, s ∈ Sol I}

ist eine Polynomialzeitrelation.

– Für jedes Argument (I, s) ist der Funktionswert Val(I, s) in Polynomialzeit berechen-
bar.

Erstes Ziel der algorithmischen Behandlung eine kombinatorischen Optimierungspro-


blems Π ist für jede zulässige Eingabe I die Berechnung eines sopt ∈ OptSol I, wobei
OptSol I diejenige Teilmenge zulässiger Lösungen aus Sol I ist, für die Val(I, s) bei fe-
stem I je nach Problemstellung maximiert oder minimiert wird. Wir sprechen von einem
Maximierungs- bzw. Minimierungsproblem. In beiden Fällen bezeichnen wir mit

Opt I := Val(I, sopt ) (für ein sopt ∈ OptSol I)

den Wert einer optimalen Lösung auf die Eingabe I.


Nun kann es sein, daß sich ein kombinatorisches Optimierungsproblem einer effizienten
exakten Lösung hartnäckig widersetzt. Dann sind wir vielleicht mit einer approximativen
Lösung dieses Problems zufrieden.

Definition 11.20 Sei Π ein kombinatorisches Optimierungsproblem.


Ein Polynomialzeitalgorithmus A, der zu jeder zulässigen Eingabe I ∈ Π eine zulässige
Lösung A(I) ∈ Sol I berechnet, heißt polynomialer Approximationsalgorithmus (PTA) für
das Problem Π.

185
Definition 11.21 Die Güte Γ(I, A(I)) eines PTA A für eine zulässige Eingabe I eines
kombinatorischen Optimierungsproblems ist ein Maß dafür, wie weit der Wert der von A
auf I berechneten Lösung vom Optimum für I abweicht:
(
Opt I
Val(I,A(I))
falls Π eine Maximierungsproblem ist;
Γ(I, A(I)) := Val(I,A(I))
Opt I
falls Π eine Minimierungsproblem ist.

11.4 Dynamische Programmierung


Wie im Abschnitt 11.1 wird bei der Berechnung einer Lösung eines Optimierungsproblems
vermöge dynamischer Programmierung auf Lösungen von Teilproblemen zurückgegriffen.
Im Gegensatz zu rekursiven Algorithmen wird über die Lösungen der Teilprobleme in einer
Tabelle buchgeführt.
Wir machen uns die Technik des dynamischen Programmierens am Beispiel des Rucksack-
Optimierungsproblems MaxKNAPSACK klar.

11.4.1 Identifikation einer geeigneten Teilproblemstruktur


Sei
I := (w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , W ) ∈ N2n+1

eine zulässige Eingabe des Problems 3, wobei wir unterstellen, daß alle Gegenstände ein
positives Gewicht haben. Für k = 0, 1, . . . , n und V = 0, 1, . . . , W definieren wir das
Teilproblem

I(k, V ) := (w1 , w2 , . . . , wk , c1 , c2 , . . . , ck , V ) (11.42)

und ein (n + 1) × (W + 1)-Feld

Opt := (Opt[k, V ]) k=0,1,...,n , (11.43)


V =0,1,...,W

wobei Opt[k, V ] für den Wert einer optimalen Lösung des Teilproblems I(k, V ) vorgesehen
ist.

11.4.2 Optimalitätsgleichungen
Nun muß gezeigt werden, wie man die Tabelle (11.43) für die Teilprobleme (11.42) ausfüllt.
Dazu dienen die sogenannten Optimalitätsgleichungen.
Sicherlich kann man aus null Gegenständen keinerlei Gewinn erzielen. Dasselbe gilt,
wenn man keine Gegenstände in den Rucksack legen darf:
Opt[k, 0] = 0 (k ∈ [0, n]) (11.44)
Opt[0, V ] = 0 (V ∈ [1, W ]) (11.45)

186
Für eine optimale Lösung des Problems I(k, V ) (k · V > 0) gibt es zwei Möglichkeiten:
Sie umfaßt den Gegenstand k, oder das ist nicht der Fall. Der erste Fall ist natürlich nur
möglich, wenn V ≥ wk ist. Wir erhalten:
(
max{Opt[k − 1, V ], Opt[k − 1, V − wk ] + ck } falls V ≥ wk ;
Opt[k, V ] = (11.46)
Opt[k − 1, V ] andernfalls.

11.4.3 Backtracing
Liegt die Tabelle 11.43 ausgefüllt vor, kann man daraus eine optimale Lösung berechnen.
Dazu dient ein Boolscher Vektor β der Länge n, der mit dem Nullvektor initialisiert ist.
Er wird mit fallendem Feldindex ausgefüllt. Wir verfahren nach dem Sparsamkeitsprinzip
und legen einen Gegenstand nur in den Rucksack, wenn es nicht anders geht. Haben wir
für die Gegenstände n, . . . , k + 1 bereits entschieden, ob sie in den Rucksack müssen, und
steht die Entscheidung über den Gegenstand k an, so ist
k+1
X
V =W− βj wj (11.47)
j=n

das noch verfügbare Restgewicht. Ist

Opt[k, V ] > Opt[k − 1, V ], (11.48)

so muß der Gegenstand k in den Rucksack, denn anderfalls bekämen wir keine optimale
Lösung:
(
1 falls Ungleichung 11.48 erfüllt ist;
βk = (11.49)
0 sonst.

11.4.4 Algorithmus
Wir fassen unsere Erkenntnisse aus den Abschnitten 11.4.1, 11.4.2 und 11.4.3 in dem fol-
genden Algorithmus für das Problem 3 zusammen.

Algorithmus 11.22 (Dynamische Programmierung für MaxKNAPSACK)


Eingabe: Problemstellung I := (w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , W ) des Problems 3.
Großschritt 1.
Erzeuge Opt[0 . . . , n, 0 . . . , W ].
Initialisiere Opt gemäß (11.44) und (11.45).
β ← (0, 0, . . . , 0)
Großschritt 2:

187
Für k = 1, 2, . . . , n führe aus.
Für V = 1, 2, . . . , W führe aus.
Falls V ≥ wk , so
m ← Opt[k − 1, V − wk ] + ck
Andernfalls
m ← 0.
Opt[k, V ] ← max{Opt[k − 1, V ], m}
Großschritt 3.
V ←W
Für k = n, n − 1, . . . , 1 führe aus.
Falls Opt[k, V ] > Opt[k − 1, V ], so
βk ← 1
V ← V − wk .
Ausgabe: β.
Wir erhalten:
Satz 11.23 Algorithmus 11.22 arbeitet korrekt. Seine Laufzeit im Einheitskostenmaß ist
für jede Eingabe ein O (n · W ), wobei n die Anzahl der Gegenstände und W das zulässige
Gesamtgewicht des Rucksacks ist.

11.5 Greedy-Algorithmen
Greedy-Algorithmen versichern sich grundsätzlich der dicksten Brocken“ zuerst. Im Fal-

le des Rucksack-Optimierungsproblems MaxKNAPSACK bedeutet das, die Gegenstände
zuerst nach ihrer Nutzendichte“ fallend zu sortieren, um dann zu versuchen, sie in dieser

Reihenfolge in den Rucksack zu legen.

Algorithmus 11.24 (PTA SimpleGreedyKnapsack für MaxKNAPSACK)


Eingabe: Problemstellung I := (w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , W ) des Problems 3.
Großschritt 1.
Numeriere die n Gegenstände so um, daß danach
c1
w1
≥ wc22 ≥ . . . ≥ wcnn
gilt.
Großschritt 2: Initialisierung.
R ← W (Restgewicht)
i ← 0 (Index des letzten visitierten Gegenstandes)
β ← (0, 0, . . . , 0) (Befüllung des Rucksacks)
Großschritt 3.
Solange (R > 0 und i < n) führe aus.
i← i+1
Falls wi ≤ R, so

188
βi ← 1
R ← R − wi .
Ausgabe: β.
Die Laufzeit von Algorithmus 11.24 ist mit O (n log n), wobei n die Anzahl der Ge-
genstände ist, sicherlich gut. Seine Güte dagegen ist, natürlich nur für sehr ungünstige
Eingaben In (n ∈ N), beliebig schlecht:
Das zulässige Gesamtgewicht sei W = 2n . Ferner seien

c1 = 1 c2 = W − 1 c3 = 1 ...... cn = 1
w1 = 1 w2 = W w3 = 2 ...... wn = 2

Man erkennt leicht, daß Algorithmus 11.24 auf In die Befüllung (1, 0, 1, . . . , 1) mit dem
Nutzen n − 1 und dem Gewicht 2n − 3 ausgibt. Optimal ist die Befüllung (0, 1, 0, . . . , 0)
mit dem Nutzen W − 1 und dem Gewicht W . Folglich ist die Güte von Algorithmus 11.24
auf In gleich
W −1 2n − 1
Γ(In , SimpleGreedyKnapsack(In )) = = .
n−1 n−1
Wir sehen, daß für n → ∞ die Güte Γ(In , SimpleGreedyKnapsack(In )) mit hoher Rate
gegen unendlich geht.
Ursächlich dafür, daß Algorithmus 11.24 auf den vorstehend definierten Eingaben In
schwächelt, ist die Unteilbarkeit der Gegenstände. Wir wollen das genauer untersuchen und
betrachten eine Variante des Rucksack-Optimierungsproblems, bei dem die Gegenstände
beliebig teilbar sind.

Problem 5 (Maximierungsproblem FractionalKNAPSACK)


Zulässige Eingaben sind Folgen natürlicher Zahlen

I := (w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , W ) ∈ N2n+1

in kanonischer binärer Darstellung. Die Problemgröße von I ist im

Einheitskostenmaß 2n + 1;
Pn Pn
logarithmischen Kostenmaß |I| := i=1 | bin ci | + i=1 | bin wi | + | bin W |.

Zulässige Lösungen sind alle Booleschen Vektoren β = (β1 , β2 , . . . , βn ) aus [0, 1]n , die
der Bedingung
n
X
βi wi ≤ W (11.50)
i=1

genügen.

189
Bewertungsfunktion:
n
X
Val(I, β) := βi ci (11.51)
i=1

Optimierungsziel: Suche eine zulässige Lösung β von I so, daß Val(I, β) maximiert wird.

Wir passen Algorithmus 11.24 an die Besonderheiten des Problems 5 an und erhalten
den folgenden Algorithmus.

Algorithmus 11.25 (GreedyFractionalKnapsack für FractionalKNAPSACK)


Eingabe: Problemstellung I := (w1 , w2 , . . . , wn , c1 , c2 , . . . , cn , W ) des Problems 5.
Großschritt 1.
Numeriere die n Gegenstände so um, daß danach
c1
w1
≥ wc22 ≥ . . . ≥ wcnn
gilt.
Großschritt 2: Initialisierung.
R ← W (Restgewicht)
i ← 0 (Index des letzten visitierten Gegenstandes)
β ← (0, 0, . . . , 0) (Befüllung des Rucksacks)
Großschritt 3.
Solange (R > 0 und i < n) führe aus.
i← i+1
Falls wi ≤ R, so
βi ← 1
R ← R − wi .
Andernfalls
βi ← wRi
R ← 0.
Ausgabe: β.

Da man im Falle des Problems 5 die Gegenstände beliebig teilen kann, besteht die op-
timale Strategie offensichtlich darin, niemals etwas von einem Gegenstand von geringerer
Nutzenhaltigkeit einzupacken, wenn noch etwas von einem Gegenstand von höherer Nut-
zenhaltigkeit vorhanden ist. Folglich berechnet Algorithmus 11.25 für das Problem 5 stets
eine optimale Lösung. Diese ist eindeutig bestimmt und sieht unter der Voraussetzung, daß
für die Gegenstände wc11 ≥ wc22 ≥ . . . ≥ wcnn gilt, so aus

βopt = (1, . . . , 1, βi0 , 0, . . . , 0),

wobei i0 der letzte Gegenstand ist, von dem noch ein Teil in den Rucksack paßt. Die Zahl
β0 ∈ (0, 1] gibt diesen Teil bezogen auf das Gewicht wi0 an.

190
Bemerkung. Der Nutzen dieser optimalen Lösung des Problems 5 ist eine obere Schranke
für den Nutzen einer optimalen Lösung des Problems 3.

Zum Abschluß dieses Abschnittes entwerfen wir einen von einem zusätzlichen ganz-
zahligen Parameter k abhängenden Algorithmus BasisGreedyKnapsackk , der das Optimie-
rungsproblem
 MaxSimpleKNAPSACK(Problem 4) mit einer Güte kleiner als 1 + k1 in Zeit
k+1
O n löst. Die Idee ist einfach. Ausgehend von allen denkbaren Anfangsbefüllungen
unseres Rucksacks mit höchstens k Gegenständen wird Algorithmus 11.24 angewendet.
Eine beste Lösung, auf die man dabei stößt, wird ausgegeben.
Die Algorithmenschar“ BasisGreedyKnapsackk (k ∈ N) ist ein Beispiel für ein poly-

nomiales Approximationsschema (PTAS) Ak (k ∈ N): Der Algorithmus Ak muß auf jede
zulässige Eingabe I in Laufzeit |I|O(1) eine Ausgabe der Güte kleiner oder gleich 1 + k1
liefern (siehe Abschnitt 16.1 für eine Klassifikation der Approximationsalgorithmen nach
dem asymtotischen Verhalten ihrer Güte).

Algorithmus 11.26 (PTAS BasisGreedyKnapsack k für MaxSimpleKNAPSACK)


Eingabe: Problemstellung I := (w1 , w2 , . . . , wn , W ) des Problems 4.
Großschritt 1.
Numeriere die n Gegenstände so um, daß danach
w1 ≥ w2 ≥ . . . ≥ wn
gilt.
Großschritt 2: Initialisierung.
opt ← 0 (Wert der besten bisherigen Befüllung des Rucksacks)
β ← (0, 0, . . . , 0) (beste bisherige Befüllung des Rucksacks)
Erzeuge einh P i
Feld τ 1 . . . kν=1 nν von Booleschen Vektoren der Länge n;
h Pk i
n
Feld γ 1 . . . ν=1 ν von natürlichen Zahlen
und hinitialisiere diese Felder wie folgt:
Pk n
i
τ 1 . . . ν=1 ν hält alle Teilmengen von {1, . . . , n}
mit höchstens k Elementen als charakteristische Vektoren.
P 
Für alle jP= 1, 2, . . . , kν=1 nν ist
γ[j] = ni=1 τ [j]i wi .
Großschritt 3.
P 
Für j = 1, 2, . . . , kν=1 nν führe aus.
R ← W − γ[j]
i ← 0.
Solange (R > 0 und i < n) führe aus.
i←i+1
Falls wi ≤ R und τ [j]i = 0, so
τ [j]i ← 1

191
R ← R − wi
γ[j] ← γ[j] + wi .
Falls γ[j] > opt, so führe aus.
opt ← γ[j]
β ← τ [j].
Ausgabe: β.

Satz 11.27 Algorithmus


 11.26 hat für jede zulässige Eingabe I mit n Gegenständen eine
Laufzeit von O nk+1 und eine durch 1 + k1 nach oben beschränkte Güte.

Beweis.
Schritt 1. Die Größen der Felder τ und γ sind nach Satz 2.2 ein
k    e · n k
X n 
≤ = O nk .
ν=1
ν k

Eine Inspektion des Pseudocodes ergibt, daß in Großschritt 3 je Feldeintrag eine durch
O (n) beschränkte
 Rechenzeit anfällt. Folglich hat Großschritt 3 einen Zeitbedarf von
k+1
O n , der den der anderen Schritte dominiert.
Schritt P
2. Wir beweisen die folgende Hilfsaussage. Ist r1 ≥ r2 ≥ . . . ≥ rm ≥ 0, und ist
r= m r
i=1 ri , so gilt für alle i = 1, 2, . . . , m die Ungleichung i ≥ ri .
Angenommen, das Gegenteil wäre richtig. Dann gäbe es einen Index i ∈ {1, 2, . . . , m}
mit ri < ri . Dann wäre aber bereits die Summe der ersten i Zahlen größer als r. Das
steht im Widerspruch dazu, daß die Summe aller m Zahlen gleich r ist.
Schritt 3. Sei I = (w1 , w2 , . . . , wn , W ) eine Eingabe des Problems 4.
Fall 1. Es existiert eine optimale Lösung, die der Algorithmus BasisGreedyKnapsackk
erreicht. Dann gilt

Γ(I, BasisGreedyKnapsackk (I)) = 1.

Fall 2. Fall 1 tritt nicht ein. Sei

βopt = {i1 < i2 < . . . < ip }

eine optimale Lösung. Da BasisGreedyKnapsack k diese Lösung während seiner Rech-


nung auf I nie erreicht, ist p > k. Startet BasisGreedyKnapsackk von der Anfangs-
befüllung

β0 := {i1 < i2 < . . . < ik }

seine Greedy-Strategie, so berechnet er die Lösung

β1 ⊃ β0 ,

192
die er anschließend mit der bisher ermittelten besten Lösung vergleicht.
Da βopt 6⊆ β1 ist, gibt es einen Index q mit k + 1 ≤ q ≤ p, so daß iq 6∈ β1 ist.
Warum gelingt es BasisGreedyKnapsackk nicht, den Gegenstand iq in den Rucksack zu
legen? Er paßt zu dem Zeitpunkt der Entscheidung darüber nicht mehr hinein. Daraus
folgt

Val(I, β1 ) + wiq > W ≥ Opt I.

Da die zulässige Lösung β1 nicht besser sein kann als diejenige Lösung, die Algorithmus
11.26 auf die Eingabe I letztendlich ausgibt, ist

Val(I, BasisGreedyKnapsackk (I)) + wiq > Opt I.

Unter Verwendung von


p
X
Opt I = wij
j=1

und von

wi1 ≥ . . . ≥ wiq ≥ . . . ≥ wip

folgt aus der in Schritt 2 bewiesenen Hilfsaussage

Opt I Opt I
≥ ≥ wiq .
k+1 q

Wir erhalten
Opt I
Val(I, BasisGreedyKnapsackk (I)) + > Opt I,
k+1
woraus die Behauptung folgt. 

11.6 Backtracking
Wir beziehen uns in diesem Abschnitt auf ein Maximierungsproblem Π, wie wir es in
Abschnitt 11.3 definiert haben. Es wird stets eine optimale Lösung berechnet, aber zur
Beschränkung der Laufzeit sind zusätzliche Annahmen in Bezug auf die Struktur des Pro-
blems notwendig. Diese sind jedoch so allgemein, daß sie fast immer erfüllt sind. Allerdings
ergibt sich für viele interessante Probleme nicht für jede Eingabe ein bedeutender Lauf-
zeitvorteil: Sie bleibt im schlechtesten Fall exponentiell.

193
11.6.1 Backtrack-Baum. Generischer Algorithmus
Die allgemeine Situation
Sei n ∈ N eine natürliche Zahl, und seien

M1 = {a1 < a2 < . . . < aα }


M2 = {b1 < b2 < . . . < bβ }
... ...
Mn = {z1 < z2 < . . . < zω }

total geordnete endliche Menge derart, daß für jede zulässige Eingabe I ∈ Input Π der
Größe“ n

Sol I ⊆ M(n) := M1 × M2 × . . . × Mn

ist. Wie wir aus Abschnitt 11.3 wissen, muß die Menge Sol I durch einen Polynomialzeit-
algorithmus in M(n) identifizierbar sein sein.
Bemerkung. Der Parameter n ist nicht die binäre Länge von |I|. Häufig steht er in
polynomieller Relation zur Problemgröße von I im Einheitskostenmaß (siehe Abschnitt
12.5.2).
Der Backtrack-Baum zu Eingaben der Größe n des Problems Π ist ein gerichteter
geordneter Wurzelbaum (siehe Abschnitt 1.5.2), der wie folgt definiert ist.

– Die Menge seiner Knoten ist gleich


n
[
V ={ }∪ M1 × . . . × Mr .
r=1

– Die Wurzel ist mit jedem Knoten aus M1 durch eine Kante verbunden.

– Für jedes r = 1, 2, . . . , n − 1 ist jeder Knoten (m1 , . . . , mr ) ∈ M1 × . . . × Mr für jedes


mr+1 ∈ Mr+1 mit (m1 , . . . , mr , mr+1 ) ∈ M1 × . . . × Mr × Mr+1 durch eine Kante
verbunden.

– Für r = 1, 2, . . . , n − 1 ergibt sich die Anordnung der Söhne (m1 , . . . , mr , mr+1 )


(mr+1 ∈ Mr+1 ) des Knotens (m1 , . . . , mr ) kanonisch aus der Ordnung der Elemente
der Menge der Mr+1 .

– Die Blätter des Backtrack-Baumes sind alle Elemente aus M1 × . . . × Mn .

Jeder Weg im Backtrack-Baum von der Wurzel zu einem Blatt entspricht für jede
Eingabe I der Größe n umkehrbar eindeutig dem Aufbau einer potentiellen Lösung für I
von links nach rechts. Ob diese Lösung auch zulässig ist, hängt von der aktuellen Eingabe
I ab.

194
Auf der Suche nach einer optimalen Lösung durchlaufen wir die Knoten des Back-
trackbaumes zur Problemgröße n in Vorordnung (siehe Definition 1.37). Der Trick zur
Beschränkung der Laufzeit besteht darin, für möglichst viele Knoten u, die kein Blatt sind,
nach der Inspektion von u den in u wurzelnden Teilbaum Tu zu entfernen. Dazu gibt es
zwei Gründe:

1. Kein Blatt des Baumes Tu ist eine zulässige Lösung.

2. Der Baum Tu hat zwar Blätter, die zulässigen Lösungen entsprechen, aber die zu-
gehörigen Werte liegen unterhalb einer uns zu diesem Zeitpunkt bereits bekannten
unteren Schranke für die optimale Lösung.

Um diese Idee umzusetzen, betten wir unsere Algorithmen in eine Rahmenklasse ein,
die neben Datenfeldern, welche die aktuelle Eingabe I der Größe n und die Mengen Mr
(r = 1, 2, . . . , n) geeignet halten, zusätzlich

– ein Datenfeld optSol für die beste bisher erreichte zulässige Lösung von I, die ja
Blättern des Backtrack-Baumes entsprechen, und

– ein Datenfeld globalLower für eine untere Schranke für Opt I

hat.
Wie wir im weiteren (siehe Algorithmus 11.31, vorletzte Zeile) sehen werden, kann es
Zeitpunkte zur Laufzeit geben, zu denen der Wert Val von optSol bezogen auf die aktuelle
Eingabe I kleiner als der Wert von globalLower ist.
Das Abschneiden von Teilbäumen besorgt eine Methode pruning, die wir zunächst nur
spezifizieren wollen:

Algorithmus 11.28 (Spezifikation des Abschneidens von Teilbäumen)


Methodenkopf:
pruning(m1 , . . . , mr ) returns Boolean
Vorbedingung:
u = (m1 , . . . , mr ) ist ein Knoten aber kein Blatt des Backtrack-Baumes
zur aktuellen Eingabe I der Größe n.
Nachbedingung:
Wird true zurückgegeben, so gilt für alle mr+1 ∈ Mr+1 , . . . , mn ∈ Mn :
- Es ist (m1 , . . . , mr , mr+1 , . . . , mn ) 6∈ Sol I, oder
- falls v = (m1 , . . . , mr , mr+1 , . . . , mn ) zulässig ist, so ist
Val(I, v) < globalLower.

Das Backtracking läßt sich nun besonders kompakt als rekusiver Algorithmus schreiben:

Algorithmus 11.29 (Generischer Backtracking-Algorithmus)

195
Methodenkopf:
backtracking(u)
Vorbedingung:
u ist ein Knoten des Backtrack-Baumes
zur aktuellen Eingabe I der Größe n.
Großschritt 1.
Falls u Blatt ist, und Val(I, u) ≥ globalLower gilt, so führe aus.
optSol ← u
globalLower ← Val(I, u)
return.
Großschritt 2.
Ist u = (m1 , . . . , mr ) kein Blatt, und ist pruning(u) = false, so
führe für alle mr+1 ∈ Mr+1 den rekursiven Aufruf
backtracking(m1 , . . . , mr , mr+1 )
aus.

Die Suche insgesamt wird durch den Aufruf backtracking( ) des Algorithmus 11.29
gestartet. Es ist eine leichte Übungsaufgabe zu zeigen, daß nach seinem Ende das Datenfeld
optSol eine optimale Losung hält, sofern es überhaupt eine zulässige Lösung gibt.

MaxKNAPSACK als Beispiel


Der Parameter n ist die Anzahl der Gegenstände, die in einen Rucksack gelegt werden
können. Für jede Eingabe

I = (w1 , w2, . . . , wn , c1 , c2 , . . . , cn , W )

des Problems 3 der Größe n ist

M1 = M2 = . . . = Mn = {0 < 1}.

11.6.2 Beschneidung des Backtrack-Baumes I


In diesem Abschnitt geht es darum, Teilbäume des Backtrack-Baumes zu entfernen, deren
Blätter unzulässig sind.

Die allgemeine Situation


Für jede Eingabe I ∈ Input Π der Größe n gibt es ein effizient berechenbares Prädikat PI
auf der Menge der Knoten des zu n gehörigen Backtrack-Baumes so, daß

|= PI (m1 , m2 , . . . , mr ) : ⇐⇒ ∀ mr+1 . . . ∀ mn : (m1 , m2 , . . . , mr , mr+1 , . . . , mn ) 6∈ Sol I


(11.52)

196
ist.
Unsere erste Implementation der Methode pruning (Algorithmus 11.28) verwendet nur
das Prädikat aus (11.52).

Algorithmus 11.30 (Abschneiden unzulässiger Teilbäume)


Methodenkopf:
pruning(m1 , . . . , mr ) returns Boolean
Rumpf:
Falls |= PI (m1 , m2 , . . . , mr ), so return true.
return false.
Setzt man Algorithmus 11.30 in Algorithmus 11.29 ein, so erreicht man damit, daß
diejenigen Unterbäume abgeschnitten werden, deren Blätter keine zulässigen Lösungen
sind.

MaxKNAPSACK als Beispiel


Für alle m1 , m2 , . . . , mr ∈ {0, 1} ist
r
X
|= PI (m1 , m2 , . . . , mr ) : ⇐⇒ mi wi > W.
i=1

11.6.3 Beschneidung des Backtrack-Baumes II


Zur Verkleinerung des Backtrack-Baumes kann man mehr tun, als in Abschnitt 11.6.2 dar-
gestellt wurde. Man kann sich von Teilbäumen trennen, deren Blätter keine Verbesserung
des bisherigen Ergebnisses versprechen.

Die allgemeine Situation


Es gibt zwei effizient berechenbare Funktionen lower(u) und upper(u), wobei
u = (m1 , m2 , . . . , mr )
ein Knoten aber kein Blatt des Backtrack-Baumes für Eingaben der Größe n ist, so daß
für jede Eingabe I der Größe n gilt: Ist 6|= PI (u), so liegt
max {Val(I, (u, mr+1, . . . , mn )) | mr+1 ∈ Mr+1 , . . . , mn ∈ Mn , (u, mr+1 , . . . , mn ) ∈ Sol I}
in dem Intervall
[lower(u), upper(u)] .
Wir rüsten Algorithmus 11.30 wie folgt auf: Sind einige Blätter des Teilbaumes Tu zwar
zulässig, gilt aber upper(u) < globalLower, so lohnt sich ein Betreten dieses Teilbaumes
nicht. Seine Bätter sind offensichtlich unzulänglich.

197
Algorithmus 11.31 (Abschneiden unzulässiger oder unzulänglicher Teilbäume)

Methodenkopf:
pruning(m1 , . . . , mr ) returns Boolean
Rumpf:
Falls |= PI (m1 , m2 , . . . , mr ), so return true.
Falls upper(m1 , m2 , . . . , mr ) < globalLower, so return true.
globalLower ← max {globalLower, lower(m1 , m2 , . . . , mr )}
return false.

Algorithmus 11.29 mit Algorithmus 11.31 ist unser allgemeines Backtracking-Schema.

MaxKNAPSACK als Beispiel


Um die Funktionen lower und upper für das Problem 3 auf eine Eingabe

I = (w1 , w2, . . . , wn , c1 , c2 , . . . , cn , W )

zu implementieren, bedienen wir uns der Algorithmen 11.24 (SimpleGreedyKnapsack) und


11.25 (GreedyFractionalKnapsack). Ist

u = (m1 , . . . , mr ) ∈ {0, 1}r

und ist
r
!
X
I(u) := wr+1 , wr+2 , . . . , wn , cr+1 , cr+2 , . . . , cn , W − mi wi ,
i=1

so ist
r
X
lower(u) = mi ci + Val(I(u), SimpleGreedyKnapsack(I(u)))
i=1

und
r
X
upper(u) = mi ci + Val(I(u), GreedyFractionalKnapsack(I(u)))
i=1

Verfährt man für das Rucksack-Optimierungsproblem wie vorstehend beschrieben, kann


man viele Eingaben in akzeptierbarer Zeit bewältigen. Man kann jedoch zeigen, daß man
keinen Polynomialzeit-Algorithmus erhalten hat.

198
Literaturverzeichnis

[CLRS01] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein. Introduction to


Algorithms. MIT Press, 2001.

199
Kapitel 12

NP–Vollständigkeit

12.1 Einleitung
Bereits im Abschnitt 6.1 haben wir das Rucksack-Entscheidungsproblem eingeführt.

Problem 6 (Rucksack-Entscheidungsproblem KNAPSACK)


Eingaben sind Folgen natürlicher Zahlen

I := (w1 , w2 , . . . , wm , c1 , c2 , . . . , cm , W, C) ∈ N2m+2

in kanonischer Darstellung.

Die Problemgröße n = n(I) von I über {0, 1} ist ein


m m
!
X X
Θ | bin wi | + | bin ci | + | bin C| + | bin W | ,
i=1 i=1

denn es ist sinnvoll, die Zahlen durch geeignete Symbole zu trennen.

Ausgabe: Akzeptiere, falls es eine Lösung (β1 , β2 , . . . , βm ) ∈ {0, 1}m mit


m
X m
X
βi wi ≤ W und βi ci ≥ C (12.1)
i=1 i=1

gibt. (Andernfalls akzeptiere nicht.)

Bemerkung. Zunächst ist man geneigt zu fordern, ein Algorithmus zur Lösung des
Problems 6 müsse eine Eingabe verwerfen, wenn die Akzeptierungsbedingung 12.1 nicht
erfüllt ist. (Der Unterschied von unserer Definition zu dieser Forderung wird am Beispiel
einer Rechnung deutlich, die in einer unendlichen Schleife mündet. Diese akzeptiert weder
noch verwirft sie.) Algorithmen, die auf jeden Rechengang halten und entweder akzeptieren

195
oder verwerfen heißen Entscheidungsalgorithmen. Wir werden sehen, daß die Algorithmen
dieses Kapitels mit geringem Aufwand zu Entscheidungsalgorithmen aufgerüstet werden
können (siehe Satz 12.19 aus Abschnitt 12.6). Es ist jedoch unüblich, eine solche Forderung
bereits in der Definition zu erheben.
Wir haben bereits darauf hingewiesen, daß wir Eingaben über {0, 1} codieren. Wie man
das machen kann, sehen wir etwas weiter unten. Auf diese Weise können wir dem Problem
6 eine formale Sprache über {0, 1} zuordnen, die wir in diesem und in ähnlich gelagerten
Fällen mit dem zugrunde liegenden Entscheidungsproblem identifizieren:

KNAPSACK := {I | I erfüllt die Akzeptierungsbedingung 12.1.} (12.2)

Im Kapitel 11 haben wir unter Verwendung verschiedenartiger Entwurfsprinzipien meh-


rere vergebliche Versuche unternommen, einen Polynomialzeit-Algorithmus für das Rucksack-
Optimierungsproblem 3 zu konstruieren. Die dem Problem 3 innewohnende algorithmische
Kompliziertheit scheint zu groß zu sein. Natürlich wäre es optimal, den Nachweis zu führen,
daß es keinen solchen Algorithmus gibt. Aber das ist gegenwärtig nicht möglich.
Um trotzdem zu interessanten Aussagen zu kommen, betrachten wir, wie bereits mit
Problem 6 geschehen, statt des Optimierungsproblems seine Entscheidungsvariante. Man
kann aus einem Polynomialzeit-Algorithmus für das Rucksack-Entscheidungsproblem 6 mit
Hilfe von binärer Suche zu einem Polynomialzeit-Algorithmus für das Rucksack-Optimie-
rungsproblem 3 kommen (siehe Kapitel 14). Die andere Richtung ist offensichtlich: Hat
man einen Polynomialzeit-Algorithmus für das Optimierungsproblem, so kann man den
Nutzen einer optimalen Lösung ausrechnen und testen, ob er oberhalb der angegebenen
Schwelle liegt. Wir beschränken uns in diesem Kapitel auf Entscheidungsprobleme.
Es sind zwei entscheidende Ideen, die Klassifikationen der algorithmischen Kompliziert-
heit von Entscheidungsproblemen in unserem und in ähnlich gelagerten Fällen ermöglichen.

Nichtdeterminismus
Wir betrachten das Rucksack-Entscheidungsproblem 6. Hat man einen möglichen Zeugen
– eine mögliche Lösung – geraten, so kann man in Polynomialzeit verifizieren, ob die Ak-
zeptierungsbedingung erfüllt ist, es sich um einen wirklichen Zeugen handelt. Um diesen
Gedanken umsetzen zu können, studieren wir nichtdeterministische Algorithmen, die in
der Lage sind zu raten.
Wir verfeinern und erweitern Algorithmus 6.11 aus Abschnitt 6.1 geringfügig zu dem
folgenden Algorithmus, der beliebige Wörter I ∈ {0, 1}∗ bearbeitet.

Algorithmus 12.1 (NP–Algorithmus für das Rucksack-Entscheidungsproblem)

196
Großschritt 0 [Syntaxcheck, Einlesevorgang].
0.1 Teste, ob I eine Eingabe des Rucksack-Entscheidungsproblems verschlüsselt.
Ist das nicht der Fall, so verwirf I.
Andernfalls fahre fort.
0.2. Lies I bitweise ein, so daß
ci , wi (i = 1, 2, . . . , m), C, W
nun zur Verfügung stehen.
Großschritt 1 [Rate–Phase].
Rate eine Lösung (β1 , β2 , . . . βm ) ∈ {0, 1}m .
Großschritt 2 [Verifikationsphase].
P
Überprüfe, ob Pm i=1 βi wi ≤ W gilt.
Überprüfe, ob m i=1 βi ci ≥ C gilt.
Ist beides der Fall, so akzeptiere I.
Wir gehen Algorithmus 12.1 schrittweise durch.
1. Der Syntaxcheck ist weder hier noch später ein Problem. Die Codierung der Pro-
blemstellungen über {0, 1} folgt stets dem gleichen Muster. Die Verschlüsselung der
Instanzen des Rucksackproblems ist ein typisches Beispiel. Wir verfahren wie folgt.

– Zunächst wird die Eingabe I über dem Alphabet {0, 1, #, } repräsentiert:

I 7→ bin w1 # bin w2 # . . . # bin wm  bin c1 # bin c2 # . . . # bin cm  bin W  bin C

– Anschließend wird der folgende Blockcode angewandt:

0 7→ 00
# 7→ 01
 7→ 10
1 7→ 11

Der Test, ob es sich bei einer Zeichenkette über {0, 1} um eine Problemstellung des
Rucksack-Entscheidungsproblems handelt, läßt sich nicht nur in polynomialer Zeit,
sondern sogar mit logarithmisch beschränktem Speicher (siehe Abschnitt 12.2) durch-
führen.
Im weiteren lassen wir den Syntaxcheck meist weg. Er ist jedoch stets vorgeschaltet.
Ferner identifizieren wir die Problemstellung mit dem Wort, das sie binär codiert.
Für den Einlesevorgang müssen wir unsere RAM mit Befehlen aufrüsten, die ein
bitweises Einlesen der über {0, 1}n codierten Problemstellung von einem Eingabeband
in Register ermöglicht. Wir werden das in Abschnitt 12.5.2 genauer besprechen. An
dieser Stelle reicht es uns die Feststellung, daß das Einlesen eines Bits eine Zeiteinheit
kostet.

197
Bei Algorithmen, die lediglich sublinear beschränkten Speicher zur Verfügung haben
(siehe Algorithmus DPATH in Abschnitt 12.5.2), kann die Problemstellung natürlich
nicht vollständig eingelesen werden. Man muß sich vielmehr auf sinnvolle Teilwörter
beschränken, für die der Speicher ausreicht. Dieses Problem haben wir bei Algorith-
mus 12.1 jedoch nicht.

2. Für das Raten benötigen wir einen Befehl CHOICE r, wobei r ein Register aus der
Menge R1 bis R25 bezeichnet (siehe auch Abschnitt 12.5.2). Dieser Befehl inkremen-
tiert den Befehlszähler und weist dem Register r ein Element aus {0, 1} zu; er rät ein
Bit. Auf dieser Grundlage ist es nicht schwer, eine Hochsprachen-Routine choice()
zu implementieren, mit deren Hilfe Großschritt 1 die folgende Gestalt erhält:

Für i = 1, 2, . . . , m führe aus:


βi ← choice()

Durch das Raten gibt es auf jede Eingabe mehrere Rechengänge: Der Algorithmus
ist nichtdeterministisch.

3. Um akzeptieren und verwerfen zu können, benötigen wir neben dem END-Befehl zwei
neue Befehle für das Programmende: ACCEPT und REJECT (siehe Abschnitt 12.5.2).

4. In welchem Sinne akzeptiert ein nichtdeterministischer Algorithmus das Rucksack-


Entscheidungsproblem?
Ist I ∈ KNAPSACK, so muß es auf I einen Rechengang geben, der I akzeptiert.
Ist I 6∈ KNAPSACK, so gibt es auf I keinen Rechengang, der I akzeptiert.

5. Bei der Ermittlung der Laufzeit einer Rechnung zählen wir Bitoperationen (sie-
he Abschnitt 12.5.2). Wollen wir die Laufzeit des Algorithmus 12.1 insgesamt be-
urteilen, so werden uns sehr großzügige Bedingungen gewährt: Nur die Eingaben
I ∈ KNAPSACK werden bewertet, und bei diesen zählt nur der beste akzeptierende
Rechengang.

6. Nun ist unmittelbar klar, daß Algorithmus 12.1 ein nichtdeterministischer Poly-
nomialzeitalgorithmus ist, der KNAPSACK akzeptiert:

– Ist I ∈ KNAPSACK, so zählt nur ein Rechengang, bei dem wir eine für uns
günstige Befüllung des Rucksacks geraten haben. Die Verifikationsphase läuft
hier auch dann in Polynomialzeit ab, wenn wir die Bitoperationen zählen.
– Ist dagegen I 6∈ KNAPSACK, so können wir keine Befüllung des Rucksacks
raten, für welche die Verifikationsphase mit einer Akzeptierung endet.

Bereits im Abschnitt 6.1 haben wir die Komplexitätsklassen P und NP gewissermaßen


vorläufig eingeführt. Natürlich ist P ⊆ NP.

198
Warum ist Algorithmus 12.1 kein echter Polynomialzeit-Algorithmus für das Rucksack-
Entscheidungsproblem, der dessen Zugehörigkeit zur Klasse P sichert? Das liegt nicht dar-
an, daß kein realer Rechner eine Methode choice() mit der oben beschriebenen Spezifikati-
on unterstützt. Man könnte sie in ähnlicher Weise wie die Methode random() aus Abschnitt
8.3.6 implementieren. Der Hauptgrund ist vielmehr, daß es Eingaben I ∈ KNAPSACK
gibt, für die nur ein verschwindend geringer Anteil aller möglichen Befüllungen des Ruck-
sacks zu einer Akzeptierung führt.
Um zu der Frage etwas sagen zu können, ob Rate-Phasen in nichtdeterministischen
Polynomialzeit-Algorithmen einen echten Vorteil gegenüber deterministischen Polynomialzeit-
Algorithmen gewähren, brauchen wir die folgende zweite Idee, die in Abschnitt 12.8 vollständig
dargestellt ist.

Vollständigkeit
Gibt es für zwei durch ihre formalen Sprachen L1 und L2 dargestellten Entscheidungspro-
bleme eine in deterministischer Polynomialzeit berechenbare Transformation f der Einga-
ben I1 des ersten Problems auf Eingaben f (I1 ) des zweiten Problems so, daß

I1 ∈ L1 ⇐⇒ f (I1 ) ∈ L2

ist, so kann man bezogen auf Polynomialzeitalgorithmen sagen, das Problem L1 sei nicht
schwerer als das Problem L2 (siehe Algorithmus 12.24).
Es wird sich zeigen, daß das Rucksack-Entscheidungsproblem in diesem Sinne unter
allen Problemen, die einen nichtdeterministischen Polynomialzeit-Algorithmus haben, ein
schwerstes Problem ist. Solche Probleme nennt man NP-vollständig.
Man sieht sofort: Es gibt für NP-vollständige Probleme genau dann einen echten
Polynomialzeit-Algorithmus, wenn man für jeden nichtdeterministischen Polynomialzeit-
Algorithmus einen äquivalenten deterministischen finden kann. Letzteres heißt P = NP.
Es gibt mehr als 1000 praktisch relevante Probleme aus allen Teilen der Informatik, für die
dasselbe gilt.

Zusammenfassung
Wir haben bereits betont, daß es das Beste wäre, die Ungleichung P 6= NP zu beweisen.
Leider sieht es so aus, als läge der Beweis jenseits der Reichweite der verfügbaren Tech-
niken. Unsere Methoden zum Entwurf effizienter Algorithmen sind dagegen weit besser
entwickelt. Da es trotzdem nicht gelungen ist, einen Polynomialzeit-Algorithmus für ein
NP-vollständiges Problem zu entwerfen, wird allgemein angenommen, daß P 6= NP ist.
Wer diese Vermutung akzeptiert, für den ist der Nachweis der NP-Vollständigkeit für ein
Problem gleichbedeutend damit, daß es keinen Polynomialzeit-Algorithmus hat.

199
12.2 Turingakzeptoren und Komplexitätsklassen
Unser bisheriges Rechnermodell war die Registermaschine. Im Abschnitt 12.1 haben wir uns
mit ihrer Hilfe mit dem Begriff des Nichtdeterminismus bekanntgemacht. Warum führen
wir nun ein neues Modell ein?
Um vernüftig Komplexitätstheorie betreiben zu können, müssen wir Rechenvorschriften
(Programme) selbst zum Gegenstand von Rechnungen machen. Dazu ist es sehr hilfreich,
die Architektur des Modell weiter einzuschränken, ohne daß die Möglichkeiten, effiziente
Algorithmen zu implementieren, nennenswert schrumpfen: Der wahlfreie Zugriff auf jede
Speicherzelle wird zu Gunsten einer relativ engen Nachbarschaftsrelation aufgegeben. Wei-
terhin können Speicherzellen nicht mehr eine beliebige ganze Zahl sondern nur noch Sym-
bole aus einem endlichen Arbeitsalphabet“ Γ enthalten. Die letztgenannte Einschränkung

wird uns dadurch versüßt, daß nunmehr der Wechsel von einem Symbol in jedes beliebige
andere möglich ist. (Registermaschinen ließen ja nur arithmetische, logische und Verschie-
beoperationen zu.)
Zwei–Wege–Eingabeband
⊲ w1 w2 ··· wn−1 wn ⊳

Zwei–Wege–Arbeitsband

··· B B y11 y12 ··· y1ν1 B ···

.. .. ..
. . .

Zwei–Wege–Arbeitsband

··· B B yr1 yr2 ··· yrνr B ···

Endliche Kontrolle
Q: endliche Menge von Zuständen
δ: Überführungsrelation zur Berechnung
des nächsten Schrittes
q0 ∈ Q: initialer Zustand
q+ ∈ Q: akzeptierender Zustand
q− ∈ Q: verwerfender Zustand

Abbildung 12.1: Nichtdeterministischer Turingakzeptor

Ein nichtdeterministischer Turingakzeptor (NTA) ist in Abbildung 12.1 dargestellt. Die


Eingabe w = w1 w2 . . . wn über dem Alphabet {0, 1} steht auf einem Eingabeband, wobei

200
jede Zelle“ genau einen Buchstaben enthält. Die Symbole ⊲ und ⊳ heißen linker bzw.

rechter Randbegrenzer.
Die Inhalte der Zellen der Arbeitsbänder gehören zu einem endlichen Alphabet Γ ∪ {B}
(B 6∈ Γ), wobei Γ das Arbeitsalphabet und B das Blanksymbol heißt. Es ist sicherlich sinnvoll
aber nicht zwingend, die Inklusion {0, 1} ⊆ Γ zu fordern. Das Blanksymbol steht für
den unberührten Zustand der Zellen auf den Arbeitsbändern vor Beginn jeder Rechnung.
Wurde in Laufe einer Rechnung in eine Zelle etwas geschrieben, so gibt es zu diesem
Anfangszustand keine Rückkehr mehr: Das Blanksymbol kann nicht geschrieben werden.

Ganz am Anfang einer Rechnung auf eine Eingabe w = w1 w2 . . . wn befindet sich die
Maschine im initialen Zustand q0 , der Lesekopf des Eingabebandes steht auf dem linken
Randbegrenzer ⊲ und alle Arbeitsbänder sind leer.

Ein Rechenschritt von M auf w = w1 w2 . . . wn hängt von der lokalen Situation

(q, b, y1, y2 , . . . , yr )

der Maschine ab, wobei

– q ∈ Q den aktuelle Zustand,

– b den Inhalt der Zelle, auf dem der Lesekopf des Eingabebandes steht,

– (y1 , y2 , . . . , yr ) den Inhalt der Zellen, auf denen die Lese–Schreib–Köpfe der Ar-
beitsbänder stehen,

bezeichnet.

In einem Rechenschritt

– ändert die Maschine den inneren Zustand;

– verändert die Buchstaben unter den Lese–Schreib–Köpfen der Arbeitsbänder, wobei


B nicht geschrieben werden darf;

– bewegt die Köpfe der Bänder um höchstens eine Position nach links oder rechts,
wobei die Randbegrenzer ⊲ und ⊳ auf dem Eingabeband nicht überschritten werden
dürfen.

Wann ist die Rechnung beendet? Aus technischen Gründen werden wir NTAs so defi-
nieren, daß jede Rechnung syntaktisch bis in alle Ewigkeit weitergeht. Um ein semantisches
Ende einer Rechnung definieren zu können, fordern wir für jeden NTA M, daß sich stets
nichts mehr tut, wenn M einen terminalen Zustand (q+ oder q− ) erreicht hat: Die Ma-
schine bleibt in dem entsprechenden Zustand, bewegt ihre Köpfe nicht mehr und erneuert
die Inhalte der aktuellen Zellen der Arbeitsbänder. Anders ausgedrückt, ein NTA M ist

201
so definiert, daß er aus keiner lokalen Situation, deren erste Komponente ein terminaler
Zustand ist, wieder herauskommt.
Wir sagen, die Maschine würde stoppen oder halten, wenn sie einen terminalen Zustand
erreicht hat.
Es ist nicht ausgeschlossen, daß lokale Situationen, deren erste Komponente kein termi-
naler Zustand ist, für einen NTA M in der soeben beschriebenen Weise zur Falle werden.
Wir sprechen trotzdem nicht davon, daß die Maschine gehalten hat. Diese Situation inter-
pretieren wir vielmehr so, daß M sich in einer unendlichen Schleife (mit leerem Rumpf)
befindet.

Die Maschine M akzeptiert die Eingabe w, wenn es einen Rechengang von M auf w
gibt, der in den akzeptierenden Zustand q+ führt.
Man beachte jedoch, daß Eingaben w, die von M nicht akzeptiert werden, nicht in
jedem Falle dadurch verworfen werden, daß jede Rechnung von M auf w zum verwerfenden
Zustand führt. Es ist lediglich garantiert, daß es keine Rechnung zu q+ gibt.

Formal gesehen ist das Entscheidende an einem NTA M dessen Überführungsrelation


δM . Sie ist das konkrete Programm“. Die Relation δM besteht aus Elementen der folgenden

Art:

( (q, b, y1 , y2 , . . . , yr ) , (q ′ , ρ0 , (y1′ , ρ1 ), (y2′ , ρ2 ), . . . , (yr′ , ρr ))) (12.3)


| {z } | {z }
∈Q×{0,1,⊲,⊳}×(Γ∪{B})r ∈Q×{L,R,N }×(Γ×{L,R,N })r
Argumentteil Wertteil

Der Argumentteil (q, b, y1 , y2, . . . , yr ) ist die lokale Situation, in der sich die Maschine
befindet. Mit q ′ ist der Nachfolgezustand bezeichnet. Die Maschine überschreibt für al-
le j = 1, 2, . . . , r den Inhalt yj der aktuellen Zelle des j–ten Arbeitsbandes mit yj′ und
bewegt dann sämtliche Köpfe gemäß ρi (i = 0, 1, . . . , r). Das Symbol L steht für Bewege

den Kopf nach links.“, R für Bewege den Kopf nach rechts.“ und N für Bewege den Kopf
” ”
nicht.“.
Die vorstehend dargestellten Verabredungen lassen sich nun formal wie folgt fassen:

1. Die Implikationen

b = ⊲ ⇒ ρ0 6= L

und

b = ⊳ ⇒ ρ0 6= R

stehen für das Verbot, die Randbegrenzer zu überschreiten.

2. Die Forderung, daß es für jeden zulässigen Argumentteil α mindestens einen Wertteil
ω mit (α, ω) ∈ δM gibt, beschreibt, daß keine Rechnung syntaktisch gesehen jemals
endet.

202
3. Die Implikationen

(q = qf ) ⇒ (q ′ = qf , ρ0 = ρ1 = . . . = ρr = N, y1 = y1′ , . . . , yr = yr′ ) (qf ∈ {q+ , q− })

bedeuten, daß die Rechnung semantisch zu Ende ist, wenn die Maschine einen ter-
minalen Zustand erreicht hat.

Bemerkung. Die syntaktische Umsetzung eines Löschvorgangs auf den Arbeitsbändern


muß mit einem speziellen Element B ′ ∈ Γ, dem sogenannten Pseudoblank, erfolgen.

Um für das Weitere gerüstet zu sein, benötigen wir die folgenden Begriffe.

Definition 12.2 Sei M ein NTA mit r Arbeitsbändern.

1. Eine Konfiguration C von M auf die Eingabe w = w1 , w2 . . . wn ist ein Tupel

C := (q, (w, k0), (u1 , k1 ), (u2, k2 ), . . . , (ur , kr )) , (12.4)

wobei

– q der Zustand ist, in dem sich M gerade befindet;


– w = w1 w2 . . . wn die Eingabe ist;
– k0 ∈ [0, n + 1] die Position des Lesekopfes auf dem Eingabeband beschreibt,
wobei 0 und n + 1 dafür stehen, daß ⊲ bzw. ⊳ gelesen wird;
– uj der von B verschiedene Inhalt des j–ten Arbeitsbandes ist (j = 1, 2, . . . , r);
– die Zahl kj (kj ∈ [0, |uj | + 1]) die Kopfposition auf dem j–ten Arbeitsband ist
(j = 1, 2, . . . , r). (Steht der Lese–Schreibkopf auf dem Blank–Symbol unmittel-
bar links (rechts) vom nichtleeren Inhalt des j–ten Arbeitsbandes, so schreiben
wir dafür kj = 0 (kj = |uj | + 1]). Dabei bezeichnet |wj | die Länge der Zeichen-
kette uj .)

2. Eine Konfiguration C = (q, (w, k0), (u1, k1 ), (u2 , k2), . . . , (ur , kr )), wenn q = q+ (q =
q− ) heißt akzeptierend (verwerfend ).

3. Die initiale Konfiguration von M auf w ∈ {0, 1}∗ ist

C0 (w) := (q0 , (w, 0), (ǫ, 0), (ǫ, 0), . . . , (ǫ, 0)).

(Der Lesekopf des Eingabebandes steht auf dem linken Randbegrenzer ⊲, die Ar-
beitsbänder sind leer. Die Maschine ist im initialen Zustand q0 .)

4. Eine Konfiguration C ′ = (q, (w, k0′ ), (u′1, k1′ ), (u′2 , k2′ ), . . . , (u′r , kr′ )) ist unmittelbarer
Nachfolger von C = (q, (w, k0), (u1 , k1 ), (u2, k2 ), . . . , (ur , kr )) (Bezeichnung: C ⊢δ C ′
oder C ⊢ C ′ ), wenn C ′ aus C durch genau einen Rechenschritt aus C hervorgeht.

203
5. Eine Rechnung von M auf eine Eingabe w ist eine Folge CM (w) := C0 (w) ⊢ C1 ⊢
C2 ⊢ . . . ⊢ Ct . Der Wert von t heißt die Länge der Rechnung. (Die Länge kann auch
gleich ∞ sein.)

6. Eine Rechnung CM (w) von M auf w endlicher Länge t heißt akzeptierend (verwer-
fend ), wenn Ct eine akzeptierende (verwerfende) Konfiguration ist.

7. Die durch M akzeptierte formale Sprache L(M) ist die Menge aller Eingaben w ∈
{0, 1}∗, für die es eine akzeptierende Berechnung gibt.

L(M) := {w ∈ {0, 1}∗ | ∃ CM (w) : C0 (w) ⊢∗ Ct , Ct ist akzeptierende Konfiguration}

8. Sei t : N → N eine monoton wachsende Funktion. Die Maschine M heißt t–zeit-


beschränkt, wenn es für jedes w ∈ L(M) eine akzeptierende Berechnung mit einer
Länge gibt, die nach oben durch t(|w|) beschränkt ist. (Man beachte, daß diese Defi-
nition außerordentlich großzügig ist. In Betracht kommen nur akzeptierende Berech-
nungen, und da auch nur die für eine Eingabe jeweils beste.)

9. Der Speicherbedarf einer Konfiguration

C = (q, (w, k0), (u1 , k1 ), (u2, k2 ), . . . , (ur , kr ))

ist gleich der Anzahl der beschriebenen Zellen auf den Arbeitsbändern:
r
X
space(C) := |ui |.
i=1

10. Der Speicherbedarf einer Rechnung

CM (w) = C0 (w) ⊢ C1 ⊢ C2 ⊢ . . . ⊢ Ct

der Maschine M auf w ist gleich dem Maximum des Speicherbedarfs der beteiligten
Konfigurationen:

space(CM (w)) := max space(Ci ).


1≤i≤t

Da das Blank B nicht geschrieben werden darf, gilt:

space(CM (w)) = space(Ct ).

11. Sei s : N → N eine monoton wachsende Funktion. Die Maschine M heißt s–speicher-
beschränkt, wenn es für jedes w ∈ L(M) eine akzeptierende Rechnung gibt, deren
Speicherbedarf nach oben durch s(|w|) beschränkt ist. (Wiederum kommen nur die
jeweils besten akzeptierenden Berechnungen in Betracht.)

204
Eine nichtdeterministische Turingmaschine M ist kein Algorithmus im landläufigen Sinne.
Im Abschnitt 6.1 und im Abschnitt 12.1 haben wir das Rucksack–Entscheidungsproblem
betrachtet. Um herauszufinden, ob es eine Befüllung des Rucksacks mit den geforderten
Eigenschaften gibt, darf die Maschine eine solche raten. Dies entspricht der Tatsache, daß
es für manche Argumentteile α mehrere Wertteile β mit (α, β) ∈ δM gibt. (Aus Definition
12.2 wissen wir, daß es völlig uninteressant ist, was passiert wenn falsch geraten wird, oder
wenn es nichts zu raten gibt.) Ein Algorithmus ist jedoch determiniert: Jede Konfiguration
darf nur höchstens einen Nachfolger haben.

Definition 12.3 Ein NTA M, für dessen Überführungsrelation δM zu jedem Argumentteil


α genau ein Wertteile β mit (α, β) ∈ δM existiert, heißt deterministisch (Akürzung: DTA).
Statt (α, β) ∈ δM schreibt man in diesem Fall natürlich δM (α) = β.

Wir führen die folgenden Komplexitätsklassen ein.

Definition 12.4 Wir betrachten formale Sprachen L über dem Alphabet {0, 1}.

1. Die Komplexitätsklasse DTIME (t) (NTIME (t)) ist die Familie aller formalen
Sprachen, die durch einen O (t)–zeitbeschränkten DTA (NTA) akzeptiert werden
können:

DTIME (t) := {L | Es gibt einen O (t) -zeitbeschränkten DTA M mit L(M) = L.}
NTIME (t) := {L | Es gibt einen O (t) -zeitbeschränkten NTA M mit L(M) = L.}

2. Die Komplexitätsklasse P besteht aus allen formalen Sprachen, für die es einen po-
lynomialzeitbeschränkten DTA gibt, der sie akzeptiert:

[ 
P := DTIME nk
k=0

3. Die Komplexitätsklasse NP besteht aus allen formalen Sprachen, für die es einen
polynomialzeitbeschränkten NTA gibt, der sie akzeptiert:

[ 
NP := NTIME nk
k=0

4. Die Komplexitätsklasse DSPACE (s) (NSPACE (s)) ist die Familie aller formalen
Sprachen, die durch einen O (s)–speicherbeschränkten DTA (NTA) akzeptiert werden
können:

DSPACE (s) := {L | Es gibt einen O (s) -speicherbeschränkten DTA M mit L(M ) = L.}
NSPACE (s) := {L | Es gibt einen O (s) -speicherbeschränkten NTA M mit L(M ) = L.}

205
5. Die Komplexitätsklasse L besteht aus allen formalen Sprachen, für die es einen loga-
rithmisch speicherbeschränkten DTA gibt, der sie akzeptiert:

L := DSPACE (log(n))

6. Die Komplexitätsklasse NL besteht aus allen formalen Sprachen, für die es einen
logarithmisch speicherbeschränkten NTA gibt, der sie akzeptiert:

NL := NSPACE (log(n))

7. Die Komplexitätsklasse PSPACE (NPSPACE) ist die Familie aller formalen Spra-
chen, die durch einen nO(1) –speicherbeschränkten DTA (NTA) akzeptiert werden
können:

[ 
PSPACE := DSPACE nk
k=0
[∞

NPSPACE := NSPACE nk
k=0

Beispiele.
• Im Grundkurs Informatik I/II sind zahlreiche Beispiele von Entscheidungsproblemen
aus P besprochen worden. Eine Zusammenfassung findet sich im Abschnitt 6.1, eine
ausführliche Darstellung im Teil II.
• Das Studium wichtiger Vertreter der Klasse NP bilden den Hauptgegenstand dieses
Kapitels. Ein Beispiel kennen wir bereits aus dem Abschnitt 6.1 bzw. dem Abschnitt
12.1: Das Rucksack-Entscheidungsproblem.
• Das Graph-Accessibility-Problem (GAP) ist wie folgt definiert. Gegeben ist ein gerich-
teter Graph G = (V, E), dessen Knotenmenge o.B.d.A. die Menge {1, 2, . . . , m} ist.
G steht als eine kanonische binäre Codierung der Adjazenzlisten auf dem Eingabe-
band zur Verfügung: Für jeden Knoten i ∈ {1, 2, . . . , m} wird dessen Nachfolgerliste
als Zeichenkette i#j1 #j2 # . . . #jλi  über dem Alphabet {0, 1, #, } dargestellt und
wie in Abschnitt 12.1 geschildert über {0, 1} codiert. Der Graph G wird durch die
Verkettung dieser Wörter für jeden seiner Knoten dargestellt. Seine Eingabenlänge
n ist dann ein O (m2 log m).
Die formale Sprache GAP besteht aus allen binären Codierungen von solchen Gra-
phen, für die es einen gerichteten Weg vom Knoten 1 zum Knoten m gibt.
Wir beschreiben einen O (log n)-speicherbeschränkten NTA M, der GAP akzeptiert.
Angesetzt auf einen Graphen G mit m Knoten rät M einen Pfad in G, der mit 1
beginnt. Um einen Pfad v1 = 1, vi2 , . . . zu erzeugen, muß M den aktuellen Knoten vij
auf einem Arbeitsband halten. Das ist mit log m-beschränktem Speicher möglich. Aus
der Nachfolgerliste auf dem Eingabeband entscheidet sich M für einen der Nachfolger

206
von vij im Wege des Ratens. Anschließend aktualisiert M den gehaltenen Knoten und
testet, ob es sich dabei um m handelt.
Der Platzbedarf der oben beschriebenen Maschine M ist aus O (log m) ⊆ O (log n).
Wir werden im Abschnitt 12.9 sehen, daß GAP für die Klasse NL ein algorithmisch
schwerstes Problem ist.
• Das Problem GAP1 ist wie folgt definiert. Gegeben ist ein gerichteter Graph G =
(V, E), dessen Knotenmenge o.B.d.A. die Menge {1, 2, . . . , m} ist. Wiederum ist G
durch eine kanonische binäre Codierung der Adjazenzlisten gegeben ist. Die formale
Sprache GAP1 besteht aus allen binären Codierungen von solchen Graphen, für die
es einen gerichteten Weg vom Knoten 1 zum Knoten m gibt, und für die überdies
folgendes gilt: Für jeden Knoten von G gibt es höchstens einen Nachbarknoten.
Die Beschreibung eines O (log n)-speicherbeschränkten DTA M, der GAP1 akzep-
tiert, ist eine leichte Übungsaufgabe.
Man kann formalisieren, daß GAP1 für die Klasse L ein algorithmisch schwerstes
Problem ist.
Logarithmischer Raum ist also nicht viel mehr als die Modellierung eines Lesezeichens
für das Eingabeband.
• Die Auswertung einer Booleschen Formel (formale Definition siehe Abschnitt 12.10)
benötigt nur logarithmisch beschränkten Raum. Folglich gehört die Sprache, die aus
allen Paaren (F (x1 , x2 , . . . , xn ), b = (b1 , b2 , . . . , bn )), wobei F eine Boolesche Formel
in den Variablen x1 , x2 , . . . , xn und b ein Boolescher Vektor ist, für den F (b) = 1 ist,
zur Komplexitätsklasse L.
• In Informatik I/II haben wir reguläre Sprachen kennengelernt. Ist L eine reguläre
Sprache über {0, 1}, so wissen wir, daß ein endlicher Automat für jedes x die Ent-
scheidung x ∈ L? fällen kann. Folglich gehört L zu  L. Für eine kontextfreie Sprache
2
L weiß man lediglich, daß L ∈ DSPACE log n ist.
• Eine quantifizierte Boolesche Formel (QBF) ist ein Ausdruck der Form

E = Q1 x1 Q2 x2 . . . Qm xm F (x1 , x2 , . . . , xm ) , (12.5)

wobei jedes Qi entweder der Allquantor ∀ oder der Existenzquantor ∃ ist, und
F (x1 , x2 , . . . , xm ) eine Boolesche Formel in den Booleschen Variablen x1 , x2 , . . . , xm
bezeichnet.
Beispiele für Boolesche Formeln sind x∧y oder (x∨y) ∧z. Boolesche Formeln definie-
ren auf kanonische Weise Boolesche Funktionen. Die formale Einführung Boolescher
Formeln findet sich in Definition 12.33.
Werden alle Variablen einer Booleschen Formel durch Quantoren gebunden, so erhält
man eine Aussage, die entweder wahr oder falsch ist.
Die Aussage ∀x∃y(x ∨ y) ist offenbar wahr, ∀x∃y(x ∧ y) dagegen falsch.
Die formale Sprache QBF besteht aus allen binären Codierungen wahrer quantifizier-
ter Boolescher Formeln.

207
Wann ist die QBF E aus Gleichung 12.5 wahr ? Das ist genau dann der Fall, wenn

m = 0 und F ≡ 1 oder (12.6)


Q1 = ∃ und E0 oder E1 sind wahr oder (12.7)
Q1 = ∀ und E0 und E1 sind wahr, (12.8)

wobei F0 und F1 diejenigen Formeln bezeichnen, die man aus F erhält, indem man die
Variable x1 durch 0 bzw. 1 ersetzt, und Ei = Q2 x2 . . . Qm xm Fi (x2 , . . . , xm ) (i = 0, 1)
ist.
Es ist nicht schwer, einen Algorithmus zu schreiben, der unter Verwendung der Glei-
chungen 12.6, 12.7 und 12.8 mit O (m + |F |) beschränkten Platz entscheidet, ob
die QBF E aus Gleichung 12.5 wahr ist. Dabei ist |F | die Länge einer kanonischen
binären Codierung der Formel F = F (x1 , x2 , . . . , xm ).
Das Problem QBF ist folglich in PSPACE.
Wir werden im Abschnitt 12.9 sehen, daß QBF für die Klasse PSPACE ein algo-
rithmisch schwerstes Problem ist.
Für eine Zeitschranke t setzen wir stets voraus, daß sie monoton wachsend ist und stets
t(n) = Ω(n) ist. Letzteres wird angenommen, damit die Eingabe auch gelesen werden
kann.
Für Speicherschranken s gilt neben der Monotonie wie bei Zeitschranken immer s(n) =
Ω(log n).

12.3 Turingtransduktoren und Funktionenklassen


Turingtransduktoren (TT) sind stets deterministisch. In Abbildung 12.2 ist ein TT darge-
stellt.
Die Definition eines TT M mit r Arbeitsbändern gleicht bis auf die folgenden Modifi-
kationen der eines DTA (siehe Definition 12.2):

1. Die Überführungsfunktion δM ist wie folgt spezifiziert:

δM : Q × {0, 1, ⊲, ⊳} × (Γ ∪ {B})r → Q × {L, R, N} × (Γ × {L, R, N})r × {0, 1, ǫ}


(q, b, u1, u2 , . . . , ur ) → (q ′ , ρ0 , (u′1 , ρ1 ), (u′2 , ρ2 ), . . . , (u′r , ρr ), y),
(12.9)

wobei die letzte Komponente y des Bildes die Ausgabe bezeichnet, die in diesem
Schritt getätigt wird. (Diese ist entweder ein Bit oder aber das leere Wort. Die Ma-
schine ist also nicht gezwungen, in jedem Schritt etwas auszugeben.)

2. Die Implikationen

(q = qterm ) ⇒ (q ′ = qterm , ρ0 = ρ1 = . . . = ρr = N, u1 = u′1 , . . . , ur = u′r , y = ǫ)

208
Zwei–Wege–Eingabeband

⊲ x1 x2 ··· xn−1 xn ⊳

Zwei–Wege–Arbeitsband

··· B B u11 u12 ··· u1ν1 B ···

.. .. ..
. . .

Zwei–Wege–Arbeitsband

··· B B ur1 ur2 ··· urνr B ···

Endliche Kontrolle
Q: endliche Menge von Zuständen
δ: Überführungsfunktion zur Berechnung
des nächsten Schrittes
q0 ∈ Q: initialer Zustand
qterm ∈ Q: terminaler Zustand

y1 y2 y3 ··· ··· yk−1 yk ···

Ein–Weg–Ausgabeband

Abbildung 12.2: Turingtransduktor

bedeuten auch hier, daß die Rechnung semantisch zu Ende ist, wenn die Maschine den
terminalen Zustand erreicht hat. Insbesondere wird dann nichts mehr ausgegeben.
(Eine andere Regelung ließe sich schwerlich mit unserer Terminologie vereinbaren,
nach der die Maschine hält, wenn sie qterm erreicht hat.)
3. Eine Konfiguration C von M auf die Eingabe x = x1 x2 . . . xn ist ein Tupel
C := (q, (x, k0 ), (u1 , k1 ), (u2, k2 ), . . . , (ur , kr ), y) , (12.10)
wobei y ∈ {0, 1}k die bisher getätigte Ausgabe über dem Alphabet {0, 1} ist.
4. Eine Konfiguration
C = (q, (x, k0 ), (u1, k1 ), (u2 , k2 ), . . . , (ur , kr ), y = y1 y2 . . . ym )
mit q = qterm heißt terminal.

209
5. Die initiale Konfiguration von M auf x ∈ {0, 1}∗ ist

C0 (x) := (q, (x, 0), (ǫ, 0), (ǫ, 0), . . . , (ǫ, 0), ǫ).

6. Bei der Messung des Speicherbedarfs einer Konfiguration zählt die Ausgabe nicht
mit.

7. Die Maschine M hält auf eine Eingabe x, wenn

C0 (x) ⊢∗ (qterm , (x, k0 ), (u1 , k1 ), (u2, k2 ), . . . , (ur , kr ), y)

gilt. In diesem Falle heißt y die Ausgabe von M auf x. Falls die Maschine M auf x
nicht hält, gibt sie auf x auch nichts aus. Folglich berechnet M eine partielle Funktion

f : {0, 1}∗ −→ {0, 1}∗ .

8. Eine Funktion

f : {0, 1}∗ −→ {0, 1}∗ .

heißt turingberechenbar, wenn es einen TT M gibt, der f wie unter 7.) beschrieben
berechnet.

9. Die Komplexitätsklasse FP besteht aus allen total definierten Funktionen f , die


durch einen polynomialzeitbeschränkten Turingtransduktor berechenbar sind:

FP := {f | f ist durch eine nO(1) -zeitbeschränkten TT berechenbar}

10. Die Komplexitätsklasse FL besteht aus allen total definierten Funktionen f , die
durch einen Turingtransduktor berechenbar sind, der mit logarithmisch beschränktem
Speicher auskommt:

FL := {f | f ist durch eine O (log n) -speicherbeschränkten TT berechenbar}

Das Kürzel TM“ steht ab jetzt sowohl für eventuell nichtdeterministische Turingakzep-

toren als auch für Turingtransduktoren.
Es folgen einige Eigenschaften der Funktionenklassen FP und FL, die wir im Abschnitt
12.8 bei der sinnvollen Einführung der many-one-Reduzierbarkeit einer formalen Sprache
auf eine andere brauchen werden.

Definition 12.5 Eine TM M simuliert eine TM M ′ Schritt für Schritt, wenn jeder Schritt
Ci ⊢ Ci+1 der Maschine M einer Folge von Schritten Cji ⊢ Cji+1 ⊢ . . . ⊢ Cji +li der Maschine
M ′ entspricht.

Lemma 12.6 Die Klasse FP ist abgeschlossen gegenüber Verkettung.

210
Beweis. Sei für i = 1, 2 Mi ein TT, der eine Funktion fi ∈ FP nki -zeitbeschränkt
berechnet. Wir konstruieren einen TT M, der f2 ◦ f1 berechnet.
Angesetzt auf eine Eingabe x simuliert M in einer ersten Phase Schritt-für-Schritt M1 ,
wobei er ein zusätzliches Arbeitsband als Hilfsausgabeband nutzt.
Anschließend simuliert M Schritt-für-Schritt M2 , wobei das zusätzliche Arbeitsband
nun als Hilfseingabeband dient. Das Eingabeband wird in dieser Phase nicht gebraucht.
Die Ausgabe erfolgt auf dem Ausgabeband.
Man überlegt sich sofort, daß die Laufzeit von M auf jede Eingabe der Länge n durch
nk1 + nk1 ·k2 nach oben beschränkt ist. 

Lemma 12.7 Die Klasse FL ist in der Klasse FP enthalten.

Beweis. Sei M ein TT, der eine Funktion f ∈ FL berechnet. Der TT M hält auf jede
Eingabe x ∈ {0, 1}∗ , gibt f (x) aus und benutzt auf seinen r Arbeitsbändern höchstens
O (log n) Zellen.
Für eine Eingabe x sieht eine Konfiguration von M auf x gemäß Gleichung 12.10 so
aus:
 

C := q, (x, k0 ), (u1, k1 ), (u2 , k2 ), . . . , (ur , kr ), y1y2 . . . yℓ  .


| {z } | {z }
Eingabe- und Arbeitsteil Ausgabeteil

Ist die Maschine M in der Konfiguration C, so hängt der weitere Verlauf der Rechnung
nur von deren Eingabe- und Arbeitsteil ab. Die bisher getätigte Ausgabe y1 y2 . . . yℓ darf ja
nicht mehr inspiziert werden.
Da für jedes solche C und jedes i = 1, 2, . . . , r für die Inschrift des i-ten Arbeitsbandes
|ui| = O (log |x|) ist, gibt es auf eine Eingabe der Länge n nur nO(1) viele verschiedene
Eingabe- und Arbeitsteile von Konfigurationen auf diese Eingabe.
Gäbe es eine Eingabe x derart, daß M auf x mehr Schritte machte als es Eingabe- und
Arbeitsteile von Konfigurationen auf x gibt, so käme M in eine Schleife und könnte f (x)
nicht ausgeben. 

Bemerkung. Es ist offensichtlich, daß jede Funktion f ∈ FP in dem folgenden Sinne


polynomial längenbeschränkt ist: Für jedes w ∈ {0, 1}∗ ist |f (w)| = |w|O(1) .

Lemma 12.8 Die Klasse FL ist abgeschlossen gegenüber Verkettung.

Beweis. Zunächst ist man geneigt, genauso zu verfahren wie beim Beweis von Lemma
12.6. Das Problem ist, daß im allgemeinen die Länge der Zeichenkette f1 (x) zu groß ist,
um sie auf ein Arbeitsband der Maschine für f2 ◦ f1 zu schreiben.
Wir beschreiben einen TT M, der angesetzt auf eine Eingabe x den TT M2 , angesetzt
auf f1 (x), Schritt-für-Schritt simuliert. Dazu hält M auf einem zusätzlichen Arbeitsband
einen Zähler z für den Index des nächsten Bits von f1 (x), der von M2 gelesen werden muß.

211
Simulation des Leseschritts von M2 . M erzeugt auf einem zusätzlichen Arbeitsband
eine Arbeitskopie ẑ von z und startet eine Simulation von M1 angesetzt auf x. Allerdings
wird, sofern die simulierte Maschine M1 ein Bit ausgeben will, diese Ausgabe unterdrückt.
Es wird lediglich der Zähler ẑ dekrementiert. Ist der Zählerstand gleich null, so ist das Bit,
das bei M1 gerade zur Ausgabe anstünde, das gesuchte Bit von f1 (w).
Simulation der Bewegung des Lesekopfes von M2 . Im wesentlichen wird der Zähler
z inkrementiert bzw. dekrementiert. Allerdings muß vor jeder Inkrementierung getestet
werden, ob f1 (x) überhaupt ein solches Bit hat. Das geschieht in einer Weise, die zur
Simulation des Leseschritts von M2 analog ist.
Die Simulation der Aktionen von M2 auf seinen Arbeitsbändern erfolgt direkt. Dafür
hat M zusätzliche Arbeitsbänder in gleicher Zahl.
Die Speicherschranke ist gewahrt, da für jedes x der Wert f1 (x) eine Länge hat, die
durch ein Polynom in |x| beschränkt ist. Folglich benötigt die Darstellung des Zählers z
nur O (log |x|)-beschränkten Platz. 

Beispiele.

• Alle effizienten Algorithmen, die wir in dieser Vorlesung kennengelernt haben, be-
rechnen Funktionen aus FP. Man muß lediglich die binäre Größe der Eingabezahlen
unter Kontrolle halten und darauf achten, daß man keine Befehle der multiplikativen
Arithmetik verwendet. Warum das so ist, besprechen wir im Abschnitt 12.5.
• Man kann sich davon überzeugen, daß die Addition und die Multiplikation zweier
n-Bitzahlen Funktionen sind, die zu FL gehören.

Man vermutet, daß nicht alle Funktionen aus FP in FL liegen. Beweisen kann man das
jedoch nicht.

12.4 Elementare Techniken des Rechnens mit Turing-


maschinen
Die Programmierung“ von Turingmaschinen ist schwierig. Die folgenden Techniken, die

zur Erweiterung bereits vorhandener TMs dienen, schaffen etwas Erleichterung. Wir folgen
der Darstellung aus [Rei99].

12.4.1 Speicherung im endlichen Gedächtnis


Ohne den Speicher zu benutzen, kann sich eine Turingmaschine eine beschränkte Menge
von Informationen merken. Etwas formaler gesagt, kann man einen beliebigen endlichen
Automaten über dem Eingabealphabet {0, 1} in eine Turingmaschine integrieren (siehe
Teil II).

212
12.4.2 Spuren auf den Arbeitsbändern
In (12.11) ist ein Arbeitsband mit r Spuren dargestellt. In jeder dieser Spuren stehen
Elemente aus dem alten Arbeitsalphabet Γ ∪ {B}. Formal werden Spuren dadurch einge-
richtet, daß man das Arbeitsalphabet Γ durch (Γ ∪ {B})r \ {(B, B, . . . , B)} ersetzt. Das
Tupel (B, B, . . . , B) wird das neue Blanksymbol.

. . . b1,i−2 b1,i−1 b1,i b1,i+1 b1,i+2 . . .


. . . b2,i−2 b2,i−1 b2,i b2,i+1 b2,i+2 . . .
.. .. .. .. .. (12.11)
. . . . .
. . . br,i−2 br,i−1 br,i br,i+1 r2,i+2 . . .

Der Aufwärtspfeil ↑ verweist auf die Zelle mit dem Inhalt (b1,i , b2,i , . . . , br,i ) ∈ (Γ∪{B})r .
Dieses Tupel wird in einem Rechenschritt gelesen und als Ganzes verändert. Das bedeutet
natürlich nicht, daß der Inhalt jeder einzelnen Spur verändert werden muß.

12.4.3 Simulation eines zweiseitig unendlichen Bandes durch ein


einseitiges
Ein zweiseitig unendliches Band kann durch ein einseitiges simuliert werden, indem man
es wie folgt faltet:

b0 b1 b2 b3 . . .
. . . b−2 b−1 b0 b1 b2 . . . → (12.12)
⊲ b−1 b−2 b−3 . . .

Die Simulation ist ohne Zeitverzug und Speicherverlust möglich. Man muß sich im
endlichen Gedächtnis (siehe Abschnitt 12.4.1) lediglich merken, ob man sich auf der oberen
oder auf der unteren Spur befindet.

12.4.4 Markieren von Speicherzellen


Sei M ⊇ {⋆, N, } eine endliche Menge von Marken. Wir können den Spurtrick (siehe
Abschnitt 12.4.2) dazu verwenden, einzelne Speicherzellen durch Plazierung von Elementen
aus M auf der zweiten Spur zu markieren:

. . . bj bj+1 bj+2 bj+3 bj+4 bj+5 bj+6 bj+7 . . .


(12.13)
... ⋆ B B N B B  B ...

12.4.5 Kopieren und Verschieben von Blöcken


Ein Block ist eine konsekutive Folge von Speicherzellen.

213
Es soll ein Block der Länge λ an eine andere Stelle kopiert werden. Steht ein zweites
Band zur Verfügung, so wird der Block zunächst auf dieses Band ggf. unter Verwendung
einer zusätzlichen Spur umkopiert, um von dort an seine endgültige Position zu gelangen.
Der Zeitaufwand dafür ist O (λ).
Ohne ein zusätzliches Band werden Anfangsposition (◮) und Endposition (◭) des
Blockes und die Anfangsposition (⊲) desjenigen Abschnitts auf dem Arbeitsband, wo-
hin der Block kopiert werden soll, auf einer zusätzlichen Spur markiert. Nun wird jedes
einzelne Symbol des Blockes kopiert. Dazu müssen das Ende der aktuellen Kopie (⊳) und
der nächste Buchstabe (↑), der zur Kopie ansteht, ebenfalls markiert werden:

. . . b1 b2 . . . bi−1 bi bi+1 . . . bλ . . . . . . . . . b1 b2 . . . bi−1 . . .


(12.14)
... ◮ B ... B ↑ B ... ◭ ......... ⊲ B ... ⊳ ...

Da der Kopf zum Transport jedes Symbols um 2 · δ Zellen bewegt werden muß, wobei δ
der geplante Abstand zwischen Original und Kopie ist, und noch eine Verschiebung der
Markierungen hinzukommt, ist der Zeitaufwand dafür O (λ · δ).
Bei der Verschiebung von Blöcken geht man ähnlich vor. Der Zeitaufwand ist O (λ),
sofern ein zusätzliches Band zur Verfügung steht. Andernfalls liegt er bei O (λ · δ). Die
Zahlen λ und δ haben die gleiche Bedeutung wie oben.

12.4.6 Zähler
Auf einer zusätzlichen Spur wird ein binärer Zähler Z = zβ−1 zβ−2 . . . z0 mit einem vor-
gegeben Anfangswert mitgeführt. Das niederwertigste Bit befindet sich immer auf Höhe
des Kopfes. Wenn vorhanden, ist es natürlich bequemer, ein zusätzliches Band dafür zu
verwenden. Gezählt wird nun, indem der Zähler nach den aus der Schule bekannten Regeln
(Schulmethode) der binären Addition (Subtraktion) inkrementiert (dekrementiert) wird.
Wir betrachten zwei Anwendungen. In beiden Fällen setzen wir der Einfachheit voraus,
daß wir für den Zähler ein zusätzliches Arbeitsband zur Verfügung haben.

Zählen der Rechenschritte


Hierbei handelt es sich eigentlich um eine Simulation. Der Schrittzähler wird mit 0 initia-
lisiert. Jeder Rechenschritt der Maschine, deren Schritte gezählt werden sollen, wird nun
dadurch simuliert, daß zunächst der eigentliche Schritt ausgeführt und anschließend der
Zähler inkrementiert wird.

Markierung eines Bandabschnittes


Angenommen, auf einem zusätzlichen Arbeitsband ist die Länge Z > 0 desjenigen Blocks
auf einem Arbeitsband gespeichert, der markiert werden soll. Wir wollen der Einfachheit
annehmen, daß das in Rede stehende Arbeitsband leer ist.
Wir markieren auf einer zusätzlichen Spur den linken und gleich rechts daneben den
rechten Randbegrenzer des zu markierenden Blocks. Nun verschieben wir den rechten

214
Randbegrenzer um eine Zelle nach rechts und dekrementieren den Zähler solange, bis daß
der Zähler gleich null ist.

Zeit– und Speicherplatzanalyse


Die Speicherplatzanalyse der vorstehenden Algorithmen ist einfach. Nach Lemma 1.1 wer-
den ⌈log2 (Zmax +1)⌉ zusätzliche Speicherzellen gebraucht, wobei Zmax der maximale Zähler-
stand ist.
Die Zeitanalyse für das Aufwärtszählen von null bis N und für das Abwärtszählen von
N bis null haben wir bereits im Abschnitt 11.2.4 durchgeführt. Nach Satz 11.11 werden
2 · N Rechenschritte gebraucht.

12.4.7 Bandreduktion
Wie kann man r Arbeitsbänder durch ein Arbeitsband simulieren? Das geschieht unter Ein-
satz von Spuren auf dem simulierenden Band (siehe Abschnitt 12.4.2). Die lokale Situation
der r Bänder

. . . b1,i−2 b1,i−1 b1,i b1,i+1 b1,i+2 . . . Band 1


↑ Kopfposition 1
. . . b2,i−2 b2,i−1 b2,i b2,i+1 b2,i−2 . . . Band 2
↑ Kopfposition 2
.. .. .. .. ..
. . . . .
. . . br,i−2 br,i−1 br,i br,i+1 r2,i−2 . . . Band r
↑ Kopfposition r

wird durch ein Band gemäß (12.11) repräsentiert.


Ein Rechenschritt auf den r Bändern entspricht leider nicht nur einem Rechenschritt
auf dem simulierenden Band, da sich die Köpfe auf den zu simulierenden Bändern in unter-
schiedliche Richtungen bewegen können. Das muß durch eine Verschiebung der Spurinhalte
ausgeglichen werden. Ist s das Maximum über den auf den r zu simulierenden Bändern
zum aktuellen Zeitpunkt belegten Speicher, so sind dazu nach Abschnitt 12.4.5 O (r · s)
Schritte notwendig.
Der Speicherbedarf auf dem simulierenden Band ist ebenfalls ein O (r · s).
Bemerkung. Natürlich kann man auch das Eingabeband in die vorstehende Simulation
mit einbeziehen.

12.4.8 Nichtdeterministisches Raten


Ein NTA rät in einer Konfiguration C ein Element aus einer endlichen Menge A, falls er
von C ausgehend eine Folge nichtdetermistischer Schritte ausführt, die er in eine von |A|
möglichen Konfigurationen C1 , C2 , . . . , C|A| überführt.

215
Soll beispielsweise ein Element aus {0, 1}k geraten und auf ein Arbeitsband geschrieben
werden, so wird auf einer zusätzlichen Spur oder, falls es vorhanden ist, auf einem weiteren
Arbeitsband ein Zähler installiert und mit bin k initialisiert. Nun wird durch nichtdetermi-
nistischen Übergang ein Bit auf das Arbeitsband geschrieben, der Kopf nach rechts bewegt
und der Zähler dekrementiert, bis daß der Zählerinhalt gleich null ist. Aus Abschnitt 12.4.6
folgt, daß der soeben beschriebene Vorgang O (k) Rechenschritte kostet.
Dem Leser sollte nun klar sein, wie z.B. die Rate-Phase des Algorithmus 6.11 auf einer
TM zu implementieren ist.

12.5 Über die Robustheit der Klasse P


Wir haben Komplexitätsklassen wie P, FP, NP, L, FL und NL mit Hilfe des Rechner-
modells der k-Band Turingmaschine eingeführt. Eine Komplexitätsklasse kann nur dann
von wirklich zentraler Bedeutung sein, wenn ihr Umfang nicht von den technischen Beson-
derheiten eines Maschinenmodells abhängt.
In diesem Abschnitt werden wir zur Stützung der folgenden These beitragen: Die Kom-
plexitätsklasse P, die für den Theoretischen Informatiker für alle Entscheidungsprobleme
steht, die eine effiziente Lösung zulassen, ist von der Wahl eines heutzutage realistischen
Maschinenmodells unabhängig.

– Im Abschnitt 12.5.1 zeigen wir unter Verwendung der Techniken aus Abschnitt 12.4,
daß die Klasse P nicht von der konkreten Ausgestaltung des Modells der Turingma-
schine abhängt.

– Im Abschnitt 12.5.2 führen wir für Registermaschine die logarithmischen Kostenmaße


ein. Die Simulationsergebnisse dieses Abschnitts zeigen: Definierte man für Register-
maschinen eine zu P analoge Klasse, so fiele diese mit P zusammen.
Ferner werden wir nutzen, daß diese Simulationen den Speicheraufwand jeweils nur
um einen konstanten Faktor vergrößern: Zum Entwurf von Algorithmen für Turing-
maschinen mit z.B. logarithmisch beschränktem Speicher können wir uns nun der
Registermaschine bedienen.

Erst Rechner, wie die Quantenrechner, die gänzlich anders konzipiert sind, scheinen die
Robustheit der Klasse P erschüttern zu können.

12.5.1 Simulationen unter Turingmaschinen


k-Band TM gegen 1-Band TM
Satz 12.9 Jede t-zeit– und s-speicherbeschränkte r-Band-TM M läßt sich durch eine
O (s · t)-zeit und O (s)-speicherbeschränkte 1-Band-TM M ′ mit einseitig unendlichem Ar-
beitsband Schritt für Schritt simulieren.

216
Beweis. Die Aussage ist eine unmittelbare Folgerung unserer Überlegungen aus Ab-
schnitt 12.4.7 und 12.4.3. 

Aus Satz 12.9 folgt, daß sich beispielsweise die Komplexitätsklassen P, FP, NP, L und
NL nicht veränderten, wenn man sich bei ihrer Definition auf 1-Band-TM beschränkte.

Der einfache nichtdeterministische Turingakzeptor ENTA


Die Turingmaschine wurde von Alan Turing 1936 mit dem Ziele eingeführt, algorithmische
Berechnungen von der Art zu formalisieren, wie sie ein Menschen üblicherweise durchführt.
Dieses Urmodell“ hatte nur ein einziges einseitig unendliches Band über einem Alphabet

Σ, das als Eingabe-, Arbeits- und Ausgabeband diente.
Wir greifen nun diese Form der Turingmaschine auf, beschränken uns auf Akzeptoren,
erlauben aber Nichtdeterminismus. Von dem Arbeitsalphabet Σ nehmen wir an, daß es
die Menge {0, 1} umfaßt. Aus technischen Gründen (siehe Beweis des Satzes 12.38) wird
der linke Rand des Bandes durch zwei Randbegrenzer ⊲ markiert. Wir werden sehen, daß
der linke der beiden Randbegrenzer vom Lese-Schreib-Kopf der Maschine niemals besucht
wird. Wir sprechen von einfachen nichtdeterministischen Turingakzeptoren (ENTA).
Ist M ein ENTA, so ist die Überführungsrelation δM eine Teilmenge von
Q × (Σ ∪ {⊲, B}) × Q × (Σ ∪ {⊲}) × {L, R, N},
| {z } | {z }
Argumentteil Wertteil

die die Eigenschaften aus Abschnitt 12.2 hat. Ist insbesondere (q, σ, q ′ , σ ′ , ρ) ∈ δM so gilt.
– Ist σ = ⊲, so ist σ ′ = ⊲ und ρ 6= L.
– Stets ist σ ′ 6= B.
Diese Vereinbarungen sichern, daß jede Konfiguration
| ⊲ |⊲ |σ1 |σ2 | . . . |σℓ |γ1 |γ2 | . . . |γr |B |B | . . .
↑ (12.15)
q
mit σi , γj ∈ Σ (i = 1, 2, . . . , ℓ, r = 1, 2, . . . , r) sich eindeutig als Wort
⊲ ⊲ σ1 σ2 . . . σℓ q γ1 γ2 . . . γr (12.16)
über Σ ∪ {⊲} darstellen läßt. Die initiale Konfiguration auf eine Eingabe w1 w2 . . . wn ∈
{0, 1}n
| ⊲ |⊲ |w1 |w2 | . . . |wn |B |B | . . .
↑ (12.17)
q0
hat dann die Darstellung
⊲ q0 ⊲ w1 w2 . . . wn . (12.18)
Aus den Ergebnissen des Abschnitts 12.4 ergibt sich leicht.

217
Satz 12.10 Jeder Polynomialzeit-NTA läßt sich durch einen Polynomialzeit-ENTA Schritt
für Schritt simulieren.

12.5.2 Registermaschinen gegen Turingmaschinen


Im Abschnitt 12.4 haben wir einige Techniken zur Programmierung von Turingmaschinen
besprochen. Müßten wir damit jeden effizienten Algorithmus, den wir bisher kennengelernt
haben, auf Turingmaschinen implementieren, wären wir auf längere Zeit ausgelastet. Wir
brauchen Aussagen zur effizienten Simulation von Registermaschinen durch Turingmaschi-
nen und umgekehrt. Durch sie werden unsere Erkenntnisse über effiziente Algorithmen,
die wir in dieser Vorlesung erworben haben, in die Welt der Turingmaschinen übertragen:
Alle dort besprochenen Probleme gehören zur Komplexitätsklasse P, sofern es sich um
Entscheidungsprobleme handelt, und zur Komplexitätsklasse FP andernfalls. Haben wir
umgekehrt effiziente Algorithmen für Turingmaschinen entworfen, so lassen sich diese mit
erträglichem Verlust auf Registermaschinen übertragen.
Um Registermaschinen mit Turingmaschinen fair vergleichen zu können, müssen wir
jedoch statt des Einheitskostenmaßes das logarithmische Kostenmaß verwenden.

Registermaschinen mit logarithmischem Kostenmaß


Um unsere GRAM mit der Turingmaschine vergleichbar zu machen, müssen wir sie mit
einem Ein– und einem Ausgabeband über {0, 1} und den zugehörigen Befehlen ausrüsten,
die für den Datenfluß zwischen dem Register R1 und den beiden Bändern sorgen:
INPUT Lesen eines Eingabesymbols.
OUTPUT Schreiben eines Ausgabesymbols.
MOVE L Bewegung des Eingabekopfes nach links.
MOVE R Bewegung des Eingabekopfes nach rechts.
Die Elemente des Eingabealphabets {0, 1} werden mit den entsprechenden natürlichen
Zahlen identifiziert. Liest der Eingabekopf bei einer INPUT-Operation ein Symbol aus {0, 1},
so wird die zugehörige Zahl in das Register R1 geschrieben. Eine OUTPUT-Operation schreibt
R1 mod 2 auf das Ausgabeband.
Wie bereits im Abschnitt 12.1 dargelegt, benötigen wir neben dem Befehl END für das
Programmende noch die Befehle ACCEPT und REJECT. Sie stehen für das akzeptierende
bzw. verwerfende Ende einer Rechnung und werden eingesetzt, wenn es darum geht, Ent-
scheidungsprobleme zu lösen. Bei der Berechnung von Funktionen bleibt es bei dem Befehl
END.
Schließlich kann man in Analogie zur nichtdeterministischen Turingmaschine den Be-
fehlssatz um den Befehl CHOICE r (r eines der Register R1-25) erweitern. Dieser Befehl
inkrementiert den Befehlszähler PC und weist dem Register r den Wert 0 oder den Wert 1
zu: Er rät ein Bit.
Das Einheitskostenmaß aus Abschnitt 7.2 hat zur Grundlage, daß die binäre Länge der
Register- und Speicherzelleninhalte sowie der verwendeten Adressen die Verarbeitungsbrei-
te des aktuellen Rechners nicht übersteigt. Beim Studium der theoretischen Grundlagen

218
der Informatik ist eine solche Annahme nicht sinnvoll. Viele in der Praxis vorkommende
Probleme blieben unverständlich. Wir benötigen das logarithmische Kostenmaß, das den
Ressourcenverbrauch einer Rechnung auch von der binären Länge (siehe Abschnitt 1.1) der
verwendeten Zahlen und Adressen abhängen läßt.
Wir betrachten die Registermaschine (GRAM) aus Abschnitt 7.1 ohne die Befehle
der multiplikativen Arithmetik, die wir mit GRAM+ bezeichnen. (Den Grund für diesen
Ausschluß besprechen wir später. Einem besorgten Zeitgenossen, der einwenden mag, daß
man ohne Multiplikation nicht auskomme, halten wir entgegen, daß wir bereits in der Schule
gelernt haben, wie man die multiplikative auf die additive Arithmetik zurückführt.) Ist X
eine Speicherzelle oder ein Register, so bezeichnet λ(X) die binäre Länge des Inhalts von X.
Wir sprechen auch von der (aktuellen) binären Länge des Registers bzw. der Speicherzelle.
Logarithmisches Zeitmaß. Die Ausführung jedes Befehls aus Tabelle 7.1 benötigt soviele
Zeittakte, wie die Summe der binären Längen der beteiligten Register, Speicherzellen,
Adressen und Konstanten angibt (siehe Tabelle 12.1). Die Befehle zur Kommunikation
des Registers R1 mit dem Eingabe– und dem Ausgabeband, zum Programmende und
zum Raten eines Bits benötigen einen Zeittakt.

Programmende
END, ACCEPT, REJECT 1
Transportbefehle
LOAD r1,a(r2) λ(r1) + λ(r2) + λ(a)
STORE a(r1),r2 λ(r1) + λ(r2) + λ(a)
C-LOAD r,m λ(r) + λ(m)
Additive Arithmetik
ADD r1,r2,r3 λ(r1) + λ(r2) + λ(r3)
C-ADD r1,r2,m λ(r1) + λ(r2) + λ(m)
SUB r1,r2,r3 λ(r1) + λ(r2) + λ(r3)
Ordnung
SLT r1,r2,r3 λ(r1) + λ(r2) + λ(r3)
Sprünge
JMP r λ(r)
JAL r λ(r)
Verzweigungen
BEQ r,a λ(r) + λ(a)
BNQ r,a λ(r) + λ(a)

Tabelle 12.1: Logarithmische Zeitkosten der Assemblerbefehle der GRAM aus Tabelle 7.1

Um das logarithmisches Speichermaß für die Rechnung einer GRAM auf eine Eingabe
sinnvoll zu definieren, müssen wir uns einige Gedanken mehr machen.

219
Seien
0 ≤α1 < α2 < . . .< αh−1 <αh
−1 ≥β1 > β2 > . . . > βs−1 >βs
die Adressen der Speicherzelle auf der Halde bzw. auf dem Laufzeitstapel (siehe Abbildung
7.1), die im gesamten Verlauf der Rechnung benutzt werden, und seien µ(α1 ), µ(α2 ), . . .,
µ(αh ) bzw. µ(β1 ), µ(β2 ), . . ., µ(βs ) die zugehörigen Speicherzellen selbst. Ist X ein Register
oder eine Speicherzelle, so bezeichnet λmax (X) das Maximum über die λ(X), wobei über
jeden Zeitpunkt der Rechnung maximiert wird.
Zuerst denkt man daran, die Zahl
31
X h
X s
X h
X s
X
λmax (Ri) + | bin αi | + | bin −βi | + λmax (µ(αi )) + λmax (µ(βi ))
i=1 i=1 i=1 i=1 i=1
(12.19)
als Speicherbedarf der in Rede stehenden Rechnung zu definieren. Man überlegt sich aber
leicht, daß man hier zu hoch greift. Griffe man nämlich im Zuge der Rechnung auf den
Hauptspeicher der GRAM nur zu wie eine 1-Band-Turingmaschine auf ihr Arbeitsband,
und betriebe man auf diese Weise die GRAM wie eine Turingmaschine, so hätte nach Term
12.19 diese Turingmaschine“ einen Speicherbedarf, der um einen logarithmischen Faktor

größer wäre, als der in Definition 12.2 festgelegte.
Kleinmütig geworden, könnten man nun auf den Gedanken kommen, die Adressen bei
der Definition des Speicherbedarfs ganz wegzulassen und den Ausdruck
31
X h
X s
X
λmax (Ri) + λmax (µ(αi )) + λmax (µ(βi )) (12.20)
i=1 i=1 i=1

zu verwenden. Dann allerdings wäre man in der Lage, durch Auswahl nur weniger Regi-
ster aus einem großen Bereich zusätzlich Informationen speichern, ohne den Speicherbedarf
dafür vollständig in Rechnung gestellt zu bekommen. Die Zahl m etwa wäre mit Speicher-
bedarf eins darzustellen: Die Speicherzelle µ(m) erhält den Wert eins, die Speicherzellen
µ(i) bleiben für i = 0, 1, . . . , m − 1 unberührt. Erst µ(−1) ist wieder benutzt. Allgemein
könnten die Abstände zwischen den benutzten Speicherzellen als impliziter dafür aber fast
kostenloser Speicher angesehen werden. Eine solche Definition wäre verfehlt.
Die Lösung liegt in der Mitte. Wir definieren
31
X h
X s
X h
X s
X
λmax (Ri) + | bin(αi − αi−1 )| + | bin(βi−1 − βi )| + λmax (µ(αi )) + λmax (µ(βi ))
i=1 i=1 i=1 i=1 i=1
(12.21)

als den Speicherbedarf der Rechnung, wobei α0 := 0 und β0 := −1 ist. Wir erreichen da-
durch, daß die GRAM+ , wenn sie wie eine 1-Band-TM betrieben wird, auch den Speicherbe-
darf gemäß Definition 12.2 hat. Ferner vermeiden wir den im vorigen Absatz beschriebenen
Effekt.

220
Wechselseitige Simulation von GRAM+ und TM
Nun sind wir in der Lage, die Aussagen zur wechselseitigen Simulation von Register- und
Turingmaschinen formulieren zu können. Auf Beweise wollen wir verzichten. Der interes-
sierte Leser findet sie in [Rei99].

Satz 12.11 (Simulation von GRAMs durch TM) Sei R eine im logarithmischen Maß
t-zeit- und s-speicherbeschränkte GRAM+ . Dann existiert eine O (s · t)-zeit- und O (s)-
speicherbeschränkte DTM, die R simuliert.

Warum haben wir auf die Befehle der multiplikativen Arithmetik verzichtet? Andern-
falls läßt sich Satz 12.11 nicht beweisen.

Satz 12.12 (Simulation von TM durch GRAMs) Eine t-zeit- und s-speicherbeschränk-
te DTM kann durch eine GRAM+ simuliert werden, die

1. im Einheitskostenmaß O (t)-zeit- und O (s)-speicherbeschränkt ist;

2. im logarithmischen Kostenmaß O (t · log s)-zeit- und O (s)-speicherbeschränkt ist.

Wann arbeitet eine GRAM+ mit o(n)-beschränktem Speicher?


Die Programmierung“ von Turingmaschinen ist auch unter Einsatz der Techniken aus

Abschnitt 12.4 sperrig. Indem wir Satz 12.11 nutzen, können wir uns auf die GRAM+
mit Ein- und Ausgabeband zurückziehen. Wann arbeitet ein Algorithmus auf der GRAM+
unter Verwendung des logarithmischen Kostenmaßes o(n)-speicherbeschränkt? Wir behan-
deln exemplarisch eine hinreichende Bedingung dafür, daß der Speicherbedarf ein O (log n)
ist. Sei dazu n die binäre Länge der Eingabe.

– Weder die Eingabe noch in der Regel die Ausgabe können intern vollständig gespei-
chert werden. Man muß vielmehr den Speicheraufwand für die Ein- und die Aus-
gabe vom Speicherbedarf der Rechnung trennen. Letzterer muß sich logarithmisch
beschränken lassen.
Es können nur jeweils O (log n) Bits der Eingabe zu einer Zeit im Hauptspeicher
gehalten werden.
Zur Berechnung eines Ausgabebits kann man nur auf O (log n) bisher schon ausge-
gebene Bits zugreifen, denn nur dazu reicht der Arbeitsspeicher.

– Zu jedem Zeitpunkt ist die Summe der binären Längen der Datenfelder sämtlicher
Objekte auf der Halde ein O (log n). Die größte während der Laufzeit vergebene
Adresse auf der Halde ist ein nO(1) .

– Bei der Auswertung eines beliebigen arithmetischen Ausdrucks müssen alle dabei auf-
tretenden Zwischenergebnisse mit O (log n) Bits darstellbar sein. (Wir gehen davon
aus, daß diese Zwischenergebnisse in einem Register stehen.)

221
– Zu jeder Zeit der Rechnung muß die im logarithmischen Kostenmaß gemessene Höhe
des Laufzeitstapel ein O (log n) sein. Das ist insbesondere dann der Fall, wenn

– zu jeder Zeit höchstens O (1) Rahmen auf dem Laufzeitstapel liegen;


– bei jedem Methodenaufruf jeder Aktualparameter und jede lokale Variable mit
O (log n) Bits darstellbar ist.

Alternativ kann die Anzahl der Inkarnationsrahmen auf dem Laufzeitstapel auch
ein O (log n) sein. Dann muß jeder Aktualparameter, jede lokale Variable und jedes
Register durch O (1) viele Bits darstellbar sein.

Wir betrachten als Beispiel das im Abschnitt 12.2 eingeführte Graph-Accessibility-


Problem GAP. Es liegt in NL. Welche deterministische Speicherkomplexität hat dieses
Problem?

Satz 12.13 (Satz von Savitch) Es ist GAP ∈ DSPACE log2 n .

Beweis. Sei G = ({1, 2, . . . , m}, E) ein gerichteter Graph auf der Knotenmenge {1, 2, . . . , m}.
Wir bemerken folgendes. Für zwei Knoten x 6= y ∈ {1, 2, . . . , m}

1. hat jeder gerichteten Weg von x nach y in G die Länge kleiner oder gleich m;

2. gibt es einen gerichtete Weg von x nach y in G der Länge kleiner oder gleich k
(2 ≤ k ≤ m), wenn es einen Mittelknoten z mit den folgenden Eigenschaften gibt:

– Die Knoten x und z sind in G durch einen gerichteten Weg der Länge kleiner
oder gleich ⌊k/2⌋ verbunden.
– Es gibt in G einen gerichteten Weg der Länge kleiner oder gleich ⌈k/2⌉ von z
nach y.

Wir entwerfen nun auf der GRAM+ mit logarithmischem Kostenmaß einen Algorithmus
DPATH(x, y, k) returns {0, 1}
mit der folgenden Spezifikation: Angesetzt auf G gibt DPATH(x, y, k) gibt genau dann 1
zurück, wenn es in G einen gerichteten Pfad von x nach y der Länge kleiner oder gleich k
gibt. Anderfalls wird 0 zurückgegeben.
Algorithmus DPATH(x, y, k)
Großschritt 1. [Basis]
Falls x = y, return 1.
Falls k = 1, so führe aus.
Falls (x, y) ∈ E, return 1.
Andernfalls return 0.
Großschritt 2. [Rekursion]
b←0

222
Für z = 1, 2, . . . , m führe aus.
bb ← DPATH(x, z, ⌊k/2⌋) ∧ DPATH(z, y, ⌈k/2⌉)
b ← b ∨ bb
return b.

Zur Laufzeit von DPATH(x, y, k) sind sämtliche vorkommenden Variablen in ihrer binären
Länge durch eine Funktion aus O (log m) beschränkt. Aus Gleichung 1.4 folgt, daß auf dem
Laufzeitstapel zu keiner Zeit mehr als ⌈log2 k⌉ Inkarnationsblätter der Methode DPATH lie-
gen. Folglich hat DPATH(x, y, k) einen durch O (log2 m · log2 k) beschränkten Speicherbe-
darf.
Angesetzt auf G entscheidet DPATH(1, m, m), ob es in G einen gerichteten Weg von 1
nach m gibt. Das geschieht mit O log2 m beschränktem Speicher. 

Der Algorithmus DPATH aus dem Beweis von Satz 12.13 hat relativ weitreichende Aus-
wirkungen.

Korollar 12.14 Für jede von uns betrachtete Speicherschranke s ist



NSPACE (s(n)) ⊆ DSPACE s2 (n) .

Der Beweis von Korollar 12.14 wird mit der sogenannten Erreichbarkeitstechnik geführt,
die hier kurz skizziert werden soll. (Wer es genauer wissen will, der sei auf [Pap94], Ab-
schnitt 7.3 verwiesen.) Ist M ein s-speicherbeschränkter NTA und x ∈ {0, 1}n eine Eingabe
— wegen Satz 12.9 können wir uns auf einen 1-Band-NTA beschränken —, so definiert man
den Graphen der partiellen Konfigurationen von M auf x wie folgt:

– Die Knoten sind die partiellen Konfigurationen von M auf Eingaben der Länge |x|.
Aus einer Konfiguration von M auf die Eingabe x

C := (q, (x, k0 ), (u1, k1 ))

mit |u1| ≤ s(|x|) (siehe Defintion 12.2, Gleichung 12.4) erhält man eine partielle
Konfiguration, indem man die Eingabe x wegläßt:

Ĉ := (q, k0 , (u1, k1 )) .

Als Restriktion bleibt für die natürliche Zahl k0 , die die Position des Lesekopfes auf
dem Eingabeband beschreibt, die Ungleichungskette 0 ≤ k0 ≤ |x| + 1 bestehen.

– Seien D = Ĉ und D ′ := Ĉ ′ zwei partielle Konfiguration, wobei C und C ′ zwei


Konfigurationen von M auf x sind. Dann ist D mit D ′ durch eine Kante im Graphen
der partiellen Konfigurationen von M auf x verbunden, wenn C ⊢ C ′ ist.

Für den Graphen der partiellen Konfigurationen von M auf x gilt offenbar folgendes:

223
– Jeder Knoten läßt sich mit O (s(|x|))-beschränkten Speicher auf einem Arbeitsband
darstellen. Hätte man die Eingabe aus der Konfiguration nicht gestrichen, ginge das
für sublineare Speicherschranken s nicht.

– Der NTA M akzeptiert die Eingabe x genau dann, wenn es in dem Graphen einen
gerichteten Weg von der initialen partiellen Konfiguration zu einer terminalen gibt.
Zum Beweis von Korollar 12.14 wird M auf x simuliert, indem man den Algorithmus
DPATH auf den Graphen der partieller Konfigurationen von M auf x anwendet. Das geht,
weil die aktuelle vollständige Konfiguration von M auf x mit s-beschränktem Speicher
gehalten werden kann: Die Eingabe x steht auf dem Eingabeband, die aktuelle partielle
Konfiguration ist intern gespeichert.
Aus Korollar 12.14 folgt sofort.
Korollar 12.15 Es ist
PSPACE = NPSPACE.

12.6 Konstruierbarkeit
Man mag sich daran stoßen, daß unser Akzeptierungsbegriff aus Definition 12.2 sowohl für
zeit- als auch für speicherbeschränkte Berechnungen so großzügig ist: Nur die günstigsten
akzeptierenden Rechnungen zählen. Abhilfe schaffen die folgenden beiden Begriffe.

Definition 12.16 – Eine Zeitschranke t heißt konstruierbar, wenn es eine determini-


stische k-Band Turingmaschine gibt, die angesetzt auf eine beliebige Eingabe der
Länge n ≥ 1 die Zeichenkette bin t(n) auf einem Arbeitsband berechnet und dafür
insgesamt nur O (t(n)) Schritte benötigt.

– Eine Speicherschranke s heißt konstruierbar, wenn es eine deterministische k-Band


Turingmaschine gibt, die angesetzt auf eine beliebige Eingabe der Länge n ≥ 1
die Zeichenkette bin s(n) auf einem Arbeitsband berechnet und dafür insgesamt nur
O (s(n)) Speicherzellen auf den Arbeitsbändern benötigt.

Für eine Ressourcenschranke f gilt offenbar folgendes. Ist f als Zeitsschranke konstruier-
bar, so ist f auch als Platzschranke konstruierbar. Man kann dazu dieselbe Turingmaschine
M nehmen. Da M O (f (n))-zeitbeschränkt ist, kann sie auch nur O (f (n)) Speicherzellen
benutzen.
Zunächst hat man den Eindruck, daß Konstruierbarkeit eine recht exklusive Eigenschaft
für Komplexitätsschranken sein muß. Gemessen an der Gesamtheit aller nur denkbaren
Schranken ist das auch der Fall. Aber alle wichtigen Ressourcenschranken sind konstruier-
bar. Wir begnügen uns mit einigen Beispielen.

Lemma 12.17 Sowohl die Speicherschranke ⌊log2 n⌋ als auch die Speicherschranke ⌈log2 n⌉
sind konstruierbar

224
Beweis. Wir begnügen uns mit der Speicherschranke ⌊log2 n⌋. Aus Lemma 1.10 wissen
wir, daß ⌊log2 n⌋ = | bin n| + 1 ist.
Wir beschreiben eine Turingmaschine M, die das Gewünschte leistet. In ihrer ersten
Phase richtet M auf einem Arbeitsband einen Zähler Z mit dem Anfangswert 0 ein. Nun
läuft M die Eingabe x der Länge n ab und inkrementiert für jedes gelesene Bit den Zähler.
Hat M die Eingabe gelesen, ist der Zählerinhalt gleich |x| = n, binär dargestellt. Wir
wissen, daß die binäre Länge dieses Zählers um eins vergrößert gleich ⌊log2 n⌋ ist. Deshalb
ist es zielführend, wenn M in einer zweiten Phase den Zähler Z um ein beliebiges Bit
verlängert und mit dieser Zeichenkette nochmals das gleiche macht wie soeben mit der
Eingabe. Der Inhalt dieses zweiten Zählers ist dann die Binärdarstellung von ⌊log2 n⌋.
Der Speicherbedarf von M wird dominiert von der maximalen Länge des ersten Zählers.


Lemma 12.18 Für jedes feste natürliche k ≥ 1 ist die Zeitschranke nk konstruierbar.

Beweis. Wir führen den Beweis induktiv über k.


Anfang: k = 1. Die Turingmaschine M1 verhält sich so wie die Maschine aus dem
Beweis von Lemma 12.17 in ihrer ersten Phase. Die Aussage über die Rechenzeit haben
wir in Abschnitt 11.2.4 bewiesen.
Schritt: k auf k + 1. Die Maschine Mk+1 simuliert zunächst Mk . Danach befindet sich
das Wort bin nk auf einem Arbeitsband. Wir fassen dieses Arbeitsband als Abwärtszähler
A mit dem Inhalt nk auf. Nun simuliert M nk mal die Maschine M1 , wobei der Zähler Z
den M1 inkrementiert, bei jeder neuen Iteration nicht zurückgesetzt wird.
Ist A gleich null und die letzte Simulation von M1 abgeschlossen, so ist der Inhalt des
Binärzählers Z gleich nk+1 .  
Die Rechenzeit ist nach Abschnitt 12.4.6 O nk+1 + nk = O nk+1 . 

Satz 12.19 Sei t eine konstruierbare Zeitschranke. Dann läßt sich jeder t-zeitbeschränk-
te NTA M ′ durch einen O (t)-zeitbeschränkten NTA M simulieren, dessen sämtliche Re-
chengänge eine Länge haben, die ein O (t) ist.

Beweis. Angesetzt auf eine Eingabe x der Länge n richtet M auf einem zusätzlichen
Arbeitsband einen Abwärtszähler A ein und initialisiert ihn mit der Binärdarstellung von
t(n). Das ist in Zeit O (t(n)) möglich, da die Zeitschranke t konstruierbar ist.
Nun simuliert M den NTA M ′ Schritt-für-Schritt, dekrementiert aber nach jedem simu-
lierten Schritt den Zähler A um eins. Solange der Zähler noch nicht gleich null ist, verhält
sich M was die Akzeptierung oder Verwerfung angeht wie M ′ . Ist der Zählerstand dagegen
gleich null, und hat der letzte Schritt nicht zu einem terminalen Zustand geführt, verwirft
M wegen Zeitüberschreitung.
Diese Verwerfungen sind unschädlich, weil alle von M ′ akzeptierten Eingaben x eine
akzeptierende Berechnung haben, deren Länge kleiner oder gleich t(|x|) ist. 

225
Satz 12.20 Sei s eine konstruierbare Speicherschranke. Dann läßt sich jeder s-speicher-
beschränkte NTA M ′ durch einen O (s)-speicherbeschränkten NTA M simulieren, dessen
sämtliche Rechengänge nur O (s)-beschränkten Speicher benötigen.

Beweis. Zunächst können wir gemäß Abschnitt 12.4.3 annehmen, daß alle Arbeitsbänder
von M ′ nur einseitig unendlich sind.
Angesetzt auf eine Eingabe x der Länge n berechnet M auf einem zusätzlichen Arbeits-
band die Binärdarstellung von s(n). Das geht in O (s(n))-beschränktem Speicher, da s eine
konstruierbare Speicherschranke ist. Anschließend markiert M auf allen Arbeitsbändern
von M ′ auf einer zusätzlichen Spur gemäß Abschnitt 12.4.4 Anfangsstücke der Länge s(n).
Nun simuliert M den NTA M ′ Schritt-für-Schritt und verhält sich dabei was die Akzep-
tierung oder Verwerfung angeht wie M ′ . Versucht die Simulation von M ′ dagegen, einen
markierten Bereich zu verlassen, so verwirft M wegen Speicherüberschreitung.
Diese Verwerfungen sind unschädlich, weil alle von M ′ akzeptierten Eingabe x eine
akzeptierende Berechnung haben, die mit Speicherbedarf kleiner oder gleich t(|x|) auskom-
men. 

Bemerkungen.
• Als unmittelbare Folgerung aus dem Inhalt dieses Abschnitts erhalten wir, daß für
konstruierbare Zeit- bzw. Speicherschranken die zugehörige deterministische Kom-
plexitätsklasse abgeschlossen gegenüber Komplementbildung ist: Gehört eine formale
Sprache L dazu, so auch ihr Komplement {0, 1}∗ \ L.
• Für nichtdeterministische Speicherkomplexitätsklassen NSPACE (s) gilt ebenfallls
die Abgeschlossenheit gegenüber Komplementbildung. Der Beweis ist jedoch nicht
trivial.
• Für nichtdeterministische Zeitkomplexitätsklassen gilt die Abgeschlossenheit gegenüber
Komplementbildung dagegen als sehr unwahrscheinlich. Insbesondere ist das für die
Klasse NP beachtlich.

12.7 Hierarchiesätze
Aus der Programmierpraxis ist die Evidenz für die folgenden beiden Aussagen überwälti-
gend.

Satz 12.21 (Zeithierarchiesatz) Seien t und T Zeitschranken, und sei die Zeitschranke
T konstruierbar.
Ist t · log t = o(T ), so ist

DTIME (t) ⊂ DTIME (T ) .

Satz 12.22 (Speicherhierarchiesatz) Seien s und S Speicherschranken, und sei die


Speicherschranke S konstruierbar.

226
Ist s = o(S), so ist

DSPACE (s) ⊂ DSPACE (S) .

Bemerkungen.
• Man beachte unsere generelle Voraussetzungen über Ressourcenschranken vom Ende
von Abschnitt 12.2.
• Was die Beweise angeht, so stelle man sich nicht vor, daß man von einer interes-
santen Sprache z.B. aus DSPACE (S) zeigt, daß sie nicht in DSPACE (s) liegt.
Der Kern der Beweise ist vielmehr ein Diagonalisierungsargument. Der Nachweis un-
terer Schranken für konkret definierte Probleme ist bis auf wenige Ausnahmen ein
ungelöstes Problem.
• Auch für nichtdeterminitische Komplexitätsklassen kann man ähnliche Hierarchiesätze
beweisen.

12.8 Many-One-Reduzierbarkeit, Vollständigkeit


Seien L1 und L2 zwei formale Sprachen über dem Alphabet {0, 1}. Wir suchen nach einem
Begriff, der uns die Aussage
Die Sprache L1 ist algorithmisch nicht schwerer zu entscheiden als die Sprache L2 .“

formalisiert.

Definition 12.23 Sei f : {0, 1}∗ → {0, 1}∗ eine vermöge eines TT berechenbare Funktion.
Die Sprache L1 heißt längs der Funktion f auf die Sprache L2 reduzierbar, wenn für alle
Wörter w ∈ {0, 1}∗ die Äquivalenz

w ∈ L1 ⇐⇒ f (w) ∈ L2

erfüllt ist.

Sei A2 ein Algorithmus, der die formale Sprache L2 entscheidet. Dann ist Algorithmus
12.24 für jedes Wort w ∈ {0, 1}∗ ein Entscheidungsalgorithmus für die Sprache L2 . Verwen-
det man diesen Algorithmus, so löst Definition 12.23 unser Formalisierungsproblem, wenn
die Berechnungskomplexität der Funktion f gegenüber der Komplexität des Algorithmus
A2 nicht ins Gewicht fällt.

Algorithmus 12.24 (Allgemeiner Reduktionsalgorithmus)


Großschritt 1:
Berechne f (w).
Großschritt 2:
Entscheide mit Hilfe des Algorithmus A2 , ob f (w) ∈ L2 ist.

227
Die Reduzierbarkeit mit Hilfe einer Funktion f nennt man many-one-Reduzierbarkeit:
Ein Element w ′ aus {0, 1}∗ kann für mehrere w — die Urbilder von w ′ unter f — dazu
dienen, die Frage w ∈ L1 ? zu beantworten. Kann f als injektive Funktion gewählt werden,
so spricht man von einer one-one-Reduzierbarkeit.
Definition 12.25 Eine formale Sprache L1 ist auf eine formale Sprache reduzierbar in
polynomialer Zeit (alternative Bezeichnung: FP-reduzierbar), falls es eine Funktion f ∈ FP
gibt, längs derer L1 auf L2 reduzierbar ist. (Bezeichnungen: L1 ≤pol L2 oder L1 ≤FP L2 .)
Definition 12.26 Eine formale Sprache L1 ist auf eine formale Sprache logspace-reduzierbar
(alternative Bezeichnung: FL-reduzierbar), falls es eine Funktion f ∈ FL gibt, längs derer
L1 auf L2 reduzierbar ist. (Bezeichnungen: L1 ≤log L2 oder L1 ≤FL L2 .)
Lemma 12.27 Die Logspace-Reduzierbarkeit impliziert die Polynomialzeit-Reduzierbarkeit.

Beweis. Die Aussage ist eine unmittelbare Folgerung aus Lemma 12.7. 

Lemma 12.28 Sowohl die Logspace-Reduzierbarkeitsrelation als auch die Polynomialzeit-


Reduzierbarkeitsrelation sind transitiv.

Beweis. Die Aussage ist eine unmittelbare Folgerung aus Lemma 12.8 bzw. Lemma 12.6.


Lemma 12.29 Ist L1 ≤FP L2 , so gelten die folgenden Implikation.


L2 ∈ PSPACE =⇒ L1 ∈ PSPACE
L2 ∈ NP =⇒ L1 ∈ NP
L2 ∈ P =⇒ L1 ∈ P
Das heißt, diese Komplexitätsklassen sind unter der Relation ≤FP und damit auch unter
der Relation ≤FL abgeschlossen.

Beweis. Die drei Aussagen haben analoge Beweise. Wir beschränken uns auf die Abge-
schlossenheit der Klasse P. Dieser Beweis wiederum ist dem von Lemma 12.6 sehr ähnlich.
Sei M ein TT, der eine Funktion f ∈ FP in nk -beschränkter Zeit berechnet, längs
derer sich die Sprache L1 auf die Sprache L2 reduziert läßt. Sei ferner M2 ein DTA, der die
Sprache L2 in nℓ -beschränkter Zeit akzeptiert. Wir konstruieren einen DTA M1 , der die
formale Sprache L1 akzeptiert.
Angesetzt auf eine Eingabe x simuliert M1 in einer ersten Phase Schritt-für-Schritt M,
wobei er ein zusätzliches Arbeitsband als Hilfsausgabeband nutzt.
Anschließend simuliert M1 Schritt-für-Schritt M2 , wobei das zusätzliche Arbeitsband
nun als Hilfseingabeband dient.
Man überlegt sich sofort, daß die Laufzeit von M auf jede Eingabe der Länge n, die zu
L1 gehört, durch nk + nk·ℓ nach oben beschränkt ist. 

228
Lemma 12.30 Ist L1 ≤FL L2 , so gilt die Implikation

L2 ∈ NL =⇒ L1 ∈ NL.

Das heißt, die Komplexitätsklasse NL ist unter der Relation ≤FL abgeschlossen.

Beweis. Der Beweis verwendet den Trick aus dem Beweis von Lemma 12.8.
Sei M ein TT, der eine Funktion f ∈ FL, längs derer sich die Sprache L1 auf die
Sprache L2 reduzieren läßt, mit O (log n)-beschränktem Speicher berechnet. Sei ferner M2
ein O (log n)-speicherbeschränkter NTA, der die Sprache L2 akzeptiert.
Wir beschreiben einen NTA M1 , der angesetzt auf eine Eingabe x den NTA M2 ange-
setzt auf f (x) Schritt-für-Schritt simuliert. Dazu hält M auf einem zusätzlichen Arbeits-
band einen Zähler für den Index des nächsten Bits von f (x), der von M2 gelesen werden
muß.
Simulation des Leseschritts von M2 . M1 erzeugt auf einem zusätzlichen Arbeitsband
eine Arbeitskopie ẑ von z und startet eine Simulation von M angesetzt auf x. Allerdings
wird, sofern die simulierte Maschine M ein Bit ausgeben will, diese Ausgabe unterdrückt.
Es wird lediglich der Zähler ẑ dekrementiert. Ist der Zählerstand gleich null, so ist das Bit,
das bei M gerade zur Ausgabe anstünde, das gesuchte Bit von f (w).
Simulation der Bewegung des Lesekopfes von M2 . Im wesentlichen wird der Zähler
z inkrementiert bzw. dekrementiert. Allerdings muß vor jeder Inkrementierung getestet
werden, ob f (x) überhaupt ein solches Bit hat. Das geschieht in einer Weise, die zur
Simulation des Leseschritts von M2 analog ist.
Die Simulation der Aktionen von M2 auf seinen Arbeitsbändern erfolgt direkt. Dafür
hat M1 zusätzliche Arbeitsbänder in gleicher Zahl.
Für jedes x hat der Wert f (x) eine Länge, die durch ein Polynom in |x| beschränkt
ist. Folglich benötigt die Darstellung des Zählers z nur O (log |x|)-beschränkten Platz.
Für jedes x mit f (x) ∈ L2 gibt es eine akzeptierende Rechnung von M2 auf f (x), die mit
O (log |f (x)|) = O (|x|) beschränktem Speicher auskommt. Die Simulation dieser Rechnung
durch M1 sichert, daß die Speicherschranke respektiert wird. 

Definition 12.31 Sei ≤ eine transitive Reduzierbarkeitsrelation für formale Sprachen und
C eine Komplexitätsklasse formaler Sprachen, die bzgl. der Relation ≤ abgeschlossen ist.

1. Eine formale Sprache L heißt C-hart bzgl. der Reduzierbarkeitsrelation ≤, wenn für
jedes L′ aus C die Relation L′ ≤ L gilt.

2. Eine formale Sprache L heißt C-vollständig bzgl. der Reduzierbarkeitsrelation ≤,


wenn L bzgl. ≤ C-hart ist und überdies L zu C gehört.

Bei der Anwendung von Definition 12.31 muß man Vorsicht walten lassen. Ein vollständi-
ges Problem einer Komplexitätsklasse soll ein im intuitiven Sinne schwerstes Problem die-
ser Klasse sein und somit Informationen über die Komplexitätsklasse selbst liefern. Ist der

229
Reduktionsbegriff zu stark, so gelingt das nicht: Jede einelementige Sprache ist beispiels-
weise L-vollständig bzgl. Logspace-Reduktionen und P-vollständig bzgl. Polynomialzeit-
Reduktionen.
Im weiteren bezieht sich der Begriff der
– NP-Vollständigkeit und PSPACE-Vollständigkeit immer auf Polynomialzeit-Re-
duktionen;

– NL-Vollständigkeit immer auf Logspace-Reduktionen.

Beispiele.
• GAP1 ist L-vollständig. (Natürlich ist diese Aussage nur für eine schächere als die
Logspace-Reduktion sinnvoll.)
• GAP ist NL-vollständig.
• Das Rucksack-Entscheidungsproblem KNAPSACK ist NP-vollständig. Weitere Bei-
spiele werden wir im Verlauf dieses Kapitels kennenlernen.
• QBF ist PSPACE-vollständig.

Welche Bedeutung haben (natürliche und interessante) vollständige Probleme von Kom-
plexitätsklassen in der Theoretischen Informatik?
Einerseits bringt man durch die Ermittlung eines interessanten vollständigen Problems
die Information, die in der Klasse steckt, gewissermaßen auf den Punkt. Am Entscheidungs-
problem GAP1 kann man z.B. sehr gut sehen, was eine logarithmisch speicherbeschränkte
Rechnung zu leisten vermag. Erst vollständige Probleme machen Komplexitätsklassen be-
sonders interessant.
Andererseits ist die Vollständigkeit eines Problems für eine als sehr reichhaltig ange-
sehene Komplexitätsklasse Ausweis der Schwierigkeit des Problems. Spiel-Liebhaber zum
Beispiel, die mit Sokoban Probleme haben, können sich damit trösten, daß dessen kanoni-
sche Entscheidungsvariante PSPACE-vollständig ist.
Bemerkung. Eine transitive Reduktionsrelation ≤ definiert vermöge

L ≡ L′ ⇐⇒ L ≤ L′ und L′ ≤ L

auf der Menge aller formalen Sprachen über {0, 1} eine Äquivalenzrelation. Es folgt sofort,
daß diese Äquivalenzrelation bzgl. der Reduktionsrelation ≤ eine Kongruenzrelation ist:
Sind [L] und [L′ ] zwei Äquivalenzklassen bzgl. ≡ und ist L ≤ L′ , so ist L1 ≤ L2 für alle
L1 ∈ [L] und alle L2 ∈ [L′ ].
Folglich läßt sich die Relation ≤ auf die Äquivalenzklassen bzgl. ≡ durch repräsentan-
tenweise Definition übertragen:

[L] ≤ [L′ ] ⇐⇒ L ≤ L′ .

Es ist leicht einzusehen, daß auf den Äquivalenzklassen bzgl. ≡ die Relation ≤ eine Halb-
ordnung ist. In diesem Zusammenhang heißen die Äquivalenzklassen auch Grade.

230
Die NP-vollständigen und die PSPACE-vollständigen Probleme beispielsweise bilden
den Grad NP-C bzw. den Grad PSPACE-C bzgl. der Polynomialzeitäquivalenz ≡FP .
Aus Satz 12.32 ergibt sich sofort, daß NP-C kleiner oder gleich PSPACE-C ist.

12.9 Das Verhältnis von Determinismus zu Nichtde-


terminismus
Wir haben bereits im Abschnitt 12.1 gesagt, daß das Verhältnis von Determinismus zu
Nichtdeterminismus zu den wichtigsten Fragen der Theoretischen Informatik gehört. Es ist
die Frage danach, ob Raten unter bestimmten Bedingungen etwas nützt.
Warum ist NPSPACE = PSPACE? Die polynomiale Speicherschranke ist so schwach,
daß Algorithmen die nur ihr gehorchen, außerordentlich mächtig sind: Alles was man sinn-
voller Weise raten kann, kann man auch ausprobieren.
Eine duale Aussage gilt für Komplexitätsklassen, die durch außerordentlich strenge
Ressourcenbeschränkungen definiert sind. In solchen Fällen kann man beweisen, daß Raten
etwas nützt. Wir haben leider nicht die Zeit, darauf näher einzugehen.
Wir haben zwei noch offene sogenannte N-ND-Probleme kennengelernt:

Das L-NL-Problem. Ist die Inklusion L ⊆ NL echt?

Das P-NP-Problem. Ist die Inklusion P ⊆ NP echt?

In beiden Fällen vermutet man eine positive Antwort. Im Falle des P-NP-Problems
geht man sogar davon aus. Nach Satz 12.32 konzentrieren wir uns auf das P-NP-Problem.

Satz 12.32 Es ist

L ⊆ NL ⊆ P ⊆ NP ⊆ PSPACE.

Beweis. Die einzige nichttriviale Inklusion ist NL ⊆ P. Zu ihrem Beweise genügt es,
für das NL-vollständige Problem GAP einen Polynomialzeitalgorithmus anzugeben. Eine
Tiefensuche leistet das. 

Bemerkung. Aus Satz 12.22 weiß man, daß die Inklusion L ⊆ PSPACE echt ist. Eine
Inklusion des Turmes der Komplexitätsklassen aus Satz 12.32 ist folglich ebenfalls echt,
aber man weiß nicht welche.

12.10 Die NP-Vollständigkeit von SAT


Sei V := {x1 , x2 , . . .} eine abzählbare Menge Boolescher Variablen und seinen ∧, ∨ und ¬
die Symbole für die logischen Operationen Konjunktion, Disjunktion bzw. Negation. (Eine
Variable heißt Boolesch, wenn sie nur Werte aus {0, 1} annehmen kann.)

231
Definition 12.33 Die Menge der vollständig geklammerten Booleschen Formeln in den
Variablen V ist wie folgt rekursiv defininiert.
Anfang. Sämtliche Variablen aus V und die Konstanten 0 und 1 sind vollständig geklam-
merte Boolesche Formeln.
Rekursion. Sind F , F1 und F2 vollständig geklammerte Boolesche Formeln, so auch (¬F ),
(F1 ∧ F2 ) und (F1 ∨ F2 ).
Eine Boolesche Formel F heißt Formel über der Variablenmenge {x1 , x2 , . . . , xn }, wenn nur
Variablen aus dieser Menge in der Darstellung von F vorkommen.

Statt (¬F ) schreibt man gerne F . Das gilt besonders für den Fall, daß F lediglich
eine Boolesche Variable oder eine Konstante ist. Für die Negation einer Variablen x ist in
bestimmten Zusammenhängen die Bezeichnung x0 üblich. Dann steht x1 für die Variable
x selbst. Das Operationssymbol für die Konjunktion wird oft weggelassen.
Die Semantik, die jeder vollständig geklammerten Booleschen Formel F eine Boolesche
Funktion zuordnet, ist jedem geläufig: Ist F = F (x1 , x2 , . . . , xn ) eine Boolesche Formel
über {x1 , x2 , . . . , xn }, so liefert jede Belegung

β : {x1 , x2 , . . . , xn } → {0, 1}

der in Rede stehenden Variablenmenge mit Booleschen Werten auf natürliche Weise einen
Wert b ∈ {0, 1} der Formel F auf diese Belegung. Wir schreiben dafür

F (β(x1 ), β(x2 ), . . . , β(xn )) = b

oder

β(F ) = b.

Auf diese Weise stellt F eine Boolesche Funktion

f : {0, 1}n → {0, 1}

dar. Häufig identifizieren wir die dargestellte Funktion mit der darstellenden Formel.
Auf Grund der üblichen Bindungsregel (Negation vor Konjuktion vor Disjunktion) und
der Assoziativität von Konjunktion und Disjunktion geht man von vollständig geklammer-
ten Booleschen Formeln gerne zu (unvollständig geklammerten) Booleschen Formeln über.
So steht beispielsweise

x1 ∧ x2 ∨ x¯4 ∧ x9

für

((x1 ∧ x2 ) ∨ ((¬x4 ) ∧ x9 )).

232
Definition 12.34 Eine Boolesche Formel F über {x1 , x2 , . . . , xn } heißt genau dann erfüll-
bar, wenn die dargestellte Funktion nicht identisch null ist. Eine der Belegung der Variablen
{x1 , x2 , . . . , xn }, die zum Funktionswert 1 führt, heißt erfüllende Belegung der Formel F .

Wir wollen Boolesche Formeln zum Gegenstand von Rechnungen auf Turingmaschi-
nen machen. Wir müssen deshalb die Menge F aller Booleschen Formeln in V über dem
Alphabet {0, 1} codieren.

Erster Schritt. Zunächst codieren wir die Menge V wie folgt über dem Alphabet {0, 1, x}:

V −→ {0, 1, x}
xi 7→ x bin(i).

Dadurch sind die Booleschen Formel bereits endlich repräsentiert:

F ⊂ {0, 1, x, ∧, ∨, ¬, (, )}∗.

Zweiter Schritt. Um zu einer Darstellung über {0, 1} zu kommen, verwenden wir den
folgenden Blockcode.

{0, 1, x, ∧, ∨, ¬, (, )} −→ {0, 1}3


0 7→ 000
1 7→ 001
x 7→ 010
∧ 7→ 011
∨ 7→ 100
¬ 7→ 101
( 7→ 110
) 7→ 111.

Wir sind in besonderem Maße an Booleschen Formeln mit spezieller Syntax interessiert.

Definition 12.35 1. Boolesche Variablen und deren Negationen heißen Literale.

2. Ein Literal y widerspricht einem Literal z, wenn es eine Variable xi so gibt, daß
{y, z} = {xi , ¬xi } ist.

3. Eine Klausel ist eine Disjunktion von Literalen.

4. Ein Monom ist einen Konjunktion von Literalen.

5. Eine konjunktive Form ist eine Konjunktion von Klauseln, wobei keine Klausel dop-
pelt auftritt.

233
6. Eine disjunktive Form ist eine Disjunktion von Monomen, wobei kein Monom doppelt
auftritt.
7. Ist f (x1 , x2 , . . . , xn ) eine Boolesche Funktion, so heißt eine f darstellende

– konjunktive Form, die eine Konjunktion von Klauseln aus



{ xb11 ∨ xb22 ∨ . . . ∨ xbnn | b1 , b2 , . . . , bn ∈ {0, 1}}

ist, konjunktive Normalform der Funktion f .


– disjunktive Form, die eine Disjunktion von Monomen aus

{xb11 ∧ xb22 ∧ . . . ∧ xbnn | b1 , b2 , . . . , bn ∈ {0, 1}}

ist, disjunktive Normalform der Funktion f .

Aus dem Grundkurs Informatik I/II ist der folgende Satz wohlbekannt. Er sichert, daß
sich Boolesche Funktionen sowohl durch konjunktive als auch durch disjunktive Formen
darstellen lassen.

Satz 12.36 Jede Boolesche Funktion hat eine eindeutig bestimmte konjunktive und eine
eindeutig bestimmte disjunktive Normalform.

Definition 12.37 Die formale Sprache SAT ist die Menge aller erfüllbaren konjunktiven
Formen:

SAT := {F | F ist erfüllbare konjunktive Form.}

Satz 12.38 Das Entscheidungsproblem SAT ist NP–vollständig.

Beweis. Teil 1. Wir müssen zeigen, daß die Sprache SAT in NP liegt. Dazu geben wir
den folgenden nichtdeterministischen Polynomialzeitalgorithmus an, der für jede Boolesche
Formel in konjunktiver Form F testet, ob sie erfüllbar ist.
Großschritt 1. Teste, ob die Eingabe F eine konjunktive Form ist. (Das sich dieser Syntax-
check in Polynomialzeit durchführen läßt, ist eine nicht allzu anspruchsvolle Übungs-
aufgabe.)
Großschritt 2. Rate eine erfüllende
(e1 , e2 , . . . , en )
der Booleschen Variablen, von denen F abhängt.
Großschritt 3. Verifiziere, ob
F (e1 , e2 , . . . , en ) = 1
ist. (Man überlegt sich sofort, daß die Auswertung einer Booleschen Formel in Poly-
nomialzeit möglich ist.)

234
Teil 2. Sei L eine formale Sprache aus NP. Wir müssen L auf SAT in polynomialzeit-
reduzieren. Dazu sei M ein ENTA, für den gilt:

– L = L(M);

– Für jede Eingabe w ∈ L gibt es eine akzeptierende Rechnung der Länge kleiner oder
gleich nk − 3.

Wir erinnern uns daran, daß jede Rechnung eines NTA syntaktisch gesehen beliebig
verlängert werden kann, wobei es aus einer terminalen lokalen Situation kein Entrinnen
gibt. Folglich sind wir berechtigt, im weiteren nur Rechnungen der Länge nk −3 in Betracht
zu ziehen. In deren Verlauf können höchstens nk − 3 Speicherzellen mit einer Inschrift ver-
sehen werden. Der beschriftete Teil des Bandes ist in seiner Länge durch nk −1 beschränkt,
da die beiden linken Randbegrenzer von Anfang an vorhanden sind.
Wir definieren die Menge Γ als die disjunktive Vereinigung

Γ := Σ ∪ {⊲, B} ∪ Q,

wobei Σ ⊇ {0, 1} das Bandalphabet und Q die Zustandsmenge des ENTA M ist. Wir
ordnen jeder Rechnung CM (w) der Länge nk − 3 der Maschine M auf eine Eingabe w ∈
{0, 1}n eine Tafel T := TCM (w)

⊲q0 ⊲ w1 w2 . . . wn B ... . . .B
.. ..
. .
⊲⊲γi1 γi2 . . .qζi γi,λi+1 γi,λi +2 . . . γi,λi +ρi . . .B (12.22)
.. ..
. .
⊲⊲γnk −2,1 γnk −2,2 . . .qζnk −2 γnk −2,λnk −2 +1 γnk −2,λnk −2 +2 . . . γnk −2,λnk −2 +ρnk −2 . . .B

aus der Menge der Abbildungen {T | T : {1, 2, . . . , nk − 2} × {1, 2, . . . , nk } → Γ} zu. Jede


Zeile von TCM (w) entspricht in kanonischer Weise einer Konfiguration der Maschine M auf
die Eingabe w während der Rechnung CM (w).
Wir bemerken, daß weder der am weitesten links liegende Randbegrenzer ⊲“ noch

das am weitesten rechts liegende Blanksymbol B“ einer beliebigen Zeile einer der in

Rede stehenden Tafeln T jemals vom Lese-Schreib-Kopf von M erreicht wird: Für den
Randbegrenzer ist das technisch ausgeschlossen. Zum Besuch des Blanksymbols reicht
die Zeit nicht.
Nun führen wir für jede Eingabenlänge n und jeden möglichen Eintrag

(i, j) ∈ {1, 2, . . . , nk − 2} × {1, 2, . . . , nk }

unserer Tafeln eine Menge Boolescher Variablen

Un(i,j) := {xi,j,γ | γ ∈ Γ}

235
ein und definieren
k −2 nk
n[ [
Un := Un(i,j)
i=1 j=1

Jede Tafel wird als Belegung der Variablen aus Un aufgefaßt. Dabei ist für jedes (i, j) ∈
{1, 2, . . . , nk − 2} × {1, 2, . . . , nk } und jedes γ ∈ Γ die Variable xi,j,γ genau dann gleich 1,
wenn für die zugehörige Tafel Tij = γ ist.
Natürlich gehört nicht zu jeder Belegung der Variablen aus Un eine Tafel. Wir be-
schränken uns auf solche, für die das der Fall ist und überdies die erste Spalte ausschließlich
aus Randbegrenzern ⊲, die letzte Spalte aus lauter Blanks B besteht. Diese Belegungen
geben Anlaß zu den folgenden Bezeichnungen: Eine Belegung

β : Un → {0, 1}

heißt zulässig, wenn für jedes i ∈ {1, 2, . . . , nk − 2} und jedes j ∈ {1, 2, . . . , nk } genau ein
γ ∈ Γ mit

β(xi,j,γ ) = 1

existiert und für alle i = 1, 2, . . . , nk − 2 gilt:

β(xi,1,⊲) = 1 β(xi,nk ,B ) = 1.

Ist β eine zulässige Belegung, so bezeichnet T (β) die zugehörige Tafel.


Wir reduzieren L auf SAT, indem wir einen nO(1) –zeitbeschränkten Algorithmus beschrei-
ben, der für jede Eingabe w ∈ {0, 1}n eine konjunktive Form Φw über den Variablen aus
Un ausgibt, welche die folgenden Eigenschaften hat.

– Jede Belegung β der Variablenmenge Un , welche die konjunktive Form Φw erfüllt,


ist auch zulässig.

– Jede zulässige Belegung β der Variablenmenge Un erfüllt die konjunktive Form Φw


genau dann, wenn die Tafel T (β) einer akzeptierenden Berechnung des ENTA M
auf die Eingabe w entspricht.
Die Zielformel Φw wird die folgende Gestalt haben:

Φw = Φadm ∧ Φinput
w ∧ Φaccept ∧ Φcomp (12.23)

Die Teilformeln aus 12.23 werden in Zeit nO(1) konstruierbar und, sofern nötig, in eine
konjunktive Form überführbar sein. Ferner werden sie die folgenden Eigenschaften haben.

Zulässigkeit. Jede Belegung β der Variablenmenge Un erfüllt Φadm genau dann, wenn β
zulässig ist.

236
Eingabe. Jede zulässige Belegung β der Variablenmenge Un erfüllt Φinput
w genau dann, wenn
(β)
T1 die initiale Konfiguration des ENTA M auf die Eingabe w ist.

Akzeptierung. Jede zulässige Belegung β der Variablenmenge Un erfüllt Φaccept genau dann,
wenn es ein j mit β(xnk −2,j,q+ ) = 1 gibt.

Nachfolgereigenschaft. Jede zulässige Belegung β der Variablenmenge Un mit β Φinputw =
comp
1 erfüllt Φ genau dann, wenn für sie die folgenden beiden Bedingungen gelten.
(β)
1. Für jeden Zeilenindex i ∈ {2, 3, . . . , nk −2} repräsentiert Ti eine Konfiguration
von M auf eine Eingabe der Länge n.
(β)
2. Für jeden Zeilenindex i ∈ {1, 2, . . . , nk − 3} ist darüber hinaus Ti+1 eine Nach-
(β)
folgerkonfiguration von Ti .

Effizienz. Die Formeln Φadm , Φinput


w , Φaccept und Φcomp sind in Zeit nO(1) konstruierbar und
in eine konjunktive Form überführbar.

Zur Zulässigkeit. Die Formel


k −2 nk −1
n^
! ! k −2
n^
^ _ ^ 
adm
Φ := xijγ ∧ (x̄ijγ1 ∨ x̄ijγ2 ) ∧ xi,1,⊲ ∧ xi,nk ,B (12.24)
i=1 j=2 γ∈Γ γ1 6=γ2 i=1

hat offensichtlich die geforderten Eigenschaften.


Zur Eingabe. Sei w = w1 w2 . . . wn , wobei die wi die einzelnen Bits sind. Dann hat die
Formel
n k
^
input
Φ := x1,1,⊲ ∧ x1,2,q0 ∧ x1,3,⊲ ∧ x1,4,w1 ∧ x1,5,w2 ∧ . . . ∧ x1,n+3,wn ∧ x1,j,B (12.25)
j=n+4

die geforderte Eigenschaft.


Zur Akzeptierung. Offensichtlich ist Formel 12.26 das, was wir suchen:
k −2
n_
Φaccept := xnk −2,j,q+ . (12.26)
j=2

Zur Nachfolgereigenschaft. Es genügt, die konjunktive Form Φcomp gemäß


k −3
n^
Φ comp
= Φcomp
i (12.27)
i=1

darzustellen, wobei die Formeln Φcomp


i die folgenden Eigenschaften haben.

237
– Φcomp
i ist eine konjunktive Form, die sich aus einer Zeichenkette der Länge n in
Polynomialzeit berechnen läßt.

– Φcomp
i hängt nur von den Variablen

{xi,j,γ , xi+1,j,γ | j = 1, 2, . . . , nk , γ ∈ Γ}

ab.
(β)
– Jede zulässige Belegung β der Variablen aus Un , für welche die Zeile Ti eine Konfi-
guration von M auf eine Eingabe der Länge n darstellt, erfüllt die konjunktive Form
(β) (β)
Φcomp
i genau dann, wenn die Zeile Ti+1 eine Nachfolgerkonfiguration der Zeile Ti
repräsentiert.

Sei i ∈ {2, 3, . . . , nk − 3} beliebig aber fest gewählt. Wir konstruieren eine Formel Φcomp
i
mit den vorstehend genannten Eigenschaften.
(β)
Wir können voraussetzen, daß β zulässig ist, und daß es sich bei Zeile Ti um eine
Konfiguration handelt. Wir überdecken das Zeilenpaar (i, i + 1) auf die folgende Weise
mit 2 × 3–Fenstern, wobei zwei benachbarte Fenster genau eine Spalte gemeinsam haben.
(Folglich wird die mittlere Spalte jedes solchen Fensters ausschließlich von diesem Fenster
überdeckt.)

1. Wir legen um den einzigen Eintrag von Zeile i, der gleich einem Zustand q ∈ Q ist,
auf die folgende Weise das zentrale Fenster :

b q a
(12.28)
∗ ∗ ∗

Das ist stets möglich, da der Lese-Schreibkopf niemals auf dem am weitesten links
liegenden Randbegrenzer steht.

2. Ausgehend von dem zentralen Fenster wird die Überdeckung nach links und nach
rechts solange ausgedehnt, bis daß die Paare von Einträgen
(β) (β)
Ti,2 Ti,nk −1
(β) bzw. (β)
Ti+1,2 Ti+1,nk −1

überdeckt sind. (Die Überdeckung der Paare

(β) (β)
Ti,1 Ti,nk
(β) und (β)
Ti+1,1 Ti+1,nk

ist fakultativ. Da zulässige Belegungen die erste und die letzte Spalte der zugehörigen
Tafel fixieren, können wir auf deren Überdeckung verzichten.) Diese Fenster wollen
wir peripher nennen.

238
Die vorstehend beschriebene Überdeckung nennen wir die ausgezeichnete Überdeckung des
Zeilenpaars (i, i + 1). Man sieht sofort ein, daß sie stets existiert und eindeutig bestimmt
(β)
ist, sofern die Zeile Ti, einer Konfiguration entspricht.
Jedes 2 × 3–Fenster einer Überdeckung des Zeilenpaares (i, i + 1) ist durch den Index
seiner mittleren Spalte eindeutig bestimmt. Die Folge f dieser Indizes für eine ausgezeich-
nete Überdeckung hängt von der Anzahl der Spalten nk und der Position ιQ des Zustandes
in der i-ten Zeile ab. Es gilt:


f(0,0) := (2, 4, . . . , nk − 2) falls ιQ gerade und nk gerade ist;

f
(1,0) := (3, 5, . . . , nk − 1) falls ιQ ungerade und nk gerade ist;
f= (12.29)


f(0,1) := (2, 4, . . . , nk − 1) falls ιQ gerade und nk ungerade ist;

f(1,1) := (3, 5, . . . , nk − 2) falls ιQ ungerade und nk ungerade ist.

(β)
Sei β eine zulässige Belegung derart, daß Ti, einer Konfiguration entspricht. Wir wol-
len an den Fenstern der ausgezeichneten Überdeckung der Zeilen i und i + 1 eineindeutig
(β) (β)
erkennen, ob Zeile Ti+1, einer Nachfolgerkonfiguration von Zeile Ti, entspricht. Da die
Überführungsrelation δM unseres ENTA M ihre Wirkung ausschließlich im zentralen Fen-
ster entfaltet, sind die folgenden beiden Bedingungen dafür notwendig und hinreichend:

1. Es gibt ein Element aus δM , das die obere Zeile des zentralen Fensters in dessen
untere Zeile überführt. Wichtig ist dabei, daß die untere Zeile des zentralen Fensters
durch die obere Zeile und das entsprechende Element aus δM eindeutig bestimmt ist.

2. Der obere und der untere Eintrag der mittleren Spalte ist für alle peripheren Fen-
ster gleich. Darüber hinaus enthält die obere Zeile eines peripheren Fensters keinen
Zustand.

Darstellung zentraler Fenster durch Boolesche Formeln. Wir müßten für jeden Daten-
satz

ς = (q, a, q ′, a′ , ρ)

aus δM und jede (dann mittlere) Spalte j = 2, 3, . . . , nk − 1 eine Fensterfunktion definieren.


Wir tun das exemplarisch für die Datensätze des Typs

ςL = (q, a, q ′ , a′ , L).
(ς )
Die zu definierende Funktion ΦijL ist auf den zu dem Fenster

Ti,j−1 Ti,j Ti,j+1


(12.30)
Ti+1,j−1 Ti+1,j Ti+1,j+1

gehörigen Variablen aus Un definiert. Sie muß die folgende Eigenschaft haben:

239
(ς ) (β) (β)
Eine zulässige Belegung β erfüllt ΦijL genau dann, wenn Ti,j = q und Ti,j+1 = a und
(β) (β) (β) (β)
Ti+1,j−1 = q ′ und Ti+1,j+1 = a′ und Ti,j−1 = Ti+1,j .
Dies leistet die folgende Formel.
(ς )
^
ΦijL = xi,j,q ∧ xi,j+1,a ∧ xi+1,j−1,q′ ∧ xi+1,j+1,a′ ∧ (x̄i,j−1,γ ⊕ xi+1,j,γ ) (12.31)
γ∈Γ

Natürlich gilt für alle ς 6= ς ′ , daß die zugehörigen Fensterfunktionen disjunkt sind:
(ς) (ς ′ )
Φij ∧ Φij = 0. (12.32)

Die Gesamtfunktion für dieses Fenster als zentralem Fenster ist


_ (ς)
Φcent
ij = Φij , (12.33)
ς∈δM

wobei es sich dabei wegen Gleichung 12.32 um eine disjunkte Vereinigung handelt.
Darstellung peripherer Fenster durch Boolesche Formeln. Wir suchen nach einer Funk-
(peripher)
tion Φij , die wiederum auf den Variablen definiert ist, die zu dem Fenster 12.30
gehören. Ihre definierende Eigenschaft ist:
(β) (β)
Eine zulässige Belegung β erfüllt Φperipher
ij genau dann, wenn Ti,j = Ti+1,j ist und
(β) (β) (β)
Ti,j−1, Ti,j , Ti,j+1 6∈ Q gilt.
Die Formel
^ ^
Φperipher
ij = x̄i,k,q ∧ (x̄i,j,γ ⊕ xi+1,j,γ ) (12.34)
q∈Q γ∈Γ\Q
k=j−1,j,j+1

leistet das Verlangte.


Wir beobachten, daß die Funktionen Φcent
ij und Φperipher
ij disjunkt sind:

Φcent
ij ∧ Φperipher
ij =0 (12.35)

Da grundsätzlich jedes Fenster sowohl zentral als auch peripher sein kann, definieren
wir:

Φcomp
ij := Φcent
ij ∨ Φperipher
ij . (12.36)

Aus den Gleichungen 12.32 und 12.35 folgt: Jede zulässige Belegung β der Variablen aus Un ,
(β)
für welche die Zeile Ti eine Konfiguration von M auf eine Eingabe der Länge n darstellt,
comp
erfüllt die Formel Φij genau dann, wenn folgendes gilt: Das 2 × 3–Fenster mit dem
mittleren Spaltenindex j ist entweder ein zentrales Fenster, das genau ein Transformation
ς ∈ δM widerspiegelt, oder es ist ein peripheres Fenster.

240
Nun müssen die Φcomp
ij (j = 2, 3, . . . , nk − 1) nur noch zu der Funktion Φcomp
i zusam-
mengesetzt werden. Wir greifen auf das zurück, was wir im Vorfeld von Gleichung 12.29
gesagt haben.
V  V 
comp comp
 Φ
j∈f(0,0) ij ∨ Φ
j∈f(1,0) ij falls nk gerade ist;
Φcomp
i :=  V comp
 V comp
 (12.37)

j∈f(0,1) Φij ∨ j∈f(1,1) Φij falls nk ungerade ist.

(β)
Warum erfüllt jede zulässige Belegung β der Variablen aus Un , für welche die Zeile Ti
eine Konfiguration von M auf eine Eingabe der Länge n darstellt, die durch Gleichung 12.37
(β)
definierte Formel Φcomp
i genau dann, wenn die Zeile Ti+1 eine Nachfolgerkonfiguration der
(β)
Zeile Ti repräsentiert?
(β) (β)
Ist Ti+1 eine Nachfolgerkonfiguration der Zeile Ti V , und ist f die ausgezeichnete Über-
deckung (siehe Gleichung 12.29), so ist die Teilformel j∈f Φcomp ij erfüllt.
comp V
Ist andererseits Φi erfüllt, so muß es eine erfüllte Teilformel j∈f Φcompij geben. Die
Folge f repräsentiert eine ausgezeichnete Überdeckung, deren sämtliche Fensterformeln
(β)
Φcomp
ij erfüllt (j ∈ f) sind. Da nach Voraussetzung die Zeile Ti eine Konfiguration von
M auf eine Eingabe der Länge n darstellt, sichert dies (siehe Bemerkung im Anschluß an
(β) (β)
Gleichung 12.36), daß Ti+1 eine Nachfolgerkonfiguration von Ti darstellt.
Warum läßt sich die in Gleichung 12.37 definierte Formel Φcompi in Zeit nO(1) in eine
konjunktive Form transformieren?
Jede der Formeln Φcomp
ij hängt nur von O (1) vielen Variablen ab und kann folglich
in ZeitVO (1) in konjunktive Normalform gebracht werden. Folglich kann jede der Teilfor-
meln j∈f Φcomp
ij in Zeit nO(1) in eine konjunktive Form gebracht werden. Schließlich lassen
V  V 
comp comp
sich Formeln von der Art Φ
j∈f′ ij ∨ Φ
j∈f′′ ij durch einfache Anwendung des
Distributivgesetzes in Zeit nO(1) in eine konjunktive Form überführen.

Zur Effizienz. Aus den angegebenen Konstruktionen für die Formeln Φadm , Φinput
w , Φaccept
comp O(1)
und Φ ergibt sich unmittelbar, daß sie in Zeit n aufgestellt werden können. Die
comp
Transformation der Φi in konjuktive Formen haben wir soeben besprochen. 

12.11 Einschränkungen und Varianten von SAT


Nach Definition 12.35 ist klar, daß sich jede konjunktive Form

r
^
F = Kr (12.38)
i=1

241
in den Variablen {x1 , x2 , . . . , xn } sich umkehrbar eindeutig als Menge ihrer Klauseln1 dar-
stellen läßt:

F = {Ki | i = 1, 2, . . . , r}. (12.39)

Das folgende Lemma ist eine einfache, aber sehr nützliche Charakterisierung für die
Erfüllbarkeit der konjunktiven Form F aus Gleichung 12.39.
Lemma 12.39 Die konjunktive Form F aus Gleichung 12.39 ist genau dann erfüllbar,
wenn es eine Funktion

α : F = {Ki | i = 1, 2, . . . , r} → {x1 , x2 , . . . , xn , x1 , x2 , . . . , xn }

derart gibt, daß


– für jedes i = 1, 2, . . . , r das Literal α(Ki ) ein Literal der Klausel Ki ist;

– für alle i 6= j die Literale α(Ki ) und α(Kj ) einander nicht widersprechen: α(Ki) 6=
α(Kj ). (Wir nennen eine solche Funktion α auch eine widerspruchsfreie Auswahl-
funktion.)

Beweis. Ist F erfüllbar, so gibt es einen Booleschen Vektor b ∈ {0, 1}n , der F erfüllt.
Das heißt insbeondere für jede der Klauseln Ki , daß es ein Literal y aus Ki geben muß,
das durch b erfüllt wird. Sei y ein solches Literal für Ki . Wir definieren α(Ki ) := y.
Die so ausgewählten Literale können einander nicht widersprechen, da sie alle durch
den Vektor b erfüllt werden.
Sei α eine Auswahlfunktion. Wir definieren wie folgt für F einen erfüllenden Vektor
b = (b1 , b2 , . . . , bn ). Für jedes i = 1, 2, . . . , r gibt α(Ki ) Anlaß, eine Komponente von b
e
festzulegen: Ist α(Ki) = xj j , so setzen wir bj := ej . Komponenten von b, die auf diese
Weise nicht erreicht werden, legen wir beliebig fest.
Natürlich kann es passieren, daß einer Komponente von b mehrfach ein Wert zugewiesen
wird. Das ist genau dann der Fall, wenn es ein i und ein j derart gibt, daß den Literalen
α(Ki ) und α(Kj ) dieselbe Variable zugrunde liegt. Wegen der Widerspruchsfreiheit der
Auswahlfunktion α sind dann die Literale α(Ki ) und α(Kj ) und damit die zugewiesenen
Werte gleich.
Man erkennt unschwer, daß ein Boolescher Vektor b, der auf die vorstehend beschrie-
e
bene Weise entstanden ist, die konjunktive Form F erfüllt: Ist α(Ki ) = xj j , so sorgt die
Komponente bj für die Erfüllung der Klausel Ki . 

Definition 12.40 Die konjunktive Form F aus Gleichung 12.39 heißt genau dann (exakte)
k-CF, wenn für jedes i = 1, 2, . . . , r gilt: Die Klausel Ki besteht aus (genau) k paarweise
verschiedenen Literalen, die einander nicht widersprechen.
1
Aus technischen Gründen — siehe Beweis von Satz 12.46 — ist eine Klausel nicht eindeutig durch die
in ihr vorkommenden Literale bestimmt. Es kann z.B. sein, daß K1 = K2 = (x ∨ y ∨ z) ist.

242
Definition 12.41 Die formale Sprache kSAT ist für k ≥ 2 die Menge aller erfüllbaren
k-CF:
kSAT := {F | F ist erfüllbare k-CF.}
kSATexakt := {F | F ist erfüllbare exakte k-CF.}

Die formale Sprache

3SAT2,3 ⊂ 3SAT
besteht aus allen erfüllbaren 3-CF F , für die überdies gilt: Jede Variable kommt in F
höchstens dreimal, jedes Literal höchstens zweimal vor.
Satz 12.42 Das Problem 3SATexakt und damit das Problem 3SAT ist NP-vollständig.

Beweis. Ein NP-Vollständigkeitsbeweis besteht, wie wir aus dem Beweis des Satzes 12.38
wissen, aus zwei Teilen. Man muß zunächst zeigen, daß das in Rede stehende Problem in
NP liegt. Für 3SAT geht das in der gleichen Weise wie im Beweis von Satz 12.38 dargestellt.
Nun müssen wir jedes Problem aus NP auf 3SATexakt reduzieren. Das geschieht, indem
wir einen Polynomialzeit-Algorithmus angeben, der SAT auf 3SATexakt reduziert:
Eingabe: eine konjunktive Form F gemäß Gleichung 12.39.
Ausgabe: eine exakte 3-CF gemäß Definition 12.40, die genau dann erfüllbar ist, wenn die
Eingabeform F erfüllbar ist.
Sei F im folgenden die aktuelle konjunktive Form.
Großschritt 1.
– Streiche aus jeder Klausel mehrfach vorkommende Literale bis auf einen Repräsen-
tanten.
– Streiche jede Klausel, in der eine Variable und deren Negation vorkommt.
Bemerkung. Warum kann man Klauseln, die einen Term x ∨ x“ enthalten, streichen? Sie

sind stets erfüllt.
Großschritt 2. Hier geht es darum, Klauseln der Länge eins zu entfernen. Die Grundidee
ist einfach: Solche Klausel können nur erfüllt werden, indem man das Literal eins setzt. Da
der Wert für die Boolesche Variable, die y zugrunde liegt, nun festgelegt ist, muß dieser
Wert in alle anderen Klauseln eingesetzt werden. Für jede Klausel K ′ ∈ F , die von dieser
Einsetzung betroffen ist, sind zwei Fälle möglich.
Entweder die Klausel K ′ ist dadurch erfüllt. Dann kann sie gestrichen werden. Ist F
nach dieser Streichung leer, war die Eingabeform erfüllbar, und wir geben eine erfüllbare
exakte 3-CF aus.
Oder das ist nicht der Fall. Dann kommt y in K ′ vor, und es muß ein anderes Literal
herhalten, um K ′ zu erfüllen: Das Literal y wird aus K ′ entfernt. Ist K ′ nun leer, so ist
eine Erfüllung von K ′ nicht mehr möglich. Die Eingabe-Form war folglich unerfüllbar. Wir
reagieren darauf, indem wir eine unerfüllbare exakte 3-CF ausgeben:

243
Solange F eine Klausel K = y (y Literal) enthält, führe aus.
Setze y ← 1
Entferne K aus F .
Falls F = ∅, so führe aus.
Gib eine erfüllbare exakte 3-CF aus und breche ab:
return (x1 ∨ x2 ∨ x3 ).
Für alle K ′ ∈ F führe aus.
Falls y in K ′ vorkommt, so führe aus.
Entferne K ′ aus F .
Bemerkung. K ′ ist dann erfüllt.
Falls F = ∅, so return (x1 ∨ x2 ∨ x3 ).
Falls y in K ′ vorkommt, so führe aus.
Entferne y aus K.
Ist K ′ nun leer, so führe aus.
Gib eine nichterfüllbare
V exakte 3-CF aus und breche ab:
return e1 ,e2,e3 ∈{0,1} xe11 ∨ xe23 ∨ xe33 .

Großschritt 3. Nun müssen Klauseln der Länge 2 ersetzt werden.

Für jede Klausel K ∈ F führe aus:


Ist K = (y1 ∨ y2 ) (y1 , y2 Literale), so ersetze K durch die Klauseln
(y1 ∨ y2 ∨ zK ) und (y1 ∨ y2 ∨ z K ).
Bemerkung. Die Variable zK ist eine neue Variable, die nur an dieser Stelle vorkommt.
Der Sinn dieser Konstruktion besteht darin, daß eine Auswahlfunktion α für die Neuform
wenigstens eines der Altliterale y1 und y2 auswählen muß. Dadurch ist die Altform genau
dann erfüllbar, wenn die Neuform erfüllbar ist.
Großschritt 4. Schließlich werden Klauseln der Länge ≥ 4 ersetzt.
Für jedes (y1 ∨ y2 ∨ . . . ∨ yk ) ∈ F (k ≥ 4, yi für i = 1, 2, . . . k Literale) führe aus:
Ersetze (y1 ∨ y2 ∨ . . . ∨ yk ) durch die folgenden Klauseln:
(y1 ∨ y2 ∨ zK,1)
(z K,1 ∨ y3 ∨ zK,2)
...
(z K,i ∨ yi+2 ∨ zK,i+1 )
...
(z K,k−3 ∨ yk−1 ∨ yk ).
Bemerkung. Die Variablen zK,1 ,zK,2, . . ., zK,k−3 sind neu. Für sie läßt sich das wiederholen,
was wir am Ende von Großschritt 3 über die Variable zK gesagt haben. 

Man kann das Problem 3SAT weiter einschränken, ohne die Qualität der NP-Vollständig-
keit zu verlieren.

244
Satz 12.43 Das Problem 3SAT2,3 ist NP-vollständig.

Beweisskizze. Offenbar liegt 3SAT2,3 in NP. Folglich genügt es, das Problem 3SAT auf
3SAT2,3 zu reduzieren.
Sei F eine Instanz von 3SAT. Für jede Variable x von F , die in F mehr als einmal
vorkommt, führen wir folgendes durch.
1. Kommt die Variable x in F k-mal vor, so ersetzten wir jedes Vorkommen durch eine
neue Variable. Statt der Variablen x kommen in F nun die Variablen x1 , x2 , . . ., xk
vor.

2. Wir erzwingen, daß unsere modifizierte Form nur dann erfüllbar ist, wenn die Va-
riablen x1 , x2 , . . ., xk mit dem gleichen Wert belegt werden, indem wir sie mit der
2-CF

(x1 ∨ x2 ) ∧ (x2 ∨ x3 ) ∧ . . . ∧ (xk ∨ x1 )

verunden, die zu der Formel

(x1 → x2 ) ∧ (x2 → x3 ) ∧ . . . ∧ (xk → x1 )

äquivalent ist. (Die Boolesche Funktion x → y, die Implikation, ist genau dann 0,
wenn x = 1 und y = 0 ist.) Diese ist offenbar genau dann erfüllt, wenn x1 = x2 =
. . . = xk ist.


Was ist mit dem Problem 2SAT? Wie der folgende Satz zeigt, wird die Erfüllbarkeit
konjunktiver Formen einfacher, wenn man sich auf Klauseln der Länge zwei beschränkt.

Satz 12.44 Das Problem 2SAT ist NL-vollständig.

Die Beweisidee von Satz 12.44 sei hier kurz skizziert. Zunächst wissen wir, daß die
Disjunktion A ∨ B zweier Aussagen A und B logisch zu der Implikation A → B äquivalent
ist. Diese Tatsache regt uns zu der folgenden Konstruktion an.
Ist F eine 2-CF über {x1 , x2 , . . . , xm }, so ersetzen wir zunächst jede Klausel von F , die
nur aus einem Literal y besteht, durch y ∨ y. Nun ordnen wir dem so modifizierten F den
folgenden gerichteten Graphen G(F ) zu. Die Knotenmenge von G(F ) ist

{x1 , x2 , . . . , xm , ¬x1 , ¬x2 , . . . , ¬xm , }.

Zwei Knoten y1 und y2 sind genau dann durch eine Kante y1 → y2 verbunden, wenn y1 ∨ y2
eine Klausel von F ist.
Man überlegt sich leicht, daß die 2-CF F genau dann nicht erfüllbar ist, wenn es eine
Variable xi (i = 1, 2, . . . , n) derart gibt, daß es in F (G) sowohl einen gerichteten Weg von
xi nach ¬xi als auch einen gerichteten Weg von ¬xi nach xi gibt.

245
Auf dieser Grundlage kann man sich leicht überlegen, daß 2SAT in NL liegt. Die NL-
Vollständigkeit beweist man, indem man die große Ähnlichkeit des oben beschriebenen We-
geproblems in F (G) mit dem in Abschnitt 12.2 betrachteten Graph-Accessibility-Problem
GAP ausnutzt.

Man kann sich auch die Frage stellen, wieviele Klauseln einer k-CF maximal gleichzeitig
erfüllbar sind. Das zugehörige Entscheidungsproblem ist MAXkSAT.

Problem 7 (Entscheidungsproblem MAXkSAT)


Eingabe: eine k-CF F = {Ki | i = 1, 2, . . . , r} in {x1 , x2 , . . . , xn } gemäß Definition 12.40
und eine natürliche Zahl c.
Ausgabe: Akzeptiere genau dann, wenn es einen Booleschen Vektor (β1 , β2 , . . . , βn ) ∈
{0, 1}n gibt, der mindestens c Klauseln aus {Ki | i = 1, 2, . . . , r} erfüllt.
Man ist nicht verwundert, daß der folgende Satz gilt.
Satz 12.45 Das Problem MAX3SAT ist NP-vollständig.

Beweis. Einerseits ist klar, daß MAX3SAT in NP liegt. Man muß ja nur die entspre-
chende Belegung der Variablen raten. Andererseits ist die Funktion
F 7→ (F, |F |)
offenbar eine FP-Reduktion von 3SAT auf MAX3SAT. 

Es ist erstaunlich, daß hier der Übergang zu Klauseln mit nur zwei Literalen unschädlich
ist, wenn es um den Erhalt der NP-Vollständigkeit geht.
Satz 12.46 Das Problem MAX2SAT ist NP-vollständig.

Beweis. Da MAX2SAT offenbar in NP liegt, genügt es, 3SATexakt auf MAX2SAT zu


reduzieren. Dazu sei F eine 3-CF.
1. Sei K ∈ F eine Klausel von F mit K = (x ∨ y ∨ z), wobei x, y und z Literale sind.
Seien schließlich v = vw und w = wK neue Variablen. Wir definieren
FK (v, w, x, y, z) := (v ∨ w) ∧ (v ∨ x) ∧ (v ∨ y) ∧ (v ∨ z) ∧
| {z }
=:Φ1

(v ∨ w) ∧ (v ∨ x) ∧ (v ∨ y) ∧ (v ∨ z) ∧
| {z }
=:Φ2

(x ∨ y) ∧ (x ∨ z) ∧ (y ∨ z) ∧
| {z }
=:Φ3

(x ∨ w) ∧ (y ∨ w) ∧ (z ∨ w) . (12.40)
| {z }
=:Φ4

246
2. Wir beobachten, daß die durch Gleichung 12.40 in Beweisschritt 1 definierte konjunk-
tive Form FK (v, w, x, y, z) symmetrisch in x, y und z ist.

3. Wir beweisen, daß K = (x ∨ y ∨ z) genau dann erfüllt ist, wenn die Anzahl der
erfüllten Klauseln von FK größer oder gleich 11 ist.
Ist v = 1, so sind die drei Klauseln aus Φ1 erfüllt, andernfalls sind es alle Klauseln
aus Φ2 . Im übrigen sind diese beiden Fälle symmetrisch. Wir können im weiteren
o.B.d.A. voraussetzen, daß v = 1 ist. Die vier Klauseln aus Φ1 sind also stets erfüllt.
Wir unterscheiden nun danach, ob ein, zwei oder alle drei Literale der Klausel K =
(x∨y∨z) erfüllt sind. Wegen Beweisschritt 2 können wir uns o.B.d.A. auf die folgende
Fallunterscheidung beschränken.

Fall 1: x = 1 und y = z = 0. Offenbar sind alle drei Klauseln aus Φ3 erfüllt.


Zusammen mit den vier erfüllten Klauseln aus Φ1 haben wir einen Sockeln von 7
erfüllten Klauseln.
Wählen wir w = 0, so können wir alle drei Klauseln aus Φ4 aber nur eine Klausel aus
Φ2 erfüllen. Andernfalls wäre es nur eine Klausel aus Φ4 , dafür aber zwei Klauseln
aus Φ2 . Für w = 1 sind folglich 11 Klauseln erfüllt.

Fall 2: x = y = 1 und z = 0. Offenbar sind zwei Klauseln aus Φ3 erfüllt. Zusammen


mit den vier Klauseln aus Φ1 haben wir einen Grundstock von 6 erfüllten Klauseln.
Für w = 0 kommen zwei Klauseln aus Φ2 und drei Klauseln aus Φ4 hinzu. Für w = 1
sind es drei Klauseln aus Φ2 , aber nur zwei Klauseln aus Φ4 , die erfüllt sind. Folglich
sind sowohl für w = 0 als auch für w = 1 genau 11 Klauseln erfüllt.

Fall 3: x = y = z = 1. Hier ist keine Klausel aus Φ3 , aber unabhängig von der Wahl
von w alle drei Klauseln Φ4 aus erfüllt. Im Fall w = 1 sind darüber hinaus alle vier
Klauseln aus Φ2 erfüllt.

4. Man überlegt sich leicht in einer zu Beweisschritt 3 analogen Weise, daß (x ∨ y ∨ z)


genau dann nicht erfüllt ist, wenn die Anzahl der erfüllten Klauseln von FK kleiner
oder gleich 10 ist. (Hier ist der Fall w = 0 am günstigsten.)
V
5. Offenbar gilt nun: Die 3-CF F ist genau dann erfüllbar, wenn für die 2-Form K∈F FK
mindestens 11 · |F | Klauseln gleichzeitig erfüllt werden können.

247
Literaturverzeichnis

[Pap94] C. H. Papadimitriou. Computational Complexity. Addison–Wesley, 1994.

[Rei99] K. R. Reischuk. Komplexitätstheorie. Leitfäden der Informatik. Teubner Verlag,


1999.

248
Kapitel 13

Einige wichtige NP–vollständige


Probleme

Es folgen einige wichtige NP-vollständige Probleme. Wer mehr wissen will, der studiere die
entsprechenden Kapitel aus [Pap94].

13.1 Aufteilungsprobleme für Mengen und Zahlen


Definition 13.1 Seien H, B und G disjunkte endliche Mengen gleicher Mächtigkeit q,
und sei P ⊆ H × B × G eine dreistellige Relation über diesen Mengen.
Eine Teilrelation M ⊆ P mit

M = {(hi , bi , gi ) | i = 1, 2, . . . , q}

mit

H = {h1 , h2 , . . . , hq } B = {b1 , b2 , . . . , bq } G = {h1 , h2 , . . . , hq }

heißt dreidimensionales Matching für D = (H, B, G, P ).


Ein Tripel m = (h, b, g) überdeckt seine Komponenten h, b und g.

Problem 8 (Dreidimensionales Matching 3DM)


Eingabe: ein 4-Tupel D = (H, B, G, P ) mit den Eigenschaften aus Definition 13.1.

Ausgabe: Akzeptiere genau dann, wenn die Relation P ein dreidimensionales Matching
enthält.

Man kann sich das Entscheidungsproblem 3DM wie folgt veranschaulichen. Eine gleiche
Anzahl von Jungen (B) und Mädchen (G) wollen Lebensgemeinschaften gründen. Dazu
stehen Häuser (H) in entsprechender Anzahl zur Verfügung. Gegeben ist ferner eine Menge

249
P von Präferenztripeln: Ist (h, b, g) ∈ P , so glauben das Mädchen g und der Junge b in
dem Haus h miteinander auskommen zu können.
Jedes Mädchen, jeder Junge und jedes Haus sollten von mehreren Präferenztripeln
überdeckt werden, denn ansonsten wäre das Problem algorithmisch uninteressant.
Die Frage ist, ob die Jungen und Mädchen in den zur Verfügung stehenden Häusern so
zueinander finden können, daß sie dort gemäß P miteinander auskommen.
Den Beweis des folgenden Satzes führen wir in Anlehnung an [Pap94].

Satz 13.2 Das Entscheidungsproblem 3DM ist NP-vollständig.

Beweis. Man überzeugt sich in der üblichen Weise davon, daß 3DM ∈ NP ist.
Um zu zeigen, daß 3DM NP-vollständig ist, reduzieren wir 3SATexakt auf 3DM. Wir
brauchen einen Polynomialzeit-Algorithmus, der angesetzt auf eine exakte 3-CF

F = {K1 , K2 , . . . , Kr } mit
Kj = aj ∨ bj ∨ cj (j = 1, 2, . . . , r)

über der Variablenmenge

VF = {x1 , x2 , . . . , xn }

eine Instanz

DF = (HF , BF , GF , PF )

von 3DM derart berechnet, daß PF genau dann ein dreidimensionales Matching M enthält,
wenn die Formel F erfüllbar ist.
Die Instanz DF hat
r
[ (j) (j) (j) (j)
HF := {x , x2 , . . . , x(j) (j)
n , x1 , x2 , . . . , xn }
| 1 {z }
j=1
=:H(Kj )

als Häusermenge: Jede Klausel Kj (j = 1, 2, . . . , r) bekommt ihren eigenen vollständigen


Satz H(Kj ) von Literalen und mutiert zu K̂j , indem die Literale über VF durch die ent-
sprechenden Literale aus H(Kj ) ersetzt werden. (Ist beispielsweise K7 = x1 ∨ x2 ∨ x5 , so
(7) (7) (7)
ist K̂7 = x1 ∨ x2 ∨ x5 .)
Die Menge der Präferenztripel MF besteht aus drei Teilen.
Der Klauselteil von F besteht für jedes j = 1, 2, . . . , r aus der Menge von Präferenztri-
peln
(j) (j) (j) (j)
P (Kj ) ⊆ {x1 , x2 , . . . , x(j) (j)
n , x1 , x2 , . . . , xn } × {βj } × {γj },

250
die wie folgt definiert ist:

(z (j) , βj , γj ) ∈ P (Kj ) ⇐⇒ Das Literal z kommt in Kj vor.

Der Klauselteil PK insgesamt besteht aus der Vereinigung der P (Kj ) für j = 1, 2, . . . , r.
Für j = 1, 2, . . . , r kommen die Paare (βj , γj ) nur im Klauselteil vor.
Ist M ′ eine Überdeckung der Paare {(βj , γj ) | j = 1, 2, . . . , r} mit |M ′ | = r, so definiert
M ′ auf die folgende Weise eine Auswahlfunktion:

α : {K1 , K2 , . . . , Kr } → {aj , bj , cj | j = 1, 2, . . . , r}

α(Kj ) = z : ⇐⇒ z (j) , βj , γj ∈ M ′ .

Ist umgekeht α eine Auswahlfunktion, so ist die zugehörige Menge M ′ ⊂ PK



M ′ := (z (j) , βj , γj ) | α(Kj ) = z

eine Überdeckung der Paare {(βj , γj ) | j = 1, 2, . . . , r} mit |M ′ | = r.


Wir müssen in der Konstruktion von DF nun so fortfahren, daß folgendes gesichert ist:
1. Ist M ⊆ PF ein dreidimensionales Matching und ist M ′ := M ∩ PK , so ist die zu
M ′ gehörige Auswahlfunktion α widerspruchsfrei.

2. Ist umgekehrt α eine widerspruchsfreie Auswahlfunktion, so läßt sich die zugehöri-


ge Überdeckung M ′ der Paare {(βj , γj ) | j = 1, 2, . . . , r} mit |M ′ | = r zu einem
dreidimensionalen Matching auffüllen.
Der nun folgende Variablenteil hat die Aufgabe, die Eigenschaften aus der vorstehenden
Box sicherzustellen.
Der Variablenteil. Wir führen für jede Variable xi ∈ VF neue Mengen
(1) (2) (r)
B(xi ) := {bi , bi , . . . , bi }
(1) (2) (r)
G(xi ) := {gi , gi , . . . , gi }

von r = #Klauseln Jungen und Mädchen ein und konstruieren ein P (xi ) mit

(j) (j)
P (xi ) ⊆ {xi , xi | j = 1, 2, . . . , r} × B(xi ) × G(xi )

so, daß für jedes j = 1, 2, . . . , r und alle µ und ν gilt:


(j) (µ) (ν)
(xi , bi , gi ) ∈ P (xi ) ⇐⇒ µ = j und ν = j.
(j) (µ) (ν)
(xi , bi , gi ) ∈ P (xi ) ⇐⇒ µ ≡ (j + 1) (mod r) und ν = j.

Für den Fall r = 4 ist die Relation P (xi ) in Abbildung 13.1 graphisch dargestellt: Die
Elemente von P (xi ) sind genau durch die Ecken der äußeren (grünen) Dreiecke definiert.

251
(1)
xi

(4) (1)
xi xi

(1) (1)
bi gi
(4) (2)
gi bi

(4) (2)
xi xi
(2)
(4)
gi
bi
(3) (3)
gi bi

(3) (2)
xi xi

(3)
xi

Abbildung 13.1: Der Teil der 3DM-Instanz für die Variable xi einer 3-CF mit vier Klauseln

Die Elemente aus B(xi ) und G(xi ) können ausschließlich von Tripeln aus P (xi ) über-
deckt werden.
Die folgende Beobachtung, die man sich sehr schön anhand von Abbildung 13.1 ver-
deutlichen kann, ist von zentraler Bedeutung. Für jedes i = 1, 2, . . . , n gibt es genau zwei
Möglichkeiten, die Mengen B(xi ) und G(xi ) zu überdecken. Entweder geschieht das durch
die Tripel
(1) (1) (1) (2) (2) (2) (r) (r) (r)
(xi , bi , gi ) (xi , bi , gi ) ... (xi , bi , gi ) (13.1)

oder durch die Tripel


(1) (2) (1) (2) (3) (2) (r) (1) (r)
(xi , bi , gi ) (xi , bi , gi ) ... (xi , bi , gi ) (13.2)

Der Auffüllteil dient der Lösung des folgendes Problems. Es gibt bisher zuwenige Jungen
und Mädchen, um alle 2nr Häuser besetzen zu können:

n
[ n
[

{β1 , β2 , . . . , βr } ∪ B(xi ) = nr + r {γ1 , γ2 , . . . , γr } ∪ G(xi ) = nr + r

i=1 i=1

252
Deshalb müssen die folgenden Auffüllmengen konstruiert werden:

B̂ := {β̂1 , β̂2 , . . . β̂nr−r } Ĝ := {γ̂1, γ̂2 , . . . γ̂nr−r }.

Diese Auffüllmengen können nur durch die folgenden Präferenztripel überdeckt werden:
(j) (j)
P̂ := {(xi , β̂k , γ̂k ), (xi , β̂k , γ̂k ) | i = 1, 2, . . . , n, j = 1, 2, . . . , r k = 1, 2, . . . , nr − r}

Nun beweisen wir die Korrektheit der Reduktion.


1. Ein dreidimensionales Matching aus PF muß für jedes i = 1, 2, . . . , n die Mengen
B(xi ) und G(xi ) überdecken. Diese jungen Damen und Herren wohnen, wie oben
(j)
dargestellt, entweder alle in einem Haus“ mit Dach (in einem der Häuser“ xi für
” ” (j)
j = 1, 2 . . . , r) oder alle in einem Haus“ ohne Dach (in einem der Häusern“ xi
” ”
für j = 1, 2 . . . , r). Die durch die Überdeckung der Paare (βj , γj ) (j = 1, 2, . . . , r) aus
dem Klauselteil definierte Auswahlfunktion α ist folglich widerspruchsfrei.

2. Ist F erfüllbar, und ist α eine widerspruchsfreie Auswahlfunktion, so füllen wir die
zu α gehörige Überdeckung M ′ der Paare {(βj , γj ) | j = 1, 2, . . . , r} mit |M ′ | = r wie
folgt zu einem dreidimensionalen Matching M auf:

(a) Ist α(Kj ) = xi , so wählt man für die Mengen B(xi ) und G(xi ) die Überdeckung
(13.2).
(b) Ist α(Kj ) = x̄i , so wählt man für die Mengen B(xi ) und G(xi ) die Überdeckung
(13.1).
(c) Gibt es für ein xi keine Klausel K mit α(K) ∈ {xi , xi }, so ist die Überdeckung
der Mengen B(xi ) und G(xi ) nicht festgelegt.
(d) Alle bisher nicht überdeckten Elemente aus HF werden durch den Auffüllteil
überdeckt.


Problem 9 (Das Partitionsproblem PARTITION für Folgen natürlicher Zahlen)

Eingabe: ein Paar (A, s) bestehend aus


– einer endlichen Indexmenge A und
– einer Funktion s : A → N.
Ausgabe: Akzeptiere genau dann, wenn es eine Teilmenge A′ ⊆ A derart gibt, daß
X X
s(a) = s(a).
a∈A′ a6∈A′

253
Wir nennen eine Teilmenge A′ , sofern sie existiert, partitionierende Teilmenge von A.
Wir bemerken,
P daß für die Existenz einer partionierenden Teilmenge notwendig ist, daß
die Zahl a6∈A s(a) gerade ist.

Satz 13.3 Das Entscheidungsproblem PARTITION ist NP-vollständig.

Beweis. Wir reduzieren 3DM auf PARTITION. Sei (H, B, G, M) mit

H := {h0 , h1 , . . . , hq−1 }
B := {b0 , b1 , . . . , bq−1 }
G := {g0 , g1 , . . . , gq−1}

und P = {m1 , m2 , . . . , mk }.
Wir arithmetrisieren Elemente m aus P durch Zahlen der Länge 3q über den Ziffern
{0, 1, . . . , k}. Das Ziffernmuster einer solchen Zahl ist in (13.3) dargestellt.

(q − 1)-te . . . λ-te . . . 0-te (q − 1)-te . . . µ-te . . . 0-te (q − 1)-te . . . ν-te . . . 0-te


| {z }| {z }| {z }
Ziffer des H-Teils Ziffer des B-Teils Ziffer des G-Teils
(13.3)

Ist m = (hλ , bµ , gν ) ∈ M, so ist

Ziffernindex 3q − 1 . . . λ + 2q . . . 2q 2q − 1 . . . µ + q . . . q q − 1 . . . ν . . . 0
Ziffernwert 0 ... 1 ... 0 0 ... 1 ... 0 0 ... 1 ... 0
(13.4)

das Ziffernmuster der Arithmetrisierung s̃(m) gemäß (13.3). Der Wert dieser Arithmetri-
sierung ist

s̃(m) := (k + 1)λ+2q + (k + 1)µ+q + (k + 1)ν . (13.5)

Eine Teilmenge M ⊆ P wird durch


X
s̃(M) := s̃(m) (13.6)
m∈M

arithmetrisiert. Jetzt erkennen wir den Sinn dessen, die Menge {0, 1, . . . , k} als Ziffern-
menge zu verwenden: Bei der Berechnung von s̃(M) gemäß (13.6) gibt es in keiner der 3q
Spalten einen Übertrag. Insbesondere ist M genau dann ein dreidimensionales Matching,
wenn
3q−1
X (k + 1)3q − 1
β := s̃(M) = (k + 1)i = (13.7)
i=0
k

254
ist. Wir schlußfolgern, daß falls
s̃(P ) < β (13.8)
ist, die Eingabe P kein dreidimensionales Matching enthält.
Für den Fall s̃(P ) ≥ β“ definieren wir

A := P ∪ {a1 , a2 }, (13.9)
wobei a1 und a2 zwei neue Elemente sind, und
s : A −→ N
m ∈ P 7→ s̃(m) (13.10)
a1 7→ 2s(P ) − β (13.11)
a2 7→ s(P ) + β. (13.12)
Wir beobachten, daß
s(A) = 4s(P ) (13.13)
ist.
Wir zeigen: Ist s̃(P ) = s(P ) ≥ β, so hat die Eingabe P genau dann ein dreidiemsionales
Matching M ⊆ P , wenn die Indexmenge A eine partitionierende Teilmenge A′ enthält.
(⇒) Sei M ⊆ P das dreidimensionale Matching. Wir setzen
A′ := M ∪ {a1 }
und erhalten
s(A′ ) = 2s(P ) − β + β = 2s(P ).
Wegen (13.13) ist A′ partitionierende Teilmenge.
(⇐) Sei A′ ⊂ A eine partitionierende Teilmenge. Dann ist natürlich auch A \ A′ parti-
tionierend. Wegen
s(a1 ) + s(a2 ) = 3s(P ),
können nicht sowohl a1 als auch a2 in einer partitionierenden Teilmenge sein. Wir nehmen
o.B.d.A.
a1 ∈ A′
an. Dann gilt für
M := A′ \ {a1 }
wegen (13.11)
s(M) = β,
woraus folgt, daß M ein dreidimensionales Matching ist.
Wir fassen die soeben konstruierte Reduktion zusammen.

255
Großschritt 1.
Ist Ungleichung 13.8 erfüllt, so gib eine unlösbare Instanz von PARTITION aus.
Großschritt 2.
Berechne die Instanz (A, s) gemäß (13.5), (13.9), (13.10), (13.11) und (13.12)
und gib sie aus.


Satz 13.4 Das Entscheidungsproblem KNAPSACK ist NP-vollständig.

Beweis. Wir reduzieren PARTITION auf KNAPSACK. Sei (A, s) eine Instanz von
PARTITION, wobei wir o.B.d.A. annehmen wollen, daß A = {1, 2, . . . , n} ist.

Großschritt
Pn 1.
Ist i=1 s(n) ungerade, so gib eine unlösbare Instanz von KNAPSACK aus.
Großschritt 2.
Berechne die Instanz P 
I(A, s) := w1 = c1 = s(1), . . . , wn = cn = s(n), W = C = 21 · ni=1 s(n)
und gib sie aus.
Die Korrektheit des vorstehenden Reduktionsalgorithmus ergibt sich aus den folgenden
beiden PBemerkungen.
Ist ni=1 s(n) ungerade,
P so kann es keine partitionierende Indexmenge geben.
Im Falle, daß ni=1 s(n) gerade ist, hat I(A, s) genau dann eine Lösung, wenn A eine
partitionierende Indexmenge hat. 

13.2 Probleme aus der Graphentheorie


Definition 13.5 Sei G = (V, E) ein ungerichteter Graph. Eine Teilmenge V ′ der Knoten-
menge V heißt
– Clique, wenn der durch V ′ in G induzierte Teilgraph der vollständige Teilgraph ist.
– unabhängige Menge (IS), wenn der durch V ′ in G induzierte Teilgraph keine Kanten
enthält.
– Vertex Cover (VC), wenn jede Kante e aus E zu einem Knoten aus V ′ inzidiert.

Ist G = (V, E) ein ungerichteter Graph, so heißt Ḡ = (V, Ē) der zu G komplementäre
Graph. Es ist
(u, v) ∈ Ē : ⇐⇒ (u, v) 6∈ E.
Die drei Begriffe aus Definition 13.5 sind eng miteinander verwandt.

256
Lemma 13.6 Sei G = (V, E) ein ungerichteter Graph, und sei V ′ eine Teilmenge der
Knotenmenge V .
Dann gilt. Die Menge V ′ ist genau dann Clique in G, wenn V ′ im komplementären
Graphen Ḡ eine unabhängige Menge ist.

Beweis. Es ist

V ′ ist Clique in G ⇐⇒ ∀u∀v {u, v} ⊆ V ′ , u 6= v ⇒ {u, v} ∈ E

⇐⇒ ∀u∀v {u, v} ⊆ V ′ , u 6= v ⇒ {u, v} 6∈ Ē (Definition von Ē)
⇐⇒ V ′ ist IS in Ḡ.

Lemma 13.7 Sei G = (V, E) ein ungerichteter Graph, und sei V ′ eine Teilmenge der
Knotenmenge V .
Die Menge V ′ ist genau dann eine Clique in G, wenn V \V ′ im komplementären Graphen
Ḡ ein Vertex Cover ist.

Beweis. Es ist

V ′ ist Clique in G ⇐⇒ ∀u∀v {u, v} ⊆ V ′ , u 6= v ⇒ {u, v} ∈ E

⇐⇒ ∀u∀v {u, v} ∈ Ē ⇒ {u, v} ∩ (V \ V ′ ) 6= ∅ (Kontraposition)
⇐⇒ V \ V ′ ist VC von Ḡ.

Problem 10 (Entscheidungsproblem CLIQUE)


Eingabe: ein Graph G = (V, E) und ein Schwellenwert c ∈ N mit c ≤ |V |.

Ausgabe: Akzeptiere genau dann, wenn G eine Clique V ′ mit |V ′ | ≥ c hat.

Problem 11 (Entscheidungsproblem IS)


Eingabe: ein Graph G = (V, E) und ein Schwellenwert c ∈ N mit c ≤ |V |.

Ausgabe: Akzeptiere genau dann, wenn G eine IS V ′ mit |V ′ | ≥ c hat.

Problem 12 (Entscheidungsproblem VC)


Eingabe: ein Graph G = (V, E) und ein Schwellenwert c ∈ N mit c ≤ |V |.

257
Ausgabe: Akzeptiere genau dann, wenn G ein VC V ′ mit |V ′ | ≤ c hat.

Satz 13.8 Das Entscheidungsproblem CLIQUE ist NP-vollständig.

Beweis. Die Zugehörigkeit von CLIQUE zur Klasse NP ist klar.


Wir reduzieren 3SATexakt auf CLIQUE. Sei F = {K1 , K2 , . . . , Kr } eine exakte 3-CF,
wobei für j = 1, 2, . . . , r die Klausel Kj = aj ∨ bj ∨ cj ist.
Wir konstruieren in Polynomialzeit einen Graphen

GF := (VF , EF )

wie folgt:

VF := {(Kj , aj ), (Kj , bj ), (Kj , cj ) | j = 1, 2, . . . , r}


EF ∋ {(K, d), (K ′, d′ )} ⇐⇒ K 6= K ′ und d′ 6= d

Man überlegt sich leicht, daß

– GF nur Cliquen der Mächtigkeit kleiner oder gleich r haben kann;

– jede widerspruchsfreie Auswahlfunktion α zu einen r-Clique {(K, α(K)) | K ∈ F }


führt;

– jede r-Clique als Graph einer widerspruchsfreien Auswahlfunktion abgesehen werden


kann.

Folglich ist die Abbildung

F = {K1 , K2 , . . . , Kr } 7→ (GF , r)

eine Polynomialzeit-Reduktion von 3SATexakt auf CLIQUE. 

Korollar 13.9 Das Entscheidungsproblem IS ist NP-vollständig.

Beweis. Die Zugehörigkeit von IS zur Klasse NP ist klar.


Gemäß Lemma 13.6 ist die Abbildung

(G, c) 7→ (G, c)

eine Polynomialzeit-Reduktion von CLIQUE auf IS. 

Korollar 13.10 Das Entscheidungsproblem VC ist NP-vollständig.

258
Beweis. Die Zugehörigkeit von VC zur Klasse NP ist klar.
Gemäß Lemma 13.7 ist die Abbildung

(G, c) 7→ (G, |V | − c)

eine Polynomialzeit-Reduktion von CLIQUE auf VC. 

Definition 13.11 Sei G = (V, E) ein ungerichteter Graph. Ein Kreis C in G heißt Hamil-
tonsch (HC), wenn er jeden Knoten aus V genau einmal enthält.

Man überlegt sich leicht Beispiele für Graphen, die einen HC haben, und für Graphen,
die keinen HC haben.

Problem 13 (Entscheidungsproblem HC)


Eingabe: ein Graph G = (V, E).

Ausgabe: Akzeptiere genau dann, wenn G einen HC hat.

Satz 13.12 Das Entscheidungsproblem HC ist NP-vollständig.

Beweis. Siehe [CLRS01]. 

Die NP-Vollständigkeit des folgenden Rundreiseproblems ist der Grund dafür, warum
Transportoptimierung schwierig ist.

Problem 14 (Entscheidungsproblem TSP)


Eingabe: eine Abbildung

γ : {1, 2, . . . , n}2 → N

mit

γ(i, i) = 0 (i = 1, 2, . . . , n)

und eine Schwelle c ∈ N.

Ausgabe: Akzeptiere, falls es eine Permutation π der Menge der Städte“ {1, 2, . . . , n}

gibt, die für eine Rundreise steht, für deren Kosten gilt:
n−1
X
γ(π) := γ(π(n), π(1)) + γ(π(i), π(i + 1)) ≤ c.
i=1

259
Satz 13.13 Das Rundreiseproblem TSP ist NP-vollständig.

Beweis. Offenbar ist TSP in NP.


Für jeden Graphen mit n Knoten kann man o.B.d.A. annehmen, daß seine Knotenmenge
gleich {1, 2, . . . , n} ist.
Man sieht sofort ein, daß die Abbildung

G = ({1, 2, . . . , n}, E) 7→ (γG , 0)

mit

γG : {1, 2, . . . , n}2 → N,

wobei für i 6= j
(
0 falls (i, j) ∈ E;
γG (i, j) :=
1 falls (i, j) 6∈ E

ist, eine Polynomialzeit-Reduktion von HC auf TSP ist. 

260
Literaturverzeichnis

[CLRS01] T. H. Cormen, C. E. Leiserson, R. L. Rivest, and C. Stein. Introduction to


Algorithms. MIT Press, 2001.

[Pap94] C. H. Papadimitriou. Computational Complexity. Addison–Wesley, 1994.

261