Edelkamp,Algorithmische Probleme

Werbung
Informatik-II Vorlesungs- und U bungsskript
Stefan Edelkamp
Institut fur Informatik
Georges Kohler Allee
79110 Freiburg
eMail: [email protected]
12. Juli 2000
2
Das Vorlesungsskript ist in Anlehnung an die Informatik II - Vorlesung von Prof. Dr.
Ottmann an der Universitat Freiburg im Sommersemester 2000 entstanden. Es bietet eine
sinnvolle Erganzung zum Buch Algorithmen und Datenstrukturen (Ottmann/Widmayer,
erschienen im Spektrum Verlag) insbesondere in der Bereitstellung von Java-Programmen,
Aufgaben und deren Musterlosungen.
Ich wunsche allen algorithmisch interessierten Lesern viel Freude an dem Skriptum
und bin uber Kommentare und Verbesserungsvorschlage stets dankbar.
Freiburg, 12. Juli 2000
Stefan Edelkamp
Inhaltsverzeichnis
I Vorlesungsfolien
11
1 Leistungsanalyse
13
1.1 Leistungsverhalten von Algorithmen . . . . . . .
1.1.1 Fallunterscheidung . . . . . . . . . . . .
1.1.2 Messung des Leistungsverhaltens . . . .
1.1.3 Kostenmae . . . . . . . . . . . . . . . .
1.1.4 Beispiel . . . . . . . . . . . . . . . . . .
1.2 Funktionenklassen . . . . . . . . . . . . . . . . .
1.2.1 Landau'sche O-Notation . . . . . . . . .
1.2.2 Bemerkungen . . . . . . . . . . . . . . .
1.2.3 Visualisierung . . . . . . . . . . . . . . .
1.2.4 Aufgaben . . . . . . . . . . . . . . . . .
1.2.5 Losungen . . . . . . . . . . . . . . . . .
1.2.6 Weitere Eigenschaften . . . . . . . . . .
1.2.7 O-Kalkul . . . . . . . . . . . . . . . . . .
1.2.8 Hierarchie von Groenordnungen . . . .
1.2.9 Skalierbarkeiten . . . . . . . . . . . . . .
1.2.10 Exponentielle Algorithmen . . . . . . . .
1.2.11 Maximaler Zeitaufwand cost(A) in Java
1.3 Beispiele . . . . . . . . . . . . . . . . . . . . . .
1.3.1 Das Maxsummenproblem . . . . . . . . .
1.3.2 Der normale Algorithmus . . . . . . . .
1.3.3 Divide-And-Conquer . . . . . . . . . . .
1.3.4 Der clevere Algorithmus . . . . . . . . .
1.3.5 Zusammestellung der Ergebnisse . . . . .
2 Elementare Datenstrukturen
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
14
14
14
15
15
16
16
16
17
17
18
18
18
19
19
20
21
22
22
22
23
23
24
25
2.1 U bersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3
INHALTSVERZEICHNIS
4
2.3 Arrays . . . . . . . . . . . . . . . . . . . . . . .
2.4 Listen . . . . . . . . . . . . . . . . . . . . . . .
2.4.1 Alternative mit Zeigern . . . . . . . . . .
2.4.2 Implementationen . . . . . . . . . . . . .
2.4.3 Grundtypen und Knoten . . . . . . . . .
2.4.4 Arraydarstellung . . . . . . . . . . . . .
2.4.5 Dynamisch . . . . . . . . . . . . . . . . .
2.4.6 Verfeinerung . . . . . . . . . . . . . . . .
2.4.7 Visualisierungen . . . . . . . . . . . . . .
2.4.8 Doppelte Verkettung . . . . . . . . . . .
2.5 Stapel/Stack . . . . . . . . . . . . . . . . . . . .
2.5.1 Beispiel: Tower-of-Hanoi Problem . . . .
2.5.2 Rekursive Losung . . . . . . . . . . . . .
2.5.3 Aufrufbaum . . . . . . . . . . . . . . . .
2.5.4 Spezielle Stackimplementierung fur TOH
2.5.5 Testen: Tower-of-Hanoi-Stack . . . . . .
2.6 Schlange/Queue: . . . . . . . . . . . . . . . . .
2.6.1 Operationen . . . . . . . . . . . . . . . .
2.6.2 Anwendung: Topologische Sortierung . .
2.6.3 Verwendete Datenstrukturen . . . . . . .
2.6.4 Beispiel: . . . . . . . . . . . . . . . . . .
2.7 Mengen . . . . . . . . . . . . . . . . . . . . . .
3 Elementare Sortierverfahren
3.1 Das Sortierproblem . . . . . . . . . . .
3.1.1 Allgemeine Sortierverfahren . .
3.1.2 Elementare Sortierverfahren . .
3.2 Grundlegende Implementation . . . . .
3.3 Auswahlsort . . . . . . . . . . . . . . .
3.4 Sortieren durch Einfugen . . . . . . . .
3.5 Shellsort . . . . . . . . . . . . . . . . .
3.5.1 Beispiel zu Shellsort . . . . . .
3.5.2 Implementation: Shellsort . . .
3.6 Mergesort . . . . . . . . . . . . . . . .
3.6.1 Verschmelzen zweier Teilfolgen .
3.6.2 Analyse von Mergesort . . . . .
3.6.3 Reines 2-Wege-Mergesort . . . .
3.7 Quicksort . . . . . . . . . . . . . . . .
3.7.1 Implementation . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
28
29
29
29
30
31
32
32
33
34
36
36
37
38
38
40
41
41
42
43
43
45
47
48
48
49
50
52
53
54
54
54
56
56
57
58
59
59
INHALTSVERZEICHNIS
5
3.7.2 Implementation: Aufteilungsschritt . . . . . . . . . . . . . . . . . . 61
3.7.3 Analyse von Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.7.4 Average Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4 Schnelle Sortierverfahren
4.1
4.2
4.3
4.4
Kriterien fur Sortierverfahren . . . . . . . . .
State-of-the-Art . . . . . . . . . . . . . . . . .
Average Case Analyse Quicksort . . . . . . . .
Clever-Quicksort . . . . . . . . . . . . . . . .
4.4.1 Implementation . . . . . . . . . . . . .
4.5 Heapsort . . . . . . . . . . . . . . . . . . . . .
4.5.1 Veranschaulichung . . . . . . . . . . .
4.5.2 Entfernen des Maximums . . . . . . .
4.5.3 Versickern . . . . . . . . . . . . . . . .
4.5.4 Erstellen eines Heaps . . . . . . . . . .
4.5.5 Iteriertes Versickern . . . . . . . . . . .
4.5.6 Analyse . . . . . . . . . . . . . . . . .
4.5.7 Bottom-up-Heapsort . . . . . . . . . .
4.5.8 Bottom-Up-Heapsort: Implementation
4.5.9 Ergebnisse . . . . . . . . . . . . . . . .
4.6 WEAK-HEAPSORT . . . . . . . . . . . . . .
4.6.1 Verschmelzen . . . . . . . . . . . . . .
4.6.2 Generierung eines Weak-Heaps . . . . .
4.6.3 Sortierungsphase . . . . . . . . . . . .
4.6.4 Arrayeinbettung . . . . . . . . . . . . .
4.6.5 Implementation . . . . . . . . . . . . .
4.6.6 Laufzeitanalyse . . . . . . . . . . . . .
4.7 Quick-Heapsort . . . . . . . . . . . . . . . . .
4.7.1 EXTERNAL-HEAPSORT . . . . . . .
4.7.2 Implementation: QUICK-HEAPSORT
5 Spezielle Sortierverfahren
5.1 Radixsort . . . . . . . . . . . . . . . . .
5.1.1 Radix-exchange-sort . . . . . . .
5.1.2 Radix-exchange-sort . . . . . . .
5.1.3 Analyse von Radix-exchange-sort
5.1.4 Implementation: Decomposable .
5.1.5 Implementation: Ganzzahlen . . .
5.1.6 Implementation: Zeichenketten .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
67
68
69
70
73
73
75
76
76
77
78
80
80
81
82
83
84
84
85
86
86
87
88
89
89
90
91
92
92
92
93
94
94
95
INHALTSVERZEICHNIS
6
5.2
5.3
5.4
5.5
5.1.7 Implementation: Radix-Exchange . . . .
Sortieren durch Fachverteilung . . . . . . . . . .
5.2.1 Analyse . . . . . . . . . . . . . . . . . .
5.2.2 Implementation: Fachverteilung . . . . .
Das Auswahlproblem . . . . . . . . . . . . . . .
5.3.1 Implementation (Rahmen) . . . . . . . .
5.3.2 Divide-&-Conquer Losung . . . . . . . .
5.3.3 Wahl des Pivotelements . . . . . . . . .
5.3.4 Median-of-Median . . . . . . . . . . . . .
Sortieren vorsortierter Daten . . . . . . . . . . .
5.4.1 Optimale Nutzung der Vorsortierung . .
Untere Schranke fur allgemeine Sortierverfahren
5.5.1 Entscheidungsbaume . . . . . . . . . . .
6 Suchverfahren und Suchbaume
6.1 Problemstellung . . . . . . . . . . . .
6.2 Binare Suche . . . . . . . . . . . . .
6.2.1 Binare Suche ohne Rekursion
6.2.2 Analyse . . . . . . . . . . . .
6.3 Fibonacci-Suche . . . . . . . . . . . .
6.3.1 Implementation . . . . . . . .
6.4 Exponentielle Suche . . . . . . . . . .
6.5 Interpolationssuche . . . . . . . . . .
6.6 Selbstanordnende lineare Listen . . .
6.6.1 Gute von Move-To-Front . . .
6.6.2 Amortisierung . . . . . . . . .
6.6.3 Exkurs Binarzahler . . . . . .
6.6.4 Move-To-Front Analyse . . . .
6.6.5 Schlufolgerung . . . . . . . .
6.7 Suchbaume . . . . . . . . . . . . . .
6.7.1 Suchbaume . . . . . . . . . .
6.7.2 Beispiel . . . . . . . . . . . .
6.7.3 Einfugen/Suchen . . . . . . .
6.7.4 Sonderfalle . . . . . . . . . . .
6.7.5 Loschen . . . . . . . . . . . .
6.7.6 Durchlaufreihenfolgen . . . .
6.7.7 Implementation . . . . . . . .
6.7.8 Interne Pfadlange . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
95
97
97
98
100
100
101
102
102
103
104
105
105
107
108
109
109
110
112
112
114
115
116
116
116
117
118
119
120
120
121
122
123
124
125
125
126
INHALTSVERZEICHNIS
7
7 Balancierte Baume
129
7.1 Durchlaufreihenfolgen . . . . . . . . . . . . . .
7.1.1 Eigenschaften . . . . . . . . . . . . . .
7.1.2 Sortieren mit naturlichen Suchbaumen
7.1.3 Interne Pfadlange . . . . . . . . . . . .
7.1.4 Implementation . . . . . . . . . . . . .
7.2 AVL-Baume . . . . . . . . . . . . . . . . . . .
7.2.1 Einfugen . . . . . . . . . . . . . . . . .
7.2.2 Entfernen . . . . . . . . . . . . . . . .
7.2.3 Implementation . . . . . . . . . . . . .
7.2.4 Rotationen . . . . . . . . . . . . . . .
7.2.5 Einfugen . . . . . . . . . . . . . . . . .
7.2.6 Loschen . . . . . . . . . . . . . . . . .
7.3 Bayer-Baume . . . . . . . . . . . . . . . . . .
7.3.1 Tiefe . . . . . . . . . . . . . . . . . . .
7.3.2 Einfugen . . . . . . . . . . . . . . . . .
7.3.3 Loschen . . . . . . . . . . . . . . . . .
7.3.4 Anhangen (2-3 Baum) . . . . . . . . .
7.3.5 Aufteilen (2-3 Baum) . . . . . . . . . .
7.4 Bruder-Baume . . . . . . . . . . . . . . . . . .
7.4.1 Hohe . . . . . . . . . . . . . . . . . . .
7.4.2 Eigenschaften . . . . . . . . . . . . . .
7.4.3 Implementation Bruder-Baume . . . .
7.4.4 Suche und Einfugen in Bruder-Baumen
7.4.5 Falle in Up . . . . . . . . . . . . . . .
7.4.6 Weitere Falle in Up . . . . . . . . . . .
7.5 Rot-Schwarz-Baume . . . . . . . . . . . . . .
8 Hashverfahren
8.1 Geschlossene Hashverfahren . . .
8.1.1 Hashverfahren . . . . . . .
8.1.2 Hashverfahren|Beispiele .
8.1.3 Probleme . . . . . . . . .
8.1.4 Hashfunktion . . . . . . .
8.1.5 Hashverfahren . . . . . . .
8.1.6 Implementation in Java .
8.1.7 Wahl der Hash-Funktion .
8.1.8 Multiplikative Methode . .
8.1.9 Universelles Hashing . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
130
130
130
131
131
133
133
133
134
135
136
137
138
138
138
139
139
140
141
141
143
144
144
145
146
148
149
150
150
150
151
152
152
153
153
154
155
INHALTSVERZEICHNIS
8
8.1.10 Beispiel . . . . . . . . . . . . . . . . .
8.1.11 Universelles Hashing . . . . . . . . . .
8.1.12 Hashing mit Verkettung der U berlaufer
8.1.13 Verkettung der U berlaufer . . . . . . .
8.1.14 Implementation in Java . . . . . . . .
8.1.15 Implementation: Einfugen . . . . . . .
8.1.16 Implementation: Loschen . . . . . . . .
8.1.17 Test-Programm . . . . . . . . . . . . .
8.1.18 Analyse der direkten Verkettung . . . .
8.2 Oene Hashverfahren . . . . . . . . . . . . . .
8.2.1 Situation im Oenen Hashing . . . . .
8.2.2 Sondierungsfolgen . . . . . . . . . . . .
8.2.3 Kriterien . . . . . . . . . . . . . . . . .
8.2.4 Implementation: Oene Hashtabelle . .
8.2.5 Suchen . . . . . . . . . . . . . . . . . .
8.2.6 Einfugen . . . . . . . . . . . . . . . . .
8.2.7 Test-Programm . . . . . . . . . . . . .
8.2.8 Lineares Sondieren . . . . . . . . . . .
8.2.9 Quadratisches Sondieren . . . . . . . .
8.2.10 Uniformes Sondieren . . . . . . . . . .
8.2.11 Analyse ideales Hashing . . . . . . . .
8.2.12 Double Hashing . . . . . . . . . . . . .
8.2.13 Beispiel . . . . . . . . . . . . . . . . .
8.2.14 Verbesserung der erfolgreichen Suche .
8.2.15 Verbesserung der erfolgreichen Suche .
8.2.16 Brent und Binarbaum Sondieren . . . .
8.2.17 Beispiel . . . . . . . . . . . . . . . . .
8.2.18 Verbesserung der erfolglosen Suche . .
8.2.19 Beispiel: Ordered double Hashing . . .
8.2.20 Implementation: Suchen . . . . . . . .
8.2.21 Implementation Einfugen . . . . . . . .
9 Graphalgorithmen
9.1 Graphen . . . . . . . . . . . . . . . . . .
9.1.1 Implementation . . . . . . . . . .
9.1.2 Implementation Adjazenzmatrix .
9.1.3 Implementation Adjazenzliste . .
9.1.4 Tiefensuche . . . . . . . . . . . .
9.1.5 Topologische Sortierung . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
155
156
156
157
158
158
159
159
160
162
162
162
163
163
164
165
166
167
167
168
168
170
170
171
172
172
173
174
174
175
175
177
178
178
179
180
181
181
INHALTSVERZEICHNIS
9
II Aufgaben
183
10 Theorie
11 Praxis
185
229
10
INHALTSVERZEICHNIS
Teil I
Vorlesungsfolien
11
Kapitel 1
Leistungsanalyse
13
KAPITEL 1. LEISTUNGSANALYSE
14
1.1 Leistungsverhalten von Algorithmen
Ezienzanalyse:
Speicherplatzkomplexitat: Wird primarer, sekundarer Speicherplatz ezient genutzt?
Laufzeitkomplexitat: Steht die Laufzeit im akzeptablen/vernunftigen/optimalen Verhaltnis zur Aufgabe?
Theorie: liefert untere Schranke, die fur jeden Algorithmus gilt, der das Problem lost.
Spezieller Algorithmus liefert obere Schranke fur die Losung des Problems.
Erforschung von oberen und unteren Schranken: Eziente Algorithmen und Komplexitatstheorie (Zweige der Theoretischen Informatik)
1.1.1 Fallunterscheidung
Sei x Eingabe, jxj Lange von x, P das betrachtete Problem und TP (x) die Laufzeit von
P auf x.
Der beste Fall (best case): Oft leicht zu bestimmen, kommt in der Praxis jedoch selten
vor.
Der schlechteste Fall (worst case): Gibt Sicherheit, meist leicht zu bestimmen. Fur
viele Algorithmen Laufzeit auf Eingaben bestimmter Lange gleich gro.
TP (n) = supfTP (x)j jxj = n; x Eingabe fur P g
Im amortisierten worst case wird der durchschnittliche Aufwand fur eine schlechtestmogliche Folge von Operationen gemessen (technisch anspruchsvoll).
Der mittlere Fall (average case): Nicht leicht zu handhaben, in der Praxis jedoch
relevant. Denition
(qn Wahrscheinlichkeitsverteilung):
TP;q (n) = Pjxj n;x Eingabe fur P TP (x)qn(x)
Woruber wird gemittelt? Trit (uniforme) Wahrscheinlichkeitsverteilung zu?
n
=
1.1.2 Messung des Leistungsverhaltens
1. Betrachte konkrete Implementierung auf konkreter Hardware. Mi Laufzeit fur reprasentative Eingaben.
2. Berechne Verbrauch an Platz und Zeit fur idealisierte Referenzmachine:
a) Random Access Machine (RAM)
b) Registermachine (RM)
c) Turingmachine (TM)
1.1. LEISTUNGSVERHALTEN VON ALGORITHMEN
15
3. Bestimme Anzahlen gewisser (teurer) Grundoperationen, z.B.
# Vergleiche bzw. # Bewegung von Daten (beim Sortieren)
# Multiplikationen / Divisionen (fur numerische Verfahren)
Fur 2. und 3.: Beschreibe Aufwand eines Algorithmus als Funktion der Groe des Inputs
(kann verschieden gemessen werden).
1.1.3 Kostenmae
Einheitskostenma: Annahme, jedes Datenelement belegt nur beschrankten Speicher-
platz.
Damit: Groe des Input bestimmt durch Anzahl der Datenelemente
Beispiel: Sortierproblem
Logarithmisches Kostenma: Annahme, ein einzelnes Datenelement kann beliebig
gro werden, die Groe beeinut die Komplexitat.
Groe der Eingabe bestimmt durch die Summe der Groen der Elemente.
(fur n > 0 ist die # Bits zur Darstellung von n = dlog (n + 1)e)
Beispiel: Primzahlzerlegung (je groer die Zahl desto schwieriger)
2
1.1.4 Beispiel
Taktzahl eines Von-Neuman Addierwerks bei der Addition von 1 zu einer Zahl i
der Lange n, d.h. 0 i 2n . Sie entspricht 1 plus der Anzahl der Einsen am Ende der
1
Binardarstellung von i.
Die worst case Rechenzeit betragt n + 1 Takte.
Wir nehmen eine Gleichverteilung auf der Eingabenmenge an. Es gibt 2n k Eingaben, die
mit (0; 1; : : : ; 1) enden, wobei am Ende k 1 Einsen stehen. Hinzu kommt die Eingabe
i = 2n 1 fur die das Addierwerk n + 1 Takte benotigt.
Die average
case Rechenzeit Ta (n) betragt also:
P
n
2 ( Pkn 2n k k + (n + 1))
Es ist kn 2n k k = (Tafel) = 2n
2 n.
n
n
Demnach ist Ta(n) = 2 (2
2 n + (n + 1)) = 2 2 n.
Es genugen also im Durchschnitt 2 Takte um ein Inkrement von 1 durchzufuhren.
1
1
+1
+1
16
1.2 Funktionenklassen
KAPITEL 1. LEISTUNGSANALYSE
Gro-O-Notation: O-, - und - Notation beschreiben obere, untere bzw. genaue Schranken.
Idee: Beim Aufwand kommt es auf Summanden und konstante Faktoren letztendlich
nicht an.
Grunde:
- Interesse an asymptotischen Verhalten fur groe Eingaben,
- Genaue Analyse oft technisch sehr aufwendig/unmoglich
- Lineare Beschleunigungen sind leicht moglich (Tausch von Hard- und Software)
Ziel: Einteilung der Komplexitatsmessungen nach Funktionenklassen, z.B. O(f ) Menge
von Funktionen, die in der Groenordnung von f sind.
1.2.1 Landau'sche O-Notation
f = O(g) (in Worten: f wachst nicht schneller als g), wenn 9c 2 ; n 2 ; 8n n :
f (n) cg(n).
f = (g) (f wachst mindestens so schnell wie g), wenn g = O(f ) ist.
f = (g) (f und g sind von der gleichen Wachstumsordnung), wenn f = O(g) und
g = O(f ) gelten.
f = o(g) (f wachst langsamer als g), wenn f (n)=g(n) eine Nullfolge ist.
f = !(g) (f wachst schneller als g), wenn g = o(f ) ist.
+
IR
0
IN
0
In Ottmann/Widmayer
O(g) = ff j 9a > 0; b > 0; 8nf (n) ag(n) + bg
und
(g) = fgj 9c > 0; 9 1 viele n : f (n) cg(n)g
1.2.2 Bemerkungen
Schreibweise: f = O(g) statt f 2 O(g)
Gleichungsfolgen: In der O-Notation mussen stets von links nach rechts gelesen werden.
So ist
10n + n = O(n ) = o(n )
sinnvoll, wahrend
10n + n = o(n ) = O(n )
ebenso sinnlos ist wie
10n + n = O(n ) = (n )
Minimalitat: Die angegebene Groenordnung mu nicht minimal gewahlt sein (s.o.)
Asymptotik: Wie gro n sein mu bleibt unklar
2
2
2
2
2
2
3
3
2
1.2. FUNKTIONENKLASSEN
17
Konstanten: Die Konstanten c und n (bzw. a und b) haben fur kleine n groen Einu.
0
1.2.3 Visualisierung
f (n) 2 O(g(n))
, 9n 2 ; c 2 8n n : f (n) cg(n)
( nlim
!1 f (n)=g (n) = c
0
IN
+
IR
0
c=0.5
f(n)= n
g(n)=n
n0
Die Regel von L'Hospital
nlim
!1 f (n)=g (n) = c ( nlim
!1 f
0 (n)=g 0(n) = c
1.2.4 Aufgaben
Aufgabe 1: Zeigen Sie durch die Wahl geeigneter Konstanten c und n , da (n + 1) in
0
2
der Klasse O(n ) liegt.
Aufgabe 2: Es gilt die folgende Beziehung: Falls der Grenzwert von f (n)=g(n) fur n gegen
unendlich durch eine Konstante c beschrankt ist (wir schreiben limn!1 f (n)=g(n) c), so
gilt f (n) 2 O(g(n)). Zeigen Sie unter Verwendung dieses Sachverhaltes abermals (n+1) 2
O(n ).
2
2
2
KAPITEL 1. LEISTUNGSANALYSE
18
Aufgabe 3: Die Regel von L'Hospital besagt: Falls der Grenzwert f 0(n)=g0(n) fur n
gegen unendlich existiert (f 0 ist die Ableitung von f ), so ist er gleich dem Grenzwert von
f (n)=g(n). Zeigen Sie mit Hilfe dieser Regel letztmalig (n + 1) 2 O(n ).
Aufgabe 4: Sei P (n) ein Polynom vom Grad k. Zeigen Sie: O(log P (n)) = O(log n)
2
2
1.2.5 Losungen
Aufgabe 1: Zu zeigen ist (n +1) p cn oder (pc 1)n 2n 1 0. Wir wahlen c = 2. Die
Nullstellen von n 2np 1 sind 2 + 1 und 2 + 1. Die Parabel ist nach oben geonet.
Also ist fur n n = d 2 + 1e der Wert (n + 1) 2n und demnach (n + 1) 2 O(n ).
Aufgabe 2: limn!1 n n n = limn!1 n =nn =n = limn!1(1 + 2=n + 1=n ) = 1:
Aufgabe 3:
2
2
2
2
2
0
2 (1+2
2 +2 +1
2
2
+1
2
2
2)
2
2
Da limn!1 2=2 = 1 gilt, ist limn!1 nn = 1 und somit limn!1 n2 n2n = 1:
Aufgabe 4: P (n) = O(nk ) und damit O(log P (n)) = O(log nk ) = O(k log n) = O(log n).
2 +2
2
+2 +1
1.2.6 Weitere Eigenschaften
1. Fur monoton steigende Funktionen f 6 0 sind beide Denitionen fur Gro-O aquivalent.
2. Es gibt Funktionen in denen beide Denitionen voneinander abweichen (erste Denition ist scharfer).
3. Fur alle k ist nk 2 o(2n)
4. Es seien p und p Polynome vom Grad d bzw. d , wobei die Koezienten von nd1
und nd2 positiv sind. Dann gilt:
1
2
1
2
a) p 2 (p ) , d = d
1
2
1
b) p 2 o(p ) , d < d
1
2
1
2
2
b) p 2 !(p ) , d > d
1
2
1
2
5. Fur alle k > 0 und > 0 gilt logk n = o(n)
6. 2n= = o(2n)
Beweis: U bungsaufgabe. Gleichzeitig ein Beleg, da die Analysis-Vorlesung Anwendung
hat.
2
1.2.7 O-Kalkul
f = O(f )
O(O(f )) = O(f )
1.2. FUNKTIONENKLASSEN
19
kO(f ) = O(f ) fur konstantes k
O(f + k) = O(f ) fur konstantes k
Additionsregel: O(f ) + O(g) = O(maxff; gg) Multiplikationsregel: O(f ) O(g) =
O(f g).
Beweis: Wissen 9c ; c 2 und n ; n 2 mit T (n) c f (n) fur alle n n und
T (n) c g(n) fur alle n n .
1
2
+
IR
2
2
1
2
IN
1
1
1
2
Additionsregel: T (n)+ T (n) c maxff (n); g(n)g fur c = c + c und n maxfn ; n g.
Multiplikationsregel: T (n)T (n) cf (n)g(n) fur c = c c und n maxfn ; n g.
Additionsregel fur die Komplexitat der Hintereinanderausfuhrung der Programme. Multiplikationsregel fur die Komplexitat von ineinandergeschachtelten Schleifen.
1
2
1
1
2
1 2
2
0
0
1
1
2
2
1.2.8 Hierarchie von Groenordnungen
O(1): konstante Funktionen
O(log n): Logarithmische Funktionen
O(log n): Quadratisch logarithmische Funktionen
O(n): Lineare Funktionen
O(n log n): - keine spezielle Bezeichnung O(n ): quadratische Funktionen
O(n ): kubische Funktionen
O(nk ): polynomielle Funktionen
genauer: f heit polynomiell beschrankt, wenn es ein Polynom p mit f = O(p) gibt.
O(2n): exponentielle Funktionen
genauer: f wachst exponentiell, wenn es ein > 0 mit f = (2n ) gibt.
2
2
3
1.2.9 Skalierbarkeiten
Annahme: Rechenschritt 0.001 Sekunden. Maximale Eingabelange bei gegebener Re-
chenzeit.
Laufzeit T(n) 1 Sekunde 1 Minute 1 Stunde
n
1000
60000 3600000
n log n
140
4895 204094
n
31
244
1897
n
10
39
153
2n
9
15
21
Maximale Eingabelange in Abhangigkeit der Technologie (log 10p = log p +log 10 log p,
3:16 10; 2:15 10; 2 : 10)
2
3
2
3
33
KAPITEL 1. LEISTUNGSANALYSE
20
Laufzeit T(n) alt neu (d.h. 10-mal schneller)
n
p
10p
n log n
p
(fast 10)p
n
p
3:16p
n
p
2:15p
n
2
p
p + 3:3
100n < dn =125e ab n > 111
10n < :5n log n ab n > 2
2
3
3
20
1.2.10 Exponentielle Algorithmen
Seerosenbeispiel:
Schachbrett (Reis), Zinseszins (Manhattan), Borse (Kybernetik), Zellteilung
(Qualitativ-Quantitativ), Radioaktiver Zerfall (Exponentiell langsam)
In der Vorlesung Informatik III wird die Grauzone zwischen den garantiert polynomiellen
und garantiert exponentiellen Laufzeiten analysiert.
EXPTIME
PSPACE
NP
P
NC
1.2. FUNKTIONENKLASSEN
1.2.11 Maximaler Zeitaufwand cost(A) in Java
1. A ist einfache Zuweisung (read, write, . . . ): cost(A) = const 2 O(1)
2. A ist Folge von Anweisung: Additionsregel anwenden
3. A ist if-Anweisung:
a) if (cond) B; cost(A) = cost(cond) + cost(B )
b) if (cond) B; else C cost(A) = cost(cond) + maxfcost(B ); cost(C )g
4. A ist eineP Schleife:
cost(A) = Umlauf ifcost(Anweisungen i) + cost(Terminierungsbedingung i)g
oft
cost(A) = #Umlaufe fcost(Anweisungen) + cost(Terminierungsbedingung)g
21
KAPITEL 1. LEISTUNGSANALYSE
22
1.3 Beispiele
Problem Bubble-Sort: Sortiere die ganzen Zahlen in dem Array a[1..n]
Algorithmus:
repeat
for i= 1..n-1
if (a[i] > a[i+1])
swap(a[i],a[i+1])
until (keine Vertauschungen mehr notwendig)
Analyse der Anzahl von Vergleichen:
worst case: Innere Schleife: n 1 Vergleiche, maximal n Durchlaufe (Minimum an a[n])
der ausseren Schleife ) O(n2)
best case: Sortiertes Inputarray: Ende nach einem Durchlauf ) O(n)
average case: Fakt: Minimum wandert pro Durchlauf der ausseren Schleife 1 Schritt
nach links.
Annahme: Jede Position fur Min gleichwahrscheinlich. ) mindestens
1O (n)+2O (n)+:::+nO (n)
= O(n2) viele Vergleiche werden ausgefuhrt.
n
1.3.1 Das Maxsummenproblem
Problem: Finde ein Index-Paar (i; j ) in einem Array a[1..n] fur das f (i; j ) = ai +: : :+aj
maximal ist. Als Rechenschritte zahlen arithmetische Operationen und Vergleiche.
Der naive Algorithmus:
Es sollen alle f (i; j ) berechnet werden und dann der maximale f -Wert ermittelt werden.
Oensichtlich genugen zur Berechung von f (i; j ) genau j i Additionen. Der Algoritmus
startet mit max f (1; 1) und aktualisiert max wenn notig.
Es gibt j Paare der Form
(; j )
P
# Vergleiche: V (n) = ( P jn jP) 1 = n(n + 1)=2 1
# Additionen: A (n) = in jn(j i) = (Tafel) = 1=6n 1=6n
# Zusammen: T (n) = V (n) + A (n) = 1=6n + 1=2n + 1=3n 1
1
1
1
1
1
3
1
1
3
1
2
1.3.2 Der normale Algorithmus
Der naive Ansatz berechnet a + a fur f (1; 2); f (1; 3); : : :; f (1; n), also (n 1)-mal.
Besser geht's mit folgender Erkenntnis. Es gilt:
f (i; j + 1) = f (i; j ) + aj
Damit braucht man fur alle f (i; )-Werte genau (n i) Additionen.
1
2
+1
1.3. BEISPIELE
23
# Vergleiche: V (n) = VP(n) = n(n + 1)=2P 1
# Additionen: A (n) = in(n i) = kn k = n(n 1)=2
# Zusammen: T (n) = V (n) + A (n) = n 1.
2
1
2
1
2
2
2
2
1
1
1.3.3 Divide-And-Conquer
Annahme: n = 2k . Unterteilung in drei Klassen:
1 i; j n=2 (Divide-And-Conquer)
1 i n=2 < j n (Direkt)
n=2 < i j n (Divide-And-Conquer)
g(i) = ai + : : : + an= und h(j ) = an= + : : : + aj
Dann: f (i; j ) = g(i) + h(j )
Um f zu maximieren, maximiere g und h einzeln.
Berechne nacheinander g(n=2) = a[n=2]; g(n=2 1); : : : ; g(1) und den max. g Wert in
(n=2 1) Vergleichen und (n=2 1) Additionen. Berechne h analog. Damit insgesamt
n 1 Additionen und n 2 Vergleiche, also 2n 3 Operationen.
Rekursionsgleichung (2 Vergleiche um Maximum der Gruppen zu nden)
T (1) = 0
T (2k ) = 2T (2k ) + 2 2k 1 ) (Tafel)
) T (2k ) = (2k 1)2k + 1 = (2 log n 1)n + 1
2
3
3
3
2+1
1
3
1.3.4 Der clevere Algorithmus
Wir wollen Problem in [1::n] losen und verwalten dabei nach dem Lesen von ak in max
den groten Wert von f (i; j ) aller Paare (i; j ) fur 1 i j k.
Fur k = 1 setzen wir max auf a .
Konkurrenten sind Paare (i; j ) mit k < i und Paare (i; j ) mit i k < j . uber die erste
Menge wissen wir noch nichts, fur die zweite Menge verwalten wir max = max ik fg(i)g
mit g(i) = ai + : : : + ak .
Sei nun ak gelesen.
gneu(i) = galt(i) + ak , fur 1 i k
gneu(i) = ak
, fur i = k + 1
Also maxneu = maxfmaxalt +ak ; ak g
Fur maxneu kommen folgende Paare in Frage:
1 i j k (maximaler Wert maxalt )
1 i k; j = k + 1 (maximaler Wert maxneu)
Bei der Verarbeitung von ak , 2 k n, genugen also 3 Operationen, demnach ist
T (n) = 3n 3:
1
1
+1
+1
+1
+1
4
+1
KAPITEL 1. LEISTUNGSANALYSE
24
1.3.5 Zusammestellung der Ergebnisse
T (n) = 1=6n + 1=2n + 1=3n 1
T (n) = n 1
T (n) = (2 log n 1)n + 1
T (n) = 3n 3
n
T (n) T (n) T (n) T (n)
2
19
15
13
9
2
815
255
113
45
2
45759
4095
705 189
2
2829055 65535 3841 765
2 179481599 1048575 19457 3069
2
> 5 10
10 950273 98301
Die Rechenzeiten richten sich nach den Groenordnungen (n ; n ; n log n; n) und nicht
nach den Vorfaktoren.
Es sollte sich das beruhigende Gefuhl breitmachen, da es sich lohnt clever zu sein.
Ottmann/Widmayer: Berechnungen in O-Notationen
3
1
2
2
2
3
4
1
2
3
4
2
4
6
8
10
15
12
9
3
2
Kapitel 2
Elementare Datenstrukturen
25
26
2.1 U bersicht
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
Motivation: Wozu sind Datenstrukturen nutzlich?
Arrays mit Adressierung
Listen mit
- 4 verschiedenen Implementierungen
Stapel/Schlangen
- Anwendung Stapel: Tower of Hanoi
- Anwendung Schlange: Topologische Sortierung
Mengen dargestellt als
- Bitvektoren
2.2. MOTIVATION
27
2.2 Motivation
Algorithmus: Endliche Sequenz von Instruktionen, die angeben, wie ein Problem gelost
werden soll. Genauer: Berechenbare Prozedur zusammen mit einer Menge von moglichen
Eingaben, die einen oder eine Menge von Werten als zulassige Ausgabe liefert.
Datenstrukturen (DS): Dienen zur Organisation von Daten, um bestimmte Operationen (ezient) zu unterstutzen.
Hier und heute: Werden DS isoliert betrachtet, in der Praxis sind sie wichtige Bestandteile von Algorithmen.
Verbindung: Datenstrukturen lassen sich selbstverstandlich ineinander schachteln.
Eignung: Demnach ist es stets nutzlich, sich Anwendungs- und Nichtanwendungssituationen zu uberlegen
Unabhangigkeit: Darstellung im Grunde unabhangig von der Programmiersprache
Konkretisierung: Imperative Programmiersprachen, speziell: Programmiersprache Java
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
28
2.3 Arrays
1-dimensional: Array a fur den Bereich [1::n] sieht n Positionen fur Daten des gewahlten
Typs vor.
Darstellung im Rechner: Array liegt fur ein n an Position N + 1; : : : N + n. Mit a[i]
sprechen wir das Datum an Position N + i an.
Wesentlich: Arrays erlauben den Zugri auf Komponenten uber einen (berechenbaren)
Index
Zugrie: O(1). Keine Unterstutzung von Operationen vom Typ Nimm a[i] und fuge es
zwischen a[j ] und a[j + 1] ein.
2-dimensional: Array [1::n ] [1::n ]. Position (i; j ) bei Anfangsadresse N ndet sich
an
N + (i 1)n + j
k-dimensional: Array [1::n ] : : : [1::nk ] Element a[i ] : : : [ik ] ndet sich an Position
1
2
2
1
1
8
9
< X
Y =
N +:
(ij 1)
nl ; + ik
j k
j<lk
1
1
2.4. LISTEN
29
2.4 Listen
Denition: Sei L = (a ; a ; : : : ; an) geordnete Folge von Objekten eines bestimmten
1
2
Typs, dann
a Anfangsobjekt (Kopf), an Endobjekt (Schwanz),
ai Nachfolger von ai, ai Vorganger von ai .
Nachfolger von an und Vorganger von a ist null.
Falls n = 0 heit die Liste leer.
1
+1
+1
1
Operationen:
(x; L): Fuge das Objekt x in L ein. Ergebnis ist eine Liste.
find(x; L): Falls x 2 L ist Antwort true, sonst false
delete(x; L): Entferne x aus L. Falls x 62 L Fehlerbehandlung. Ergebnis ist eine Liste.
Weiterhin: pred(x; L), succ(x; L), null(L), first(L), last(L), length(L),
union(L ; L ; L), copy(L; L0 ), split(x; L; L ; L )
insert
1
2
1
2
2.4.1 Alternative mit Zeigern
Zeiger (Handle): p zum Zugri, z.B. vom Typ Object
Operationen:
(x; p; L): Fuge das Objekt x an Stelle p in L ein. Ergebnis ist eine Liste.
delete(p; L): Entferne Element an Stelle p aus L. Ergebnis ist eine Liste.
search(x; L): Liefert als Zeiger die erste Position, an der x in L vorkommt, und null,
falls x in L nicht vorkommt
Weiterhin: pred(p; L), succ(p; L), null(L), first(L), last(L), length(L),
union(L ; L ; L), copy(L; L0 ), split(p; L; L ; L )
insert
1
2
1
2
2.4.2 Implementationen
Version 1: Als Array. Zeiger p vom Typ int (in search: p = 0 falls nicht vorhanden).
Version 2: Einfach verkettet, Listenende null
L
-
a
1
-
a
2
-
:::
-
an
r
Version 3: Einfach verkettet, Listenanfang und -ende dummy Elementzeiger ist uber
next zu erreichen
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
30
-
a
-
1
a
-
2
:::
-
an
?
-
6
6
tail
head
Version 4: Doppelt verkettet, Listenanfang und Listenende null
r
-
a
1
a
2
:::
6
an
r
6
head
tail
2.4.3 Grundtypen und Knoten
Der Grundtyp beinhaltet Schlussel bzw. Datensatz
public class Grundtyp {
int key; // eventuell weitere Komponenten
Grundtyp()
{ this.key = 0; }
Grundtyp(int key) { this.key = key; }
public boolean equals(Grundtyp dat) {
return this.key == dat.key;
}
public String toString() {
StringBuffer st = new StringBuffer(""+key);
return st.toString();
}
}
Knoten dient zur Speicherung des Dateninhaltes und der (einfachen) Verkettung von
Listenelementen. Wird ausschliesslich in der zugehoerigen Listenklasse genutzt.
class Knoten {
Grundtyp dat;
Knoten next;
// Grundtyp
// Zeiger auf Nachfolgerknoten
Knoten(Grundtyp dat, Knoten next) {
2.4. LISTEN
this.dat = dat; this.next = next;
}
}
2.4.4 Arraydarstellung
Liste reprasentiert als Array:
public class Liste {
Grundtyp[] L;
int elzahl;
Liste(int maxelzahl) {
L = new Grundtyp[maxelzahl+1]; elzahl = 0;
}
public int search(Grundtyp x) {
L[0] = x;
int pos = elzahl;
while (!L[pos].equals(x)) pos--;
return pos;
}
public void insert(Grundtyp x, int p) {
for (int pos=elzahl; pos >= p; pos--)
L[pos+1] = L[pos];
L[p] = x; elzahl++;
}
public void delete(int p) {
elzahl--;
for(int pos=p;pos<=elzahl;pos++)
L[pos] = L[pos+1];
}
public String toString () {
StringBuffer st = new StringBuffer("");
for (int pos = 1; pos <= elzahl; pos++)
st.append(pos == elzahl ?
L[pos].toString():
L[pos].toString()+", ");
return st.toString();
}
}
31
32
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
2.4.5 Dynamisch
Liste implementiert als einfach verkettete Struktur:
public class Liste {
Knoten head;
// Kopfelement
Liste() { head = null; } // leere Liste
public void insert (Grundtyp x) {
head = new Knoten(x,head);
}
public boolean find(Grundtyp x) {
for (Knoten i = head;i != null;i = i.next)
if (i.dat.equals(x)) return true;
return false;
}
public void delete(Grundtyp x) throws Exception {
if (head.dat.equals(x)) head = head.next;
for (Knoten i = head;i.next != null;i = i.next)
if (i.next.dat.equals(x)) {
i.next = i.next.next; return
}
throw new Exception("x kommt nicht vor");
}
public String toString() {
StringBuffer st = new StringBuffer("");
for (Knoten i = head;i != null;i = i.next)
st.append(i.next == null
? i.dat.toString()
: i.dat.toString()+", ");
return st.toString();
}
}
2.4.6 Verfeinerung
. . . nach (Ottmann / Widmayer):
public class Liste {
2.4. LISTEN
33
Knoten head, tail;
Liste() {
Grundtyp dummy = new Grundtyp();
head = new Knoten(dummy,null);
tail = new Knoten(dummy,head);
head.next = tail;
}
public void insert (Grundtyp x, Knoten p) {
Knoten hilf;
if (p == tail) hilf = tail; else hilf = p.next;
p.next = new Knoten(p.dat,hilf);
p.dat = x;
if (p == tail)
tail = tail.next;
if (hilf == tail) tail.next = p.next;
}
public void delete (Grundtyp x) throws Exception {
tail.dat = x;
Knoten pos = head;
while(!pos.next.dat.equals(x)) pos = pos.next;
if (pos.next != tail) pos.next = pos.next.next;
else throw new Exception("x kommt nicht vor");
if (pos.next == tail) tail.next = pos;
}
public Knoten search(Grundtyp x) {
tail.dat = x;
Knoten pos = head;
do pos = pos.next; while (!pos.dat.equals(x));
if (pos == tail) return null;
return pos;
}
...
}
2.4.7 Visualisierungen
Einfache Verkettung
Vor dem Einfugen
6
head
a
1
-
:::
-
ap
1
-
ap
-
:::
-
an
?
-
6
tail
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
34
Nach dem Einfugen
-
a
-
1
:::
-
ap
1
-
x
-
ap
-
:::
6
-
an
?
-
6
head
tail
Doppelte Verkettung
Loschen in doppelt verketteter Liste:
:::
a
ap
p
::: 1
6
p
2.4.8 Doppelte Verkettung
public class Knoten {
Grundtyp dat;
// Grundtyp
Knoten next;
// Zeiger auf Nachfolgerknoten
Knoten prev;
// Zeiger auf Vorgaengerknoten
...
}
public class Liste {
Knoten head, tail;
Liste() { head = null; tail = null; }
public void insert (Grundtyp x) {
if (head == null)
head = tail = new Knoten(x,null,null);
else insert(x,head);
}
public void insert (Grundtyp x, Knoten p) {
Knoten hilf = new Knoten(x,p.next,p);
p.next = hilf;
if (hilf.next == null) tail = hilf;
else hilf.next.prev = hilf;
}
public void delete (Grundtyp x, Knoten p) {
if (p == head) head = p.next;
-
ap
+1
:::
:::
2.4. LISTEN
p.prev.next = p.next;
if (p == tail) tail = p.prev;
p.next.prev = p.prev;
}
public Knoten search(Grundtyp x) {
for (Knoten i = head;i != null;i = i.next)
if (i.dat.equals(x)) return i;
return null;
}
}
35
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
36
2.5 Stapel/Stack
LIFO Speicher
Operationen empty, push, pop (, peek)
maxelzahl
top
rr
rr
2
1
0
9
>
=
>
;
9
>
>
>
=
>
>
>
;
frei
Stapel
Implementation: In Java in der Klassenbibliothek vorhanden: java.util.Stack
public class Stack extends Vector {
// Default Constructor: public Stack()
// Public Instance methods
public boolean empty();
public Object peek() throws EmptyStackException;
public Object pop() throws EmptyStackException;
public Object push(Object item);
public int search(Object o)
}
}
2.5.1 Beispiel: Tower-of-Hanoi Problem
Problem: n Scheiben: 1; 2; 3; : : : ; n,
3 Pfeiler: A,B,C (1,2,3).
Invarianz: Immer kleinere Scheibe auf der Groeren.
2.5. STAPEL/STACK
37
A
B
C
TOH: Live-Vorfuhrung
2.5.2 Rekursive Losung
class Pfeiler {
String name;
Pfeiler(String n) { name=n;}
public String toString(){ return name; }
}
public class ToHP {
static long count = 0;
static Pfeiler A, B, C;
public static void main (String args[]){
int n = Integer.parseInt (args[3]);
A = new Pfeiler (args[0]);
B = new Pfeiler (args[1]);
C = new Pfeiler (args[2]);
umschichten (n, A, B, C);
System.out.println(count+" Scheibenbewegungen.");
}
public static void umschichten
(int n, Pfeiler von, Pfeiler nach, Pfeiler mit) {
if (n > 0) {
umschichten(n-1,von, mit, nach);
System.out.println(von+" --> "+nach);
count++;
umschichten(n-1,mit, nach, von);
}
}
}
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
38
2.5.3 Aufrufbaum
Tupel der Form: (n; von; nach; mit) :
4,1,2,3
3,3,2,1
3,1,3,2
2,2,3,1
2,1,2,3
1,1,3,2
1,3,2,1
1,2,1,3
1,1,3,2
Stackreihenfolge:
| |
| |
| |
| |
+--+
| |
| |
| |
| |
+--+
| |
| |
| |
| |
+--+
| |
| |
| |
| |
+--+
Problem: Ausgabe in inneren Knoten
2.5.4 Spezielle Stackimplementierung fur TOH
Genauer: Lineare Liste ohne explizites Knotenelement
class TohStack {
int [] arr;
TohStack next;
TohStack (){ next = null; }
// Konstruktor 1
TohStack (int [] a, TohStack n){ // Konstruktor 2
arr = a;
2.5. STAPEL/STACK
next = n;
}
public boolean empty () { return next == null; }
public void push (int a, int b, int c, int d) {
int [] A = { a, b, c, d };
if ( empty () ) next = this;
else next = new TohStack(arr, next);
arr = A;
}
public int [] pop (){
int [] a = arr;
if ( next == this ) next = null;
else { arr = next.arr; next = next.next; }
return a;
}
}
39
40
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
2.5.5 Testen: Tower-of-Hanoi-Stack
Idee: Auosung der Rekursion durch Verwaltung der lokalen Variablen auf einem Stack.
public class tohStackTest {
public static void main (String args[]) {
TohStack s = new TohStack ();
s.push (Integer.parseInt(args[0]), 1, 2, 3);
while (!s.empty ()) {
int [] a = s.pop();
if (a[0] < 0) System.out.println (
"bewege Scheibe Nr. "+(-a[0])+
" von Pfeiler "+a[1]+" nach "+a[2]);
else if (a[0] != 0) {
s.push (a[0]-1, a[3], a[2], a[1]);
s.push ( -a[0], a[1], a[2], 0
);
s.push (a[0]-1, a[1], a[3], a[2]);
}
}
}
}
//
//
//
//
//
//
> javac tohStackTest.java
> java tohStackTest 4
bewege Scheibe Nr. 1 von Pfeiler 1 nach 3
bewege Scheibe Nr. 2 von Pfeiler 1 nach 2
bewege Scheibe Nr. 1 von Pfeiler 3 nach 2
...
2.6. SCHLANGE/QUEUE:
41
2.6 Schlange/Queue:
FIFO Speicher: empty, enqueue, dequeuefrei
0
6
@
size-1
6
tail
@
@
head
Schlange
class Queue {
Object element[];
int lastOp, tail, head, size;
static final int deque = 1;
static final int enque = 0;
Queue(int sz) {
size = sz; head = 0; tail = size-1; lastOp = deque;
element = new Object[sz];
}
public boolean empty() { return head==(tail+ 1)%size;}
public void enqueue(Object elem) throws Exception ...
public Object dequeue() throws Exception ...
}
2.6.1 Operationen
public void enqueue(Object elem) throws Exception {
if ((head == (tail+1) % size) && lastOp == enque)
throw new Exception("Queue full");
lastOp = enque;
tail = (tail + 1) % size;
element[tail] = elem;
}
public Object dequeue() throws Exception {
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
42
if ((head == (tail+1) % size) && (lastOp == deque))
throw new Exception("Queue empty");
Object temp = element[head];
lastOp = deque;
head = (head + 1) % size;
return temp;
}
Test
public static void main(String argv[]) {
Queue Q = new Queue(10);
try {
Q.enqueue(new String("Haus"));
Q.enqueue(new Integer(10));
Object o = Q.dequeue();
System.out.println(o);
}
catch (Exception e) {
System.out.println(e);
}
}
2.6.2 Anwendung: Topologische Sortierung
Denition: Die Relation ist eine vollstandige bzw. partielle Ordnung auf M , wenn
(1)-(4) bzw. (2)-(4) gelten.
(1) 8x; y 2 M : x y oder y x
(2) 8x 2 M : x x
(3) 8x; y 2 M : x y und y x ) x = y
(4) 8x; y 2 M : x y und y z ) x z
Beispiel: Potenzmenge 2M einer Menge M bezuglich Teilmengenrelation partiell aber
nicht vollstandig geordnet.
Topologische Sortierung: Gegeben M und partiell. Gesucht Einbettung in vollstandige Ordnung, d.h. die Element von M in eine Reihenfolge m ; : : : ; mn bringen, wobei
mi mj fur i < j gilt.
Lemma: Jede partiell geordnete Menge lat sich topologisch sortieren.
Beweisidee: Es genugt, die Existenz eines minimalen Elements z in M zu zeigen. z mu
nicht eindeutig sein. Wahle m = z und sortiere M fzg topologisch.
1
1
2.6. SCHLANGE/QUEUE:
43
2.6.3 Verwendete Datenstrukturen
A: Array der Lange n, A[i] naturliche Zahl
V : Array der Lange n, V [i] Zeiger auf L[i]
L[i]: Liste von Zahlenpaaren
Q: Queue (Schlange), die ebenfalls Zahlen enthalt
Algorithmus:
E ingabe: p Paare (i; j ), mit xi xj und M = fx1; : : : ; xn g
(1) Setze alle A[i] auf 0, alle L[i] und Q seien leer
(2) Wird (j; k) gelesen, wird A[k] um 1 erhoht und in L[j ] eingetragen.
(3) Durchlaufe A: Schreibe alle k mit A[k] = 0 in Q
(4) While Q 6= fg
j = Q.dequeue ()
Gebe xj aus
Durchlaufe L[j ]: Wird k gefunden, wird A[k] um
1 verringert. Falls A(k) = 0 ist, Q.enqueue (k)
Satz: Der Algorithmus lost das Problem Topologische Sortierung in O(n + p) Zeit.
2.6.4 Beispiel:
Eingabe: (1,3), (3,7), (7,4), (4,6), (9,2), (9,5),
(2,8), (5,8), (8,6), (9,7), (9,4)
Datenstruktur nach der Vorverarbeitung:
1
2
3
4
5
A:[0
1
1
2
1
V:1,3 2,8 3,7 4,6 5,8
6
2
7
8
9
2
2
0]
7,4 8,6 9,2
9,5
9,7
9,4
Ablauf des Algorithmus:
Q
| 1 2 3 4 5 6 7 8 9
-----------+--------------------------{1,9}
| 0 1 1 2 1 2 2 2 0
|
|
|
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
44
|
|
|
2.7. MENGEN
45
2.7 Mengen
Unterscheidung: Bitvektordarstellung und Listendarstellung
Eignung: Bitvektordarstellung fur feste Grundmenge G = f1; : : : ; ng. und (relativ)
groen Teilmengen M von G.
Bitvektor-Darstellung: M durch Array A mit
A[i] = 1 , i 2 M , und A[i] = 0 sonst.
Operationen:
- i 2 M?
- fuge i zu M hinzu?
- entferne i aus M ?
- M =M [M ?
- M =M \M ?
-M =M M ?
- M = M M ?
1
2
1
2
1
1
2
2
Machinenwortdarstellung: w Bits in einem Machinenwort. Komplexitat entweder O(1)
oder O(dn=we)
46
KAPITEL 2. ELEMENTARE DATENSTRUKTUREN
Kapitel 3
Elementare Sortierverfahren
47
48
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
3.1 Das Sortierproblem
Gegeben: Folge s[1]; : : : ; s[n] von n Satzen; jeder Satz s[i] hat einen Schlussel k[i].
Gesucht: Permutation derart, da die Anordnung der Satze gema die Schlussel in
aufsteigende Reihenfolge bringt:
k k k n :
(1)
(2)
( )
Ausgabe: Satze in aufsteigender Reihenfolge
- in einem Array (internes Sortieren )
- in einer Datei auf Festplatte oder Magnetband (externes Sortieren )
In situ-Verfahren: Verfahren, die wenig zusatzlichen Speicherplatz benotigen.
3.1.1 Allgemeine Sortierverfahren
Schlusselvergleiche (<, =, >) sind die einzige verwendbare Information zur Losung des
Sortierproblems.
Beispiele:
(a) Elementare Verfahren:
Laufzeit (worst case)
Bubblesort, Auswahlsort
Sortieren durch Einfugen, Shellsort
(b) Mergesort
(c) Quicksort
(d) Clever-Quicksort
(e) Heapsort
(f) Weak-Heapsort
2. Untere Schranken
3. Spezielle Sortierverfahren:
Schlussel sind ganze Zahlen
Verwendung von , =, shift, mod, etc.
(a) Radix (exchange-) Sort
(b) Sortieren durch Fachverteilung
4. Sortieren vorsortierter Daten:
Verfahren fur besondere Eingaben
3.1. DAS SORTIERPROBLEM
49
3.1.2 Elementare Sortierverfahren
Laufzeitmessung:
a. # Schlusselvergleiche (comparisons)
Cmin(n) (best case)
Cmax (n) (worst case)
Cav (n) (average case)
b. # Datenbewegungen (movements) Mmin , Mmax , Mav
Im average case: Mittelung uber alle n! Anordnungen von n verschiedenen Satzen
Messung der Anzahl der Vergleiche
1. Fur die meisten Sortierverfahren: Mx 2 O(Cx), x = max, min, av
2. Datenbewegung: Pointerzuweisung, Schlusselvergleich: Vergleich der einzelnen Zeichen
Beispiel: Bubblesort
Cmin(n) = n 1;
Mmin = 0
Cmax (n) = 21 n(n 1) = Mmax
Cav (n) 2 O(n );
2
Mav (n) 2 O(n )
2
50
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
3.2 Grundlegende Implementation
Rahmenprogramm: SortAlgTest.java: Programm zum Testen von Sortieralgorithmen
public class SortAlgTest {
public static void main(String args[]){
int vec[] = {15, 2, 43, 17, 4, 8, 47};
if (args.length != 0) {
vec = new int [args.length];
for (int j=0; j<args.length;j++) {
vec[j] = Integer.valueOf(args[j]).intValue();
}
}
/* t[0] wird als Stopper verwendet */
OrderableInt t[] = OrderableInt.array (vec);
SortAlgorithm.printArray(t);
SortAlgorithm.sort(t);
SortAlgorithm.printArray (t);
}
}
Interface: Orderable.java: Beschreibt das Verhalten vergleichbarer Objekte
interface Orderable {
public boolean equal
public boolean greater
public boolean greaterEqual
public boolean less
public boolean lessEqual
public Orderable minKey
}
(Orderable
(Orderable
(Orderable
(Orderable
(Orderable
();
o);
o);
o);
o);
o);
Sortierbasisklasse: SortAlgorithm.java bietet abzuleitende Klasse fur die Sortieralgorithmen
class SortAlgorithm {
static void swap(Object A[], int i, int j){
Object o = A[i]; A[i] = A[j]; A[j] = o;
}
static void sort (Orderable A[]) {}
static void printArray (Orderable A[]) {
3.2. GRUNDLEGENDE IMPLEMENTATION
for (int i = 1; i < A.length; i++)
System.out.print(A[i].toString()+" ");
System.out.println();
}
}
51
52
3.3 Auswahlsort
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
Idee: Bestimme der Reihe nach das i-kleinste Element im Rest der Liste (1 i n) und
tausche es an die Position i
Implementation
class AuswahlSort extends SortAlgorithm {
static void sort (Orderable A[]) {
for (int i = 1; i < A.length-1; i++) {
int min = i;
for (int j = i+1; j <= A.length-1; j++) {
if (A[j].less(A[min])) {
min = j;
}
}
swap(A,i,min);
}
}
}
Analyse: U bungsaufgabe

3.4. SORTIEREN DURCH EINFUGEN
53
3.4 Sortieren durch Einfugen
class EinfuegeSort extends SortAlgorithm {
static void sort (Orderable A[]) {
for (int i = 2; i < A.length; i++) {
Orderable temp = A[i];
int j = i - 1;
while (j >= 1 && A[j].greater(temp)) {
A[j+1] = A[j];
j--;
}
A[j+1] = temp;
}
}
}
Analyse von Insertion Sort
Einfugen des i-ten Elementes benotigt
mindestens 1, hochstens i 1 Vergleiche und
mindestens 2, hochstens i + 1 Bewegungen
(i = 2; : : : ; n)
Cmin (n) = n 1
Cmax =
n
X
i=2
(i 1) = n(n 1)
2
Mmin (n) = 2(n 1)
n
X
Mmax (n) = (i + 1) = n(n2 1) + 2(n 1)
i=2
Im Mittel: Die Halfte von s ; : : : ; si ist groer als si
0
1
Cav (n) Mav (n) nX1 i + 1
i=1
nX1 i
i=1
2
2
= (n )
2
1 + 2 = (n )
2
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
54
3.5 Shellsort
Folge 1 = h < h < h < < hk
1. Zu hi > 0, j = 0; : : : ; hi 1, betrachte Teilfolgen
sj ; sj h ; sj h ; : : :
2. Sortiere jeder dieser Teilfolgen durch Einfugen
3. Falls hi > 1, erniedrige hi auf einen Wert hi und gehe zu 1, sonst fertig;
Satz: Worst case Laufzeit: O(n log n), falls hi 2 f2p3q < N g
Satz: (Li and Vitanyi 1999): Average case fur alle Sequenzen (k pass) in (kn
1
+ i
2
3
+2 i
1
2
3.5.1 Beispiel zu Shellsort
h = 5, h = 3, h = 1
Eingabefolge: 15 2 43 17
3
2
1
h =5
3
h =3
2
h =1
1
15 2 43 17
4
8 47
4
8 47
8 2 43 17
4 15 47
8 2 43 17
4 15 47
8 2 15 17
4 43 47
8 2 15 17
4 43 47
2 4
3.5.2 Implementation: Shellsort
class ShellSort extends SortAlgorithm {
/* Sortierverfahren nach D.L. Shell */
static void sort (Orderable A[]) {
int n = A.length - 1;
int h = n / 2;
8 15 17 43 47
=k ).
1+1
3.5. SHELLSORT
while (h > 1) {
for (int j = 1; j <= h; j++) {
sort (A, h, j);
}
h = h / 2;
}
sort(A, 1, 1);
}
static void sort (Orderable A[],int h,int k) {
for (int i = k + h; i < A.length; i = i + h) {
Orderable temp = A[i];
int j = i - h;
while (j >= 1 && temp < A[j]) {
A[j+h] = A[j];
j = j - h;
}
A[j+h] = temp;
}
}
}
55
56
3.6 Mergesort
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
Mergesort|Sortieren durch Verschmelzen
Prinzip von Mergesort
Sortieren durch rekursives Verschmelzen von sortierten Teilfolgen
Eingabe: unsortierte Folge L
Ausgabe: sortierte Folge
if |L| = 1
return L
L1 = erste Haelfte von L
L2 = zweite Haelfte von L
Mergesort(L1); Mergesort(L2)
return Merge(L1,L2)
3.6.1 Verschmelzen zweier Teilfolgen
Merge Verschmelze die sortierten Folgen A[l..m] und A[m+1..r] zu einer Folge
static void merge(Orderable A[],int l,int m,int r) {
comparable B [] = new Orderable[A.length];
int i = l;
// Zeiger in A[l],...,A[m]
int j = m + 1;
// Zeiger in A[m+1],...,A[r]
int k = l;
// Zeiger in B[l],...,B[r]
while (i <= m && j <= r) {
if (A[i].less(A[j])) {
B[k] = A[i]; i++;
}
else {
B[k] = A[j]; j++;
}
k++;
}
if (i > m) { // erste Teilfolge erschoepft
for (int h = j; h <= r; h++, k++) B[k] = A[h];
}
else {
// zweite Teilfolge erschoepft
for (int h = i; h <= m; h++, k++) B[k] = A[h];
}
for (int h = l; h <= r; h++) A[h] = B[h];
}
3.6. MERGESORT
57
Beispiel:
2
4
8 15 17 25 43 47 62
9 12 15 16 28 33 37
Mergesort
class mergeSort {
/* sortiert das ganze Array */
public static void sort (Orderable A[]){
sort(A,1,A.length-1);
}
static void merge ...
static void sort (Orderable A[], int l, int r){
if (r > l) {
int m = (l + r) / 2;
sort(A, l, m);
sort(A, m+1, r);
merge(A, l, m, r);
}
}
}
Beispiel: 8 6 7 3 4 5 2 1
divide
8 6 7 3 4 5 2 1
divide
8 6 7 3 4 5 2 1
divide
8 6 7 3 4 5 2 1
merge
3.6.2 Analyse von Mergesort
Schlusselvergleiche fur Merge:
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
58
worst-case: n + n 1
best-case: min(n ; n )
n =r m+1=
n = m l = Lange der linken Teilfolge
n = r m + 1 = Lange der rechten Teilfolge
Rekursionsgleichung fur Laufzeit von Mergesort
1
2
1
2
2
1
2
T (n) = 2T (n=2) + O(n)
O(n) = n=2 + n=2 1 worst case
n=2
best case
) T (n) = n log n (n 1)
zusatzlicher Speicher: O(n)
3.6.3 Reines 2-Wege-Mergesort
Ansatz:
for i = 0 to log n
verschmelze Teilfolgen der Lange 2i
8
6
3
1
6
8
6
2
9
3
8
3
3
9
9
4
4
4
1
6
7
7
2
7
2
1
4
8
1
2
7
9
Naturliches 2-Wege-Mergesort Ansatz: Wie reines Mergesort mit Ausnutzung vorsortierter Teilfolgen
8
6
2
1
6
8
3
2
Cmin = n 1
9
9
4
3
3
2
6
4
4
3
7
6
7
4
8
7
2
7
9
8
1
1
1
9
Cav ; Cmax = O(n log n)
3.7. QUICKSORT
59
3.7 Quicksort
Sortieren durch Teilen
Divide-&-Conquer Prinzip
Sehr gute Laufzeit im Mittel, schlechte Laufzeit im worst case.
Quicksort Eingabe: unsortierte Liste L
sortierte Liste
if (|L| <= 1)
return L
else waehle Pivotelement p aus L
L< = {a in L | a < p}
L> = {a in L | a > p}
return
[Quicksort(L<)] + [p] + [Quicksort(L>)]
Lin
L<
p
Quicksort
3.7.1 Implementation
Eingliederung in das Rahmenprogramm:
class QuickSort extends SortAlgorithm {
static void sort (Orderable A[]){
/* sortiert das ganze Array */
sort (A, 1, A.length-1);
}
p
L>
Quicksort
60
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
static void sort (Orderable A[], int l, int r){
/* sortiert das Array zwischen Grenzen l und r */
if (r > l) {
// mind. 2 Elemente in A[l..r]
int i = divide(A, l, r);
sort (A, l, i-1);
sort (A, i+1, r);
}
}
static int divide (Orderable A [], int l, int r) {
/* teilt das Array zwischen l und r mit Hilfe
des Pivot-Elements in zwei Teile auf und gibt
die Position des Pivot-Elementes zurueck */
...
}
}
Der Aufteilungsschritt
divide(A; l; r):
- liefert den Index des Pivotelements in A
- ausfuhrbar in Zeit O(r l)
3.7. QUICKSORT
l
3.7.2 Implementation: Aufteilungsschritt
static int divide (Orderable A [], int l, int r) {
// teilt das Array zwischen l und r mit Hilfe
// des Pivot-Elements in zwei Teile auf und gibt
// die Position des Pivot-Elementes zurueck
r
61
62
int i = l-1;
int j = r;
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
// linker Zeiger auf Array
// rechter Zeiger auf Array
Orderable pivot = A [r]; // das Pivot-Element
while (true){
// "Endlos"-Schleife
do i++; while (i < j && A[i].less(pivot));
do j--; while (i < j && A[j].greater(pivot));
if (i >= j) {
swap (A, i, r);
return i;
// Abbruch der Schleife
}
swap (A, i, j);
}
}
3.7.3 Analyse von Quicksort
Gunstigster Fall:
log n
Tmin (n) = O(n log n)
Schlechtester Fall:
3.7. QUICKSORT
63
n
Tmax (n) = O(n )
2
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
64
Best Case: Pivotelement teilt Folge jeweils genau zur Halfte auf
Aufteilungsaufwand fur Folge der Lange n: n 1
Tmin (n) = Schusselvergleiche von Quicksort im besten Fall (Cmin )
Tmin(1) = 0
Tmin (n) 2Tmin(n=2) + n 1
) Tmin (n) n log n (n 1)
Worst Case: Eine der beiden durch Aufteilung entstehenden Folgen ist leer
(z.B. aufsteigend sortierte Folge von Schlusseln)
Tmax (n) = Schusselvergleiche von Quicksort im schlechtesten Fall (Cmax)
Tmax (1) = 0
Tmax (n) = Tmax (n 1) + n 1
) Tmax (n) = Tmax (n 2) + (n 2) + n 1
...
= T (1) + 1 + + (n 2) + (n 1)
= n(n2 1) = O(n )
2
3.7.4 Average Case
Anahmen:
1. n paarweise verschiedene Schlussel
2. Alle n! Permutationen gleichwahrscheinlich
Fakt:
3. Teilfolgen des Aufteilungsschritts wieder zufallig
) P (Pivotelement hat Rang k) = 1=n.
Rekursionsgleichung fur mittlere (erwartete) Anzahl von Schusselvergleichen
Tav (n) = E [C (n)]
Tav (0) = 0
3.7. QUICKSORT
65
Tav (1) = 0
n
X
Tav (n) = n1 (Tav (k 1) + Tav (n k)) + n 1
k
=1
66
KAPITEL 3. ELEMENTARE SORTIERVERFAHREN
Kapitel 4
Schnelle Sortierverfahren
67
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
68
4.1 Kriterien fur Sortierverfahren
Allgemeinheit,
Einfachheit,
In Situ{Eigenschaft (wenig Platz),
Schlusselvergleiche schwer und
grotmoglichste Anzahl der Vergleiche n log n + cn,
ubrigen Operation c(n log n).
4.2. STATE-OF-THE-ART
69
4.2 State-of-the-Art
Untere Schranke: Cmax(n) > Cav (n) dlog(n!)e 1 n log n 1:4427n,
Ziel: Cmax(n) bzw. Cav (n) n log n + cn fur kleines c
Quicksort-Varianten:
QUICKSORT (Hoare 1962) Cav (n) 1:386n log n 2:846n + O(log n)
CLEVER-QUICKSORT (Hoare 1962) Cav (n) 1:188n log n 2:255n + O(log n)
QUICK-HEAPSORT (Cantone & Cincotti 2000) Cav (n) = n log n + 3n + o(n)
QUICK-WEAK-HEAPSORT (U bung)
Cav (n) = n log n + 0:2n + o(n)
Heapsort-Varianten:
HEAPSORT (Williams 1964) bzw. (Floyd 1964) Cmax(n) = 2n log n + O(n)
BOTTOM-UP-HEAPSORT (Wegener 1993) Cmax(n) = 1:5n log n + O(n) allerdings
Cav (n) = n log n + O(n)
WEAK-HEAPSORT (Dutton 1993)
Cmax (n) = n log n + 0:1n RELAXED-WEAK-HEAPSORT (U bung)
Cmax (n) = n log n 0:9n
70
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
4.3 Average Case Analyse Quicksort
Anahmen:
- n paarweise verschiedene Schlussel
- Alle n! Permutationen gleichwahrscheinlich
Fakt:
Teilfolgen des Aufteilungsschritts wieder zufallig
) P (Pivotelement hat Rang k) = 1=n.
Rekursionsgleichung:
Cav (0) = 0
Cav (1) = 0
n
X
Cav (n) = n 1 + n1 (Cav (k 1) + Cav (n k))
k
=1
Satz (Geschlossene Form)
Cav (n) 1:386n log n 2:846n + O(log n)
Beweis:
Exemplarisch losen wir die Rekursionsgleichung, wobei wir Cav durch C abkurzen. Durch
Multiplikation mit n und Zusammenfassen der beiden Summanden (gleicher Indexbereich)
gilt
nC (n) = n(n 1) + 2
n
X
i=1
C (i 1)
In dieser Gleichung kommen noch C (0); : : : ; C (n) vor. Um die Zahl verschiedener Werte auf 2 zu senken, betrachten wir die oben gewonnene Gleichung auch fur n 1 und
subtrahieren diese Gleichung von der Gleichung fur n.
(n 1)C (n 1) = (n 1)(n 2) + 2
nX1
i=1
C (i 1)
Demnach ist
nC (n) (n 1)C (n 1) =
n(n 1) (n 1)(n 2) + 2C (n 1)
Vereinfachung und Division durch (n + 1)n liefert
4.3. AVERAGE CASE ANALYSE QUICKSORT
71
C (n) = C (n 1) + 2(n 1)
n+1
n
n(n + 1)
Es ist nun naheliegend, Z (n) = C (n)=(n + 1) zu untersuchen.
Z (n) = Z (n 1) + n2((nn + 1)
1)
= Z (n 2) + (2(nn 1)2)n + n2((nn + 1)
1) = : : :
n i 1
X
= Z (1) + 2
i i(i + 1)
=2
Da C (1) = 0, ist Z (1) = 0. Es ist gut zu wissen, da
1 =1
1
i(i + 1) i i + 1
gilt. Also ist
n i 1
X
Z (n) = 2 i(i + 1)
i
n i 1
nX i 2
X
= 2
2
i
i
i
i
n
X
= 1 + 2 1 2n 1
n+1
i i
=2
+1
=2
=3
=3
Die Reihe 1+ + + + : : : tritt so haug auf, da sie einen Namen, namlich Harmonische
Reiche H (n), erhalten hat. Es folgt
Z (n) = 2H (n) 2 2 nn + 11 = 2H (n) 4 + n +4 1
und
2
C (n) = (n + 1)Z (n) = 2(n + 1) H (n) 2 + n + 1
= 2(n + 1)H (n) 4n
1
2
1
3
1
4
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
72
Was konnen wir uber H (n) aussagen? Aus der Betrachtung der Riemannschen Summen
folgt
Zn 1
Zn1
H
(
n
)
x
x
und damit
+1
1
1
ln(n + 1) H (n) 1 + ln n
Ohne groere Probleme lat sich sogar zeigen, da H (n) ln n konvergiert. Der Grenzwert
heit Eulersche Konstante, es gilt 0:57721 : : : Schlielich ist ln n = (log n) ln 2.
Insgesamt gilt also
C (n) = 2(n + 1)H (n) 4n
1:386n log n 2:846n + O(log n)
Gleiche Analyse wie Randomisiertes Quicksort
- Pivot = zufalliges Element aus A[l::r]
- vertausche A[r] mit Pivot
- weiter wie bisher
Worst-case: Laufzeit jetzt zufallig Cex(n)
4.4. CLEVER-QUICKSORT
73
4.4 Clever-Quicksort
Auswahl des Pivotelementes (Median-of-3 Strategie):
- a) m = (l + r)=2 oder b) m = l + 1
- Pivot: mittleres Element von fA[l]; A[m]; A[r]g
- vertausche A[r] mit Pivot
- weiter wie bisher
Worst-case:
- verschwindet in a) fur auf- bzw. absteigende Sortierung
- existiert immer noch
Bemerkung: Median von 3 Objekte kann in durchschnittlich 8/3 Vergleichen gefunden
werden.
) Divide im Mittel n 3 + 8=3 = n 1=3 Vergleiche.
Wahrscheinlichkeit, da x an der Position k steht, ist gleich (k 1)(n k)= n , da k 1
Positionen fur das kleinere und n k Positionen fur das groere Objekt.
3
8 0, fur n 2 f0; 1g
>
>
< 1, fur n = 2
Pk n
Cav (n) = >
>
: n + =1
n
(
1
3
k
1)(
k)(V (k
V (n k))
1)+
(3)
n
Satz: Schlusselvergleiche im Mittel
Cav (n) 1:188 n log n 2:255 n + O(log n)
4.4.1 Implementation
Bemerkung: Siehe Sedgewick: The analysis of quicksort programs, Acta Informatica,
Journal of Algorithms, 15(1):76-100, 1993
static void sort(Orderable A[], int left,int right) {
if (right-left >= 3) {
if(A[right].less(A[left+1])) swap(A,left+1,right);
if(A[right].less(A[left])) swap(A,left,right);
if(A[left].less(A[left+1])) swap(A,left+1,left);
int i = left+1, j = right;
Orderable v = A[left];
do {
do { i++; } while (A[i].less(v));
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
74
do { j--; } while (v.less(A[j]));
if (j >= i) swap(A,i,j);
}
while(j >= i);
swap(A,left,j);
if (j-left < right-i+1) {
sort(A,left,j-1);
sort(A,i,right);
}
else {
sort(A,i,right);
sort(A,left,j-1);
}
}
else
threesort(A,left,right);
}
}
4.5. HEAPSORT
75
4.5 Heapsort
Ezientes Sortieren durch Auswahlen
Prinzip von Heapsort: Sortieren durch wiederholtes Auswahlen des Maximums (Auswahlsort). Verwende Struktur (Heap), die Bestimmung des Maximums ezient unterstutzt.
Verwandle unsortierte Folge F in einen Heap
while (F <> {})
gebe maximales Element von F aus
entferne maximales Element aus F und
verwandle Restfolge wieder in einen Heap
Denition:
Folge F = (k ; k ; : : : ; kn) von Schlusseln heit Heap, wenn fur alle i = 1; 2; : : : ; n=2 gilt:
ki k i und ki k i
1
2
2
2 +1
1 2 3 4 5 6 7
47 17 43 15 8 4 2
47 15 17 8 43 4 2
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
76
4.5.1 Veranschaulichung
1
2
3
4
5
6
7
47 17 43 15 8
4
2
1
2
4
3
5
6
7
Vollstandiger Binarbaum mit Positionsnummern:
- Level i hat 2i Knoten (auer dem letzten Level)
- Knoten von oben nach unten und von links nach rechts numeriert
- Knoten i hat Knoten 2i als linken und Knoten 2i + 1 als rechten Sohn
Heapbedingung:
- Schlussel(p) Schlussel(pl )
- Schlussel(p) Schlussel(pr )
Maximum ist an der Wurzel (Position 1)
4.5.2 Entfernen des Maximums
1. Entferne k
1
4.5. HEAPSORT
77
1
43
17
15
8
2
4

2. Ubertrage
kn an die Wurzel
1
17
15
43
8
4
3. Versickere k
1
1
17
15
4.5.3 Versickern
Allgemeiner: versickere ki in i; : : : ; l
8
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
78
ki
k2i k2i+1
i
2i
2i + 1
l
class HeapSort extends SortAlgorithm {
static void pushdown(Orderable A[],int i,int n) {
while (2*i <= n) { // i hat linken Sohn
int j = 2*i;
if (j < n && A[j].less(A[j+1]))
j = j + 1;
// 2i + 1 ist groesserer Sohn
if (A[i].less(A[j])) {
swap(A,i,j);
i = j;
// versickere weiter
}
else i = n;
// exit loop
}
}
...
}
4.5.4 Erstellen eines Heaps
Beobachtung:
Die Elemente A[n=2]; : : : ; A[n] erfullen bereits die Heap-Bedingung
4.5. HEAPSORT
79
1 15
2 2
4 17
5 4
3 43
6 8
public static void heapify (Orderable A[]) {
// verwandle A in einen Heap
int n = A.length-1;
for (int i = n/2; i >= 1; i--) {
pushdown(A,i,n);
}
}
A uere Schleife
public static void sort (Orderable A[]){
heapify(A);
// generiere Heap
for (int i = A.length-1; i >= 2; i--) {
swap(A,1,i);
pushdown(A,1,i-1); // versickere Wurzel
}
}
7 47
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
80
4.5.5 Iteriertes Versickern
20
21
2k
2
2k
1
n = 2k 1
(k 1) Tiefe # Elemente # Vergleiche
0
2k =2
0
k
1
2 =2
21
k
2
2 =2
22
kX i
...
...
... Gesamtaufwand = 2k
2 2i
i
i
2k =2i
2i
...
...
...
k 1 2k =2k
2(k 1)
1
0
1
1
1
2
1
=0
1
1
1
4.5.6 Analyse
Wissen:
1
Falls A Heap mit n Schlusseln
max(A) : 1, deletemax(A) : 2 log n (2 Vergl. pro Niveau)
heapify(A) : 2n
4.5. HEAPSORT
81
Insgesamt:
Laufzeit : O(n log n), genauer: 2n log n + 2n Schlusselvergleiche
Platzbedarf : n fur das Eingabearray + O(1)
Schlimmster Fall in Heapsort:
Es wird immer das kleinste Element versickert
Beobachtung:
Die erwartete Tiefe eines versickerten Elementes im Heap ist gro.
Idee Bottom-up-Heapsort:
- Bestimme nur groeren der beiden Sohne mit einem Schlusselvergleich pro Niveau
- sinke immer bis zu einem Blatt (search-leaf)
- steige dann wieder auf (bottom-up-search)
4.5.7 Bottom-up-Heapsort
15
14
12
8
13
11
4
2
10
1
7
3
5
6
15
14
8
12
11
5
13
1
10
2
4
7
3
6
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
82
15
14
9
12
1
11
8
13
10
2
4
4.5.8 Bottom-Up-Heapsort: Implementation
Versickern:
void pushdown(int root,int m) {
int j = LeafSearch(root,m);
j = BottomUpSearch(root,j);
Interchange(root,j);
}
Suche speziellen Weg:
static int LeafSearch(Orderable A[],int root,int m) {
int i = 0, j = Path[i++] = root;
while((2*j)<m) {
if (A[2*j+1].less(A[2*j])) Path[i++] = j = 2*j;
else
Path[i++] = j = 2*j+1;
}
if(2*j==size) j = Path[i++] = m;
return j;
}
Suche Einsinkposition:
static int BottomUpSearch(Orderable A[],int i,int j){
while(j>i && A[j].less(A[i])) j /= 2;
return j;
}
7
5
3
6
4.5. HEAPSORT
83
Ringtausch ezienter als iteriertes Vertauschen:
static void Interchange(Orderable A[],int i,int j){
int k=0; Orderable v = A[Path[0]];
for(;Path[k]<j;k++) A[Path[k]] = A[Path[k+1]];
A[Path[k]] = v;
}
4.5.9 Ergebnisse
Satz: Bottom-up Heapsort fuhrt zum Sortieren einer Folge von n Schlusseln im schlechte-
sten Fall nur 1:5n log n + (2 c(n))n + O(log n) Schlusselvergleiche aus (Wegener 1993).
Fleischer (1991) sowie Schaer und Sedgewick (1993) haben worst-case Beispiele angegeben, bei denen die Anzahl der wesentlichen Vergleiche fur BOTTOM-UP-HEAPSORT
gleich 1:5n log n o(n log n) ist.
Satz: Im Mittel benotigt Bottom-up Heapsort (nur) n log n + O(n) Schlusselvergleiche
(Li & Vitanyi 1993).
2
Experimente:
Sei d(n) so gewahlt, da n log n + d(n)n die erwartete Anzahl an Schlusselvergleichen von
BOTTOM-UP-HEAPSORT ist. Dann liegt d(n) im Intervall von [0.34,0.39]. Diese Zahl
ist gro fur n 2k und klein fur n 1:4 2k .
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
84
4.6 WEAK-HEAPSORT
Die Datenstruktur Weak-Heap ...
75
70
35
6
32
10
0
36
11
zerfallt in Substrukturen:
1
T
Th
2
T1
T2
k
Tk
4.6.1 Verschmelzen
Merge ( Knoten x , durch y beschriebener Baum ):
5
4.6. WEAK-HEAPSORT
a)
85
b)
x
y
x
y
lT
rT
rT
Voraussetzung: (x; lT (y)), (y; rT (y)) Weak{Heaps.
Fall a) a[x] > a[y], Fall b) a[x] a[y].
Satz: Merge liefert einen Weak{Heap und benotigt einen Vergleich.
Achtung: Teilbaumrotation mu realisiert werden.
4.6.2 Generierung eines Weak-Heaps
Heapify :
lT
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
86
Zur Initialisierung des Weak-Heaps wird jeder Knoten j mittels Merge mit seinem Groelternteil Gparent(j ) verbunden (bottom{up).
Satz: Heapify liefert in n 1 Vergleichen einen Weak-Heap.
4.6.3 Sortierungsphase
MergeForest ( derzeitige Groe m ):
m’
m’
1
2
m’
T1
T2
m’
k
x
Tk
m
Satz: MergeForest liefert in einen Weak{Heap{Wald einen neuorganisierten Weak{Heap.
4.6.4 Arrayeinbettung
Problem der Arrayeinbettung:
1. Die Wurzel root(T ) wird auf die Arrayposition 0 abgebildet.
2. Falls der Knoten v 2 T auf die Arrayposition x abgebildet wird, so wird lchild(v)
auf die Position 2x + Reverse[x] und rchild(v) auf die Position 2x + 1 Reverse[x]
abgebildet.
4.6. WEAK-HEAPSORT
87
75
a)
b)
70
35
32
10
x
75
70
6
8
11
35
36
4
m
5
32
1
10
m
6
x
0
36
5
11
75
c)
70
35
x
32
6
0
36
5
m
In den Fallen b) und c) liegen m und x auf unterschiedlichen Leveln, d.h. depth(x) =
depth(m) 1 = dlog(m + 1)e 1
In Fall a) gilt hingegen depth(x) = depth(m) = dlog(m + 1)e.
11
4
4.6.5 Implementation
static int Gparent(int j) {
while ((j & 1) == 0) j /= 2;
return (j / 2);
}
static void Merge(int i,int j,
Orderable A[], boolean Reverse[]) {
if (A[i].less(A[j])) {
swap(A,i,j); Reverse[j] = !Reverse[j];
}
}
static void MergeForest(int m,
Orderable A[], boolean Reverse[]) {
int x=1;
while ((2*x + (Reverse[x]?1:0)) < m)
x = 2*x + (Reverse[x]?1:0);
do
{ Merge(m,x,A,Reverse); x /= 2; }
while (x>0);
}
static void sort(Orderable A[]) {
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
88
for(int i=1; i < A.length; i++ ) A[i-1] = A[i];
boolean Reverse [] = new boolean[A.length];
for (int i = A.length-2; i >= 1; i--)
Merge(Gparent(i),i,A,Reverse);
A[A.length-1] = A[0];
for (int i = A.length - 2; i >= 2; i--)
MergeForest(i,A,Reverse);
}
4.6.6 Laufzeitanalyse
Satz: Sei k = dlog ne. Die Anzahl der Vergleiche in WEAK-HEAPSORT is durch nk
2k + n 1 n log n + 0:086013n beschrankt.
Beweis: Die Aufrufe von MergeForest(i) benotigen hochstens Pni dlog(i +1)e = nk 2k
1
=2
Vergleiche.
Zusammen mit den n 1 Vergleichen um den Weak-Heap aufzubauen haben wir also
insgesamt nk 2k + n 1 Vergleiche.
Fur alle n ndet sich ein x in [0; 1] with nk 2k + n 1 = n log n + nx n2x + n 1 =
n log n + n(x 2x + 1) 1.
Einfache Analysis (Ableitungen) zeigt, da die Funktion f (x) = x 2x + 1 ihr Maximum
an x = ln ln 2= ln 2 mit Funktionswert f (x ) = 0:086013 annimmt.
Experimente: Sei d(n) so gewahlt, da n log n + d(n) die erwartete Anzahl von Vergleichen fur WEAK{HEAPSORT ist. Dann ist d(n) 2 [ 0:47; 0:42]. Weiterhin ist d(n)
klein fur n 2k und gro fur n 1:4 2k .
0
0
4.7. QUICK-HEAPSORT
89
4.7 Quick-Heapsort
Idee: Hybrid-Algorithmus, verbindet den Divide-and-Conquer Ansatz von QUICKSORT
mit HEAPSORT.
Trick: Aufteilung des Elementarrays in umgekehrter Richtung, d.h. A[1::j 1] groer als
Pivot A[j ] und A[j + 1::n] kleiner-gleich dem Pivot an j .
Einseitiges Heapsort: EXTERNAL-HEAPSORT wird fur das kleinere Elementarray
aufgerufen. Ist dies der erste Teil so konstruiert QUICK-HEAPSORT einen Max-, sonst
einen Min-Heap.
Versickerung in EXTERNAL-HEAPSORT: Das Wurzelelement wird jeweils durch
das kleinere Kind ersetzt (1 Vergleich) und an die Endposition in den jeweils anderen
Teilbereich geschrieben (Tausch).
Rekursion: Cav (n) Mittel von EXTERNAL-HEAPSORT. Cav (1) = 0, Cav (2) = 1 und
Cav (2n) = nP
2n + 1 + n nj Cav (j 1) +o Cav (2n j )+
P n C (2n j ) + C (j 1)
av
j n
av
Satz: Im Mittel sortiert QUICK-HEAPSORT
n Elemente in n log n + 3n + o(n) Vergleichen.
1
2
=1
2
= +1
4.7.1 EXTERNAL-HEAPSORT
a) Fulle Lucke in Max-Heap mit jeweils kleinerem Kind und gebe resultierendes Blatt
zuruck
static int max_special_leaf(Orderable A[],
int left, int right) {
int i = left+1; // left son of root
while( i<right ) {
if (A[i].less(A[i+1])) i++;
A[left + (i-left+1)/2 - 1] = A[i];
i = left + 2*(i-left+1) - 1;
}
if (i == right) {
A[left+(i-left+1)/2 - 1] = A[i];
i = left + 2*(i-left+1) - 1;
}
return left + (i-left)/2;
}
b) Sortiere A in den Grenzen I = [heapleft::heapright] hinein in den Bereich [workright
jI j + 1::workright]
90
KAPITEL 4. SCHNELLE SORTIERVERFAHREN
static void external_maxheap_sort(Orderable A[],
int heapleft, int heapright, int workright) {
build_max_heap(A, heapleft, heapright);
for(int j=heapright; j>=heapleft; j--) {
Orderable tmp = A[workright];
A[workright--] = A[heapleft];
int l = max_special_leaf(A, heapleft, heapright);
A[l] = tmp;
}
}
4.7.2 Implementation: QUICK-HEAPSORT
c) Der Aufteilungsschritt:
static void quickheap(Orderable A[],
int left, int right) {
while (right-left > 0) {
int i = left, j = right+1;
Orderable v = A[left];
do {
do j--; while (j>=i && A[j].less(v));
do i++; while (i<=j && v.lessEqual(A[i]));
if (j > i) swap(A,i,j);
}
while(j >= i);
swap(A,left, right - (j-left));
if( (j-left) >= (right-j) ) {
external_minheap_sort( A, j+1, right, left );
left = right - (j-left) + 1;
}
else {
external_maxheap_sort( A, left+1, j, right );
right = right - (j-left) - 1;
}
}
}
d) Sortierung im Bereich A[1::n]:
public static void sort(Orderable A[]) {
quickheap(A,1,A.length-1);
}
Kapitel 5
Spezielle Sortierverfahren
91
KAPITEL 5. SPEZIELLE SORTIERVERFAHREN
92
5.1 Radixsort
Idee: Nutzt Zahldarstellung der Schlussel anstelle von Vergleichen
Schlussel k Wort uber Alphabet mit m Elementen
Xl
k = (kl ; kl ; : : : ; k ; k )m = ki mi
i
ki 2 f0; : : : ; m 1g
m = 10 Dezimalzahlen
m = 2 Binarzahlen
m = 26 Zeichenketten uber fa; : : : ; zg
Funktion: zm (k; i) = ki
1
1
0
=0
5.1.1 Radix-exchange-sort
Sortieren durch rekursive Aufteilung
nach hochstwertigen Ziern (Bits)
Eingabe: Folge F von binaeren Schluesseln
mit Bits an Positionen 0,...,l,
Ausgabe: Nach Bitpositionen sortierte Folge
if (|F| = 1 || b < 0) return F
Sortiere F nach Bit b (Quicksort)
F0 = {k in F | z_2(b,k) = 0}
F1 = {k in F | z_2(b,k) = 1}
return Radix-exchange-sort(F0,b-1) +
Radix-exchange-sort(F1,b-1)
Beispiel: F = (6; 7; 4; 2; 3) { (110; 111; 100; 010; 011)
5.1.2
110 111 100 010
b = 2 011 010 100 111
b = 1 011 010 100 111
b = 0 010 011
110
Problem: Informationsgewinn pro Operation nur ein Bit
Radix-exchange-sort
011
110
110
111
Denition: Die unterscheidenen Praxe einer Menge fa ; : : : ; ang von Zeichenketten sind
die kurzesten Praxe von fa ; : : : ; ang, die paarweise verschieden sind.
1
1
Der unterscheidende Prax einer Zeichenkette ai, die Prax einer anderen Zeichenkette
ist, ist ai selbst.
Beispiel
5.1. RADIXSORT
93
SOCKEL
SOCRATES
SODA
SOFORT
SOFA
SORT
SORTIEREN
si = unterscheidender Prax von ai
S=
n
X
i=1
jsij
5.1.3 Analyse von Radix-exchange-sort
Aufteilung bei Radix-exchange-sort:
F00
F0
F
F10
F01
F1
Laufzeit von Radix-exchange-sort:
- konstanter Aufwand pro Aufteilungsschritt fur ai (Bit b testen, tauschen)
- # Aufteilungsschritte fur ai = jsij
T (a ; : : : ; an) =
1
n
X
i=1
jsij = O(n + S )
Anders:
T (a ; : : : ; an)
= jf(ai ; A) j ai beteiligt an Aufteilungsschritt Agj
n
X
=
jfA j ai beteiligt an Aufteilungsschritt Agj
1
i=1
F11
94
KAPITEL 5. SPEZIELLE SORTIERVERFAHREN
5.1.4 Implementation: Decomposable
Beschreibt das Verhalten von Objekten, bei denen auf eine Zier zugegrien werden kann
abstract class decomposable {
// Wertebereich der Zeichen: 0 .. range - 1
public int range () { return 0; }
// Index eines Zeichens an Stelle i
public abstract int zeichen (int i);
// Anzahl der Zeichen des Objektes
public abstract int laenge ();
public abstract void
printArray(decomposable arr[],int l,int r);
}
5.1.5 Implementation: Ganzzahlen
class decomposableInt extends decomposable {
protected int i ; // key
// hier koennte noch ein Info-typ kommen
decomposableInt (int i){ this.i = i; }
public int range () { return 2; } // Bitstring
// gib Bit i zurueck (0-tes Bit steht ganz rechts)
public int bit(int stelle){
int mask = 1 << stelle;
if ((mask & this.intValue()) == 0) return 0;
return 1;
}
// gib Zeichen i zurueck
public int zeichen (int stelle){
return this.bit(31 - stelle);
}
public int laenge () { return 32; }
// Gibt die variable Laenge zurueck
public int varLaenge () {
return (int)
Math.round(Math.log(this.intValue()) /
Math.log(2.0) + 0.5);
}
int intValue() { return i; } // Gibt Key zurueck
static void printArray ...
}
5.1. RADIXSORT
5.1.6 Implementation: Zeichenketten
class decomposableString extends decomposable {
protected String s ; // key
// hier koennte noch ein Info-typ kommen
decomposableString (String s){ this.s = s; }
// Wir betrachten Zeichen mit Unicode in [0 .. 127]
public int range () { return 128; }
// gib Zeichen i zurueck falls vorhanden
public int zeichen (int stelle){
if (stelle < s.length ())
return
new Character(s.charAt(stelle)).hashCode()%128;
return -1;
}
public int laenge() { return s.length (); }
public void
printArray(decomposable arr[],int l,int r) {
for (int i = l ; i <= r; i++)
System.out.print(arr[i].string()+" ");
System.out.println ();
}
}
5.1.7 Implementation: Radix-Exchange
class radixExchangeSort {
/* sortiert das ganze Array */
static void sort (decomposable A[]){
int n = A.length-1;
int max = 1;
for (int i = 2; i <= n; i++) {
if (A[i].intValue () > A[max].intValue ())
max = i;
}
sort (A, 1, n, A[max].varLaenge ());
}
/* sortiert das Array zwischen den Grenzen l und r
abhaengig von Bit b */
static void sort
(decomposable A[], int l, int r, int b){
95
96
KAPITEL 5. SPEZIELLE SORTIERVERFAHREN
if (r > l && b >= 0){
int i = l-1;
// linker Zeiger auf Array
int j = r+1;
// rechter Zeiger auf Array
while (true){
// "Endlos"-Schleife
do i++; while (i <= r && A[i].bit(b) == 0);
do j--; while (j >= l && A[j].bit(b) == 1);
if (i >= j) break; // Abbruch der Schleife
swap (A, i, j);
}
sort (A, l, i-1, b-1);
sort (A, i, r, b-1);
}
}
}
5.2. SORTIEREN DURCH FACHVERTEILUNG
97
5.2 Sortieren durch Fachverteilung
Zuerst nach dem letzten Zeichen sortieren
Beispiel: F = 434; 528; 154; 176; 783; 204; 351; 218; 900
Verteilen nach Zier 3: 900 351
F0
F1
F2
204
154
783 434
F3
F4
F5
176
F6
F7
218
528
F8
F9
Sammeln:
900, 351, 783, 434, 154, 204, 176, 528, 218
204
Verteilen nach Zier 2: 900 218 528 434
F0
F1
F2
F3
F4
154
351
F5
F6
176 783
F7
F8
F9
Sammeln:
900, 204, 218, 528, 434, 351, 154, 176, 783
Verteilen nach Zier 1:
F0
176 218
154 204 351 434 528
F1
F2
F3
F4
F5
F6
783
F7
154, 176, 204, 218, 351, 434, 528, 783, 900
5.2.1 Analyse
Aufwand pro Durchlauf: l = Lange der Zeichenketten in Bits
Anzahl Durchlaufe: l= log m
Aufwand pro Durchlauf: O(n + m)
) Gesamtaufwand: O((n + m) l= log m)
Speicherplatz: O(n + m)
Vergleich:
Operationen
Radix-exchange-sort O(n + S )
O((n + m) l= log m)
Fachverteilung
Vergleich der Aufteilungen
Radix-exchange-sort:
F8
900 Sammeln:
F9
98
KAPITEL 5. SPEZIELLE SORTIERVERFAHREN
Sortieren durch Fachverteilung:
5.2.2 Implementation: Fachverteilung
class Fachverteilung {
static void sort (decomposable A[]) {
5.2. SORTIEREN DURCH FACHVERTEILUNG
int n = A.length-1;
int m = A[0].range ();
list fach [] = new list [m+1];
// Initialisiere die Faecher
for (int i = 0; i <= m; i++) {
fach[i] = new list ();
}
// Berechne die max. Laenge einer Zeichenkette
int max = 1;
for (int i = 2; i <= n; i++) {
if (A[i].laenge () > A[max].laenge ())
max = i;
}
// Sortiere die Zeichenketten
for (int z = A[max].laenge () - 1; z >= 0; z--) {
/* Verteile A[1] .. A[n] nach Zeichen z auf
fach[1] .. fach[m];
fach[0] enthaelt Zeichenketten,
die kein z-tes Zeichen haben */
for (int i = 1; i <= n; i++)
fach [A[i].zeichen (z) + 1].append (A[i]);
/* Sammle die Zeichen wieder ein */
int i = 0;
for (int j = 0; j <= m; j++) {
while (! fach[j].isEmpty()) {
A[++i] = fach[j].removeFirst();
}
}
}
}
}
99
100
KAPITEL 5. SPEZIELLE SORTIERVERFAHREN
5.3 Das Auswahlproblem
Problem: Finde das i-kleinste Element in einer Liste F mit n Elementen
Naive Losung
j = 0
while (j < i)
bestimme kleinstes Element a_{min}
entferne a_{min} aus F;
j = j + 1;
return a_{min}
Anzahl der Schritte: O(i n) fur i = n=2 (Median): O(n ) (Sortieren ist besser)
Verfahren mit Heap
2
verwandle F in einen min-Heap
j = 0;
while (j < i)
a_{min} = delete-min(F);
j = j + 1;
return a_{min}
Anzahl der Schritte: O(n + i log n)
fur i = n=2 (Median): O(n log n)
5.3.1 Implementation (Rahmen)
Basisklasse:
class SelectAlgorithm {
static void swap (Object A[], int i, int j) {
Object o = A[i]; A[i] = A[j]; A[j] = o;
}
static void select (Orderable A[], int i) {
IthElement.select(A, i);
}
static void printArray (Orderable A[]) {
for (int i = 1; i < A.length; i++)
System.out.print(A[i].toString()+" ");
System.out.println();
}
}
5.3. DAS AUSWAHLPROBLEM
In IthElement
101
:
extends SelectAlgorithm
public static Orderable select(Orderable A[],int i){
// Suche das i-groesste Element in A[1],...,A[n]
int n = A.length - 1;
A [0].minKey();
// Stopper
if (i <= n) return A[selectIndex (A, i, 1, n)];
return A[0];
}
5.3.2 Divide-&-Conquer Losung
Idee: Aufteilung von F = (a ; : : : ; an) in zwei Gruppen bzgl. Pivotelement p (Quicksort)
1
L<
Lin
p
Quicksort
p
L>
Quicksort
public static int selectIndex
(Orderable A[],int i,int l,int r) {
// Suche den Index des i-groessten Elementes
// in A[l],...,A[r]
if (r > l) {
int p = pivotElement(A, l, r);
int m = Quicksort.divide(A, l, r, p);
if (i <= m - l)
return selectIndex (A, i, l, m - 1);
return selectIndex (A, i - (m - l), m, r);
}
else return l;
}
Nur eine der zwei durch Aufteilung entstandenen Folgen wird weiter betrachtet.
KAPITEL 5. SPEZIELLE SORTIERVERFAHREN
102
5.3.3 Wahl des Pivotelements
1. p = ar folgt T (n) T (n 1) + O(n)
Laufzeit im schlimmsten Fall: O(n )
2
Beispiel: Auswahl des Minimums in aufsteigend sortierter Folge
2. Randomisiert
p = ein zufalliges Element aus a ; : : : ; an
erwartete Laufzeit: (U bung)
1
3. Median-of-Median
Input: eine Folge F = (a ; : : : ; an)
Output: ein Element a 2 F , so da
- jfaj j aj > agj 3(d d n ee 2) 3n=10 6
- jfaj j aj > agj 3(d d n ee 2) 3n=10 6
) Absplitten eines konstanten Teils = 7dn=10e + 6
1
1
2
1
2
5
5
5.3.4 Median-of-Median
Algorithmus:
1) Unterteile F in 5er-Gruppen G ,. . . ,Gdn= e
2) 8j 2 f1; : : : ; dn=5eg sort (Gj )
3) 8j 2 f1; : : : ; dn=5eg b[j ] Median (Gj )
4) return select (b; dn=10e; 1; dn=5e)
S (n) = # Vergleiche Select
G(n) + n + S (d7n=10 + 6e)
G(n) = # Vergleiche Median-of-Median
c00 n + S (dn=5e)
S (n) S (d7n=10 + 6e) + S (dn=5e) + c0n
Satz: S (n) cn
1
5
5.4. SORTIEREN VORSORTIERTER DATEN
5.4 Sortieren vorsortierter Daten
Spezielle Eingaben treten hauger auf, Daten sind vorsortiert
Vorsortierungsmae: 1. Anzahl der Inversionen
inv(F ) = jf(i; j ) j 1 j < i n; kj > kigj
Beispiel: 15 2 43 17 4 8 47
3 Inversionen, an denen 15 beteiligt ist; 3 + 3 + 2 = 8 Inversionen
F aufsteigend sortiert: inv(F ) = 0
nX
F absteigend sortiert: inv(F ) = i = n(n2 1)
i
inv mit die globale Vorsortierung
Beispiel:
n=2 + 1; : : : ; n; 1; : : : ; n=2
1
=1
2. Anzahl aufsteigend sortierter Teilfolgen
runs(F ) = jfi j 1 i n; ki > ki gj + 1
+1
= Anzahl aufsteigend sortierter Teilfolgen
Beispiel: F : 15 2 43 17 4 8 47
runs(F ) = 4
F aufsteigend sortiert: runs(F ) = 1
F absteigend sortiert: runs(F ) = n
runs mit die lokale Vorsortierung
3. Langste aufsteigende Teilfolge
las(F ) = Lange der longest ascending subsequence(F )
= maxft j 91 i < < it n;
ki1 ki2 ki g
1
t
1 las(F ) n
Beispiel:
F : 15; 2; 43; 17; 4; 8; 47, las(F ) = 4
rem(F ) = n las(F )
F aufsteigend: rem(F ) = 0
absteigend: rem(F ) = n 1
103
KAPITEL 5. SPEZIELLE SORTIERVERFAHREN
104
5.4.1 Optimale Nutzung der Vorsortierung
m-optimales, allgemeines Sortierverfahren
Ziel: fur jeden Wert m von m soll nur die fur Folgen dieses Vorsortiertheitsgrades notige
Schrittzahl verwendet werden.
Untere Schranke fur Anzahl der notigen Schlusselvergleiche Cm0 ?
Anzahl der Blatter im Entscheidungsbaum mit m(F ) m
0
0
jfF jm(F ) m gj:
0
)
Denition
Cm0 log jfF jm(F ) m gj
0
Ein Sortierverfahren A heit m-optimal, falls es eine Konstante c gibt, so da fur alle
n und alle Folgen F mit Lange n die Zeit TA(F ) zum Sortieren von F mit A wie folgt
beschrankt ist:
TA (F ) c (n + log jfF 0j m(F 0) m(F )gj):
 ALLGEMEINE SORTIERVERFAHREN
5.5. UNTERE SCHRANKE FUR
105
5.5 Untere Schranke fur allgemeine Sortierverfahren
Satz:
Zum Sortieren einer Folge von n Schlusseln mit einem allgemeinen Sortierverfahren sind
im Worst-case dlog n!e = (n log n) Vergleichsoperationen zwischen zwei Schlusseln erforderlich. Im Mittel benotigt man wenigstens dlog n!e 1 = (n log n) viele Vergleiche.
Bemerkung:
p n = n Nach der Stirling-Formel konvergiert der Quotient von n! und
2n
e gegen 1. Daher ist
+1 2
log n! n log n n log e + O(log n) n log n 1:4427n
Modellierung von allgemeinen Sortierverfahren
Entscheidungsbaume.
5.5.1 Entscheidungsbaume
Sortierverfahren A
Entscheidungsbaum TA;n zur Modellierung von des Ablaufs von A auf Folgen der Lange
n enthalt:
- fur jede der n! Permutationen ein Blatt
- innere Knoten repasentieren eine Vergleichsoperation und haben zwei Sohne
- Weg W von der Wurzel zu einem Blatt v:
- die Vergleiche an den Knoten von W identizieren die Permutation v von v
- entsprechen den von A durchgefuhrten Vergleichen, falls die Eingabe v ist
Beispiel:
Sortieren durch Einfugen fur Folge F =<k ; k ; k ; k > von 4 Schlusseln
1
2
3
4
1:4
AA
1423
4123
A
1:4
L
1432
4132
PP
P
1:3
b
"
"
b
"
b
PP
PP
3:4
L
4312
AA
2:4
L
4213
A
AA
2413
2143
L
1:4
"
"
L
L
3:4
2134
A
3412
3142
L
1:4
L
L
2:4
@
@
3124
@
AA
1:3
A
L
3:4
L
1342
1324
L
L
L
b
b
b
b
2:4
1243
b
2:4
L
L
1234
3:4
"
"
"
"
"
2:3 b
b
2:3
@
@
@
3:4
2:4
2431
1:4
2:4
3:4
4321
A
AA
L
L
3421
L
L
3241
3214
4231
A
AA
L
L
2341
L
L
2314
1:4
PP
1:2 PP
106
KAPITEL 5. SPEZIELLE SORTIERVERFAHREN
Kapitel 6
Suchverfahren und Suchbaume
107

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
108
6.1 Problemstellung
Problem: Gegeben Folge F = (a ; : : : ; an). Finde das Element mit Schlussel k.
Rahmen:
1
class SearchAlgorithm {
static int searchb(Orderable A[], Orderable k) {
return search(A,k);
}
}
Einfachstes Verfahren: Sequentielle, lineare Suche
class SequentialSearch extends SearchAlgorithm {
public static int search(Orderable A[],Orderable k){
/* Durchsucht A[1], .., A[n] nach Element k und
liefert den Index i mit A[i] = k; -1 sonst */
A[0] = k;
// Stopper
int i = A.length;
do i--; while (!A[i].equal(k));
if (i != 0)
// A[i] ist gesuchtes Element
return i;
else // es gibt kein Element mit Schluessel k
return -1;
}
}
Analyse:
- schlechtester Fall:
n+1
n
X
1
- im Mittel: n i = n1 n(n2+ 1) = n +2 1
i=1
 SUCHE
6.2. BINARE
6.2 Binare Suche
Klasse
class BinarySearch extends SearchAlgorithm
Hauptroutine
public static int search(Orderable A[],Orderable k){
/* Durchsucht A[1], .., A[n] nach Element k und
liefert den groessten Index i >= 1 mit
A[i] <= k; 0 sonst */
int n = A.length;
return search(A, 1, n, k);
}
Rekursiver Aufruf
public static int search
(Orderable A[], int l, int r, Orderable k){
/* Durchsucht A[1], .., A[n] nach Element k
und liefert den groessten Index
l <= i <= r mit A[i] <= k; l-1 sonst */
if (l > r) // Suche erfolglos
return l-1;
int m = (l + r) / 2;
if (k.less(A[m]))
return search(A, l, m - 1, k);
if (k.greater(A[m]))
return search(A, m + 1, r, k);
else // A[m] = k
return m;
}
6.2.1 Binare Suche ohne Rekursion
public static int search(Orderable A[],Orderable k){
int n = A.length, l = 1, r = n;
while (l <= r) {
int m = (l + r) / 2;
if (k.less(A[m])) { r = m - 1; }
else if (k.greater(A[m])) { l = m + 1; }
else return m;
109

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
110
}
return l-1;
}
Annahme: Binarer Vergl.-Operator mit 3 Ausgangen
Worst Case (n = 2k 1): bei k = log(n + 1) Vergl.
Average Case (n = 2k 1):
8
12
4
2
1
10
6
3
5
7
14
11
9
13
15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
6.2.2 Analyse
Auswertung: Pki i2i
=1
1
1 2k
=
... =
1 2 + : : : + 1 2k =
1 2 + 1 2 + : : : + 1 2k =
=
0
1
1
1
1
1
2k
...
2k
2k
k2k
2k
2
1
1
2k + 1
 SUCHE
6.2. BINARE
111
Erwartungswert:
k
X
E = ( i2i )=n
i=1
(k2k
1
=
2k + 1)=n
= ((n + 1) log(n + 1))=n (n + 1)=n + 1=n
= (n + 1) log(n + 1)=n log(n + 1) 1

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
112
6.3 Fibonacci-Suche
Erinnerung:
F = 0; F = 1; Fn = Fn + Fn
0
1
1
Verfahren:
fur (n 2):
2
Vergleiche den Schlussel an i = Fn mit k.
- A[i]:key > k: Durchsuche linke Fn
1 Elemente
- A[i]:key < k: Durchsuche rechte Fn
1 Elemente
1
i
2
2
1
Fn
2
Fn
1
N
1
1
Fn 1
Analyse: Durchsuchen von Fn 1 Elementen in max. n Schlusselvergleichen. Nun ist
p !n!
p !n
1
1
+
5
1
5
Fn = p
2
2
5
n
c 1:618 ; mit einer Konstanten c:
Ergo: Cmax (N ) = O(log : (N + 1)) = O(log N )
1 618
2
6.3.1 Implementation
public static int search(Orderable A[],Orderable k){
/* Durchsucht A[1], .., A[n] nach Element k und
liefert den Index i mit A[i] = k; -1 sonst */
int n = A.length-1;
int fibMinus2 = 1, fibMinus1 = 1, fib = 2;
while (fib - 1 < n) {
fibMinus2 = fibMinus1;
fibMinus1 = fib;
fib
= fibMinus1 + fibMinus2;
}
int offset = 0;
6.3. FIBONACCI-SUCHE
while (fib > 1) {
/* Durchsuche den Bereich [offset+1,offset+fib-1]
nach Schluessel k (Falls fib = 2, dann besteht
[offset+1,offset+fib-1] aus einem Element!) */
int m = min(offset + fibMinus2,n);
if (k.less(A[m])) {
// Durchsuche [offset+1,offset+fibMinus2-1]
fib
= fibMinus2;
fibMinus1 = fibMinus1 - fibMinus2;
fibMinus2 = fib - fibMinus1;
}
else if (k.greater(A[m])) {
// Durchsuche [offset+fibMinus2+1,offset+fib-1]
offset
= m;
fib
= fibMinus1;
fibMinus1 = fibMinus2;
fibMinus2 = fib - fibMinus1;
}
else // A[m] = k
return m;
}
return -1;
}
}
113

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
114
6.4 Exponentielle Suche
Annahme: n sehr gro, i mit ai = k klein
Denke-Zahl-Aus: Logarithmische Anzahl von Fragen
Eingabe: Sortierte Folge a ; : : : ; an, Schlussel k
Ausgabe: Index i mit ai = k
1
j = 1;
while (k > a_j) j = 2*j;
return search (a,j/2,j,k);
Index j
von aj
1 2
4
Analyse:
- aj k: dlog ie
- Binare Suche: 2dlog(i=2 + 1)e
Gesamtaufwand: O(log i)
8
a8 < k
i
16
a16 > k
6.5. INTERPOLATIONSSUCHE
115
6.5 Interpolationssuche
Idee: Suche von Namen im Telefonbuch, z.B. Bayer und Zimmermann
l
al
?
Suchschlussel
k
ar a
r
Erwartete Position von k (bei Gleichverteilung aller gespeicherten Schlussel):
Analyse:
l + (r l) ak aal
r
l
- im schlechtesten Fall: O(n)
- im Mittel bei Gleichverteilung: O(log log n)

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
116
6.6 Selbstanordnende lineare Listen
MF-Regel (Move-to-front): Mache ein Element zum ersten Element der Liste, nachdem
auf das Element (als Ergebnis einer erfolgreichen Suche) zugegrien wurde.
T-Regel (Transpose): Vertausche ein Element mit dem unmittelbar vorangehenden, nachdem auf das Element zugegrien wurde.
FC-Regel (Frequency Count): Nach jedem Zugri auf ein Element wird dessen Haugkeitszahler um 1 erhoht. Ferner wird die Liste nach jedem Zugri neu geordnet und zwar
so, da die Haugkeitszahler der Elemente in absteigender Reihenfolge sind.
Beispiel Liste L = f1; 2; 3; 4; 5; 6; 7g.
a) Greife 10x nacheinander auf 1; : : : ; 7 zu.
b) Greife 10x
auf 1, dann zehnmal
auf 2, usw. zu.
78
78
MTF: a) 2 = 6:7
b) 2 = 1:3
Durchschnittliche (statischen) Zugriskosten: (10 Pi i)=70 = 4
+7 9 7
10 7
+9 7 1
10 7
7
=1
6.6.1 Gute von Move-To-Front
Experimente: MTF besser als T und FC (U bung)
Ziel: Vergleich MTF mit beliebiger Strategie
s = s s s : : : sm : Folge von Suchanfragen
A : Verfahren
zur Selbstanordnung
P
m
CA(s) = P i CA(si) : Kosten zur Verarbeitung von s
VA(s) = Pmi VA(si) : # Vertauschungen nach vorn
HA(s) = mi HA(si) : # Vertauschungen n. hinten
Bemerkung: Fur MTF-, T- und FC-Regel gilt: HMTF (s) = HT (s) = HFC (s) = 0
Weiterhin: VA(s) CA(s) m.
Satz: Fur jeden Algorithmus A zur Selbstanordnung von Listen und fur jede Folge s von
m Zugrisoperationen gilt
CMTF (s) 2 CA(s) + HA(s) VA(s) m:
D.h. (grob) MTF-Regel hochstens doppelt so schlecht ist wie jeder andere Algorithmus
zur Selbstanordnung von Listen.
1 2 3
=1
=1
=1
6.6.2 Amortisierung
Ziel: Durchschnittlicher Aufwand fur eine schlechtestmogliche Folge von Operationen
Potentialmethode (Tarjan et al.)
Bankkontoparadigma (Mehlhorn et al.)
6.6. SELBSTANORDNENDE LINEARE LISTEN
117
Idee: Zahle fur billige Operationen etwas mehr und verwende Erspartes, um fur teure
Operationen zu zahlen
Kosten: tl wirkliche Kosten der l-ten Operation
Kontostand/Potential l nach Ausfuhrung der l-ten Operation
Amortisierte Zeit: al wirkliche Schrittzahl (Zeit) tl plus die Dierenz l l der
1
Kontostande
m
X
l=1
m
X
l=1
al =
tl =
6.6.3 Exkurs Binarzahler
l = # Einsen im Zahler
m
X
l=1
m
X
l=1
tl + m ; also
m
X
al + m al
0
0
l=1

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
118
Operation
0
Zahlerstand
0000
tl
1
0001
1
1
2
2
0010
2
1
2
3
0011
1
2
2
4
0100
3
1
3 + (1 2) = 2
5
0101
1
2
2
6
0110
2
2
2
7
0111
1
3
2
8
1000
4
1
4 + (1 3) = 2
9
1001
..
.
1
2
tl
l
2
..
.
al = 2
..
.
l
..
.
m
l
al = tl + l
l 1
0
6.6.4 Move-To-Front Analyse
Kontostand: Sei LAl Liste nach l-ter Operation fur Verfahren A, bal(L ; L ) Anzahl der
Inversionen in einer Liste L bzgl. L , d.h. bal(L ; L ) =
jf(i; j ) j pos (i) > pos (j ) und pos (i) < pos (j )gj
1
1
1
2
2
1
1
2
2
2
l = bal(LAl ; LMF
l )
a) = bal(LA ; LMF ) = 0 und b) l 0
0
Beispiel:
0
0
L : 4; 3; 5; 1; 7; 2; 6
L : 3; 6; 2; 5; 1; 4; 7
In L : 3 vor 4, 6 vor 2, 6 vor 5, 6 vor 1, 6 vor 4, 6 vor 7, 2 vor 5, 2 vor 1, 2 vor 4, 2 vor 7,
5 vor 4, 1 vor 4, aber in L der Reihe nach jeweils die umgekehrte Relation; alle anderen
Paare stehen in L und L in derselben Anordnung.
1
2
2
1
2
1
6.6. SELBSTANORDNENDE LINEARE LISTEN
119
! bal(L ; L ) = 12 = bal(L ; L ).
Umbenennung:
1
2
2
1
L : 1; 2; 3; 4; 5; 6; 7
L : 2; 7; 6; 3; 4; 1; 5
1
2
6.6.5 Schlufolgerung
Wissen: tAl = i und tMF
l =k
1
LA :
:::
2
1
r r r r r
xi
r r r r r
i
xi
k
LMTF :
i
:::
xi : # Elemente vor i in LMTF und nach i in LA (k 1 xi) : # Elemente vor i in LMTF
und LA
tl +)bal(xLi A+0 ; (LkMTF10) xibal
) A(sl ):
bal(LA 0 ; LMTF 0) = bala(lLA=; LMTF
) (LVAA;(LslMTF
)+H
Damit:
= k xi + (k 1 xi) VA(sl ) + HA(sl )
= 2(k xi ) X
m1 VA (sl ) + HA (sl )
) CMTF2(is) 1 VA(sal )l + bal
HA((L;sl )L: ) bal(L0 ; L00)
l=1
2CA(s) + HA(s) VA(s) m
120
6.7 Suchbaume

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
Baume: Baume sind verallgemeinerte Listenstrukturen.
Nachfolger: Endlich viele Kinder/Sohne eines direkten Vorgangers/Vaters, i.a.
(an)geordnet (erstes, zweites, drittes Kind). Ordnung : max. Anzahl von Kindern.
Darstellung: (ungerichgeter) Verbund von Knoten mit ausgezeichneter Wurzel. Knoten
ohne (bzw. mit null)-Nachfolger heien Blatter, sonst innere Knoten.
Rekursive Denition: Baume mit Ordnung d, Hohe h:
(1) Der aus einem einzigen Knoten bestehende Baum ist ein Baum der Ordnung d. Die
Hohe h ist 0.
(2) Sind t1; : : : ; td beliebige Baume der Ordnung d, so erhalt man einen (weiteren) Baum
der Ordnung d, indem man die Wurzeln von t1 ; : : : ; td zu Sohnen einer neugeschaenen
Wurzel w macht. Die Hohe h ist maxfh(t1); : : : ; h(td )g + 1
w
b
b
b
A
1A
A
t
b
b
p
p
p
p
p
p
p
p bp A
A
t
A
2 A
b
t
A
d A
Festlegung: d = 2 Binarbaume, d > 2 Vielwegbaume.
6.7.1 Suchbaume
Annahme: Total geornete Menge von Schlusseln.
Charakterisierung: Die Schlussel im linken Teilbaum von p sind samtlich kleiner als
der Schlussel von p, und dieser ist wiederum kleiner als samtliche Schlussel im rechten
Teilbaum von p.
Implementation: (Schlussel ist Ganzzahl)
public class Knoten {
Knoten leftson, rightson;
int key; // Hier koennte noch ein Infotype kommen
Knoten (int key) { this.key = key; }
public String toString() {
StringBuffer st = new StringBuffer(""+key);
return st.toString();
}
}

6.7. SUCHBAUME
Worterbuch
121
-
Dictionary: Operationen Suchen/Einfugen/Entfernen
search/insert/delete
public class Baum {
Knoten Wurzel;
Baum() { Wurzel = null; }
public Knoten Suchen(int k) ...
public void Einfuegen(int k) ...
public void Entfernen(int k) ...
public String toString() ...
}
6.7.2 Beispiel
Ohne Stopper:
Wurzel
PP
PP
q
q14 q
Mit Stopper:
HH
HH
HH
HH
H
j
H
3
q qPPP
q1 q
q27 qHH
-
q 15 q
q39 q
-

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
122
Wurzel
27
q
qHH
-
q1 q
HH
H
HH
HH
j
H
q 3 Pq PP
HH
PP
PP
q
q39 q
q 15 q
q14 q
6.7.3 Einfugen/Suchen
Top-Level Aufrufe:
public Knoten Suchen(int k) {
return Suchen(Wurzel,k);
}
public void Einfuegen(int k) {
Wurzel = Einfuegen(Wurzel,k);
}
Rekursive Aufrufe:
Knoten Suchen(Knoten p, int x) {
if (p == null) return null;
if (x < p.key)
return Suchen(p.leftson,x);
else
if (x > p.key)
return Suchen(p.rightson,x);
return p;
}
Knoten Einfuegen(Knoten p, int k) {
if (p == null) return new Knoten(k);
- ? ?
-
qx q

6.7. SUCHBAUME
123
else
if (k < p.key)
p.leftson = Einfuegen(p.leftson,k);
else
if (k > p.key)
p.rightson = Einfuegen(p.rightson,k);
else
return null; // Schluessel kam vor
return p;
}
6.7.4 Sonderfalle
Entartung:
q 1 qA
A
AU
q 3 qA
A
AU
q 14 qA
A
AU
q 15 qA
A
AU
27
q qA
A
Loschen eines Knotens:
AU

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
124
q x q@
p
y
q q@
p
@
@
@
@
@
@
R
@
q q
pp
p
@
R
@
q q
=)
y
q qA
6.7.5 Loschen AAU
q
public void Entfernen(int k) {
Wurzel = Entfernen(Wurzel,k);
}
Knoten vatersymnach (Knoten p) {
if (p.rightson.leftson != null) {
p = p.rightson;
while (p.leftson.leftson != null)
p = p.leftson;
}
return p;
}
Knoten Entfernen(Knoten p, int k) {
if (p == null) return p;
if (k < p.key)
p.leftson = Entfernen(p.leftson,k);
else
if (k > p.key)
p.rightson = Entfernen(p.rightson,k);
else {
@
q
pp
p
y
q qA
A
AU

6.7. SUCHBAUME
125
if (p.leftson == null)
return p.rightson;
if (p.rightson == null)
return p.leftson;
Knoten q = vatersymnach(p);
if (q == p) {
p.key = p.rightson.key;
q.rightson = q.rightson.rightson;
}
else {
p.key = p.leftson.key;
q.leftson = q.leftson.rightson;
}
}
return p;
}
6.7.6 Durchlaufreihenfolgen
Voraussetzung:
Sei T (r) ein geordneter Baum (die Kinder eines Knotens sind linear geordnet) mit Wurzel
r und Kindern v ; : : : ; vn.
Denition:
1
Dann ist der Postorderdurchlauf post(T ) deniert als post(T (v )) : : : post(T (vk )) r. Entsprechend sind der Inorderdurchlauf in(T ) als in(T (v )) r in(T (v )) : : : in(T (vk )) und der
Preorderdurchlauf pre(T ) als r pre(T (v )) : : : pre(T (vk )) festgelegt.
1
1
2
1
1. Preorder und Inorder beschreiben einen Baum T nicht eindeutig. Gegenbeispiel:
pre(T ) = pre(T ) = 1; 2; 3; 4; 5; 6
in(T ) = in(T ) = 2; 1; 4; 3; 5; 6
Einen binaren Baum ubrigens schon (U bung)
2. Preorder und Postorder beschreiben T eindeutig (U bung).
1
1
2
2
6.7.7 Implementation
String Inorder(Knoten p) { // Symm. Reihenfolge
if (p == null) return new String("");
StringBuffer st = new
StringBuffer(Inorder(p.leftson) +
"(" + p.toString() + ")" +
Inorder(p.rightson));
126

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
return st.toString();
}
String Preorder(Knoten p) { // Hauptreihenfolge
if (p == null) return new String("");
StringBuffer st = new
StringBuffer("(" + p.toString() + ")" +
Preorder(p.leftson) +
Preorder(p.rightson));
return st.toString();
}
String Postorder(Knoten p) { // Nebenreihenfolge
if (p == null) return new String("");
StringBuffer st = new
StringBuffer(Postorder(p.leftson) +
Postorder(p.rightson) +
"(" + p.toString() + ")");
return st.toString();
}
public String toString() {
StringBuffer st = new StringBuffer(
"Preorder : " + Preorder(Wurzel) + "\n" +
"Inorder : " + Inorder(Wurzel) + "\n" +
"Postorder: " + Postorder(Wurzel));
return st.toString();
}
6.7.8 Interne Pfadlange
(0): Ist t ein Blatt, so ist I (t) = 0.
(1): Ist t ein Baum mit linkem Teilbaum mit Wurzel tl und rechtem Teilbaum mit Wurzel
tr , so ist
X
I (t) := I (Itl()t+
inneren
) =I (tr ) + Zahl der(Tiefe(
p) Knoten
+ 1) von t:
p
p innerer
Knoten von t
EI (n) : Erwartungswert fur die interne Pfadlange eines zufallig erzeugten binaren Suchbaumes mit n inneren Knoten, so gilt
EI (0) = 0; EI (1) = 1;
n
X
EI (n) = n1 (EI (k 1) + EI (n k) + n)
k
=1

6.7. SUCHBAUME
127
n
X
n
X
=1
=1
= n + 1 ( EI (k 1) + EI (n k))
nk
k
Satz: EI (n) 1:386n log n 0:846n + O(log n):
Beweis: QUICKSORT Average-Case (+2N )
Beweis:
2
I) QUICKSORT Average-Case (+2N )
II) Direkt:
N
X
EI (N + 1) = (N + 1) + N 2+ 1 EI (k);
k=0
und daher
(N + 1) EI (N + 1) = (N + 1) + 2 2
N EI (N ) = N + 2 2
NX1
k=0
N
X
k=0
EI (k)
EI (k):
Aus den beiden letzten Gleichungen folgt
(N + 1)EI (N + 1) N EI (N ) = 2N + 1 + 2 EI (N )
(N + 1)EI (N + 1) = (N + 2)EI (N ) + 2N + 1
N + 2 EI (N ):
EI (N + 1) = 2NN ++11 + N
+1
Nun zeigt man leicht durch vollstandige Induktion uber N , da fur alle N 1 gilt:
EI (N ) = 2(N + 1)HN 3N
Dabei bezeichnet HN = 1+ +: : :+ N die N -te harmonische Zahl, die wie folgt abgeschatzt
werden kann:
HN = ln N + + 21N + O( N1 )
1
2
1
2
Dabei ist = 0:5772 : : : die sogenannte Eulersche Konstante.
Damit ist
EI (N ) = 2N ln N (3 2 ) N + 2 ln N + 1 + 2 + O( N1 )
128

KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME
und daher
EI (N ) = 2 ln N (3 2 ) + 2 ln N + : : :
N
N
2
=
log N (3 2 ) + 2 lnN + : : :
log e
N
2
log
2
= log e log N (3 2 ) + 2 lnNN + : : :
1:386 log N (3 2 ) + 2 lnNN + : : :
2
2
10
2
10
2
Kapitel 7
Balancierte Baume
129
130
7.1 Durchlaufreihenfolgen

KAPITEL 7. BALANCIERTE BAUME
Voraussetzung:
Sei T (r) ein geordneter Baum (die Kinder eines Knotens sind linear geordnet) mit Wurzel
r und Kindern v ; : : : ; vk (dabei ist k = 0 moglich).
Denition:
1
Dann ist der Postorderdurchlauf post(T ) deniert als post(T (v )) : : : post(T (vk )) r. Entsprechend sind der Inorderdurchlauf in(T ) als in(T (v )) r in(T (v )) : : : in(T (vk )), der
Preorderdurchlauf pre(T ) als r pre(T (v )) : : : pre(T (vk )) und der Levelorderdurchlauf als
lev(T ) als r lev(T (v )) r lev(T (v )) : : : r lev(T (vk )) r festgelegt.
1
1
2
1
1
2
7.1.1 Eigenschaften
Beispiel:
Preorder: D,E,B,H,K,L,I,F,G,C,A
Postorder: A,B,D,E,C,F,H,I,K,L,G
Inorder: D,B,E,A,H,F,K,I,L,C,G
Levelorder: A,B,D,B,E,B,A,C,F,H,F,I,K,I,L,I,C,G,C,A
1. Pre- In-, und Postorder haben jeweils die Lange n.
2. Pre- In-, und Postorder allein nicht eindeutig.
3. Levelorder hat die Lange 2n 1
4. Levelorder ist eindeutig. (Induktion: jlev(T )j = 1 ist klar. Ansonsten ist der erste
Knoten Wurzel. Zwischen den verschiedenen Darstellungen der Wurzel nden wir
die Levelorder der Teilbaume.)
5. Preorder und Inorder beschreiben einen Baum T nicht eindeutig. Gegenbeispiel:
pre(T ) = pre(T ) = 1; 2; 3; 4; 5; 6
in(T ) = in(T ) = 2; 1; 4; 3; 5; 6
Einen binaren Baum ubrigens schon (U bung)
6. Preorder und Postorder beschreiben T eindeutig (U bung).
1
1
2
2
7.1.2 Sortieren mit naturlichen Suchbaumen
Idee: Baue fur die Eingabefolge einen naturlichen Suchbaum auf und gebe die Schlussel
in symmetrischer Reihenfolge (Inorder) aus.
7.1. DURCHLAUFREIHENFOLGEN
131
Bemerkung: Abhangig von der Eingabereihenfolge kann der Suchbaum degenerieren.
Komplexitat: Abhangig von der internen Pfadlange
Schlechtester Fall: Vorsortierung ) (n ) Schritte.
Bester Fall: Ausgeglichener Suchbaum (Analyse ahnlich wie die des mittleren Falles von
binarer Suche) ) O(n log n) | mit Faktor 1 vor n log n
Mittlerer Fall: Erwartungswert fur interne Pfadlange ist 1:386n log n 0:846n +
2
2
O(log n).
P.S. Beliebte Prufungsfrage
7.1.3 Interne Pfadlange
(0): Ist t ein Blatt, so ist I (t) = 0.
(1): Ist t ein Baum mit linkem Teilbaum mit Wurzel tl und rechtem Teilbaum mit Wurzel
tr , so ist
X
I (t) := I (Itl()t+
inneren
p) Knoten
+ 1) von t:
) =I (tr ) + Zahl der(Tiefe(
p
p innerer
Knoten von t
EI (n) : Erwartungswert fur die interne Pfadlange eines zufallig erzeugten binaren Suchbaumes mit n inneren Knoten, so gilt
EI (0) = 0; EI (1) = 1;
n
X
EI (n) = n1 (EI (k 1) + EI (n k) + n)
k
n
n
X
X
= n + n1 ( EI (k 1) + EI (n k))
k
k
=1
=1
Satz: EI (n) 1:386n log n 0:846n + O(log n):
Beweis: QUICKSORT Average-Case (+2N )
2
7.1.4 Implementation
String Inorder(Knoten p) { // Symm. Reihenfolge
if (p == null) return new String("");
StringBuffer st = new
StringBuffer(Inorder(p.leftson) +
"(" + p.toString() + ")" +
Inorder(p.rightson));
return st.toString();
}
=1
132

KAPITEL 7. BALANCIERTE BAUME
String Preorder(Knoten p) { // Hauptreihenfolge
if (p == null) return new String("");
StringBuffer st = new
StringBuffer("(" + p.toString() + ")" +
Preorder(p.leftson) +
Preorder(p.rightson));
return st.toString();
}
String Postorder(Knoten p) { // Nebenreihenfolge
if (p == null) return new String("");
StringBuffer st = new
StringBuffer(Postorder(p.leftson) +
Postorder(p.rightson) +
"(" + p.toString() + ")");
return st.toString();
}
public String toString() {
StringBuffer st = new StringBuffer(
"Preorder : " + Preorder(Wurzel) + "\n" +
"Inorder : " + Inorder(Wurzel) + "\n" +
"Postorder: " + Postorder(Wurzel));
return st.toString();
}

7.2. AVL-BAUME
133
7.2 AVL-Baume
Namensgebung: Schopfer Adelson-Velskii und Landis (1962)
Denition: Ein binarer Suchbaum heit AVL-Baum, wenn fur jeden Knoten gilt, da
sich die Tiefe des rechten Teilbaumes d(Tr ) und die Tiefe des linken Teilbaumes d(Tl) um
maximal 1 unterscheiden.
Balancegrad: b(v) = d(Tl) d(Tr ) 2 f 1; 0; 1g
Satz: Die Tiefepeines AVL-Baumes
p mit n Daten betragt mindestens dlog(n + 1)e 1 und
hochstens log( 5(n + 2))= log(( 5 + 1)=2) 1:44 log n:
Beweis: Die untere Schranke gilt sogar fur alle binaren Suchbaume.
z.Z. minimale Anzahl der Knoten in AVL-Baum A(d) der Tiefe d ist F (d + 2) 1, denn
F (dp+ 2) 1 = A(d) pn ) d+3 log( 5(n + 2))= log(( 5 + 1)=2) 1:44 log n
Nun ist gema der Balancebedingung
A(d) = 1 + A(d 1) + A(d 2) =
1 + F (d + 1) 1 + F (d) 1 = F (d + 2) 1
7.2.1 Einfugen
O.b.d.A. Suchpfad ruckwarts vom rechten Sohn. Die Tiefe des rechten Teilbaumes sei
gewachsen.
Fall 1. Vorher b(v) = 1. Setze b(v) = 0.
Fall 2. Vorher b(v) = 0. Setze b(v) = 1. (Rebalancierung am Vater fortsetzen.)
Fall 3. Vorher b(v) = 1 (aktuell gilt b(v) = 2). Sei x rechter Sohn von v und w der
linke Sohn von x.
Fall 3a. Rechter Teilbaum von x gewachsen: Linksrotation an v. Setze b(v) = 0; b(x) = 0
Fall 3b. Rechter Teilbaum von w gewachsen: Rechtsrotation an x, dann Linksrotation an
v. Setze b(w) = 0; b(v) = 1; b(x) = 0
Fall 3c. Linke Teilbaum von w gewachsen: Rechtsrotation an x, dann Linksrotation an
v. Setze b(w) = 0; b(v) = 0; b(x) = 1
Fall 3d. w hat keinen Sohn. Rechtsrotation an x, dann Linksrotation an v. Setze b(w) =
0; b(v) = 0; b(x) = 1
Rebalancierung nach einer Rotation oder Doppelrotation abgeschlossen.
7.2.2 Entfernen
O.b.d.A. Datum in linken Teilbaum von v geloscht.
Fall 1. Vorher b(v) = 1. Setze b(v) = 0. (Rebalancierung am Vater fortsetzen.)
Fall 2. Vorher b(v) = 0. Setze b(v) = 1.
134

KAPITEL 7. BALANCIERTE BAUME
Fall 3. Vorher b(v) = 1 (aktuell gilt b(v) = 2). Sei x rechter Sohn von v und w der
linke Sohn von x.
Fall 3a. Beide Teilbaum von x haben gleiche Tiefe: Linksrotation an v. Setze
b(v) = 1; b(x) = 1
Fall 3b. Rechte Teilbaum von x um 1 tiefer als der linke Teilbaum: Linksrotation an v.
Setze b(v) = 0; b(x) = 0: (Rebalancierung am Vater fortsetzen.)
Fall 3c. b(x) = 1; b(w) = 1: Rechtsrotation an x, dann Linksrotation an v. Setze
b(w) = 0; b(v) = 0; b(x) = 1. (Rebalancierung am Vater fortsetzen.)
Fall 3d. b(x) = 1; b(w) = 1: Rechtsrotation an x, dann Linksrotation an v. Setze
b(w) = 0; b(v) = 0; b(x) = 0. (Rebalancierung am Vater fortsetzen.)
Fall 3e. b(x) = 1; b(w) = 1. Rechtsrotation. (Rebalancierung am Vater fortsetzen.) an
x, dann Linksrotation an v. Setze b(w) = 0; b(v) = 1; b(x) = 0. (Rebalancierung am Vater
fortsetzen.)
7.2.3 Implementation
Knotenklasse:
class AVLNode {
int content;
// Inhalt, hier integer
byte balance;
// fuer Werte -2,-1,0,1,+2
AVLNode left;
// linker Nachfolger
AVLNode right;
// rechter Nachfolger
AVLNode (int c){
// Konstruktor fuer Knoten
content = c;
//
uebergebener Inhalt
balance = 0;
//
Balance ausgeglichen
left = right = null; }// erst keine Nachfolger
}
Baumklasse:
class AVLTree {
AVLNode root;
// Die Wurzel des Baumes
boolean grown, shrunk, found; // Hilfsvariablen
AVLTree () {
root = null;
grown = shrunk = found = false;
}
boolean search (int c) ...
boolean insert (int c) {

7.2. AVL-BAUME
found = false; grown = true;
root = insert (root, c);
return !found;
}
AVLNode insert (AVLNode n, int c) ...
void rotateRight (AVLNode n) ...
void rotateLeft (AVLNode n) ...
boolean delete (int c) {
found = shrunk = true; root = delete(root, c);
return found;
}
AVLNode delete (AVLNode n, int c) ...
}
7.2.4 Rotationen
Einfache Rotation nach rechts:
void rotateRight (AVLNode n) {
AVLNode m = n.left;
int cc = n.content;
n.content = m.content;
m.content = cc;
n.left = m.left;
m.left = m.right;
m.right = n.right;
n.right = m;
int bm = 1+Math.max(-m.balance,0)+n.balance;
int bn = 1+m.balance + Math.max(0,bm);
n.balance = (byte)bn;
m.balance = (byte)bm;
}
Einfache Rotation nach links:
void rotateLeft (AVLNode n) {
AVLNode m = n.right;
int cc = n.content;
n.content = m.content;
m.content = cc;
n.right = m.right;
m.right = m.left;
135

KAPITEL 7. BALANCIERTE BAUME
136
m.left = n.left;
n.left = m;
int bm = -(1+Math.max(+m.balance,0)-n.balance);
int bn = -(1-m.balance + Math.max(0,-bm));
n.balance = (byte)bn;
m.balance = (byte)bm;
}
7.2.5 Einfugen
Fuge c in Teilbaum ein:
AVLNode insert (AVLNode n, int c){
if (n == null) return new AVLNode (c);
if (c == n.content) { found = true;
grown = false;
} else {
if (c < n.content) {
n.left = insert (n.left, c);
if (grown) n.balance--;
} else {
n.right = insert (n.right, c);
if (grown) n.balance++;
}
switch (n.balance) {
case -2: if (n.left.balance == +1)
rotateLeft (n.left); // D-Rot
rotateRight (n);
grown = false; break;
case -1: break;
case 0: grown = false; break;
case +1: break;
case +2: if (n.right.balance == -1)
rotateRight (n.right); // D-Rot
rotateLeft (n);
grown = false; break;
default: balanceError (n); break;
}
}
return n;
}

7.2. AVL-BAUME
7.2.6 Loschen
AVLNode delete (AVLNode n, int c){
if (n == null) { found = shrunk = false;
return n; }
if (c == n.content) {
if (n.left == null) return n.right;
if (n.right == null) return n.left;
n.content = c = minValue (n.right);
}
if (c < n.content) {
n.left = delete (n.left, c);
if (shrunk) n.balance++;
} else {
n.right = delete (n.right, c);
if (shrunk) n.balance--;
}
switch (n.balance) {
case -2: switch (n.left.balance) {
case +1: rotateLeft(n.left); break;
case 0: shrunk = false; break;
case -1: break;
default: balanceError (n.left); break;
}
rotateRight (n); break;
case -1: shrunk = false; break;
case 0: break;
case +1: shrunk = false; break;
case +2: switch (n.right.balance) {
case -1: rotateRight(n.right); break;
case 0: shrunk = false; break;
case +1: break;
default: balanceError (n.right); break;
}
rotateLeft (n); break;
default: balanceError (n); break;
}
return n;
}
137
138
7.3 Bayer-Baume

KAPITEL 7. BALANCIERTE BAUME
Denition: Ein Baum heit B -Baum der Ordnung m, wenn die folgenden Eigenschaften
erfullt sind.
1. Jeder Knoten mit Ausnahme der Wurzel enthalt mindestens dm=2e 1 Daten. Jeder
Knoten enthalt hochstens m 1 Daten. Die Daten sind sortiert.
2. Knoten mit k Daten x ; : : : ; xk haben k + 1 Zeiger, die auf die Bereiche
(; x ); (x ; x ); : : : ; (xk ; xk ); (xk ; ) zeigen.
3. Die Zeiger, die einen Knoten verlassen, sind entweder alle null-Zeiger (Blatter) oder
alle echte Zeiger.
4. Alle Blatter haben gleiche Tiefe.
Nutzung: Externe Speicherung (m Seitengroe)
Bemerkung: 2-3 Baume sind B -Baume der Ordnung 3.
Speicherauslatung: Fur alle Knoten mindestens 50 Prozent.
1
1
1
2
1
7.3.1 Tiefe
Satz: Tiefe eines B -Baumes der Ordnung m mit n Daten liegt im Intervall [dlogm(n +
1) 1e; dlogdm= e ((n + 1)=2)e]
Beweis: Analog zu
Satz: Die Tiefe von 2-3-Baumen mit n Daten betragt mindestens dlog (n + 1)e 1 und
hochstens dlog(n + 1)e 1
Beweis: Maximale Datenzahl in Tiefe d is 2(1 + 3 + : : : + 3d) = 3d 1 (vollstandiger
2
3
+1
ternarer Baum)
Minimale Datenzahl in Tiefe d is 1 + 2 + : : : + 2d = 2d
Fur die Tiefe d eines 2-3 Baumes mit n Daten gilt:
3d
1 n und d dlog (n + 1)e 1
und
2d
1 n und d dlog(n + 1)e 1
+1
+1
1 (vollstandiger binarer Baum)
3
+1
7.3.2 Einfugen
Suche von x In Knoten v wird x mit binarer Suche in logm Vergleichen eingefugt mit
log m + logm n log n
Nach erfolgloser Suche
Einfugen von x Gefundener null-Zeiger wird um x erweitert.
Allgemein ist Zeiger auf Knoten mit Datum y gegeben, so da die Blatter des zugehorigen
Teilbaumes eine Ebene zu tief liegen. Enthalt der Knoten (inclusive m)

7.3. BAYER-BAUME
139
- weniger als m 1 Daten, dann wird y eingefugt.
- Genau m Daten z < : : : < zm , d.h. Knoten ist voll.
Bilde 3 Knoten mit den folgenden Daten:
z ; : : : ; zdm= e (links, dm=2e -1 Daten)
zdm= e ; : : : ; zm (rechts, dm=2e -1 Daten)
y (Vater, 1 Datum)
und verteile die Nachfolgezeiger des ursprunglichen Knotens
Rekursion: Gehe Suchpfad einen Schritt zuruck und fahre analog fort
1
1
2
1
2 +1
7.3.3 Loschen
Nach erfolgreicher Suche:
1. Wenn x nicht in einem Blatt abgelegt ist, wird x mit dem groten Datum y < x
vertauscht. Dieses Datum ist mit Sicherheit in einem Blatt
2. Entferne x und null-Zeiger. Enthalt der Knoten (exclusive m)
- mindestens dm=2e Daten, dann wird x geloscht
- ein Bruder mindestens dm=2e Daten, dann genugt eine einfache Datenrotation
- alle Bruder dm=2e Daten, dann wird kontraktiert.
Betrachte o.b.d.A. linken Bruder und x0 als trennendes Datum im Vater. Dieses x0 wird
als Knoten extrahiert.
Nun wird aus den 3 Knoten ein Knoten mit 2dm=2e 2 m 1 Daten und
dm=2e + dm=2e 1 Nachfolgezeiger
Rekursion: Gehe Suchpfad einen Schritt zuruck und fahre analog fort
7.3.4 Anhangen (2-3 Baum)
Ziel: Konstruiere aus den 2-3 Baumen T und T , wobei alle Daten in T kleiner als alle
1
2
1
Daten in T sind, den 2-3-Baum T , der die Daten aus T und T enthalt.
Annahme: Schlussel in Blattern, innere Knoten entalten da grote Datum des linken
Teilbaumes und (falls vorhanden) des mittleren Teilbaumes.
1. Fall: d(T ) = d(T ). T besteht nur aus einer Wurzel, die als neuer Knoten da groere
Datum z aus T und als Teilbaume T (ohne dieses Datum) und T enthalt.
2. Fall: O.B.d.A. d(T ) > d(T ). Bilde T 0 mit Zeiger auf die Wurzel von T . Gehe von der
Wurzel in T d(T ) d(T ) 1 mal zum rechten Kind und ende an v.
2a. v hat enthalt ein Datum x: Schreibe x mit den 2 Teilbaumen in die Wurzel T 0,
zusatzlich kommt da groere Datum z aus T in die Wurzel.
2
1
1
2
1
1
1
1
2
1
2
2
2
2
1

KAPITEL 7. BALANCIERTE BAUME
140
2b. v hat enthalt zwei Daten x und y: Bilde weiteren Wurzelknoten mit y an der Wurzel,
x zur linken und z als Wurzel von T 0 zur rechten. Reihe die 4 Teilbaume entsprechend
ein.
Satz: Anhangen (engl. Concatenate) kann fur 2-3 Baume in Zeit O(jd(T ) d(T )j + 1)
durchgefuhrt werden.
1
2
7.3.5 Aufteilen (2-3 Baum)
Ziel: Konstruiere aus den 2-3 Baumen T , der a enthalt, zwei 2-3 Baume, wobei T alle
Daten x a und T alle Daten x > a enthalt.
Suche: Auf dem Weg zum Blatt mit Schlussel a werden die folgenden Informationen
1
2
gespeichert: Der Suchpfad und alle Kinder von Knoten auf dem Suchpfad.
Aufbau von T : (analog T ) Seien T ; : : : ; T m die Teilbaume, aus denen wir T zusammensetzen. Es gilt: d(T i) d(T i ).
Algorithmus: Fur alle i = m 1; : : : ; 1 entsteht T i aus dem Anhangen von T i an T i.
Schlielich ist T = T
Beobachtung: Das Anhangen aller T i mit d(T i) d0 ergibt einen 2-3 Baum der Tiefe
d0 + 1.
(Beweis durch Induktion uber d0)
Analyse: Insgesamt hangen m 1 2d(T ) 1 mal
an. Es seien d < : : : < dk so gewahlt,
j
da mindestens einer anzuhangenden Baume T Tiefe di hat, 1 i k. Die Konkatenateion mit den ein oder zwei Baumen der Tiefe di verursacht kosten von O(di di + 1)
also insgesamt O(dk d + d(T )) = O(d(T ))
Satz: Aufteilen (engl. Split) kann fur 2-3 Baume in Zeit O(d(T )) durchgefuhrt werden.
1
2
1
1
1
+1
1
1
1
1
1
1
1
1
1
+1
1
1
1
+1
1
+1
1

7.4. BRUDER-BAUME
141
7.4 Bruder-Baume
Idee: Bruder-Baume kann man in einem prazisierbaren Sinn als expandierte AVL-Baume
auffassen.
Unarer Knoten Durch Einfugen unarer Knoten an den richtigen Stellen erhalt man
einen Baum, dessen samtliche Blatter dieselbe Tiefe haben; und umgekehrt entsteht aus
einem Bruder-Baum ein hohenbalancierter Baum, wenn man die unaren Knoten mit ihren
einzigen Sohnen verschmilzt.
Denition: Ein binarer Baum heit ein Bruder-Baum, wenn jeder innere Knoten einen
oder zwei Sohne hat, jeder unare Knoten einen binaren Bruder hat und alle Blatter dieselbe Tiefe haben.
m
m
,
,
m
L
\
\
l
l
m
L
m
m
m
m
L
m
m L
\
\
\
\
L
Bruder{Baum
m
m
m
m
L
kein Bruder-Baum
m
mQ
Q
Q m
m
m m @@ m
m TT m m m TT m
L
L
L
kein Bruder-Baum
L
L
Bruder-Baum
7.4.1 Hohe
Ein Bruder-Baum mit Hohe h hat wenigstens Fh Blatter. (Fi ist die i-te FibonacciZahl.) Also umgekehrt: Ein Bruder-Baum mit N Blattern und (N 1) inneren Knoten
hat eine Hohe h 1:44 : : : log N .
+2
2

KAPITEL 7. BALANCIERTE BAUME
142
Hohe Bruder-Baume mit Blattzahl
minimaler Blattzahl
1
m
2
m
3
L
2
m m
TT
L
m
3
5
m m
m m TT m
@
@
L
L
...
h+2 h6
?
m
m
T
T
DD
D
D
D
D
D
D
D ...
6
h + 1 Fh
+4
?
Einfugen
Fall 1: p hat nur einen Sohn
p
Fall 2: p hat bereits zwei Sohne
m=)
xm
L
fertig!

7.4. BRUDER-BAUME
143
p :m
L
up(p; m; x)
Invariante: Wenn up(p; m; x) aufgerufen wird, gilt:
(1) p hat zwei Sohne pl und pr , die beide Wurzeln von 1-2-Bruder-Baumen sind.
(2) Der Knoten m ist entweder ein Blatt oder hat einen einzigen Sohn, der Wurzel eines
1-2-Bruder-Baumes ist.
(3) Schlussel im linken Teilbaum von p < x
< Schlussel im Teilbaum von m
< Schlussel von p
< Schlussel im rechten Teilbaum von p
a. [p hat einen linken Bruder mit zwei Sohnen]
b. [p hat einen rechten Bruder mit zwei Sohnen]
c. [p hat einen linken Bruder mit einem Sohn]
d. [p hat einen rechten Bruder mit einem Sohn]
e. [p hat keinen Bruder]
7.4.2 Eigenschaften
Falle: (1) binar auf unar, (3) 2 binar auf binar, (2) sonst.
Statisch: Fur jeden unaren Knoten auf Niveau l mu es einen binaren Bruder auf demselben Niveau geben. Daher gilt fur das Verhaltnis
Anzahl binare Knoten auf Niveau l und l + 1 :
U = Anzahl
Knoten insgesamt auf Niveau l und l + 1
Konguration
U
(2)
(3)
(1) und eine Kong. aus (2)
(1) und (3)
2
3
3
3
3
5
4
5
Somit U 1 und 3=5 der inneren Knoten binar
Da die amortisierte Anzahl von Aufrufen von up pro Einfugung konstant ist, gilt:
Folgerung: Der mittlere Aufwand zum Einfugen ist konstant
Bemerkung: Entsprechende Aussage fur AVL-Baume wesentlich schwieriger zu zeigen.
3
5

KAPITEL 7. BALANCIERTE BAUME
144
7.4.3 Implementation Bruder-Baume
Grundstruktur:
class Btree{
Bnode root;
private final boolean UNARY = true;
private final boolean BINARY = false;
private String s;
Btree(){
root=new Bnode(0,null,null,UNARY);
}
Bnode search(int key) ...
Bnode getFather(int key) ...
void up(Bnode p, Bnode m ,int x) ...
void insert(int key) ...
public String toString() ..
}
GetFather: Liefert den Vater des Knotens p mit p.key==key bzw. null, falls p==root.
Existiert kein Knoten p mit p.key==key, so wird der Knoten mit nachst groerem Schlussel zuruck geliefert.
Bnode getFather(int key) {
Bnode dummy = root;
Bnode d = null;
if (dummy.isunary) return null;
while (dummy!=null && dummy.key!=key){
if (dummy.isunary) dummy = dummy.leftSon;
d = dummy;
if(dummy.key < key) dummy = dummy.rightSon;
else dummy = dummy.leftSon;
}
return d;
}
7.4.4 Suche und Einfugen in Bruder-Baumen
Bnode search(int key) {
Bnode dummy = getFather(key);
if (dummy==null) return root;

7.4. BRUDER-BAUME
if (dummy.leftSon==null) return null;
dummy = (dummy.key<key) ?
dummy.rightSon :
dummy.leftSon;
return (dummy.key==key) ?
dummy :
null;
}
Insert fugt den Schlussel key in den Baum ein
void insert(int key) {
Bnode p=getFather(key);
if (p==null) {
root.key=key;
root.isunary=false;
}
else up(p,null,key);
}
Up
void up(Bnode p, Bnode m ,int x) {
Bnode father=(p!=null) ? getFather(p.key) : null;
if (p.key < x){
int temp = p.key; p.key = x; x = temp;
}
}
...
}
7.4.5 Falle in Up
1. Fall p hat keinen Bruder
if (p==root || father.isunary) {
root = new Bnode(x,
new Bnode(0,(p.leftSon==null) ? null : p.leftSon,
null,UNARY),
(p.rightSon==null) ?
new Bnode(p.key,null,null,BINARY) :
new Bnode(p.key,m,p.rightSon,BINARY),
BINARY);
}
145
146

KAPITEL 7. BALANCIERTE BAUME
2. Fall p hat einen linken Bruder mit nur einem Sohn
else
if (father.leftSon!=null &&
(father.leftSon).isunary) {
father.leftSon =
new Bnode(father.key,
(father.leftSon).leftSon,
p.leftSon,BINARY);
father.key=x;
(father.rightSon).leftSon=m;
}
3. Fall p hat einen rechten Bruder mit nur einem Sohn
else
if (father.rightSon!=null &&
(father.rightSon).isunary) {
father.rightSon =
new Bnode(father.key,
(father.rightSon).rightSon,
p.rightSon,BINARY);
father.key=x;
(father.leftSon).rightSon=m;
}
7.4.6 Weitere Falle in Up
4. Fall p hat einen linken Bruder mit zwei Sohnen
else
if(father.leftSon!=null &&
!(father.leftSon).isunary) {
(father.rightSon).leftSon=m;
int temp=father.key; father.key=x;
up (father,
new Bnode(0,
(father.leftSon).leftSon,
null,UNARY),
temp);
}

7.4. BRUDER-BAUME
5. Fall p hat einen rechten Bruder mit zwei Sohnen
else
if (father.rightSon!=null &&
!(father.rightSon).isunary) {
(father.leftSon).rightSon=m;
int temp=father.key; father.key=x;
up (father,
new Bnode(0,
(father.rightSon).rightSon,
null,UNARY),
temp);
}
147
148
7.5 Rot-Schwarz-Baume

KAPITEL 7. BALANCIERTE BAUME
(Knotengefarbte) Rotschwarz-Baume sind binare Suchbaume mit den folgenden Eigenschaften: i) Jeder Knoten ist entweder rot oder schwarz. ii) Jedes Blatt ist schwarz.
iii) Falls ein Knoten rot ist, dann sind beide Kinder schwarz. iv) Jeder Pfad von einem
Knoten zu einem Blatt enthalt die gleiche Anzahl von schwarzen Knoten.
Satz: Ein (knotengefarbter) Rotschwarzbaum mit n inneren Knoten besitzt eine Hohe
von hochstens 2 log(n + 1).
Rotation: Entsprechend den AVL-Baumen gibt es Operationen Left-Rotate und
Right-Rotate.
Einfugen: Wir starten mit dem eingefugten Knoten und der Farbung \rot" und hangeln
uns je nach Lage und Farbe der Ahnen mittels Rotationen und Umfarbungen uber die
Vorgangerverweise an den Knoten zu den Eltern und Ureltern hinauf.
Kapitel 8
Hashverfahren
149
KAPITEL 8. HASHVERFAHREN
150
8.1 Geschlossene Hashverfahren
8.1.1 Hashverfahren
Worterbuchproblem:
Suchen, Einfugen, Entfernen von Datensatzen (Schlusseln)
Ort des Datensatzes d: Berechnung aus dem Schlussel s von d
) keine Vergleiche
) konstante Zeit
Datenstruktur: lineares Feld (Array) der Groe m
Hashtabelle
Schlüssel s
0
1
2
i
m-2 m-1
Bemerkung: Aufteilung der Menge aller moglichen Schlussel anstelle der konkret vorhandenen Schlusselmenge
8.1.2 Hashverfahren|Beispiele
Beispiele:
Compiler
int 0x87C50FA4
int 0x87C50FA8
double 0x87C50FAC
String 0x87C50FB2
i
j
x
name
...
Umgebungsvariablen (Schlussel,Attribut) Liste
EDITOR=emacs
GROUP=mitarbeiter
HOST=vulcano
HOSTTYPE=sun4
LPDEST=hp5
8.1. GESCHLOSSENE HASHVERFAHREN
151
MACHTYPE=sparc
...
ausfuhrbare Programme
PATH ~/bin:/usr/local/gnu/bin:
/usr/local/bin:/usr/bin:/bin: : : :
8.1.3 Probleme
1. Groe der Hashtabelle
Nur eine kleine Teilmenge S aller moglichen Schlussel (des Universums) U kommen vor
2. Berechnung der Adresse eines Datensatzes
Schlussel sind keine ganzen Zahlen
Index hangt von der Groe der Hashtabelle ab
In Java:
public class Object {
...
public int hashCode() { ...}
...
Das Universum U sollte moglichst gleichmaig auf die Zahlen 231; : : : ; 231 1 verteilt
werden
KAPITEL 8. HASHVERFAHREN
152
8.1.4 Hashfunktion
Schlusselmenge S
Hashcode
Hashfunktion h
fur
Universum U
aller
0; : : : ; m
moglichen
SchlusHashtabelle T
sel
(H (U ) [
231; 231 1] N0)
h(s) = Hashadresse
h(s) = h(s0) () s und s sind Synonyme bzgl. h
Adrekollision
8.1.5 Hashverfahren
Beispiel fur U : alle Namen in Java mit Lange 40
) jUj = 62
Falls jUj > m: Adrekollisionen unvermeidlich
40
Hashverfahren:
1. Wahl einer moglichst \guten" Hash-Funktion
2. Strategie zur Auosung von Adrekollisionen
1
8.1. GESCHLOSSENE HASHVERFAHREN
Belegungsfaktor =
# gespeicherte Schlussel
=
Groe der Hash-Tabelle
153
jS j = n
m m
Annahme: Tabellengroe m ist fest
8.1.6 Implementation in Java
Ein Eintrag
class TableEntry {
private Object key;
private Object value;
...
}
Die Hashtabelle
abstract class HashTable {
private TableEntry[] tableEntry;
private int capacity;
HashTable (int capacity) {
this.capacity = capacity;
tableEntry = new TableEntry [capacity];
for (int i = 0; i <= capacity-1; i++)
tableEntry[i] = null;
}
protected abstract int h(Object key);
public abstract void insert(Object key,Object val);
public abstract void delete(Object key);
public abstract Object search(Object key);
}
8.1.7 Wahl der Hash-Funktion
leichte und schnelle Berechenbarkeit
gleichmaige Verteilung der Daten
(Beispiel: Compiler)
KAPITEL 8. HASHVERFAHREN
154
Divisions-Rest-Methode
Wahl von m?
Beispiele:
h(k) = k mod m
a) m gerade, letztes Bit druckt Sachverhalt aus (z.B. 0 = weiblich, 1 = mannlich)
) h(k) gerade
, k gerade
p
b) m = 2
) h(k) liefert p niedrigsten Dualziern
von k
Regel: Wahle m prim mit m 6= ri j , 0 j r 1, r = Radix der Darstellung
8.1.8 Multiplikative Methode
Wahle Konstante , 0 < < 1
1. Berechne k mod 1 = k bkc
2. h(k) = bm(k mod 1)c
Wahl von m unkritisch, wahle m = 2p:
Berechnung von h(k):
r1
0,
,
k
r0
p Bits = h(k)
Beispiel
p5: 1
= 2 0:6180339
k = 123456
m = 10000
h(k) = b10000(123456 0:61803 : : : mod 1)c
= b10000(76300; 0041151 : : : mod 1)c
= b41:151 : : :c = 41
8.1. GESCHLOSSENE HASHVERFAHREN
155
8.1.9 Universelles Hashing
Problem: h fest gewahlt ) es gibt S U mit vielen Kollisionen
Idee des universellen Hashing: Wahle Hashfunktion h zufallig
H endliche Menge von Hashfunktionen
h 2 H : U ! f0; : : : ; m 1g
Denition: H heit universell, wenn fur beliebige x; y 2 U gilt:
jfh 2 Hj h(x) = h(y)gj 1
jHj
m
Folgerung: x; y 2 U beliebig, H universell, h 2 H zufallig
PrH (h(x) = h(y)) m1
8.1.10 Beispiel
Hashtabelle: T der Groe 3, jHj = 5
Betrachte die 20 Funktionen (Menge H):
x + 0 2x + 0
x + 1 2x + 1
x + 2 2x + 2
x + 3 2x + 3
x + 4 2x + 4
(mod 5) (mod 3)
3x + 0
3x + 1
3x + 2
3x + 3
3x + 4
4x + 0
4x + 1
4x + 2
4x + 3
4x + 4
und die Schlussel 1 und 4
Es gilt:
(1
(1
(4
(4
1 + 0) mod 5 mod 3 =
1 + 4) mod 5 mod 3 =
1 + 0) mod 5 mod 3 =
1 + 4) mod 5 mod 3 =
1
0
1
0
= (1
= (1
= (4
= (4
4 + 0) mod 5 mod 3
4 + 4) mod 5 mod 3
4 + 0) mod 5 mod 3
4 + 4) mod 5 mod 3
KAPITEL 8. HASHVERFAHREN
156
8.1.11 Universelles Hashing
Denition
8
<
h(x) = h(y) und x 6= y
(x; y; h) = : 10 falls
sonst
Erweiterung von auf Mengen S U und G H:
(x; S; h) = X (x; s; h)
s2S
(x; y; G ) = X (x; y; h)
h2G
Folgerung: H ist universell, wenn fur alle x; y 2 U
(x; y; H) jHj
m
 aufer
8.1.12 Hashing mit Verkettung der Uberl
Schlussel werden in U berlauisten gespeichert
h(k) = k mod 7
8.1. GESCHLOSSENE HASHVERFAHREN
0
1
2
15
2
3
4
5
157
6
Hashtabelle T
Zeiger
53 12
43
5
U berlaufer
19
 aufer
8.1.13 Verkettung der Uberl
Suchen nach Schlussel k
Berechne h(k) und U berlauiste T [h(k)]
Suche nach k in der U berlauiste
Einfugen eines Schlussels k
Suchen nach k (erfolglos)
Einfugen in die U berlauiste
Entfernen eines Schlussels k
Suchen nach k (erfolgreich)
Entfernen aus U berlauiste
) Reine Listenoperationen
Zur Analyse: Uniform Hashing-Annahme fur h:
P (h(k) = j ) = m1
fur alle s 2 U und 0 j m 1
KAPITEL 8. HASHVERFAHREN
158
8.1.14 Implementation in Java
Die Hashtabelle
class ChainedTableEntry extends TableEntry {
private ChainedTableEntry next;
...
}
Die Hashfunktion
class ChainedHashTable \extends HashTable \{\+\\
public int h (Object key) {
return key.hashCode () % getCapacity() ;
}
...
}
Suche ein Element in der Hashtabelle
public Object search (Object key) {
ChainedTableEntry p = getTableEntry (h(key));
while (p != null && ! p.getKey().equals(key))
p = p.getNext ();
if (p != null)
return p.getValue();
else
return null;
}
8.1.15 Implementation: Einfugen
Aufgabe: Fuge ein Element mit Schlussel key und Wert value in der U berlauiste ein
(falls nicht vorhanden)
public void insert (Object key, Object value) {
ChainedTableEntry entry =
new ChainedTableEntry(key, value);
// Hole den Tabelleneintrag fuer key
int k = h (key);
ChainedTableEntry p = getTableEntry (k);
8.1. GESCHLOSSENE HASHVERFAHREN
159
if (p == null) {
setTableEntry(k,entry);
return;
}
// Suche nach key
while (! p.getKey().equals(key) &&
p.getNext() != null)
p = p.getNext ();
// Fuege das Element ein (falls nicht vorhanden)
if (! p.getKey().equals(key))
p.setNext(entry);
}
8.1.16 Implementation: Loschen
Hauptprozedur Entferne das Element mit Schluessel key (falls vorhanden)
public void delete(Object key) {
int k = h (key);
ChainedTableEntry p = getTableEntry (k);
setTableEntry(k, recDelete(p, key));
}
Rekursion: Entferne das Element mit Schluessel key rekursiv (falls vorhanden), gibt
einen Zeiger auf den Beginn der Liste p zurueck, in der key entfernt wurde
public ChainedTableEntry recDelete
(ChainedTableEntry p, Object key) {
if (p == null) return null;
if (p.getKey().equals(key))
return p.getNext();
p.setNext(recDelete(p.getNext(), key));
return p;
}
8.1.17 Test-Programm
public class ChainedHashingTest {
public static void main(String args[]){
KAPITEL 8. HASHVERFAHREN
160
int vec[] = {12, 53, 5, 15, 2, 19, 43};
if (args.length != 0) {
vec = new int [args.length];
for (int j = 0 ; j < args.length ; j++)
vec [j] = Integer.valueOf(args[j]).intValue();
}
Integer[] t = new Integer[vec.length];
for (int i = 0; i <= vec.length - 1; i++)
t[i] = new Integer(vec[i]);
ChainedHashTable h = new ChainedHashTable (7);
for (int i = 0; i <= t.length - 1; i++)
h.insert(t[i], null);
h.printTable ();
h.delete(t[1]); h.delete(t[0]); h.delete(t[6]);
h.printTable();
}
}
Ausgabe:
0:
1:
2:
3:
4:
5:
6:
-|
15 -> 43 -|
2 -|
-|
53 -|
12 -> 5 -> 19 -|
-|
0: -|
1: 15 -|
2: 2 -|
3: -|
4: -|
5: 5 -> 19 -|
6: -|
8.1.18 Analyse der direkten Verkettung
Uniform-Hashing Annahme:
alle Hashadressen werden mit gleicher Wahrscheinlichkeit gewahlt, d.h.:
Pr(h(ki) = j ) = 1=m
unabhangig von Operation zu Operation
Mittlere Kettenlange bei n Eintragen:
n=m = 8.1. GESCHLOSSENE HASHVERFAHREN
Denition:
Cn0 Erwartungswert fur die Anzahl betrachteter Eintrage bei erfolgloser Suche
Cn Erwartungswert fur die Anzahl betrachteter Eintrage bei erfolgreicher Suche
161
KAPITEL 8. HASHVERFAHREN
162
8.2 Oene Hashverfahren
8.2.1 Situation im Oenen Hashing
Idee: Unterbringung der U berlaufer an freien (\oenen") Platzen in Hashtabelle
Feste Regel: Falls T [h(k)] belegt, suche anderen Platz fur k
Beispiel: Betrachte Eintrag mit nachst kleinerem Index: h(k) 1 mod m
0
h(k)
1
Allgemeiner:
Betrachte die Folge
j = 0; : : : m 1
h(k) j mod m
8.2.2 Sondierungsfolgen
Noch Allgemeiner:
Betrachte Sondierungsfolge
h(k) s(j; k) mod m
j = 0; : : : ; m 1, fur eine gegebene Funktion s(j; k)
Beispiele fur die Funktion s(j; k):
s(j; k) = j
s(j; k) = ( 1)j d 2j e
(lineares Sondieren)
2
s(j; k) = j h0(k)
(quadratisches Sondieren)
(Double Hashing)
m-2 m-1
8.2. OFFENE HASHVERFAHREN
163
8.2.3 Kriterien
Eigenschaften von s(j; k)
Folge:
h(k) s(0; k) mod m;
h(k) s(1; k) mod m;
...
h(k) s(m 2; k) mod m;
h(k) s(m 1; k) mod m
) Permutation von 0; : : : ; m 1
Beispiel: Quadratisches Sondieren
0
1
2
3
4
5
6
(11) = 4
h
(
s j; k
) = 1; 1; 4; 4; 9; 9
Kritisch:
Entfernen von Satzen ) als entfernt markieren
- Einfugen von 4, 18, 25
- Loschen 4
- Suche 18, 25
8.2.4 Implementation: Oene Hashtabelle
class OpenHashTable extends HashTable {
protected int [] tag;
protected int occupied;
static final int EMPTY
static final int OCCUPIED
static final int DELETED
= 0;
= 1;
= 2;
164
KAPITEL 8. HASHVERFAHREN
/* Konstruktoren */
OpenHashTable () {
super ();
}
OpenHashTable (int capacity) {
super(capacity);
tag = new int [capacity];
for (int i = 0; i < capacity; i++) {
tag[i] = EMPTY;
}
occupied = 0;
}
/* Die Hashfunktion */
public int h (Object key) {
int hashValue = key.hashCode () % getCapacity ();
if (hashValue < 0)
hashValue = hashValue + getCapacity ();
return hashValue;
}
public int s (int j, Object key) {
/* quadratisches Sondieren */
if (j % 2 == 0)
return ((j + 1) / 2) * ((j + 1) / 2);
else return - ((j + 1) / 2) * ((j + 1) / 2);
}
8.2.5 Suchen
Teilprozedur: Suche in der Hashtabelle nach Eintrag mit Schlussel key und liefere den
zugehorigen Index oder -1 zuruck
public int searchIndex (Object key) {
int i = h(key);
int j = 1; // naechster Index der Sondierungsfolge
while (tag[i] != EMPTY && ! key.equals(T[i].key)) {
// Pruefe neachsten Eintrag in Sondierungsfolge
i = (h(key) - s(j++, key)) % capacity;
if (i < 0) i = i + capacity;
8.2. OFFENE HASHVERFAHREN
165
}
if (key.equals(T[i].key) && tag[i] == OCCUPIED)
return i;
else return -1;
}
Hauptprozedur: Sucht in der Hashtabelle nach Eintrag mit Schlussel key und liefert
den zugehorigen Wert oder null zuruck
public Object search(Object key) {
int i = searchIndex (key);
if (i >= 0) return T[i].value;
if (i < 0) return null;
}
8.2.6 Einfugen
Ziel: Eintrag mit Schlussel key und Wert value einfugen
public void insert (Object key, Object value) {
if (occupied == capacity) // Hashtabelle voll
return;
// Suche nach key
int i = searchIndex (key);
if (i >= 0) // Schluessel bereits vorhanden
return;
// Suche erfolglos: Fuege key ein
int j = 1; // naechster Index
int i = h(key);
while (tag[i] == OCCUPIED) {
i = (h(key) - s(j++, key)) % capacity;
if (i < 0) i = i + capacity;
}
T[i] = new TableEntry(key, value);
tag[i] = OCCUPIED;
}
KAPITEL 8. HASHVERFAHREN
166
Beobachtungen:
Falls i = h(k) - s(j,k), T[i].key = k und tag[i] = DELETED, dann wird
nachsten Einfugen nicht nach j in der Sondierungsfolge eingefugt.
Begrundung: Da tag[i] = DELETED wird k spatestens bei T [i] eingefugt
8.2.7 Test-Programm
public class OpenHashingTest {
public static void main(String args[]){
int vec[] = {12, 53, 5, 15, 2, 19, 43};
if (args.length != 0) {
vec = new int [args.length];
for (int j = 0 ; j < args.length ; j++)
vec[j] = Integer.valueOf(args[j]).intValue();
}
Integer [] t = new Integer[vec.length];
for (int i = 0; i <= vec.length - 1; i++)
t[i] = new Integer(vec[i]);
OpenHashTable h = new OpenHashTable (7);
for (int i = 0; i <= t.length - 1; i++) {
h.insert(t[i], null);
h.printTable ();
}
h.delete(t[1]); h.delete(t[0]); h.delete(t[6]);
h.printTable();
}
}
Ausgabe (Quadratisches Sondieren):
[ ]
[ ]
[ ]
[ ]
[ ]
(19)
(19)
(19)
[ ]
[ ]
[ ]
(15)
(15)
(15)
(15)
(15)
[ ] [ ] [ ] (12) [ ]
[ ] [ ] (53) (12) [ ]
[ ] [ ] (53) (12) (5)
[ ] [ ] (53) (12) (5)
(2) [ ] (53) (12) (5)
(2) [ ] (53) (12) (5)
(2) (43) (53) (12) (5)
(2) (43) (53) (12) (5)
(19) (15) (2) {43} {53} {12} (5)
k beim
8.2. OFFENE HASHVERFAHREN
8.2.8 Lineares Sondieren
Sondierungsfolge fur k:
167
s(j; k) = j
h(k); h(k) 1; h(k) 2; h(k) 3; : : :
Problem:
primare Haufung (\primary clustering")
0
1
2
3 4 5 6
5 53 12
P (nachster Schlussel bei T2) =
P (nachster Schlussel bei T0) =
!
1
1
2 1 + (1 )2
!
1
1
Cn 2 1 + (1 )
Cn0
8.2.9 Quadratisches Sondieren
s(j; k) = ( 1)j d 2j e
2
Sondierungsfolge fur k:
h(k); h(k) + 1; h(k) 1; h(k) + 4; : : :
Permutation, falls m = 4l + 3, prim
KAPITEL 8. HASHVERFAHREN
168
Problem: sekundare Haufung
1
1 !
1 + ln 1 !
1
Cn 1 2 + ln 1 Cn0
8.2.10 Uniformes Sondieren
s(j; k) = k(j )
k eine der m! Permutationen von f0; : : : ; m 1g
hangt nur von k ab
gleichwahrscheinlich fur jede Permutation
Cn0 1 1 !
1
1
Cn ln 1 Zufalliges Sondieren
s(j; k) = von k abhangige Zufallszahl
s(j; k) = s(j 0; k) moglich, aber unwahrscheinlich
8.2.11 Analyse ideales Hashing
Beim idealen Hashing wird angenommen, da n Daten in einem Speicher mit m Platzen
eingefugt werden, keine Loschungen vorgenommen worden sind und alle Kongurationen
von nbesetzten und m n unbesetzten Speicherplatzen die gleiche Wahrscheinlichkeit
1= mn besitzen. Sei Pr die Wahrscheinlichkeit, da beim der erfolglosen Suche genau r
Platze getestet werden mussen, dann gilt:
8.2. OFFENE HASHVERFAHREN
169
8 m r m
<
Pr = : 0n (r 1) = n frur>1m r m
Begrundung: Die ersten r 1 Platze sind besetzt, der r-te Platz ist frei. Auf den restlichen
m r Platzen konnen die weiteren n (r 1) besetzten Platze beliebig verteilt sein.
Die erwartete Anzahl von Test betragt demnach
Cn0
! m!
m
r
=
rPr = r n (r 1) = n
r
r
! m!
m
X
m
r
=
rm n 1= n
r
! m!
m
X
m
r
= ((m + 1) (m + 1 r)) m n 1 = n
r
! m!
m
X
m
r
= (m + 1)
(m + 1 r ) m n 1 = n
r
! m!
m
X
m
r
+
1
= (m + 1)
(m n) m n = n
r
!X
m m r + 1!
m
= (m + 1) (m n)= n
m !n
r
!
m
X
r
= (m + 1) (m n)= m
n ! r m n!
m+1
= (m + 1) (m n)= m
n ! m n!+ 1
m+1
= (m + 1) (m n)= m
n
n
= (m + 1) (m n)(m + 1)=(m n + 1)
= (m + 1)(1 (m n)=(m n + 1)
= (m + 1)=(m n + 1) m=(m n) = 1=(1 )
= 1 + + + + :::
m
X
m
X
=1
=1
=1
=1
=1
=1
=1
=1
2
3
Die durchschnittliche Anzahl der inspizierte Platze ist bei oenen Hashverfahren
n
Cn = 1=n X (m + 1)=(m k + 1)
1
k=0
KAPITEL 8. HASHVERFAHREN
1
1 !
170
= m n+ 1 m 1n + 2 + m n + 3 + : : : + m 1
= m n+ 1 (H (m + 1) + H (m n + 1))
m n+ 1 ln mm +n +1 1 1 ln 1 1 mit H (m) = 1 + 1=2 + 1=3 + : : : + 1=m ln m:
8.2.12 Double Hashing
Idee: Wahle zweite Hashfunktion h0
s(j; k) = j h0(k)
Sondierungsfolge fur k:
h(k); h(k) h0(k); h(k) 2h0(k); : : :
Permutation:
h0(0 k) 6= 0 und h0(k) 6 j m
(h (k) relativ prim zu m)
z.B. h0(k) = 1 + (k mod (m 2))
8.2.13 Beispiel
Hashfunktionen: h(k) = k mod 7
h0(k) = 1 + k mod 5
Schlusselfolge: 15, 22, 1, 29, 26
8.2. OFFENE HASHVERFAHREN
0
0
0
0
1 2
15
3
1 2
15
3
1 2
15
3
171
4
5
6
h (22) = 3
0
4
4
1 2 3 4
29
15
5 6
22
h (1) = 2
5 6
22 1
h (29) = 5
5 6
22 1
h (26) = 2
0
0
0
8.2.14 Verbesserung der erfolgreichen Suche
Beispiel:
Hashtabelle der Groe 11, Double Hashing mit
h(k) = k mod 11 und
h0(k) = 1 + (k mod (11 2)) = 1 + (k mod 9)
Bereits eingefugt: 22, 10, 37, 47, 17
Noch einzufugen: 6 und 30
h(6) = 6, h (6) = 1 + 6 = 7
0
KAPITEL 8. HASHVERFAHREN
172
0
1
2
3
22
4
5
6
7
8
9
17
47 37
10
10
h(30) = 8, h (30) = 1 + 3 = 4
0
0
1
2
22
3
4
5
47 37
6
7
6
8
17
8.2.15 Verbesserung der erfolgreichen Suche
Einfugen:
k trit in T [i] auf kalt, d.h.: i = h(k) s(j; k) = h(kalt) s(j 0; kalt)
kalt bereits in T [i] gespeichert
Idee: Suche freien Platz fur k oder kalt
Zwei Moglichkeiten:
(M1) kalt bleibt in T [i]
betrachte neue Position
h(k) s(j + 1; k) fur k
(M2) k verdrangt kalt
betrachte neue Position
h(kalt) s(j 0 + 1; kalt) fur kalt
Falls (M1) oder (M2) trit auf einen freien Platz
dann trage entsprechenden Schlussel ein fertig,
sonst verfolge (M1) oder (M2) weiter
8.2.16 Brent und Binarbaum Sondieren
Brent's Verfahren: verfolge nur (M1)
Binarbaum Sondieren
9
10
10
8.2. OFFENE HASHVERFAHREN
173
k weicht aus
k trit auf k
k trit auf k
k
k
k trit auf k
00
000
weicht aus
fertig
0000
CnBrent 1 + 2 + 4 + 15 + < 2:5
Cn0 1 1 CnBinarbaum 1 + 2 + 4 + 15 + < 2:2

8.2.17 Beispiel
:
0
weicht aus
fertig
000
k
weicht aus
fertig
00
k weicht aus
k trit auf k
0
3
4
3
4
Hashfunktionen: h(k) = k mod 7 h0(k) = 1 + k mod 5
Schlusselfolge: 12, 53, 5, 15, 2, 19
KAPITEL 8. HASHVERFAHREN
174
0
1
2
3
4 5 6
53 12
h(5) = 5 belegt mit k0 = 12
Betrachte:
h00(5) = 1 ) h(5) 1 h0(5)0 = 5 1 = 4 belegt!
h (12) = 3 ) h(12) 1 h (12) = 5 3 = 2 frei
) 5 verdrangt 12 von seinem Platz
8.2.18 Verbesserung der erfolglosen Suche
Suche nach k:
k0 > k in Sondierungsfolge: ) Suche erfolglos
Einfugen (Ordered Hashing) kleinere Schlussel verdrangen groere Schlussel
Invariante: Alle Schlussel in der Sondierungsfolge vor k sind kleiner als k (aber nicht
notwendigerweise aufsteigend sortiert)
Probleme:
Verdrangungsproze kann \Kettenreaktion" auslosen
k0 von k verdrangt: Position von k0 in Sondierungsfolge?
) s(j; k) s(j 1; k) = s(1; k), 1 j m
8.2.19 Beispiel: Ordered double Hashing
h(0 k) = k mod 7
h (k) = 1 + k mod 5
Schlusselfolge: 15, 2, 43, 4, 8
0
1 2
15 2
3
4
43
5
6
h(43) = 1, h0(43) = 4 ) h(43) h0(43) = 1 40= 4 (mod 7)
Einfugen von 4: 4 verdrangt 43! nachster Platz: 4 h (43) = 4 4 = 0 (mod 7)
Einfugen von 8: 8 verdrangt 15! nachster Platz: h(15) h0(15) = 1 1 = 0 (mod 7).
15 verdrangt 43! nachster Platz: 0 h0(43) = 0 4 = 3 (mod 7)
8.2. OFFENE HASHVERFAHREN
8.2.20 Implementation: Suchen
175
Prozedur sucht in der Hashtabelle nach Eintrag mit Schlussel key und liefert den zugehorigen Index oder -1 zuruck
public int searchIndex (Object key) {
Orderable orderableKey = (Orderable) key;
int i = h(key);
int j = 1;
// naechster Index in Sondierungsfolge
while (tag[i] != EMPTY &&
orderableKey.greater(getOrderableKey(i)) &&
j <= getCapacity()) {
// Pruefe naechsten Eintrag in Sondierungsfolge
i = (h(key) - s(j++, key)) % getCapacity();
if (i < 0) i = i + getCapacity();
}
if (tag[i] == OCCUPIED &&
orderableKey.equals(getOrderableKey(i))) {
return i;
}
else {
return -1;
}
}
8.2.21 Implementation Einfugen
Schlussel key Wert value
public void insert (Object key, Object value) {
if (occupied == getCapacity()) return;
Orderable orderableKey = (Orderable) key;
TableEntry entry = new TableEntry (key, value);
int i = h(key);
int j = 0; // Anzahl inspizierter Eintrage
while (tag[i] != EMPTY &&
! orderableKey.equals(getOrderableKey(i)) &&
(j++ < getCapacity())) {
if (orderableKey.less(getOrderableKey(i))) {
if (tag[i] == DELETED)
KAPITEL 8. HASHVERFAHREN
176
break;
else { // verdraenge den groesseren Schuessel
TableEntry help = getTableEntry(i);
setTableEntry(i, entry);
entry = help;
orderableKey = (Orderable) entry.getKey();
}
}
i = (i - s(1,orderableKey)) % getCapacity();
if (i < 0) i = i + getCapacity();
}
if (tag[i] != OCCUPIED) {
setTableEntry(i,entry);
tag[i] = OCCUPIED;
occupied++;
}
}
Kapitel 9
Graphalgorithmen
177
KAPITEL 9. GRAPHALGORITHMEN
178
9.1 Graphen
(Gerichtete) Graphen: G = (V; E ) bestehen aus Knoten V und Kanten E
Beispiel: und eignen sich zur Reprasentation z.B. von Bus- und Straenbahnverbindun-
gen der Stadt Freiburg.
Reprasentation: Man unterscheidet zwei grundlegend verschiedene Implementationen
von Graphen.
Die Adjazenzliste: An ein Array von Knoten wird fur jeden Eintrag eine Liste der
Nachbarn angehangt.
Die Adjazenzmatrix: Der Eintrag an der Stelle (i; j ) der Matrix aus Wahrheitswerten
gibt an, ob zwischen dem Knoten i und dem Knoten j eine Kante existiert oder nicht.
1
2
3
0
4
9.1.1 Implementation
Knoten:
class Knoten {
Knoten next;
int n;
Knoten(int n) {
next = null;
this.n = n;
}
Knoten(int n, Knoten k) {
next = k;
9.1. GRAPHEN
this.n = n;
}
}
Graph:
class Graph {
public static void main(String args[]) {
Adliste Al = new Adliste(10,0.3);
Admatrix Am = new Admatrix(10,0.3);
System.out.println(Am);
System.out.println(Al);
}
}
9.1.2 Implementation Adjazenzmatrix
Konstruktion: Kantendichte gema vorgegebener Dichte ausgewurfelt
class Adliste {
Knoten ad[];
Adliste(int n, double Dichte) {
int i; int j;
ad = new Knoten[n];
for(i=0;i<ad.length;i++) {
for (j=0;j<ad.length;j++) {
if (Math.random() < Dichte) {
ad[i] = new Knoten(j,ad[i]);
}
}
}
}
Ausgabe:
public String toString() {
StringBuffer st = new StringBuffer("");
int i; Knoten j;
for(i=0;i<ad.length;i++) {
for (j=ad[i];j!=null;j=j.next) {
st.append(j.n+" ");
179
KAPITEL 9. GRAPHALGORITHMEN
180
}
st.append("\n");
}
return st.toString();
}
}
9.1.3 Implementation Adjazenzliste
Konstruktion: Kantendichte gema vorgegebener Dichte ausgewurfelt
class Admatrix {
boolean ad[][];
Admatrix(int n, double Dichte) {
int i; int j;
ad = new boolean[n][n];
for(i=0;i<n;i++) {
ad[i] = new boolean[n];
for(j=0;j<n;j++) {
if (Math.random() < Dichte)
ad[i][j] = true;
}
}
}
Ausgabe:
public String toString() {
StringBuffer st = new StringBuffer("");
int i; int j;
for (i=0;i<ad.length;i++) {
for(j=0;j<ad[i].length;j++) {
st.append(ad[i][j] + " ");
}
st.append("\n");
}
return st.toString();
}
}
9.1. GRAPHEN
9.1.4 Tiefensuche
Hauptschleife:
public void DFS() {
boolean visited[] = new boolean[ad.length];
for (int i=0;i<ad.length;i++) {
if (!visited[i])
DFS(i,visited);
}
}
Rekursion:
void DFS(int i, boolean visited[]) {
System.out.println("Visiting node " +i);
visited[i] = true;
for (Knoten j=ad[i];j!=null;j=j.next) {
if (!visited[j.n])
DFS(j.n,visited);
}
}
9.1.5 Topologische Sortierung
public void topsort() {
int [] indeg = new int[ad.length];
for(int i=0;i<ad.length;i++) {
for (Knoten j=ad[i];j!=null;j=j.next) {
indeg[j.n]++;
}
}
Queue Q = new Queue(ad.length);
for(int i=0;i<ad.length;i++) {
if (indeg[i] == 0)
Q.enqueue(i);
}
int lfdNr = 0;
while (!Q.empty()) {
int i = Q.dequeue();
System.out.println(i); lfdNr++;
for (Knoten j=ad[i];j!=null;j=j.next) {
181
KAPITEL 9. GRAPHALGORITHMEN
182
indeg[j.n]--;
if (indeg[j.n] == 0)
Q.enqueue(j.n);
}
}
if (lfdNr != ad.length)
System.out.println("Graph hat einen Zyklus");
}
Teil II
Aufgaben
183
Kapitel 10
Theorie
Aufgabe 1:
Die Fibonacci-Zahlen sind fur n 2 f0; 1; 2 : : :g wie folgt rekursiv deniert:
F (0) = 0
F (1) = 1
F (n) = F (n 1) + F (n 2) , fur n 2.
Zeigen Sie z.B. durch vollstandige Induktion:
(a) Fur n 6 ist F (n) 2n=2.
(b) Fur n 0 gilt F (0) + : : : + F (n) = F (n + 2) 1.
(c) Fur n 1 ist F 2(n) = F (n 1)F (n + 1) + ( 1)n+1.
Losung zur Aufgabe 1:
(a) I.-Anfang F (6) = 8 = 26=2. F (7) = 13 > 27=2 = 11.31.
I.-Schlu: F (n) = F (n 1) + F (n 2) 2(n 1)=2 + 2(n 2)=2 2 2(n 2)=2 =
2n=2:
(b) I.-Anfang:PF (0) = F (2) 1 P
I.-Schlu: ni=0 F (i) = F (n) + ni=01 F (i) = F (n + 1) + F (n) 1 = F (n +
2) 1:
(c) I.-Anfang: F (1) = F (0)F (2) + 1 = 1.
I.-Schlu: F 2(n) = F (n)(F (n 1)+ F (n 2)) = F (n)F (n 1)+ F (n 1)2
( 1)n = F (n 1)(F (n)+ F (n 1)) ( 1)n = F (n 1)F (n +1)+( 1)n+1:
185
KAPITEL 10. THEORIE
186
Aufgabe 2:
Im Folgenden wollen wir uns einer suchfreien Losung des n{Dameproblems fur n > 3
annahern. Das Brett sei in der Anzahl von Zeilen und Spalten von 1 bis n numeriert. Desweiteren denieren wir ein Springermuster S an der Stelle (i; j ) als die Menge derjenigen
Felder, die ein Springer bei stetem Rechtshochsprung belegt, d.h.
S(i; j ) = f(i; j ); (i 1; j + 2); (i 2; j + 4); (i 3; j + 6) : : :g:
(a) Zeigen Sie: Zwei Damen innerhalb eines Springermusters behindern sich gegenseitig
nicht. Hinweis: Ein Diagonalkonikt ist genau dann gegeben, wenn die absolute
Dierenz der Zeilen gleich der absoluten Dierenz der Spalten ist.
(b) Zeigen Sie, da fur alle geraden n aus fnj(n 2) mod 6 6= 0g also fur alle n
aus f4; 6; 10; 12; 16; : : :g die Belegung S (n=2; 1) [ S (n; 2) eine Losung des n{
Dameproblems ist. Fur n = 6 sieht die Losung wie folgt aus:
1
2
3
4
5
6
1
2
3
4
5
6
Losung zur Aufgabe 2:
(a) Seien zwei Positionen (i k1; j + 2k1) und (i k2; j + 2k2) fur k1 < k2
gegeben, dann folgt aus der Annahme eines Diagonalkoniktes ji k1 (i k2)j =
jj + 2k1 (j + 2k2)j unmittelbar, da k1 = k2 gilt, Widerspruch. Da sowohl
i k1 = i k2 als auch j +2k1 = j +2k2 die Gleichheit von k1 und k2 erzwingen,
gibt es genausowenig einen Zeilen- oder Spaltenkonikt in S (i; j ).
(b) Sei (i; j ) aus S (n=2; 1) und (l; m) aus S (n; 2). Da j ungerade und m gerade ist,
gibt es keinen Spaltenkonikt der Mengen S (n 2; 1) und S (n; 2). Da i n=2
und l > n=2 gilt, gibt es genausowenig einen Zeilenkonikt innerhalb der zwei
Mengen. Bleibt z.z., da es auch keinen Konikt auf der Diagonalen gibt. Dazu
betrachte d1 = l i und d2 = jm j j. Falls d1 gleich d2 ist, gibt es einen
187
Konikt auf der Diagonalen. Annahme, d1 = d2, also entweder l i = m j
oder l i = j m.
Es gibt ein p mit l = n p und m = 2+2p und auerdem ein q mit i = n=2 q
und j = 1 + 2q. Im ersten Fall erhalten wir somit n=2 1 = 3p 3q oder
n 2 = 6(p q) im Widerspruch zur Vorraussetzung. Im zweiten Fall ergibt sich
n=2 + 1 = p q im Widerspruch zur Voraussetzung.
Bemerkungen:
(a) Fur ein ungerades n aus fnj(n 3) mod 6 6= 0g also aus f5; 7; 11; 13; 17; : : :g
lat sich eine nahezu identische Losung zu konstruieren: Beispiel n = 7:
1
1
2
3
4
5
6
7
2
3
4
5
7
6
(b) Durch Abandern der letzten drei Damen in der Konstellation S (n=2; 1) [ S (n
1; 2) lat sich eine Losung fur ein gerades n aus fnj(n 2) mod 6 = 0g bzw.
f8; 14; 20 : : :g nden. Beispiel n = 8.
1
2
3
4
5
6
7
8
1
2
3
4
6
5
7
8
KAPITEL 10. THEORIE
188
(c) Das Schema fur die ubrig gebliebene Menge ungerader n aus fnj(n 3) mod 6 =
0g bzw. aus f9; 15; 21; : : :g entsteht aus der Erganzung zu S (bn=6c; 1) [
S(n; n=3 + 1) und Streckung von S(2n=3; 2) an der Spalte n=3 + 1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
8
10 11 12 13 14 15
9
Aufgabe 3:
Da es ein Auto in Freiburg gibt, das nicht wei ist, und dieses Auto wiederum nicht
unmittelbar schwarz sein mu, ist eine schmerzliche Erkenntnis einer anonymen Umfrage.
Wir wollen uns deshalb lieber auf ein kontrastreiches schwarz-wei Problem beschranken.
Zeigen Sie, da, wie im Beispiel der Abbildung 10.1, jede Aufteilung der Ebene durch
n verschiedene Linien mit schwarz und wei eingefarbt werden kann, so da keine zwei
Regionen gleicher Farbe eine Kante teilen. Hinweis: Auch hier lat sich das Prinzip der
vollstandigen Induktion im Beweis nutzen.
Losung zur Aufgabe 3: Die Hypothese ist fur n gleich eins wahr: Farbe eine Seite
schwarz, die andere Seite wei. Nun nehme an, da n Linien in der Ebene gegeben sind und
wir n 1 Linien auswahlen. Diese Auswahl lat sich aufgrund der Induktionshypothese
189
Abbildung 10.1: Die Farbung einer mit 6 Linien aufgeteilten Ebene mit schwarz und wei
farben. Nun nehmen wir die n-te Linie hinzu. Darauf hin invertieren wir alle Farben auf
der einen Seite der Linie. Angenommen, zwei adjazente (nebeneinanderliegende) Flachen
hatten die gleiche Farbe. Falls die gemeinsame Kante nicht die n-te Linie ist, so ergibt sich
ein Widerspruch zur Induktionshypothese, da entweder keine oder beide Farben gewechselt
wurden. Falls die n-te Linie die Flachen teilt, hatten sie vorher zwar die gleiche, nun aber
eine unterschiedliche Farbe.
Bemerkung: Farbungsprobleme haben eine lange Tradition. Das Farben der Knoten
eines planaren Graphen mit vier Farben ist immer moglich. Mit drei Farben handelt es
sich um eines der schwersten Probleme der Informatik.
Aufgabe 4:
Eine Schleifeninvariante ist der Schlussel zum Beweis der Korrektheit eines iterativen
Algorithmus. Dabei nutzen wir den Index j um den Variableninhalt nach der j {ten Iteration zu beschreiben. Als Beispiel betrachten wir die Berechnung der Fakultat einer nicht
negativen ganzen Zahl m:
n = 1;
while (m > 1) { n *= m; m--; }
Die Schleifeninvariante ist: nj = Qm
k=m j +1 k: Finden Sie die Schleifeninvarianten der
folgenden Programme!
(a) Summation von Werten im Array A[0::n
s = 0;
for (i=0;i<n;i++) s += A[i];
(b) Maximum im Array A[0::n
1]:
1]:
KAPITEL 10. THEORIE
190
m = A[0];
for (i=1;i<n;i++) { if (A[i] > m) m = A[i]; }
(c) Schnelle Multiplikation y z:
x = 0;
while (z>0) {
if (z % 2 == 1) x += y;
else { y *= 2; z /= 2; }
}
(d) Exponentation yz :
x = 1;
while (z>0) { x *= y; z--; }
(e) Schnelle Exponentation yz :
x = 1;
while (z>0) {
if (z % 2 == 1) x *= y;
else { y *= y; z /= 2; }
}
Losung zur Aufgabe 4:
(a) sj = Pji=01 A[i].
(b) mj = maxfA[0]; : : : ; A[j ]g
(c) yj zj + xj = y0z0
(d) xj yjzj = y0z0
(e) xj yjzj = y0z0
Aufgabe 5:
Ben Bitdiddle hat sich ein neues Multiplikationsverfahren fur zwei naturliche Zahlen ausgedacht, das er uns an dem Beispiel der Zahlen 9 und 14 gerne vorfuhrt:
191
9
14
|||||
4
28
|||||
2
56
1
112
|||||{
total = 126
Er erklart sein Verfahren wie folgt:
Die linke Seite wird ohne Rest halbiert und die rechte Seite verdoppelt. Dabei
werden die Zeilen mit gerader linker Seite gestrichen und die nicht gestrichenen rechten Seiten aufaddiert.
(a) Beschreiben Sie den Algorithmus von Ben Bitdiddle durch ein Fludiagramm und
durch ein Struktogramm.
(b) Beschreiben Sie das Verfahren durch Angabe eines Java Programmes mult.
(c) Beweisen Sie die Korrektheit des Ansatzes.
(d) Wieviel Iterationen benotigt Ben Bitdiddles Verfahren? Ist es besser als das Verfahren der Schulmethode?
Losung zur Aufgabe 5:
(a) Fludiagramm und Struktogramm fur Bens Algorithmus: Siehe Abbildungen.
(b) public class Mult {
public static int mult (int x, int y) {
int total = 0;
while (x>0) {
if (x%2 == 1) total += y;
x /= 2; y *= 2;
}
return total;
}
public static void main (String args[]) {
int a = new Integer(args[0]).intValue();
int b = new Integer(args[1]).intValue();
System.out.println("Das Produkt von "+a+
KAPITEL 10. THEORIE
192
Start
Eingabe: x,y
total := 0
Nein
x>0
Ja
x gerade
Nein
total := total+y
Ausgabe: total
Ja
Stop
x:= x/2
y:= y*2
Eingabe: x,y
total := 0
x>0
x gerade
Ja
Nein
total := total+y
x:= x/2
y:= y*2
Ausgabe: total
193
" und " +b+" ist "+ mult(a,b));
}
}
(c) Wir kurzen total mit z ab und erhalten die Invarianz P : x 0 und z + xy = ab:
Sicher impliziert die Vorbedingung fx = a; y = b; z = 0g die Invarianz. Weiterhin
impliziert (x 0) und (nicht x > 0) da x = 0 ist. Eingesetzt in P ergibt dies
z = ab. Bleibt zu zeigen, da P bei einmaliger Ausfuhrung der Schleife
while (x>0) {
if (x%2 == 1) z += y;
x /= 2; y *= 2;
}
richtig bleibt. Durch die Division durch 2 bleibt x positiv.
Falls x gerade ist, bleiben xy und z unverandert, also gilt P .
Falls x ungerade ist, wird z += y ausgefuhrt. Dies ist notwendig, da durch die
Division ohne Rest von x durch 2 in der Multiplikation mit y einmal der Wert
0 die Werte nach dem Schleifendurchlauf. Der
y verloren geht. Seien x0; y0 und
z
Wert x hat die Darstellung 2x0 + 1. Damit gilt: xy + z = (2x0 + 1)y + z =
x0y0 + y + z = x0y0 + z0.
Zum Nachweis der totalen Korrektheit ist noch zu zeigen, da das Programm terminiert. Betrachtet man den Rumpf der while Schleife, so stellt man fest, da der
Wert von x immer (um mindestens 1) abnimmt. Das Programm halt, wenn x den
Wert 0 erreicht hat.
(d) Die Anzahl der Schleifeniterationen ist durch die Lange der Binardarstellung von a
also durch dlog(a+1)e festgelegt. Es ist dadurch etwas besser als die Schulmethode.
Aufgabe 6:
Java kennt mehrere Schleifen- und Fallunterscheidungskonstrukte, in denen Bedingungen
gepruft und ein bzw. mehrere Anweisungsteile ausgefuhrt werden. Zeigen Sie, da es
sich hier um sogenannten syntaktischen Zucker handelt, und fuhren Sie die folgenden
Anweisungen auf eine einfache while Schleife zuruck:
(a)
for(int i=anfang;i<grenze;i++) { <B> }
(b)
do { <B> } while (A)
KAPITEL 10. THEORIE
194
(c)
if (A) { <B> } else { <C> }
(d) switch (A) { case A1:<B> break; case A2:<C> break; default:
Hinweis: In (c) und (d) empehlt es sich, boolesche Variablen einzufuhren.
<D>; }
Losung zur Aufgabe 6:
(a)
int i=anfang; while (i<grenze) { <B> i++; }
(b)
<B> while (A) { <B> }
(c)
(d)
boolean b = A, c = !b;
while (b) { <B> b = false; }
while (c) { <C> c = false; }
boolean b
while (b)
while (c)
while (d)
=
{
{
{
(A==A1), c = (A==A2), d = !b && !c;
<B> b = false; }
<C> c = false; }
<D> d = false; }
Aufgabe 7:
Petra Flipop und Franz Hacker wollen das Abmeproblem (eine Variante des in der
Vorlesung besprochenen Umfullproblems ) losen. Sie haben zwei Stabe der Lange 15cm
und 42cm und wollen damit ein Seil der Lange 1m abmessen. Sie versuchen es immer
wieder, doch die beiden bemuhen sich vergeblich. Bieten Sie Ihre Hilfe an, um den beiden
zu zeigen, da die gestellte Aufgabe unlosbar ist.
(a) Geben Sie Losungen des Abmeproblems fur die Lange 99cm, fur die Lange 27cm
und fur die Lange 3cm an.
Da alle erzeugbaren Zahlen ein Vielfaches von 3 sind und 3 wiederum der grote gemeinsame Teiler von 15 und 42 ist, liegt die Vermutung nahe, da die abmebaren Langen
immer ein Vielfaches des groten gemeinsamen Teilers sind.
(b) Zeigen Sie, da es fur jede Zahl der Form 3k, k 2 IN, zwei ganze Zahlen x und
y gibt, so da 3k = 15x + 42y gilt. Hinweis: Nutzen Sie dabei die gewonnenen
Erkenntnisse des U bungsblattes 4, Aufgabe 3.
(c) Zeigen Sie, da keine andere Zahlen als die von der Form 3k,
werden, d.h. alle erzeugten Zahlen durch 3 teilbar sind.
k2
, erzeugt
IN
195
Losung zur Aufgabe 7:
(a) 99 = 1 15 + 2 42,
27 = ( 1) 15 + 1 42,
3 = 3 15 + ( 1) 42
(b) Da 3 = ggT (15; 42) gilt, gibt es nach Aufgabe 3, Blatt 4 zwei Zahlen x und y
mit 3 = 15x + 42y. Somit gibt es auch zwei Zahlen x0 = kx und y0 = ky0 mit
3k = 15x0 + 42y0.
(c) Da 3 als groter gemeinsamer Teiler sowohl 15 als auch 42 teilt, gilt 15x + 42y =
3(5x + 14y). Damit sind auch alle erzeugten Zahlen durch 3 teilbar (setze k =
5x + 14y).
Aufgabe 8:
Die Landau'sche O-Notation ist sozusagen das A und O in der Algorithmenanalyse, da sie
es erlaubt, Laufzeit- bzw. Speicherplatzfunktionen in Klassen einzuteilen. Fomal bedeutet
f (n) 2 O(g(n)), da es ein reelles c > 0 und ein naturliches n0 gibt, so da fur alle
n n0 der Wert f (n) kleiner oder gleich cg(n) ist.
(a) Zeigen Sie durch die Wahl geeigneter Konstanten c und n0, da (n + 1)2 in der
Klasse O(n2) liegt.
(b) Es gilt die folgende Beziehung: Falls der Grenzwert von f (n)=g(n) fur
n gegen unendlich durch eine Konstante c beschrankt ist (wir schreiben
limn!1 f (n)=g(n) c), so gilt f (n) 2 O(g(n)). Zeigen Sie unter Verwendung dieses Sachverhaltes abermals (n + 1)2 2 O(n2).
(c) Die Regel von L'Hospital besagt: Falls der Grenzwert f 0(n)=g0(n) fur n gegen
unendlich existiert (f 0 ist die Ableitung von f ), so ist er gleich dem Grenzwert von
f (n)=g(n). Zeigen Sie mit Hilfe dieser Regel letztmalig (n + 1)2 2 O(n2).
Losung zur Aufgabe 8:
(a) Zu zeigen ist (n + 1)2 cn2 oder (c p1)n2 2n p 1 0. Wir wahlen c = 2.
Die Nullstellen von n2 2n 1 sind p2 + 1 und 2 + 1. Die Parabel ist nach
oben geonet. Also ist fur n n0 = d 2 + 1e der Wert (n + 1)2 2n2 und
demnach (n + 1)2 2 O(n2).
(b)
n2 + 2n + 1 = lim n2(1 + 2=n + 1=n2) = lim (1+2=n+1=n2) = 1:
lim
n!1
n!1
n!1
n2
n2
KAPITEL 10. THEORIE
196
(c) Da
nlim
!1 2=2 = 1
gilt, ist
2n + 2 = 1
lim
n!1 2n
und somit
n2 + 2n + 1 = 1:
lim
n!1
n2
Aufgabe 9:
(a)
(b)
(c)
(d)
Zeigen Sie: 3n4 n2 = O(n4).
Zeigen Sie: 2n = o(n!) (Hinweis n! (n=2)n=2) .
Zeigen Sie: Fur alle k ist nk = o(2n):
Zeigen Sie: Es seien p1 und p2 Polynome vom Grad d1 bzw. d2, wobei die Koezienten von nd1 und nd2 positiv sind. Dann gilt:
d = d ) p = (p );
d < d ) p = o(p );
d > d ) p = !(p ):
(e) Zeigen Sie: Fur alle k > 0 und > 0 gilt logk n = o(n)
(f) Zeigen Sie: 2n= = o(2n):
1.
2.
3.
1
2
1
2
1
2
1
2
1
2
1
2
2
(g) Finden Sie eine Funktion, in denen beide Denitionen von aus der Vorlesung
voneinander abweichen.
(h) Fur monoton steigende Funktionen f 6 0 sind beide Denitionen fur Gro-O aus
der Vorlesung aquivalent.
Losung zur Aufgabe 9:
(a)
3n2 n2 = lim 3 1=n2 = 3:
lim
n!1
n!1 n4
1
197
(b)
n
n
2
2
n=2
nlim
!1 n! nlim
!1 (n=2)n=2 = nlim
!1(8=n) = 0:
(c) Es gilt limn!1 nk =2n = 0 durch k faches Anwenden der Regel von L'Hospital.
1 a ni , Polynom p (n) = Pd2 b ni d = d folgt
(d) Polynom p1(n) = Pdi=0
i
2
1
2
i=0 i
limn!1 p1(n)=p2(n) = ad1 =ad2 und limn!1 p2(n)=p1(n) = ad2 =ad1 . Damit
ist p1 = (p2). Falls d1 < d2, dann limn!1 p1(n)=p2(n) = 0. Damit p1 =
o(p2). Rest analog.
(e) Nach der Regel von L'Hospital gilt:
k! = = lim k(log n)
0 = nlim
k
!1 n
n!1
n
(f)
n=2 =2n =
nlim
!1 2
k
1
(log n) :
= nlim
!1 n
k
n=2
nlim
!1 1=2 = 0:
(g) Denition f (x) = 1 fur x gerade, f (x) = n fur x ungerade. Einmal f = (1)
und einmal f = (n)
(h) Cormen ! Ottmann: Sei c > 0, n0 2 N mit g(n) cf (n). Setze a = c und
b = maxff (n) j n n0g Fur n n0 ist g(n) b a f (n)+ b. Fur n > n0
ist g(n) cf (n) a f (n) + b
Ottmann ! Cormen: Seien a; b 0 mit g(n) a f (n) + b fur alle n 2 N .
Da f 6 0, existiert n0 2 N mit f (n0) > 0. Setze c = a + b=f (n0). Fur
n n0 ist cf (n) = af (n) + bf (n)=f (n0) a f (n) + b g(n) Beachte,
da f (n)=f (n0) 0, da f monoton.
Aufgabe 10:
Gegeben sei ein lineares Feld a[1..n] positiver reeller Zahlen. Gegeben seien auerdem
eine Funktion g die als Werte 0 oder 1 liefert, und die folgende Funktion gtest :
int gtest(int li, int re) {
if (li > re) return 0;
int m = (li+re)/2;
return gtest(li,m-1)+g(a[m])+gtest(m+1,re);
}
KAPITEL 10. THEORIE
198
a) Beschreiben Sie, welches Resultat die Funktion beim Aufruf gtest(1; n) fur ein
gegebenes Feld a liefert.
b) Ermitteln Sie groenordnungsmaig die Anzahl der Additionen bei der Ausfuhrung
eines Aufrufs von gtest(li; re) im schlimmsten Fall, in Abhangigkeit von j re li j,
mit Hilfe einer Rekursionsformel.
c) Geben Sie in Java ein alternatives (iteratives) Verfahren zur Ermittlung des Funktionswertes gtest an; verwenden Sie denselben Funktionskopf.
Losung zur Aufgabe 10:
(a) Pni=1 g(a[i])
(b) T (n) = 2T (n=2) + O(1). Fur n = 2k : T (2k ) = 2T (nk 1) + c = 4T (nk 2) +
2c = cnT (20) 2 O(n).
(c)
static in gtest2 (int li, int re) {
int sum = 0;
for (int i=0; i < 1000; i++) {
sum += g(a[i]);
}
}
Aufgabe 11:
Erstellen Sie zwei Pseudo-Code Programme zur iterativen Berechnung der folgenden verallgemeinerten Binomialkoezienten.
1
1
1
5
1
4
1
3
2
4
7 8
11 15 16
Das allgemeine Bildungsgesetz lautet d0 = 1; dd = 2d und hd = d h 1 + hd 11 .
a) Das erste Programm verwende einen Stapel, der verkettet gespeichert, also uber
Zeiger realisiert wird.
b) Das zweite Programm verwende ein Berechnungsschema, das mehrfache Berechnung
von gleichen Teilresultaten vermeidet.
199
Losung zur Aufgabe 11: Anstatt einer Stapellosung geben wir alternativ eine rekursive
Losung an.
class Binomial {
public static int ueber (int d, int h){
int[] tabelle = new int[d+2]; // Tabelle ZwischenErg
tabelle[0] = 1;
for (int i=0; i<=d; i++){
// Hoehe Pascal Dreieck
int hilf2 = 1;
for (int j=1; j<=i; j++){
// Breite Pasc Dreieck
if (j==i) tabelle[j] = 2*hilf2;
else {
// Sonderfall j=i
int hilf3 = tabelle[j];
tabelle[j] += hilf2;
// berechne neuen Eintrag
hilf2 = hilf3;
}
}
}
return tabelle[h];
}
public static int hoch (int d){
if (d == 0) return 1;
else return (2*hoch(d-1));
}
public static int ueber2 (int d, int h){
if (h == 0) return 1;
else if (d == h) return hoch(d);
else return ueber2 (d-1,h) + ueber2 (d-1,h-1);
}
public static void main (String[] argv){
int d = Integer.parseInt(argv[0]);
int h = Integer.parseInt(argv[1]);
int ergebnis = ueber (d,h);
System.out.println (ergebnis);
ergebnis = ueber2 (d,h);
System.out.println (ergebnis);
}
}
200
KAPITEL 10. THEORIE
Aufgabe 12:
Auswahlsort ist ein einfacher Vorlaufer von Heapsort. Gegeben sei ein Elementarray A
von n vergleichbaren Objekten. Der Algorithmus sucht fur j = 1; : : : ; n 1 in dem
Array von der Stelle j aus das kleinste Element und tauscht es an die Stelle j .
(a) Bestimmen Sie die Anzahl der Vergleiche im best, worst und average case.
(b) Besimmen Sie die Anzahl von Bewegungen im best, worst und average case.
Losung zur Aufgabe 12: Vergleiche Ottmann und Widmayer Algorithmen und Datenstrukturen Seite 66 .
(a) Cmin(n) = Cmax(n) = Cmit(n) = Pni=11(n i) = Pni=11 i = (n2)
(b) Cmin(n) = Cmax(n) = Cmit(n) = 3(n 1)
Aufgabe 13:
Denieren Sie die Begrie terminierend, determiniert und deterministisch.
Losung zur Aufgabe 13: Ein Algorithmus heit determiniert, falls er bei gleichen
Eingaben und Startbedingungen stets dasselbe Ergebnis liefert.
Ein Algorithmus terminiert, falls er fur alle Eingaben nach endlich vielen Schritten ein
Resultat liefert.
Ein Algorithmus heit deterministisch, wenn er zu jedem Zeitpunkt seiner Ausfuhrung
hochstens eine Moglichkeit der Fortsetzung besteht; ansonsten heit er nichtdeterministisch.
Aufgabe 14:
Beweisen Sie die Additionsregel O(f ) + O(g) = O(maxff; gg) und die Multiplikationsregel O(f ) O(g) = O(f g). Worin besteht der praktische Nutzen dieser Regeln?
Losung zur Aufgabe 14: Voraussetzung: 9c1; c2 2 R+ und n1; n2 2 N mit T1(n) c1f (n) fur alle n n1 und T2(n) c2f (n) fur alle n n2.
Additionsregel: T1(n) + T2 (n) c maxff (n); g(n)g fur c = c1 + c2 und n0 maxfn1; n2g.
Multiplikationsregel: T1(n)T2(n) cf (n)g(n) fur c = c1c2 und n0 maxfn1; n2g.
Additionsregel fur die Komplexitat der Hintereinanderausfuhrung der Programme. Multiplikationsregel fur die Komplexitat von ineinandergeschachtelten Schleifen.
201
Bemerkung: Denition mit g cf stimmt uberein mit g af + b, falls f
6 0 monoton.
Aufgabe 15:
Wir betrachten Funktionen von den naturlichen Zahlen in die nicht-negativen Zahlen.
(a) Welche Funktionen liegen in (1)?
(b) Wann gilt f (n) 2 O(bf (n)c)?
(c) Seien a, b, c reell mit 1 < b; 1 a; c. Zeigen Sie:
logb(an + c) 2 (log2 n)
(d) Seien f1; f2; : : : Funktionen in O(g). Gilt dann
n
h(n) = X fi(n) 2 O(ng(n))?
i=1
(e) Sei P (n) ein Polynom vom Grad k. Zeigen Sie: O(log P (n)) = O(log n).
p
(f) Zeigen Sie: (log n)3 2 O( 3 n) und allgemein (log n)k 2 O(n) fur k; > 0.
Losung zur Aufgabe 15: Die Losung der Aufgabenteile (a)-(d) ndet sich in Rolf Klein
Algorithmische Geometrie, Addison-Wesley, S. 48. In Teil (e) gilt P (n) = O(nk ) und
damit O(log P (n)) = O(log nk ) = O(k log n) = O(log n). Der erste Teil von (f) ist
eine Folgerung des zweiten (k = 3, = 1=3). Den zweiten Teil beweist man am besten
mit Hilfe der Regel von L'Hospital.
(log n) = k lim (log n) =
lim
n!1 n
n!1 n
k(k 1) lim (log n)k 1 = : : : = k! lim (log n)k 1 = 0
2 n!1 n
k n!1 n
k
Aufgabe 16:
k
1
Sie haben die naive Schulmethode zur Multiplikationen von Matrizen in Rnn kennengelernt. Wir nehmen zur Vereinfachung an, da n eine 2er Potenz ist.
(a) Wie viele Multiplikationen und Additionen sind dazu notwendig? Geben Sie jeweils
die Groenordnung in der O-Notation an.
202
KAPITEL 10. THEORIE
(b) Strassen hat 1969 einen sehr eleganten Ansatz zur gefunden, die Laufzeit der
Schulmethode zu unterbieten. Er benutzt Divide-and-Conquer und zerteilt die
Matrix jeweils in vier n=2 n=2 Blocke. Seien diese fur die Matrix A mit
A11; A12; A21; A22 bezeichnet. Zur Berechnung von C = AB erzeugt er die sieben
Hilfsterme m1 = (A12 A22)(B21 + B22), m2 = (A11 + A22)(B11 + B22),
m3 = (A11 A21)(B11 + B12), m4 = (A11 + A12)B22, m5 = A11(B12 B22),
m6 = A22(B21 B11), m7 = (A21 + A22)B11 und fugt sie wie folgt zusammen: C11 = m1 + m2 m4 + m6, C12 = m4 + m5, C21 = m6 + m7,
C22 = m2 m3 + m5 m7.
Berechnen Sie die Groenordnung der Anzahl von Elementaroperationen (Additionen und Multiplikationen), indem Sie eine Rekursionsgleichung aufstellen und das
Mastertheorem anwenden.
Losung zur Aufgabe 16: In einer Rekursion haben wir 7 Multiplikationen und 18
Additionen/Subtraktionen. Wir haben die folgende Rekursionsgleichung fur die Anzahl
der Ringoperationen in R: T (1) = 1 und T (n) = 7T (n=2) + 18n2 fur alle n = 2k
mit k > 1.
Da 7 > 22 ergibt sich nach dem Mastertheorem mit f (n) = 18n2 die geschlossene Form
T (n) = O(nlog2 7).
Bemerkungen: log2 7 2:81. Coppersmith und Winograd haben den Exponenten 1986
auf 2.38 verbessern konnen. Es ist ein oenes Problem, wie nahe die opere Schranke der
magischen n2 Grenze angenahert werden kann.
Aufgabe 17:
Countingsort ist ein Sortierverfahren ohne Vergleiche. Gegeben ist ein Array A[0..n-1]
mit A[i] 2 f0; :::; k 1g. Ausgabe ist ein sortiertes Array B[0..n-1]. Genutzt wird ein
Array C[0..k-1], in dem die Anzahlen der Elemente der Groe A[i] innerhalb gezahlt
werden. Diese Information wird dann zur Extraktion der sortierten Reihenfolge genutzt.
Schreiben Sie ein Pseudocodeprogramm fur Countingsort und zeigen Sie, da die Laufzeit
in O(n + k) liegt.
Losung zur Aufgabe 17:
CountingSort(int A[], int B[]) {
int C[] = new int[k];
for (int i=0;i<k;i++) C[i] = 0;
for (int i=0;i<n;i++) C[A[i]]++;
for (int i=0;i<k;i++) C[i] = C[i]+C[i-1];
for (int i=n-1;i>=0;i--) {
203
B[C[A[i]]-1] = A[i];
C[A[i]]--;
}
}
Die Laufzeit ist durch die Schleifenintervalle festgelegt.
Aufgabe 18:
p
Nach der Stirling-Formel konvergiert der Quotient von n! und 2nn+1=2e n : Zeigen
Sie log n! n log n 1:4427n + O(log n):
Losung zur Aufgabe 18:
(a) log n! n log n n log e + O(log n) n log n 1:4427n + O(log n)
Aufgabe 19:
Sortieren Sie die Elemente 4,7,8,9,0,1,5,3,2,6
(a) mittels Quicksort (nutzen Sie dabei die erste Zahl als Pivotelement).
(b) mittels Heapsort.
Zur Aufgabenlosung zugelasse Arbeitsmaterialien sind nur Zettel und Stift.
Losung zur Aufgabe 19:
(a) Keine Losung
(b) Aufbauphase:
(4,7,8,9,0|1,5,3,2,6) { Vergleich (6,0)
(4,7,8,9|0,1,5,3,2,6) { Vergleich (9,3,2)
(4,7,8|2,0,1,5,3,9,6) { Vergleich (1,5,8)
(4,7|1,2,0,8,5,3,9,6) { Vergleich (7,2,0),(6,7)
(0,2,1,3,6,8,5,4,9,7) { Vergleich (4,0,1),(4,2,6),(4,3,9)
Sortierphase:
(7,2,1,3,6,8,5,4,9|0) { Vergleich (7,1,2),(7,8,5)
(9,2,5,3,6,8,7,4|1,0) { Vergleich (9,2,5),(9,3,6),(9,4)
(9,3,5,4,6,8,7|2,1,0) { Vergleich (9,3,5),(9,4,6)
(7,4,5,9,6,8|3,2,1,0) { Vergleich (7,4,5),(7,9,6)
(8,6,5,9,7|4,3,2,1,0) { usw.
KAPITEL 10. THEORIE
204
Aufgabe 20:
Sei S eine Menge von n paarweise verschiedenen Elementen. Ein -Goodsplitter liefert
fur 2 [1=2; 1) (unabhangig von S ) ein Element x in der Menge S mit
jfy 2 Sj y < xgj (1 )n und jfy 2 Sj y > xgj (1 )n.
In der Vorlesung haben Sie gesehen, da der Median-der-Mediane ein deterministischer
-Goodsplitter mit 7=10 ist. Wir stellen nun einen randomisierten -Goodsplitter
vor:
Goodsplitter (S )
repeat
Wahle x 2 S zufallig
until x ist ein -Goodsplitter
Zu zeigen ist, da Goodsplitter im Mittel 1=(2 1) Versuche benotigt, um ein gutes
Splitelement I zu nden. Die Wahrscheinlichkeit p(I = i), da man dazu i Versuche
benotigt, ist [2(1 )]i 1 (1 2(1 )).
(a) Erklaren Sie, wie sich dieser Wert zusammensetzt.
(b) Berechnen Sie den Erwartungswert E [I ] = Pi1(i p(I = i)) fur die Anzahl der
Versuche.
Es ist dabei nutzlich, = 2(1 ) zu setzen und sich der Berechung
von Pi1 i=2i zu erinnern (HEAPSORT-Generierungsphase).
(c) Folgern Sie: Goodsplitter (S ) benotigt eine erwartete Zahl von hochstens n=(2
1) Vergleichen.
Losung zur Aufgabe 20:
(a) Urnenexperiment: Anteil Kugeln Mierfolg 1
Mierfolgen im i-ten Schritt Erfolg.
(b)
Erfolg. Nach i 1 Runden mit
E[I ] = X i p(I = i)
i
X
= (1 ) ii
i
X X i
= (1 )
i j<i
XX i
= (1 )
1
1
1
10
1
j 0 i>j
1
X X i 1+j
j 0 i>0
X X
= (1 ) j i 1
205
= (1 )
j 0
= (1 )
X
i1
j
(1 )
X j j0
=
j 0
= 1 1
(c) Eine Runde kosten gema Aufteilungsschritt in Divide maximal n Vergleiche. Demnach ist nE [I ] die erwartete Anzahl von Vergleichen.
Aufgabe 21:
Select (S; k) liefere die k-te Zahl aus der in der aufsteigender Ordnung sortierten Menge
S mit 1 k n.
Select (S; k)
x Goodsplitter (S)
fy 2 Sj y < xg
fy 2 Sj y > xg
cases
jS<j k : return Select (S<; k)
jS<j = k 1 : return x
jS<j < k 1 : return Select (S>; k (jS<j + 1))
S<
S>
Bermerkung: Bei diesem Algorithmus (und dem in der nachsten Aufgabe) fehlt die Abbruchbedingung fur die Rekursion.
Zeigen Sie, da falls Goodsplitter hochstens en Vergleiche braucht (e eine Konstante),
dann braucht Select hochstens (e + 1) n=(1 ) viele Vergleiche. Hinweis: Rollen Sie
die Rekursionsgleichung S (n) en + n + S (n) auf.
Losung zur Aufgabe 21:
S(n) = en + n + S(n)
= (e + 1)n + S (n)
= (e + 1)n + (e + 1)n + S (2n)
KAPITEL 10. THEORIE
206
= (e + 1)n(0 + 1 + 2 + : : :)
= (e + 1)n=(1 )
Aufgabe 22:
Die folgende Prozedur Splitsort nutzt die Bestimmung des Medians durch den Select Aufruf Select (S; dn=2e) aus Aufgabe 4.
Splitsort (S )
x Select (S; dn=2e)
fy 2 Sj y < xg
fy 2 Sj y > xg
return Splitsort (S<; x; S> )
S<
S>
Zeigen Sie: Falls Select bei der Eingabegroe n hochstens dn Vergleiche braucht (d konstant), dann braucht Splitsort hochstens O(n log n) Vergleiche.
Zusatzaufgabe: (ohne Bewertung) Zeigen Sie, da fur n = 2k ist die Anzahl der Vergleiche in Splitsort genauer durch (d + 1) n (1 + log n) beschrankt ist. Folgern Sie,
da Splitsort
mit dem randomisierten Goodsplitter aus Aufgabe 3 im Mittel maximal
5 22 1
n
(1 + log n) viele Vergleiche benotigt.
(1 )(2 2)
Losung zur Aufgabe 22: Die Rekursionsgleichung ist von der Form T (n) 2T (n=2) + cn mit einem konstanten Aufwand fur T (1) von sagen wir c. Fur n = 2k
ist T (n) = c 2k (k + 1) = c n (1 + log n). Dies lat sich leicht durch Induktion
zeigen: T (2k) = 2T (2k 1) + c 2k = 2 c 2k 1 k + c 2k = c 2k (k + 1). In
unserem Fall ist c = d + 1, so da (d + 1) n (1 + log n) = O(n log n) Vergleiche
in Splitsort notwendig sind.
Die Folgerung entsteht durch Einsetzen von den zwei vorangegangenen Aufgaben.
Aufgabe 23:
Losen Sie mithilfe des Master-Theorems die folgenden drei Rekursionsgleichungen auf und
bestimmen Sie die Groenordnung von T (n):
(a) T (n) = 8T (n=3) + n2
(b) T (n) = 9T (n=3) + n2
(c) T (n) = 10T (n=3) + n2
207
Dabei konnen Sie von den Anfangswerten und der Nicht-Ganzzahligkeit abstrahieren.
Losung zur Aufgabe 23:
(a) T (n) = (n2)
(b) T (n) = (n2 log n)
(c) T (n) = (nlog3 10)
Aufgabe 24:
Finden Sie fur die folgenden Rekursionsgleichungen die Groenordnungen fur T (n):
(a) T (n) = 2T (
pn) + log n.
(b) T (n) = n + T (n=4) + T (3n=4).
Abstrahieren Sie von den Anfangswerten und der Ganzzahligkeit der Argumente in T .
Losung zur Aufgabe 24: Uwe Schoning: Algorithmen, kurz gefat. Seite 25-27.
Aufgabe 25:
Sei T (r) ein geordneter Baum (die Kinder eines Knotens sind linear geordnet) mit Wurzel
r und Kindern v ; : : : ; vn.
Dann
ist
der
Postorderdurchlauf
post(T )
deniert
als
post(T (v )) : : : post(T (vk)) r. Entsprechend sind der Inorderdurchlauf in(T )
als in(T (v )) r in(T (v )) : : : in(T (vk )), der Preorderdurchlauf pre(T )
als r pre(T (v )) : : : pre(T (vk )) und der Levelorderdurchlauf lev(T ) als
r lev(T (v )) r lev(T (v )) : : : r lev(T (vk)) r festgelegt.
Beweisen Sie die folgenden Aussagen fur paarweise verschiedene Schlussel in T oder nden
1
1
1
2
1
1
2
Sie ein Gegenbeispiel.
(a) Aus der Levelorder kann T eindeutig rekonstruiert werden.
(b) Preorder und Inorder beschreiben einen Baum T eindeutig.
Unterscheiden Sie dabei den binaren (max. zwei Kinder) und den allgemeinen Fall.
(c) Preorder und Postorder beschreiben T eindeutig.
Losung zur Aufgabe 25:
KAPITEL 10. THEORIE
208
(a) Ja. Besteht lev(T ) nur aus einem Knoten, hat der Baum nur diesen Knoten. Ansonsten ist der erste Knoten Wurzel des Baumes. Zwischen den verschiedenen Darstellungen der Wurzel nden wir fur die Teilbaume die Levelorder. Nach Induktionsvoraussetzung konnen wir die Teilbaume eindeutig rekonstruieren.
(b) Nein. Gegenbeispiel, ex. 2 Baume T1; T2 mit pre(T1) = pre(T2) = 1; 2; 3; 4; 5; 6
in(T1) = in(T2) = 2; 1; 4; 3; 5; 6:
lev(T1) = 1; 2; 1; 3; 4; 3; 5; 3; 1; 6; 1 und lev(T2) = 1; 2; 1; 3; 4; 3; 1; 5; 1; 6; 1
Fur Binarbaume gilt die Aussage. Beweis: Nach Denition gilt
pre(T ) = r pre(T ) : : : pre(Tk)
1
und
in(T ) = in(T ) r in(T ) : : : in(Tk ):
1
2
Aus pre(T ) entnehmen wir, da der erste Knoten Wurzel von T ist. Wir suchen in
in(T ) und erhalten links und rechts von r in(T1) und in(T2). Damit kennen wir
auch die Knotenmengen von T1 und T2 und konnen pre(T1) und pre(T2) trennen.
Mit den Darstellungen fur T1 und T2 fahren wir analog fort, bis wir auf Baume mit
einem Knoten stoen.
Anmerkung: Aufgrund der Symmetrie von Preorder und Postorder konnen wir in
dem Beweis Preorder durch Postorder ersetzen.
(c) Ja. Nach Denition gilt:
pre(T ) = r pre(T ) : : : pre(Tk)
1
und
post(T ) = post(T ) : : : post(Tk) r;
1
wobei k a priori nicht bekannt ist. Oensichtlich konnen wir die Wurzel r erkennen.
Der erste Knoten in pre(T1) ist die Wurzel von T1, die in post(T1) am Ende
steht. Damit konnen wir post(T1) abtrennen. Da wir nun die Knotenmenge von
T1 kennen konnen wir auch pre(T1) abtrennen. Dieses Verfahren fuhren wir fort,
bis wir k, pre(T1), pre(T2), . . . , pre(Tk ), post(T1), post(T2), . . . , post(Tk )
kennen. Fur diese Teilbaume fahren wir analog fort, bis wir auf Baume mit einem
Knoten stoen.
Aufgabe 26:
Wir analysieren das MIN-MAX-Problem aus dem letzten Praxisblatt. Innerhalb eines
zufallig mit den n Ganzzahlelementen a[0]; : : : ; a[n 1] gefullten Arrays a soll das
minimale und maximale Element gefunden werden. Sie konnen n als gerade voraussetzen.
209
(a) Zeigen Sie, da n 1 Vergleiche notwendig sind, um eines der beiden Elemente zu
nden und ein solches Verfahren existiert.
(b) Zeigen sie, da ein einfaches Scan-Verfahren mit den Variablen
maxsofar existiert, das h
ochstens 3n=2 2 Vergleiche benotigt.
minsofar
und
(c) Zeigen Sie, da mindestens 3n=2 2 Vergleiche zur Losung des Problems notwendig
sind. Dabei beginnen Sie wie folgt:
\Sei ein korrektes Verfahren V zur Losung des MIN-MAX-Problems gegeben. Seien
SMIN (bzw. SMAX ) die Mengen derjenigen Elemente, die in einem Zustand von
V sicher groer (kleiner) sind als das Minimum (Maximum) aller.
Initial ist S =j SMIN j + j SMAX j= 0 und zum Ende von V ist S = 2n 2.
Bezeichne mit comp(a; b) = (minfa; bg; maxfa; bg) die Vergleichsoperation
zweier Werte a; b. Eine Anwendung von comp(a; b) kann die Mengen SMIN und
SMAX vergroern."
Geben Sie eine Fallunterscheidung der Vergleichsmoglichkeiten an, und der Gewinn
an Information, der sich als Summand von S ergibt.
Losung zur Aufgabe 26:
(a) Verfahren: Vergleich a[1] und a[2]. Vergleich maxfa[1]; a[2]g und a[3] usw. Dieses sind n 1 Vergleiche. Da bei jedem Vergleich, den ein Algorithmus tatigt,
hochstens ein Element als Nicht-Maximum ausscheiden kann, mussen mindestens
n 1 Vergleiche durchgefuhrt werden.
(b) Man betrachte im Scanverfahren immer zwei Arrayelemente auf einmal und vergleiche sie. Das resultierende Minimum wird mit minsofar verglichen und das resultierende Maximum mit maxsofar. Dieses fuhrt zu n=2 + (n=2 1) + (n=2 1) =
3n=2 vielen Vergleichen.
(c) Fur S ergibt sich ein Summand von
0 (ungunstig) oder 2 (gunstig), falls (a; b) 2 SMIN SMAX [SMAX SMIN ,
2, falls a; b 2= SMIN [ SMAX ,
1, falls (a; b) 2 SMIN SMIN [ SMAX SMAX ,
1 (ungunstig) oder 2 (gunstig), sonst.
KAPITEL 10. THEORIE
210
Es geht um Mindestanzahl der Vergleiche des besten Algorithmus bei der ungunstigsten Eingabe. Der Algorithmus mit den wenigsten Vergleichen wahlt dementsprechend die Falle aus (soweit moeglich) und die schlechteste Eingabe beschert ihn
dann den ungunstigsten Fall. Wenig Schritte entsprechen viel Information, deshalb
waehlt der beste aller Algorithmen den Fall mit den garantierten 2 Einheiten soweit moeglich. Dies kann er aber nur n=2 mal. Dann waehlt er einen Fall, der
ihm mindestens eine Informationseinheit liefert. Dies macht er, bis der Rest der
2n 2 2n=2 Informationseinheiten verbraucht sind. Demnach werden mindestens n=2+(2n 2 n) = 3n=2 2 viele Vergleiche im worst case durchgefuhrt.
Aufgabe 27:
Fugen Sie der Reihe nach die folgenden Schlussel in einen lexikographisch geordneten,
zunachst leeren naturlichen Suchbaum ein: eins, zwei, . . . , zehn. Danach loschen Sie die
Schlussel sieben, zwei und vier.
Losung zur Aufgabe 27: Keine Losung.
Aufgabe 28:
Zeichnen Sie fur die Schlusselmenge S = f1; 2; 3g alle Suchbaume der Hohe 3.
Losung zur Aufgabe 28:
3i
3i 1i
1i
L
L
L
L
2i
3i
2i
1i
L
L
L
L
1i
2i 2i
3i
L
L
L
L
Aufgabe 29:
Sei N = 2h 1 mit h als naturliche Zahl. Sei T (h) die Anzahl der Permutationen
einer N -elementigen Schlusselmenge, die beim Einfugen in den anfangs leeren Baum
einen vollstandigen Suchbaum erzeugen. Geben Sie ein Rekursionsgleichungssystem zur
Bestimmung von T (h) an. Berechnen Sie danach T (h) fur h 2 f0; 1; 2; 3g.
Losung zur Aufgabe 29: Es ist
8
<
h=0
T (h) = : T (h 1)12 2h 2 falls
sonst.
2h 1 1
211
Der Fall h = 0, N = 0 ist wieder einfach, so wie oben. Ansonsten (h 1, N = 2h 1)
mu das mittlere Element der Schlusselmenge zuerst in die Wurzel eingefugt werden. Fur
den linken und den rechten Teilbaum stehen dann unabhangig voneinander T (h 1)
viele Moglichkeiten zur Verfugung. Auerdem konnen noch 2h 1 1 viele Positionen in
der Sequenz von 2h 2 weiteren einzuf
ugenden
Schlusseln fur die Schlussel des linken
2h 2
Teilbaums bestimmt werden, das macht 2h 1 1 viele Moglichkeiten. Es ist
h 0 1 2 3
4 :::
T (h) 1 1 2 80 21964800 : : :
Aufgabe 30:
Wieviele verschiedene Strukturen S (N ) von Suchbaumen fur N Schlussel der Hohe N
gibt es? Geben Sie eine kurze Begrundung an!
8
<
Losung zur Aufgabe 30: S (N ) = : N1
2
falls N=0
1
sonst.
Im Fall N = 0 gibt es eine Struktur, der Baum besteht aus einem Blatt, das auch Wurzel
ist. Ansonsten kann jeder innere Knoten auer der Wurzel entweder linker oder rechter
Sohn seines Vaters sein, was jeweils 2 Moglichkeiten ergibt, nur fur die Wurzel gibt es nur
eine Moglichkeit.
Aufgabe 31:
Sei T (r) ein Baum mit Wurzel r und Kindern v1; : : : ; vn . Dann ist der Postorderdurchlauf post(T ) deniert als post(T (v1)) : : : post(T (vk )) r. Entsprechend sind der Inorderdurchlauf in(T ) als in(T (v1)) r in(T (v2)) : : : in(T (vk )), der Preorderdurchlauf pre(T ) als r pre(T (v1)) : : : pre(T (vk)) und der Levelorderdurchlauf lev(T ) als
r lev(T (v1)) r lev(T (v2)) : : : r lev(T (vk)) r festgelegt. Beweisen Sie die folgenden
Aussagen fur paarweise verschiedene Schlussel in T oder nden Sie ein Gegenbeispiel.
(a) Aus der Levelorder kann T eindeutig rekonstruiert werden.
(b) Preorder und Inorder beschreiben einen Baum T eindeutig.
Unterscheiden Sie dabei den binaren (max. zwei Kinder) und den allgemeinen Fall.
(c) Preorder und Postorder beschreiben T eindeutig.
Losung zur Aufgabe 31:
KAPITEL 10. THEORIE
212
(a) Ja. Besteht lev(T ) nur aus einem Knoten, hat der Baum nur diesen Knoten. Ansonsten ist der erste Knoten Wurzel des Baumes. Zwischen den verschiedenen Darstellungen der Wurzel nden wir fur die Teilbaume die Levelorder. Nach Induktionsvoraussetzung konnen wir die Teilbaume eindeutig rekonstruieren.
(b) Nein. Gegenbeispiel, ex. 2 Baume T1; T2 mit pre(T1) = pre(T2) = 1; 2; 3; 4; 5; 6
in(T1) = in(T2) = 2; 1; 4; 3; 5; 6:
lev(T1) = 1; 2; 1; 3; 4; 3; 5; 3; 1; 6; 1 und lev(T2) = 1; 2; 1; 3; 4; 3; 1; 5; 1; 6; 1
Fur Binarbaume gilt die Aussage. Beweis: Nach Denition gilt
pre(T ) = r pre(T ) : : : pre(Tk)
1
und
in(T ) = in(T ) r in(T ) : : : in(Tk ):
1
2
Aus pre(T ) entnehmen wir, da der erste Knoten Wurzel von T ist. Wir suchen in
in(T ) und erhalten links und rechts von r in(T1) und in(T2). Damit kennen wir
auch die Knotenmengen von T1 und T2 und konnen pre(T1) und pre(T2) trennen.
Mit den Darstellungen fur T1 und T2 fahren wir analog fort, bis wir auf Baume mit
einem Knoten stoen.
Anmerkung: Aufgrund der Symmetrie von Preorder und Postorder konnen wir in
dem Beweis Preorder durch Postorder ersetzen.
(c) Ja. Nach Denition gilt:
pre(T ) = r pre(T ) : : : pre(Tk)
1
und
post(T ) = post(T ) : : : post(Tk) r;
1
wobei k a priori nicht bekannt ist. Oensichtlich konnen wir die Wurzel r erkennen.
Der erste Knoten in pre(T1) ist die Wurzel von T1, die in post(T1) am Ende
steht. Damit konnen wir post(T1) abtrennen. Da wir nun die Knotenmenge von
T1 kennen konnen wir auch pre(T1) abtrennen. Dieses Verfahren fuhren wir fort,
bis wir k, pre(T1), pre(T2), . . . , pre(Tk ), post(T1), post(T2), . . . , post(Tk )
kennen. Fur diese Teilbaume fahren wir analog fort, bis wir auf Baume mit einem
Knoten stoen.
Aufgabe 32:
Fugen Sie die Monatsnamen nacheinander in einen lexikographisch geordneten, anfangs
213
leeren AVL-Baum ein. Wenn immer eine Rebalancierung notwendig ist, zeichnen Sie den
Baum.
Losung zur Aufgabe 32: Hartmund Guting Datenstrukturen und Algorithmen, Teubner. Seite 126-127.
Aufgabe 33:
Betrachten Sie die Struktur eines AVL-Baumes aus Abbildung 1.
Abbildung 10.2: Die Struktur eines AVL-Baumes mit 10 Schlusseln.
(a) Geben Sie die verschiedenen Strukturen der AVL-Baume an, die durch die Einfugung
von einem Schlussel entstehen konnen.
(b) Vorausgesetzt, da die Schlussel mit der gleichen Wahrscheinlichkeit in jedes der
Intervalle zwischen den bereits im Baum bendlichen Schlusseln fallen; wie gro
ist die Wahrscheinlichkeit, da die Hohe des angegebenen AVL-Baumes nach zwei
Einfugungen um eins wachst?
Losung zur Aufgabe 33:
214
KAPITEL 10. THEORIE
Erste Einfugung: Situation 1,2 und 5 in 3/11 der Falle, Situation 3,4 in 1/11 der Falle.
Zweite Einfugung: Nur in den Fallen 1 und 2 kann die Hohe des Baumes wachsen, jeweils
mit Wahrscheinlichkeit 4/12. Damit ergibt sich insgesamt eine Wahrscheinlichkeit von
2(3/11 4/12) = 2/11.
Aufgabe 34:
(Knotengefarbte) Rotschwarz-Baume sind binare Suchbaume mit den folgenden Eigenschaften: i) Jeder Knoten ist entweder rot oder schwarz. ii) Jedes Blatt ist schwarz.
iii) Falls ein Knoten rot ist, dann sind beide Kinder schwarz. iv) Jeder Pfad von einem
Knoten zu einem Blatt enthalt die gleiche Anzahl von schwarzen Knoten.
Zeigen Sie
215
(a) Ein (knotengefarbter) Rotschwarzbaum mit
von hochstens 2 log(n + 1).
n inneren Knoten besitzt eine Hohe
(b) Entsprechend den AVL-Baumen gibt es Operationen Left-Rotate und
Right-Rotate. Skizzieren Sie die Einf
arbungs- und Balancierungsoperationen
der Einfugeprozedur eines Elementes in einem Rotschwarzbaum in Pseudocode.
Dabei starten Sie mit dem eingefugten Knoten und der Farbung \rot" und hangeln
sich je nach Lage und Farbe der Ahnen mittels Rotationen und Umfarbungen uber
die Vorgangerverweise an den Knoten zu den Eltern und Ureltern hinauf.
Losung zur Aufgabe 34: Cormen, Leiserson Rivest Introduction to Algorithms Seite
263-266.
Aufgabe 35:
Persistente Datenstrukturen ermoglichen einen Zugri in die Vergangenheit der dynamischen Veranderungen. Wir konzentrieren uns auf persistente Suchbaume. Eine Moglichkeit zur Implementation ist es, eine Kopie des Suchbaumes anzulegen, wenn immer er sich
verandert.
4
3
2
4
4
3
8
7
10
2
8
7
7
8
10
5
Abbildung 10.3: Ein persistenter Suchbaum vor und nach dem Einfugen von 5.
Eine ezientere Moglichkeit (vergleiche Abbildung 2) besteht darin, die sich nicht
verandernden Teile der Datenstruktur wiederzuverwerten.
(a) Beschreiben Sie die Knoten, die bei der Einfugung eines Schlussels k oder bei der
Loschung eines Knotens y verandert werden mussen.
(b) Angenommen, die derzeitige Hohe des Suchbaumes ware durch h gegeben und Knoteninformation bestunde aus dem Schlussel, dem linken und dem rechten Nachfolger.
Was ist die Laufzeit des Einfugens eines Knotens in die persistente Suchbaumstruktur?
KAPITEL 10. THEORIE
216
(c) Geben Sie Argumente dafur an, da sich der Algorithmus bei gleicher Laufzeit auf
balancierte Suchbaume erweitern lat.
Losung zur Aufgabe 35: Kurzlosung:
(a) Es sind die Knoten auf dem Suchpfad der sich durch die Einfugung des Knotens
ergibt. Beim Loschen mu der Pfad zum symmetrischen Vorganger bzw. Nachfolger
berucksichtigt werden.
(b) Der Algorithmus lauft in O(h).
(c) Der Algo lauft dann in O(log n), da auch die Rotationsoperationen lokal entlang
eines Pfades agieren.
Ausfuhrliche Informationen uber Persistente Datenstrukturen, insbesondere, wie sich eine
Schranke von O(1) fur den zusatzlichem Platz nden lat, siehe Discroll, Sarnak, Sleator
and Tarjan, Making Data Structures Persistent, Journal of Computer and System Sciences
38, 86-124 (1989).
Aufgabe 36:
Fugen Sie der Reihe nach die folgenden Schlussel in einen zunachst leeren
(a) AVL-Baum
(b) Bruder-Baum
(c) (2-3)-Baum (B-Baum der Ordnung 3)
ein: 4,5,7,2,1,3,6. Danach loschen Sie die Schlussel 5,6 und 7. Geben Sie die Zwischenergebnisse und die Art der durchgefuhrten Operationen an.
Losung zur Aufgabe 36: Keine Losung.
Aufgabe 37:
Seien a; b; c naturliche Zahlen und n 2 fc0; c1; c2; : : :g.
8
< b;
fur n = 1
(a) Falls T (n) = :
aT (n=c) + bn; fur n > 1
so folgt
8
>
falls a < c
>
< O(n);
T (n) = > O(n log n); falls a = c
>
: O(nlogc a); falls a > c
217
8
< b;
fur n = 1
(b) Falls T (n) = :
aT (n=c) + bn2; fur n > 1
so folgt 8
2
>
falls a < c2
>
< O(n2);
T (n) = > O(n log n); falls a = c2
>
: O(nlogc a); falls a > c2
Hinweis:
Setzen Sie r = a=c bzw.Pr = a=c2 und nutzen Sie, die Darstellung T (n) =
P
n
log n
bn log
i=0c ri bzw. T (n) = bn2 i=0c ri. Diese Darstellungen kann man jeweils durch
eine einfache Induktion bestatigen. Unterscheiden Sie die Falle r < 1, r = 1 und r > 1.
Losung zur Aufgabe 37: Wir beweisen nur die zweite Aussage. Die
erste
Aussage
cn i
verlauft analog. Vorerst zeigen wir per Induktion, da T (n) = bn2 Plog
r
ist. Fur
i=0
n = 1 gilt T (n) = b. Fur n > 1 ist
T (n) = aT (n=c) + bn2
(n=c)
2 logcX
= a b nc2
ri + bn2
i=0
Xn
= a b nc2
2 logc ( ) 1
= bn2(1 + r
=
=
=
Es gibt 3 Falle.
i=0
Xn
ri + bn
logc ( ) 1
2
ri )
i=0
n
bn2(1 + X ri+1 r1+logc n)
i=0
logc n
bn2(1 + X ri + r1+logc n r1+logc n)
i=1
logc n
X
bn2 ri
i=0
logc
Fall 1: a < c2 also r < 1. Dann gilt
T (n) bn2 + X ri = bn2 1 1 r = O(n2)
i0
KAPITEL 10. THEORIE
218
Fall 2: a = c2 also r = 1. Dann gilt
T (n) = bn2(1 + logc n) = O(n2 log n)
Fall 3: a > c2 also r > 1. Dann gilt
T (n) = bn + r rr 1 1 =
= O(n r n) = O((c )
= O(a n) = O(n a)
logc
2
2 logc
logc
Aufgabe 38:
n
2 logc
logc
Zeigen Sie: Ein k-narer Baum (jeder innere Knoten hat
insgesamt (Blatter und innere Knoten) r + kr 11 Knoten.
n (a=c2 )logc n )
k Sohne) mit r Blattern hat
Losung zur Aufgabe 38: Induktion uber die Anzahl der Blatter r. Ein k-narer Baum
mit k Blattern besitzt k + (k 1)=(k 1) = k + 1 Knoten. Ein k-narer Baum mit r
Blattern besteht aus einer Wurzel r, einem Teilbaum mit r1 Blattern einem Teilbaum r2
Blattern und einem Teilbaum mit rk Blattern. Die Gesamtzahl der Knoten ist demnach
1 + r1 + rk1 11 + r2 + rk2 11 + : : : + rk + rkk 11 = 1 + r + rk k1 = r + kr 11
Aufgabe 39:
Fugen Sie die Schlusselfolge 16,44,21,5,19,22,8,33,27,30 gema der Hashfunktion h(k) =
k mod m fur m = 11 in eine Hashtabelle mittels oener Adressierung ein. Als Sondierungsfolge nutzen Sie die Funktion h(k) s(j; k) mod m. Berechnen Sie dabei den
Quotient aus der Anzahl von Vergleichen fur die Suche der einzelnen Elemente in der
endgultigen Tabelle mit der Anzahl der insgesamt gespeicherten Elemente.
(a) Verwenden Sie lineares Sondieren s(j; k) = c j mit c = 1.
(b) Verwenden Sie die quadratische Sondierungsfolge s(j; k) = ( 1)j dj=2e2.
(c) Verwenden Sie Doppel-Hashing s(j; k) = h0(k) j . Bestimmen Sie ein geeignetes
h0 (Die Eignung von h0 mu nicht exakt bewiesen werden).
(d) Geben sie fur den Aufgabenteil (a) und m = 14 moglichst allgemein alle c an, die
sich als unbrauchbar erweisen. Begrunden Sie Ihre Losung.
219
Losung zur Aufgabe 39: Kollisionen gibt es fur die Gruppen 16,5,27 { 44,22,33 und
19,8,30. Damit ergibt sich
(a) Lineares Sondieren: Mittlerer Zugri 2.5 Belegung: 44,-,30,27,5,16,33,8,19,22,21.
(b) Quadratisches Sondieren:
,27,33,16,5,30,19,8,21.
Mittlerer
Zugri:
2.2
Belegung:
(c) Doppel-Hashing: Eine mogliche Hashfunktion: s(j; k) = j ((4 mod k)
lerer Zugri: 1.9. Belegung: 44,30,8,22,33,16,-,27,19,5,21.
44,22,-
5) Mitt-
(d) Alle c mit ggt(c; m) =
6 1 sind schlecht, da die Tabelle nur zu einem Bruchteil
ausgelastet wird.
Aufgabe 40:
Sortieren Sie die Elemente 4,7,8,9,0,1,5,3,2,6
(a) mittels Quicksort (nutzen Sie dabei die erste Zahl als Pivotelement).
(b) mittels Heapsort.
Zur Aufgabenlosung zugelasse Arbeitsmaterialien sind nur Zettel und Stift.
Losung zur Aufgabe 40:
(a) (4,7,8,9,0,1,5,3,2,6)
(0,1,3,2)(4)(7,8,9,5,6)
(0)(1,3,2)(4)(7,8,9,5,6)
(0)(1)(3,2)(4)(7,8,9,5,6)
(0)(1)(2)(3)(4)(7,8,9,5,6)
(0)(1)(2)(3)(4)(5,6)(7)(8,9)
(0)(1)(2)(3)(4)(5)(6)(7)(8,9)
(0)(1)(2)(3)(4)(5)(6)(7)(8)(9)
(b) Aufbauphase:
(4,7,8,9,0|1,5,3,2,6) { Vergleich (6,0)
(4,7,8,9|0,1,5,3,2,6) { Vergleich (9,3,2)
(4,7,8|2,0,1,5,3,9,6) { Vergleich (1,5,8)
(4,7|1,2,0,8,5,3,9,6) { Vergleich (7,2,0),(6,7)
(0,2,1,3,6,8,5,4,9,7) { Vergleich (4,0,1),(4,2,6),(4,3,9)
KAPITEL 10. THEORIE
220
Sortierphase:
(7,2,1,3,6,8,5,4,9|0) { Vergleich (7,1,2),(7,8,5)
(9,2,5,3,6,8,7,4|1,0) { Vergleich (9,2,5),(9,3,6),(9,4)
(9,3,5,4,6,8,7|2,1,0) { Vergleich (9,3,5),(9,4,6)
(7,4,5,9,6,8|3,2,1,0) { Vergleich (7,4,5),(7,9,6)
(8,6,5,9,7|4,3,2,1,0) { usw.
Aufgabe 41:
Auswahlsort ist ein einfacher Vorlaufer von Heapsort. Gegeben sei ein Elementarray A
von n vergleichbaren Objekten. Der Algorithmus sucht fur j = 1; : : : ; n 1 in dem
Array von der Stelle j aus das kleinste Element und tauscht es an die Stelle j .
(a) Bestimmen Sie die Anzahl der Vergleiche im best, worst und average case.
(b) Besimmen Sie die Anzahl von Bewegungen im best, worst und average case.
Losung zur Aufgabe 41: Vergleiche Ottmann und Widmayer Algorithmen und Datenstrukturen Seite 66 .
(a) Cmin(n) = Cmax(n) = Cmit(n) = Pni=11(n i) = Pni=11 i = (n2)
(b) Cmin(n) = Cmax(n) = Cmit(n) = 3(n 1)
Aufgabe 42:
Heaps sind einfache Datenstrukturen mit einem hohen Freiheitsgrad bezuglich der Anordnung der Elemente.
(a) Zeigen Sie, da und wie der Pfad zur Stelle n in einem Heap durch die Binardarstellung von n bestimmt ist.
(b) Heaps lassen sich als Vorrangwarteschlangen nutzen. U berlegen Sie, wie man die Datenstruktur anreichern kann, um eine beidseitige Vorrangwarteschlange zu erhalten,
die sowohl die Extraktion des Minimum als auch des Maximums in logarithmischer
Zeit erlaubt.
(c) Die harte Nu (freiwillige Zusatzaufgabe): Sei f (n) die Anzahl der Heaps mit n
paarweise verschiedenen Schlusseln und si die QGoe des Teilbaumes zur Wurzel
n
i, 1 i n. Zeigen
n 1Sie, da f (n) = n!= i=1 si gilt. Nutzen Sie dabei die
Rekursion f (n) = jT1j f (jT1j)f (jT2j) mit T1 bzw. T2 als der rechte bzw. linke
Teilbaum der Wurzel.
221
Losung zur Aufgabe 42:
(a) Der Pfad P zur Stelle n = (bk : : : b0) mit k = dlog ne besitzt die Wurzelelemente
(1 bk 1 : : : bi), i 2 f0; : : : ; kg:
(b) Die Idee ist es, eine minimum-geordnete und eine maximum-geordnete Vorrangwarteschlange M bzw. M 0 zu bilden und die Elementevektoren a bzw. a0 mit einem
Querverweis zu verbinden. Genauer: Wir verwalten zwei Bijektionen und 0 , so
da ai = a0(i) und a0 (i)=a0i gilt. Da sich jede Datenstrukturoperation auf einen
Sequenz von Tauschoperationen zuruckfuhren, lassen sich fur jeden Tausch die Invarianzen (0(j )) = j und 0 ((j )) = j in O(1) Zeit bewahren.
Eine alternative Implementation als balancierten Suchbaum schliet die Aufgabe
nicht aus.
(c) Die Division durch n! liefert
f (n) = 1 f (jT j) f (jT j)
n! n jT j! jT j!
1
1
2
2
und Expansion der Formel hin zu den Blattern das gewunschte Resultat.
Aufgabe 43:
Das in der Vorlesung vorgestellte Bottom-Up-Heapsort Verfahren lat durch eine geschickte Organisation der Tauschoperationen in der Anzahl der Schlusselbewegungen noch verbessern. A ndern Sie das in der Vorlesung vorgestellte Java-Programm so ab, da keine
Schlusselbewegungen wahrend der Sinkphase oder in der Methode bubbleUp ausgefuhrt
werden. Die Schlusselbewegungen sollen in einem dritten Schritt erfolgen und keine weiteren Schlusselvergleiche erfordern. Die Anzahl der Schlusselbewegungen soll proportional
zur Tiefe der fur den Schlussel gefundenen Position sein. (Hinweis: Merken Sie sich die
Indizes der wahrend der Sinkphase betrachteten Knoten in einem Hilfsarray.)
Losung zur Aufgabe 43:
PROCEDURE LeafSearch(m,i)
j = i
WHILE 2j < m DO
IF a[2j] < a[2j + 1] THEN j = 2j ELSE j = 2j + 1
IF 2j = m THEN j=m
PROCEDURE bubbleUp(i,j)
WHILE ( (i < j) AND (a[i] < a[j]) ) DO j = j DIV 2
222
KAPITEL 10. THEORIE
PROCEDURE Interchange(i,j)
l = bin(j)-bin(i)
x = a[i]
FOR k = l-1 DOWNTO 0 DO a[j DIV 2^(k+1)] = a[j DIV 2^k]
a[j] = x
Bemerkung: Bottom{Up{Heapsort benotigt im worst-case 1:5n log n + (n) und im
average case n log n + O(n) viele Vergleiche.
Aufgabe 44:
Jeder kennt die Schwierigkeit, in einer Werkbank zu einer Schraube die passende Mutter
zu nden. Wir gehen hierbei von n Schrauben unterschiedlicher Groe und von n korrespondierenden Muttern aus. Der Vergleich der Muttern und der Schrauben untereinander
bringt keine neue Information ein. Vergleicht man hingegen eine Schraube mit einer Mutter, so stellt sich heraus, da die Schraube i) entweder pat, ii) zu klein, oder iii) zu
gro fur die ausgewahlte Mutter ist.
(a) Zeigen Sie, da jeder Algorithmus zur Losung des Problems (n log n) viele Vergleiche benotigt. Orientieren Sie sich hierbei an dem Beweis der unteren Schranke
fur allgemeine Sortierverfahren.
(b) Geben Sie einen Algorithmus an, der fur die Suche die kleinsten Schraube und ihrer
korrespondierenden Mutter 2n 2 Vergleiche benotigt.
Losung zur Aufgabe 44:
(a) Der Beweis beruht auf einem Entscheidungsbaum, an dem die inneren Knoten entsprechend den Ausgangen des Vergleiches einer Mutter mit einer Schraube den Verzweigungsgrad 3 haben. Die Aufgabe kann so interpretiert werden, da eine Permutation der Zahlen f1; 2; : : : ; ng gesucht wird, so da die (i)-te Mutter auf die
i te Schraube pat. Jedem Blatt im Entscheidungsbaum weisen wir nun eine der
n! verschiedenen Permutationen zu. Durch die Vergleiche an den Knoten mussen
analog zu der Betrachtung in allgemeinen Sortierverfahren alle Permutationen erzeugt bzw. verarbeitet werden konnen. Ein Pfad innerhalb des Baumes entspricht
demnach der Ausfuhrung eines Programmes
p zur Zuordnung von Schrauben zu Muttern. Da nach der Sterlingformel n! 2n(n=e)n gilt, ist die minimale Tiefe
des Entscheidungsbaumes log3(n!) in (n log n)
(b) Hier ist ein Scanverfahren zu empfehlen. Wir betrachten zwei Arrays M (fur Muttern) und S (fur Schrauben) und denieren minsofar als Tripel bestehend aus der
223
derzeit kleinsten Mutter oder Schraube, dem somit assoziierten Typus des Minimums und einen Verweis zu dem entsprechenden Partner in dem jeweils anderen
Array. Zu Anfang werden minsofar auf (0,Mutter,-) und zwei Zahler i und j auf
Null gesetzt. Danach wird das folgende Programmstuck aufgerufen.
while (i<n || j<n)
if (M[i] < S[j]) minsofar = (i,Mutter,-)
else if (S[j] < M[i]) minsofar = (j,Schraube,-)
else minsofar = (i,Mutter,j) j++
Es ist einsichtig, da das Verfahren in seinen schlimmsten Fall gerat, falls sowohl die
kleinste Schraube als auch die kleinste Mutter sich am Ende des Arrays bendet.
Dieser Fall lat sich jedoch ein Schritt vorher abfangen, so da insgesamt nur 2n 2
Vergleiche zur Losung des Problems notwendig sind.
Aufgabe 45:
Kategorisieren Sie die vorgestellten Sortierverfahren gema der folgenden 7 Kriterien:
1. Das Sortierverfahren sollte allgemein sein. Objekte aus beliebig geordneten Mengen
sollten sortiert werden konnen. 2. Die Implementation des Sortierverfahrens soll einfach
sein. 3. Das Sortierverfahren soll internes Sortieren ermoglichen. Dabei steht neben den
Arrayplatzen nur sehr wenig Platz zur Verfugung. 4. Die durchschnittliche Zahl von wesentlichen Vergleichen, d.h. Vergleichen zwischen zu sortierenden Objekten soll klein sein.
Sie soll fur ein moglichst kleines c durch n log n + cn beschrankt sein. 5. Die worst-case
Zahl von wesentlichen Vergleichen soll klein sein. Sie soll fur ein moglichst kleines c0 durch
n log n + c0n beschrankt sein. 6. Die Zahl der ubrigen Operationen wie Vertauschungen,
Zuweisungen und unwesentliche Vergleiche soll hochstens um ein konstanten Faktor groer
als die Zahl der wesentlichen Vergleiche sein. 7. Die CPU-Zeit fur jede ubrige Operation
soll klein gegenuber der CPU-Zeit fur einen Vergleich zweier komplexer Objekte sein.
Losung zur Aufgabe 45: (mit historischen Anmerkungen)
BUCKETSORT basiert auf der Darstellung der zu sortierenden Elemente und ist
somit kein allgemeines Verfahren (Kriterium 1).
MERGESORT ist ein Divide-and-Conquer Algorithmus und benotigt fur n = 2k
durch ein paralleles Durchlaufen der schon sortierten Teillisten nur n log n n + 1
Vergleiche, allerdings ein Array der Lange 2n. Da n recht gro sein kann, ist man an 'InSitu' Algorithmen (Kriterium 3) interessiert. Daher ist MERGESORT nur zum externen
Sortieren geeignet.
KAPITEL 10. THEORIE
224
INSERTIONSORT (Steinhaus (1958)) ist eines der einfachsten Sortierverfahren. Es
benotigt durch n-maliges Einfugen mittels binarer Suche weniger als
nX1
i=1
n
n
i=2
i=2
dlog(i + 1)e = X dlog(i)e X log(i + 1) = log(n!) + n 1
Vergleiche, aber die Anzahl der Vertauschungen liegt selbst im average-case in (n2).
Das Interesse gilt aber Sortieralgorithmen, deren Anzahl von Vertauschungen und anderen
nicht-essentiellen Operationen klein ist (Kriterium 6).
SHELLSORT (Shell (1959)) besitzt als weiteren Parameter eine Folge von naturlichen
Zahlen h1; : : : ; ht. Dabei wird sukzessiv INSERTIONSORT auf den Elementen aufgerufen, die einen Abstand hi mit i = t; : : : ; 1 zueinander besitzen. Die geeignete Auswahl
der Abstandsfolge ist sehr entscheidend fur das Laufzeitverhalten von SHELLSORT. Es
werden im folgenden die Ergebnisse fur den worst-case von Operationen (Vergleiche und
Vertauschungen) zusammengefat.
Shell (1959) schlug die Abstandsfolge (1; 2; : : : ; 2k ) vor, doch liefert sie im Falle n = 2k
eine quadratische Laufzeit. Eine von Hibbard (1963) vorgeschlagene Sequenz ergab in der
Analyse von Papernov und Stasevich (1965) O(n3=2). Pratt (1979) gab eine (log2 n)
lange Folge an, die zu (n log2 n) vielen Operationen fuhrt. Sedgewick (1986) verbesserte die Grenze von O(n3=2) auch fur O(log n) beschrankte Folgen auf O(n4=3) und
in Zusammenarbeit mit Incerpi
p (1985) gab er als Weiterentwicklung dieser Idee die obe1+= log n
re Grenze von O(n
) fur ein > 0 an. Poonen (1993) zeigte, da dies die
bestmogliche Schranke ist. Cypher (1993) bewies fur SHELLSORT Netzwerke und fur
monoton fallende Abstandsfolgen eine Mindestgroe von (n log2 n= log log n). Letztendlich fand Poonen (1993) eine untere Grenze von (n(log n= log log n)2) Operationen
unabhangig von den gewahlten Abstande auch fur SHELLSORT als allgemeines Sortierverfahren. Die Analysen begrunden sich vielfach auf die Anzahl der Inversionen (vergl.
Knuth (1973)) und auf die Betrachtung des Frobenius-Problems (Brauer (1942)).
Festgehalten werden sollte, da SHELLSORT fur vorsortierte oder mittelgroe Eingaben
(wenige Tausend) schneller ist als nahezu alle bekannten schnellen Sortierverfahren.
QUICKSORT (Hoare (1962)) ist das wohl bekannteste, auf einer Partitionierung
der Schlusselmenge beruhende Sortierverfahren. Damit liegt der wesentliche Aufwand
des Divide-and-Conquer Ansatzes im divide{Schritt (n 1 Vergleiche). Dort wird die
endgultige Position k eines ausgewahlten Elementes x ermittelt und die Schlusselmenge
in einen von x dominierten bzw. in einen x dominierenden Teil eingeteilt.
Der Algorithmus benotigt bei ungunstiger Aufteilung der Objektmenge (n2) viele Vergleiche. Fur den average-case an Vergleichen V (n) ndet sich durch die Mittelung der
225
moglichen Falle folgende Rekursionsgleichung:
8
<
V (n) = : 0n 1 + 1 Pn (V (k 1) + V (n k))
n k=1
n 2 f0; 1g
n2
Diese Summe lat sich zu folgender Gleichung vereinfachen:
V (n) = 2(n + 1)Hn 4n;
wobei Hn = Pni 1=i die n-te Partialsumme der Harmonischen Reihe bezeichnet. Die
=1
Harmonische Reihe lat sich gut approximieren und man erhalt fur die mittlere Anzahl
der Vergleiche:
V (n) 1:386n log n 2:846n + O(log n)
CLEVER-QUICKSORT ist eine von Hoare (1962) vorgeschlagene verbesserte Vari-
ante von QUICKSORT. Da das Partitionselement weit vom Median entfernt sein kann,
wird das mittlere Element x von drei zur Partition vorgeschlagenen Objekten ermittelt,
und die Partitionierung der ubrigen Objekte bezuglich dieses Elementes durchgefuhrt. Bei
ungunstiger Aufteilung der Objektmenge sind wieder (n2) viele Vergleiche notig, doch
der average-case wird deutlich gesenkt.
Der Median dreier Objekte kann in durchschnittlich 8/3 Vergleichen gefunden werden,
was leicht durch Fallunterscheidung zu verizieren ist. Damit benotigt der divide{Schritt
im Mittel n 3 + 8=3 = n 1=3 Vergleiche.
n Die Wahrscheinlichkeit, da x an der
Position k steht, ist gleich (k 1)(n k)= 3 , da es dann k 1 Positionen fur das
kleinere und n k Positionen fur das groere Objekt gibt.
Fur den average-case an Vergleichen V (n) gilt demnach folgende Rekursonsgleichung:
8
>
0
>
<
V (n) = > 1
>
:n
n 2 f0; 1g
n=2
n Pn
+
(k 1)(n k)(V (k 1) + V (n k)) n 2
k
Diese Summe lat sich fur n 6 zu folgender Gleichung zusammenfassen (Wegener
(1995)):
1
3
1
3
=1
223 + 252
V (n) = 127 (n + 1)Hn 477
n
+
147 147 147n
1:188n log n 2:255n + O(log n):
Median nicht aus drei sondern aus 2k + 1 Elementen gebildet,
1
Wird der
lat sich
das mittlere Laufzeitverhalten noch verbessern und es ergibt sich eine Komplexitat von
KAPITEL 10. THEORIE
226
kn log n + O(n) mit konstanten k > 1. Bei wachsendem k konvergiert k gegen 1
(vergl. van Emden (1970)).
Derzeit ist keine QUICKSORT-Variante bekannt, deren average-case durch n log n +
o(n log n) beschrankt ist.
HEAPSORT Die worst-case Zahl wesentlicher Vergleiche von HEAPSORT ist durch
2n log n + (2 2c(n))n + 2blog 2c beschrankt. Fur den Aufbau eines Heaps genugen
2n 2 wesentliche Vergleiche.
BOTTOM-UP-HEAPSORT
benotigt nicht mehr als 1:5n log n + (2 c(n))n +
O(log2 n) Vergleiche. Fleischer (1991) sowie Schaer und Sedgewick (1993) haben worst-
case Beispiele angegeben, bei denen die Anzahl der wesentlichen Vergleiche fur BOTTOMUP-HEAPSORT gleich 1:5n log n o(n log n) ist.
Die Durchschnittsanzahl von Vergleichen fur BOTTOM-UP-HEAPSORT ist hochstens
n log n + O(n).
WEAK-HEAPSORT erfullt alle 7 Kriterien. Die Schlusselvergleichsanzahl im
ungunstigsten Fall ist durch n log n + 0:1n beschrankt. Empirische Studien belegen,
da der mittlere Fall bei n log n + d(n)n mit d(n) aus dem Intervall [ 0:47; 0:42]
liegt.
Die Beweise zu den letzten drei Abschnitten nden sich in Weak-Heapsort, ein schnelles
Sortierverfahren, Diplomarbeit Stefan Edelkamp.
Aufgabe 46:
Wir wollen den folgenden Sachverhalt zeigen:
Sei k = dlog ne. Die maximale Anzahl von Schlusselvergleichen von WEAKHEAPSORT ist nk 2k + n 1 n log n + 0:086013n.
Der Beweis lauft wie folgt:
Es gibt durch die Aufrufe combine(i) fur i = n
nX1
i=2
1; : : : ; 2 hochstens
dlog(i + 1)e = nk 2k
(10.1)
Vergleiche. Zusammen mit den n 1 Vergleichen, die zum Aufbau des Heaps
benotigt werden, sind dies hochstens nk 2k + n 1 Vergleiche fur den
gesamten Sortieralgorithmus.
227
Fur alle n 2 N existiert ein x 2 [0; 1[, so da:
nk 2k + n 1 = n log n + nx n2x + n 1
= n log n + n(x 2x + 1) 1:
Die reelle Funktion f (x) = x 2x + 1 ist nach oben durch 0.086013
beschrankt. Damit ist die Anzahl an Vergleichen geringer als n log n +
0:086013n.
Zeigen Sie:
(a) Pni=1 i2i = (n
1)2n+1 + 2 durch vollstandige Induktion.
(b) Pni=1dlog ie = nk 2k + 1 durch eine geschickte Aufteilung der Summe nach
jeweils 2i Elementen. Sie benotigen dann zur Losung die Gleichung aus dem ersten
Aufgabenteil.
(c) Die reelle Funktion f (x) = x 2x +1 ist fur x 2 [0; 1[ nach oben durch 0.086013
beschrankt.
Losung zur Aufgabe 46:
=1
(a) Induktionsanfang n = 1: 2 = 2. Induktionsschritt: Pni=0
i
2i = (n + 1)2n+1 +
(n 1)2n+1 + 2 = n2n+2 + 2
(b)
n
X
dlog ie =
i=1
dlogX
ne
1
i2i + dlog ne(n 2d
i=1
dlogX
n 1e
1
log
ne
1
)
= 21 (
i2i) + ndlog ne 21 dlog ne2dlog ne
i=1
1
HS
= 2 ((dlog ne 2)2dlog ne + 2) + ndlog ne 12 dlog ne2dlog ne
= 12 dlog ne2dlog ne 2dlog ne + 1 + ndlog ne 21 dlog ne2dlog ne
= ndlog ne 2dlog ne + 1:
KAPITEL 10. THEORIE
228
(c)
f (x) = x ex + 1:
f 0(x) = 1 ex ln 2
= 1 2x ln 2:
f 00(x) = 2x(ln 2) :
Nach dem notwendigen Kriterium fur ein Extremum f 0(x ) = 0 ergibt sich:
ln 2
ln 2
2
0
1 2x0 ln 2 = 0
2x0 = ln12
x0 ln 2 = ln 1 ln ln 2
x0 = lnlnln22 :
Fur x 2 [0; 1] ist f 00(x) kleiner als Null. Es gilt f(0) = f(1) = 0. Somit ist
Maximalstelle.
x
0
Kapitel 11
Praxis
Aufgabe 47:
Die Quadratwurzel einer positiven reelen Zahl a lat sich naherungsweise uber die folgende
Iterationsformel bestimmen: xn+1 = (xn + a=xn)=2. Schreiben sie ein Java Programm
Wurzel, das die Wurzel der Eingabe berechnet. Dabei gilt ein Iterationswert als gut genug,
falls er von dem nachfolgenden Wert um nicht mehr als 0.00001 abweicht.
Deklarieren Sie die Variablen im Bereich des Typs double, weisen Sie zur Verarbeitung
der Eingabe a den Wert Double.valueOf(args[0]).doubleValue() zu, starten Sie mit
dem Iterationswert 1, und nutzen Sie zur Berechung des Absolutbetrages die Funktion
Math.abs.
Losung zur Aufgabe 47:
class Wurzel {
public static void main(String args[]) {
double x = 1;
double a = Double.valueOf(args[0]).doubleValue();
while (Math.abs(x - 0.5 * (x + a/x)) > .00001) {
x = 0.5 * (x + a/x);
System.out.println("Die Wurzel von "+a+" ist " + x);
}
}
}
Bemerkung: Die Formel ergibt sich aus dem (schnell konvergierenden) Iterationsverfah-
ren nach Newton. Um eine Nullstelle einer Funktion f zu nden, nutzt man die allgemeine
Iterationsvorschrift: xn+1 = xn f (xn)=f 0(xn). In unserem Fall ist f (x) = x2 a und
f 0(x) = 2x. Weiteres Beispiel: Nullstellensuche in f (x) = x3 2:6x2 2:25x + 5:85.
229
KAPITEL 11. PRAXIS
230
Die Ableitung f 0(x) ist gleich 3x2 5:2x 2:25. Zum Startwert 1 ndet sich die Nullstelle 1.5, d.h. f (x) = (x 1:5)(x2 1:1x 3:9). Die weiteren Nullstellen -1.5 und
2.6 lassen sich nun direkt bestimmen.
Aufgabe 48:
Gegeben seien folgende Denitionen:
f (n) = g(n; 0)
und
g(n; i) = 0, falls n 0 gilt, und
g(n; i) = g(n 1 2i; i + 1) + 1, sonst.
Schreiben sie ein Java-Programm Funktion, das fur jedes ubergebene Argument n aus
den naturlichen Zahlen den Wert f (n) berechnet und ausgibt! Konnen Sie in Worten
beschreiben, welche Funktion durch f berechnet wird?
Losung zur Aufgabe 48:
public class Funktion {
static int g(int n, int i) {
System.out.println("g("+n+","+i+") =");
if (n<=0) return 0;
return g(n-1-2*i,i+1)+1;
}
public static void main(String args[]) {
int x = Integer.parseInt(args[0]);
System.out.println(g(x,0));
}
}
Es wird die (nach oben abgerundete) ganzzahlige Wurzel von n berechnet. Die Berechnung
der Wurzel beruht auf dem Zusammenhang 1 + 3 + 5 : : : + 2n 1 = n2.
Aufgabe 49:
In Rekursien treen sich die drei Einwohner Herr Ackermann, Herr Fibonacci und Frau
Ulam. Bald ist eine heftige Diskussion uber die Schonheit der von Ihnen erdachten
Funktionen entbrannt. Herr Ackermann betont das auergewohnliche Wachstum seiner
231
Funktion, Herr Fibonacci unterstreicht den inneren Zusammenhang der Funktionswerte,
wahrend Frau Ulam sich an den vielen Wegen zur Eins erfreut . . .
(a) In der Zwischenzeit sollen Sie zeigen, wie leicht sich alle drei Funktionen in Java implementieren lassen. Berechnen Sie fur Herrn Ackermann a(3; 5), fur Herrn
Fibonacci F (25) und fur Frau Ulam ulam (71). Als Hinweis soll Ihnen die Implementation der Ackermannfunktion dienen:
public class Acker {
static int a(int x, int y) {
System.out.println("a("+x+","+y+") =");
if (x==0) return y+1;
if (y==0) return a(x-1,1);
return a(x-1,a(x,y-1));
}
public static void main(String args[]) {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println(a(x,y));
}
}
(b) Erweitern Sie Ihre Programme so, da zusatzlich jeweils die Anzahl der rekursiven
Aufrufe ausgegeben wird.
Losung zur Aufgabe 49:
(a)
public class Fibonacci {
static int F(int x) {
System.out.println("F("+x+") =");
if (x<=1) return x;
return F(x-1)+F(x-2);
}
public static void main(String args[]) {
int x = Integer.parseInt(args[0]);
System.out.println(F(x));
}
}
public class Ulam {
KAPITEL 11. PRAXIS
232
static long ulam(long y) {
System.out.println("ulam("+y+") =");
if (y<=1) return 1;
if (y%2 == 0) return ulam(y/2);
else return ulam(3*y+1);
}
public static void main(String args[]) {
int x = Integer.parseInt(args[0]);
System.out.println(ulam(x));
}
}
(b)
public class Fibonacci {
static int count;
static int F(int x) {
count++;
System.out.println("F("+x+") =");
if (x<=1) return x;
return F(x-1) + F(x-2);
}
public static void main(String args[]) {
count = 0;
System.out.println(F(x));
System.out.println(" Anzahl der Rekursionsaufrufe = " + count);
}
}
Aufgabe 50:
Registermaschinen sind ein idealisiertes Berechungsmodell und spiegeln die imperative
Struktur ausfuhrbarer Programme wider. In dieser Aufgabe wollen wir die Arbeitsweise einer Registermaschine mit einem Java Programm Register simulieren. Hierbei beschranken wir uns auf eine feste Anzahl von vier Registern R[1] bis R[4] und auf Registerinhalte des Datentyps long. Innerhalb der Klasse Register denieren wir deshalb als erstes ein statisches Array R mit static long[] R = new long[5]. Glucklicherweise werden damit auch schon alle Registerinhalte mit 0 initialisiert.
(a) Die Funktionen static void a(int i) ... bzw. static void s(int i) ... fur
die Registermaschinenbefehle ai bzw. si konnen Sie durch Manipulation von R[i]
sicherlich an den Stellen ... gema ihrer Denition erganzen!
233
(b) In der Hauptroutine main soll die Simulation aufbauend auf die Registermaschinendenition und die Funktionen aus Teil (a) durchgefuhrt werden. Lesen Sie als erstes
einen Parameter in das Feld R[1] ein und geben Sie das Register R[2] aus.
(c) Simulieren Sie das Registermaschinenprogramm zur Berechnung von d
Hinweise:
pne.
Das Schreiben des ersten Registerinhaltes in das zweite und dritte Register
durch (s a a ) wird zu while (R[1] != 0) f s(1); a(2); a(3); g.
Nutzen Sie die gewonnenen Erkenntnisse zur Berechnung von dpne aus Auf1
2
3 1
gabe 1 des U bungsblattes 3 (Theorie 2), d.h. realisieren Sie das Zuruckschreiben des zweiten Registers in das erste und das Registermaschinenprogramm
((s2s3a4)2(s4s3a2)4s3a2)3.
Losung zur Aufgabe 50:
(a)
(b)
(c)
public class Register {
static long[] R = new long[5];
static void a(int i) { R[i]++; }
static void s(int i) { if (R[i] > 0) R[i]--; }
...
}
...
public static void main (String args[]) {
R[1] = Long.valueOf(args[0]).longValue();
...
System.out.println(R[2]);
}
...
...
while (R[1] != 0) {
while (R[2] != 0) {
while (R[3] != 0) {
while (R[2] != 0)
while (R[4] != 0)
s(3); a(2);
s(1); a(2); a(3); }
s(2); a(1); }
{ s(2); s(3); a(4); }
{ s(4); s(3); a(2); }
KAPITEL 11. PRAXIS
234
}
...
Aufgabe 51:
Fur den Inhalt A eines unregelmaigen n-Ecks in der Ebene mit den (entgegen dem
Uhrzeigersinn gelesenen) Endpunkten (x0; y0); (x1; y1); : : : ; (xn; yn) = (x0; y0) gilt:
nX1
1
A = 2 (xkyk+1 xk+1yk)
k=0
(a) Schreiben Sie ein Java Programm Polygon, das die Koordinaten der vier Punkte in
der Reihenfolge x0,y0,x1,y1,. . . ,x3 ,y3 einliest und den Flacheninhalt dieses Vierecks
berechnet. Nutzen Sie dabei die Arrays x und y vom Typ double (Initialisierung z.B.
von x durch den Aufruf double[] x = new double[5] und Einlesen z.B. von x[0]
durch den Aufruf x[0] = Double.valueOf(args[0]).doubleValue()). Fuhren
Sie die Berechnung des Flacheninhalts A in der Schleife for(int i=0;i<4;i++)
durch.
(b) Erweitern Sie Ihr Programm von Vier- auf n-Ecke. Dabei sei n der erste Aufrufparameter.
Losung zur Aufgabe 51:
(a)
public class Polygon {
public static void main (String args[]) {
double [] x = new double[5];
double [] y = new double[5];
x[0] = Double.valueOf(args[0]).doubleValue();
y[0] = Double.valueOf(args[1]).doubleValue();
x[1] = Double.valueOf(args[2]).doubleValue();
y[1] = Double.valueOf(args[3]).doubleValue();
x[2] = Double.valueOf(args[4]).doubleValue();
y[2] = Double.valueOf(args[5]).doubleValue();
x[3] = Double.valueOf(args[6]).doubleValue();
y[3] = Double.valueOf(args[7]).doubleValue();
x[4] = x[0]; y[4] = y[0];
double A = 0;
for(int i=0;i<4;i++) A += x[i]*y[i+1] - y[i]*x[i+1];
System.out.println("Inhalt = " + A/2.0);
235
}
}
(b)
public class Polygon {
public static void main (String args[]) {
double A = 0;
int n = Integer.parseInt(args[0])+1;
double [] x = new double[n];
double [] y = new double[n];
for(int i=0;i<n-1;i++) {
x[i] = Double.valueOf(args[2*i+1]).doubleValue();
y[i] = Double.valueOf(args[2*i+2]).doubleValue();
}
x[n-1] = x[0]; y[n-1] = y[0];
for(int i=0;i<n-1;i++) A += x[i]*y[i+1] - y[i]*x[i+1];
System.out.println("Inhalt = " + A/2.0);
}
}
Bemerkung: Die Summe ergibt sich aus der sogenannten Sektorformel, mit der sich die
Flache eines Gebietes als Integral uber den Rand berechnen lat.
Aufgabe 52:
Wir beweisen den folgenden Satz konstruktiv durch die Angabe eines Programmes: Ist der
grote gemeinsame Teiler von a und b, kurz ggT (a; b), mit a; b 2 IN gleich d, so gibt es
ein x und ein y mit ax + by = d. Dazu schauen wir uns den euklidischen Algorithmus
ggT (a; 0b) =0 ggT (b; a mod b) mit 0ggT (a; 0)0 = a genauer an. Zu00 a und
b gibt es ein
00
Paar (x ; y ) = (1; 0) mit a = x a + y b und ein Paar (x ; y ) = (0; 1) mit
b =0 x00 a00 + y00 b0 . Fur a00 mod b = a qb mit q = a div b ndet sich die Darstellung
(x qx )a + (y qy )b.
(a) Schreiben Sie aufbauend auf diesen Sachverhalt ein Java Programm GGT zur Berechnung von x und y. Dabei sollen Sie x und y als statische Klassenvariablen durch
public static long x,y festgelegen und eine rekursive, d.h. sich selbst aufrufende, Prozedur public static long ggt mit den sechs Parametern long a, long
xa, long ya, long b, long xb und long yb programmieren (xa steht f
ur x0, ya
steht fur y0, xb steht fur x00 und yb fur y00). Ist der Parameter b gleich Null, so
werden x und y auf xa bzw. ya festgelegt und a zuruckgegeben. Der Aufruf von
ggt aus dem Hauptprogramm main geschieht durch long d = ggt(a,1,0,b,0,1),
wobei sich a und b wiederum aus den Aufrufparametern ergeben. Hinweise: Der
KAPITEL 11. PRAXIS
236
Java-Ausdruck a%b berechnet a mod b und der Ausdruck xa-(a/b)*xb berechnet
x0 (a div b)x00.
Testen Sie Ihr Programm an dem Beispiel a = 168 und b = 62 (Die Ausgabe
des Ausdrucks System.out.println(d+"="+x+"*"+a+"+ "+y+"*"+b) im Hauptprogramm sollte 2=-7*168+ 19*62 ergeben).
(b) Erganzen Sie das Java Programm durch die Berechnung des kleinsten gemeinsamen
Vielfachen kgV . Hinweis: kgV (a; b) = ab=ggT (a; b).
Losung zur Aufgabe 52:
(a)
(b)
public class GGT {
public static long x,y;
public static long ggt(long a,long xa,long ya,long b,long xb,long yb) {
if (b == 0) { x=xa; y=ya; return a; }
return ggt(b,xb,yb,a%b,xa-(a/b)*xb,ya-(a/b)*yb);
}
public static void main(String args[]) {
long a = Long.valueOf(args[0]).longValue();
long b = Long.valueOf(args[1]).longValue();
long d = ggt(a,1,0,b,0,1);
System.out.println(d+"="+x+"*"+a+"+ "+y+"*"+b);
...
}
}
System.out.println("Das kgV von "+a+" und "+b+" ist "+a*b/d);
Aufgabe 53:
Eine Liste ist eine (einfach verkettete) dynamische Datenstruktur, in der am Listenanfang
Kopf Elemente eingef
ugt und entfernt werden konnen. Vervollstandigen Sie die folgende
Java Spezikation einer Liste von Ganzzahlen (Liste.java).
class Element {
Element succ; int i;
// Nachfolger und Wert
Element(Element succ, int i) { this.succ = succ; this.i = i; }
}
public class Liste {
Element Kopf;
// Listenanfang
237
Liste() { Kopf = null; }
// initialisiere Listenanfang
public void Einfuegen(int i) ... // eine Ganzzahl hinzufuegen
public void Entfernen() ...
// Ganzzahl am Listenanfang enfernen
public String toString() ...
// Ausgabe (s. Praesenzuebung)
public static void main(String args[]) {
Liste L = new Liste();
// Konstruktoraufruf
for(int i=0;i<10;i++) { L.Einfuegen(i); System.out.println(L); }
for(int i=0;i<5;i++) { L.Entfernen(); System.out.println(L); }
}
}
Losung zur Aufgabe 53:
public
Kopf
}
public
Kopf
}
void Einfuegen(int i) {
= new Element(Kopf,i);
void Entfernen() {
= Kopf.succ;
StringBuffer st = new StringBuffer("");
for (Element l=Kopf;l!=null;l=l.succ) st.append(l.i+" ");
return st.toString();
Aufgabe 54:
Gegeben sei folgende abstrakte Klasse Form (Form.java) einer Flachenform.
public abstract class Form {
public abstract double Flaeche(); // berechnet Flaecheninhalt
public abstract double Umfang();
// berechnet Umfang
public abstract String toString(); // erzeugt einen String fuer beides
}
Programmieren Sie fur die folgende generische Testprozedur (FormTest.java) die abgeleiteten Klassen Rechteck (Rechteck.java) und Kreis (Kreis.java).
public class FormTest {
public static void main (String[] args) {
Form[] f = new Form[2];
// Array von zwei Formen
KAPITEL 11. PRAXIS
238
f[0] = new Kreis(3);
// Kreis mit Radius 3
f[1] = new Rechteck(3,4);
// Rechteck der Groesse 3 mal 4
for(int i=0;i<f.length;i++) { System.out.println(f[i]); }
}
}
Losung zur Aufgabe 54:
public class Rechteck extends Form {
protected double b, l;
public Rechteck(int b, int l) { this.b = b; this.l = l; }
public double Flaeche() { return b*l; }
public double Umfang() { return 2*b*l; }
public String toString() {
StringBuffer st = new StringBuffer("Rechteck.");
st.append(" Breite:"+b+ " Laenge:"+l+" Flaeche:"+
Flaeche()+" Umfang:"+Umfang());
return st.toString();
}
}
public class Kreis extends Form {
protected double r;
public Kreis(double r) { this.r = r; }
public double Flaeche() { return PI*r*r; }
public double Umfang() { return 2*Math.PI*r; }
public String toString() {
StringBuffer st = new StringBuffer(" Kreis.");
st.append(" Radius:"+r+" Flaeche:"+Flaeche()+" Umfang:"+Umfang());
return st.toString();
}
}
Aufgabe 55:
Ein Korper ist eine algebraische Struktur, die axiomatisch festgelegt ist und eine additive
und multiplikative Verknupfung besitzt. Beispiele sind die rationalen, die reellen und die
komplexen Zahlen und der Restklassenring Z/pZ zur Primzahl p.
Implementieren Sie aufbauend auf die folgende abstrakte Klasse (Koerper.java) die abgeleitete Klasse Bruchzahl (Bruchzahl.java), die die angegebenen Operationen fur gekurz-
239
te Bruche verwirklicht. Nutzen Sie hierbei die Berechnung des ggT 's nach Euklid. Der
Zahler soll eine ganze, der Nenner eine naturliche Zahl sein.
public abstract class Koerper {
public abstract Koerper Null();
//
public abstract Koerper Eins();
//
public abstract Koerper addInverses();
//
public abstract Koerper multInverses(); //
public abstract boolean isEqual(Koerper q);
public abstract Koerper add(Koerper q); //
public abstract Koerper mult(Koerper q); //
public abstract String toString();
//
}
Nullelement
Einselement
Inverses der Addition
Inverses der Multiplikation
// Gleichheit
Addition
Multiplikation
Konvertierung in String
Hinweise: Der Parameter q in mult, add und in isEqual mu durch das Voranstellen von
(Bruchzahl) explizit gecasted werden. Der Konstruktor in public class Bruchzahl
extends Koerper k
onnte wie folgt aussehen:
Bruchzahl(int z, int n) {
if (n == 0) {
System.out.println("Division durch Null"); System.exit(1);
}
if (n < 0) { this.z = -z; this.n = -n; }
else { this.z = z; this.n = n; }
int g = ggT(Math.abs(z),n); this.z /= g; this.n /= g;
}
Testen Sie ihr Programm in einer Klasse BruchTest (BruchTest.java) mit verschiedenen
Bruchaufgaben, z.B. mit (2/6 - 4/8)/6.
Losung zur Aufgabe 55:
public class Bruchzahl extends Koerper {
int z; // Zaehler
int n; // Nenner
Bruchzahl(int z, int n) {
if (n == 0) {
System.out.println("Division durch Null");
System.exit(1);
}
if (n < 0) { this.z = -z; this.n = -n; }
KAPITEL 11. PRAXIS
240
else { this.z = z; this.n = n; }
int g = ggT(Math.abs(z),n); this.z /= g; this.n /= g;
}
Bruchzahl(int z) { this.z = z; n = 1; }
public Koerper Null() { return new Bruchzahl(0,1); }
public Koerper Eins() { return new Bruchzahl(1,1);}
public Koerper addInverses() { return new Bruchzahl(-z,n); }
public Koerper multInverses() { return new Bruchzahl(n,z); }
int ggT(int a, int b) {
if (a == 0) return b; if (b == 0) return a;
if (a < b) return ggT(a,b%a); else return ggT(a%b,b);
}
public boolean isEqual(Koerper t){
Bruchzahl q = (Bruchzahl) t;
return ((z == q.z) && (n == q.n));
}
public Koerper add (Koerper t) {
Bruchzahl q = (Bruchzahl) t;
return new Bruchzahl ( n * q.z + z * q.n, n * q.n );
}
public Koerper mult(Koerper t) {
Bruchzahl q = (Bruchzahl) t;
return new Bruchzahl( z * q.z, n * q.n );
}
public String toString() {
StringBuffer st = new StringBuffer("");
if (n == 1) st.append(z); else st.append(z+"/"+n);
return st.toString();
}
}
Aufgabe 56:
Gehen Sie fur diese Aufgabe von folgendem Verhalten handelnder Personen auf einer
Buhne aus:
Erhalt der groe Zauberer Merlin die Nachricht auftritt(n; k) mit n; k 2 ,
n > 0 und 0 < k n, dann erweckt er n Feen auf Avalon in den Positionen
1; : : : ; n. Anschlieend schickt er eine gruss()-Nachricht an die Fee auf Position k.
IN
241
Erhalt Merlin die Nachricht berichte(), dann teilt er mit, wie oft er selbst seit
seinem Auftritt gruss()-Nachrichten erhielt.
Jede Fee kennt ihre Position p auf Avalon.
Eine Fee reagiert nur dann auf eine Nachricht, wenn sie erweckt ist.
Erhalt eine erweckte Fee eine gruss()-Nachricht, ist sie sofort nicht mehr erweckt ,
ehe sie aber ganz untatig wird, verschickt sie noch einige gruss()-Nachrichten: Eine
an Merlin, eine an die Fee auf Position 3p + 1, falls diese existiert und p die eigene
Position ist, und eine an die Fee auf Position p=2, wenn die eigene Position p eine
gerade Zahl ist.
Schreiben Sie ein Java-Programm Buehne.java, welches einen Auftritt von Merlin fur eine
als Argument ubergebene Anzahl n von Feen und eine ebenfalls als Argument ubergebene
Position k fur die erste von Merlin zu gruende Fee initiiert. Anschlieend soll Merlin
berichten, wie oft er selbst gegrut wurde. Beispiel fur verschiedene Programmlaufe:
> java Buehne 200 92
Ich bin Merlin und erhielt 65 gruss-Nachrichten.
> java Buehne 200 93
Ich bin Merlin und erhielt 1 gruss-Nachricht.
> java Buehne 200 94
Ich bin Merlin und erhielt 4 gruss-Nachrichten.
Losung zur Aufgabe 56:
class Merlin {
static long zaehler;
static void gruss () { zaehler++; }
static void auftritt (int n, int k) {
zaehler = 0;
if (n <= 0 || k <= 0 || k > n) return;
Fee.erwecken (n);
Fee.avalon[k].gruss ();
}
static String berichte () {
return "Ich bin Merlin und erhielt "+zaehler+
" gruss-Nachricht"+ (zaehler==1?".":"en.");
}
KAPITEL 11. PRAXIS
242
}
class Fee {
static Fee [] avalon;
int position;
boolean erweckt;
Fee (int n) { position = n; erweckt = true; }
void gruss () {
while (erweckt) {
erweckt = false;
Merlin.gruss ();
int j = 3 * position + 1 ;
if (j < avalon.length) avalon [j] .gruss ();
if (position%2 == 0) avalon [position/2] .gruss ();
}
}
static void erwecken (int n) {
avalon = new Fee [n+1];
for (int p=1; p <= n; p++)
avalon [p] = new Fee (p);
}
}
class Buehne {
public static void main (String[] args) {
int anzahl = Integer.parseInt (args[0]);
int erste = Integer.parseInt (args[1]);
Merlin.auftritt (anzahl, erste);
System.out.println ( Merlin.berichte () );
}
}
Aufgabe 57:
Die virtuelle Welt Virtu simuliert der unmittelbaren Erfahrungsbereich der Studenten
und Studentinnen der Informatik I Vorlesung: Das Gebaude 82. Virtu besteht aus vier
Raumen: Der Mensa, dem Flurbereich, dem Pool und dem Horsaal. Im Flur nden wir
einen Kopierer, im Pool mehrere Computer und im Horsaal einen Projektor. In dieser
(wenn auch leicht vereinfachten) Welt lassen sich Turen onen bzw. schliessen, Raume
betreten, Sachen nehmen und wieder ablegen. Hier ein kleines Beispiel:
Hallo. Ich heisse Studi.
243
Ich besitze: .
Ich bin im Flur.
Oben ist eine Eisentuer.
Im Norden ist eine offene Fluegeltuer.
Im Westen ist eine Holztuer.
Inventar: Kopierer.
Was Nun?
gehe
nord
Hallo. Ich heisse Studi.
Ich besitze: .
Ich bin im Hoersaal.
Im Sueden ist eine offene Fluegeltuer.
Inventar: Projektor.
Was Nun?
nehme
Projektor
Hallo. Ich heisse Studi.
Ich besitze: Projektor.
Ich bin im Hoersaal.
Im Sueden ist eine offene Fluegeltuer.
Inventar: .
(a) Vervollstandigen Sie den Spielercharakter Studi in dem beigefugten Programm.
Hinweis: Es handelt sich bei jedem Funktionsaufruf um einen Einzeiler.
(b) Erweitern Sie die virtuelle Welt Virtu nach ihren Vorstellungen. Sie konnen z.B.
mehrere Gegenstande, mehrere Raume oder gar vom Programm aus gesteuerte Personen einbauen. Die schonsten und einfallsreichsten Programme werden von der Jury
aus U bungsgruppenleitern und Assistenten ausgewahlt und am Ende der Vorlesung
pramiert.
// Virtuelle Welt "Virtu". Datei VirtuTest.java
244
KAPITEL 11. PRAXIS
import java.io.*;
// Utensilien in der virtuellen Welt
class Sache {
String name; Sache next;
Sache(String n, Raum r) { name = n; r.l.add(this); }
public String toString() {
StringBuffer st = new StringBuffer(name);
return st.toString();
}
}
// Liste zur Verwaltung von Sachen fuer Studi oder fuer Raeume
class Liste {
public Sache Kopf;
Liste() { Kopf = null; }
public void add(Sache s) { s.next = Kopf; Kopf = s; }
public boolean member(Sache s) {
for (Sache i = Kopf;i != null;i = i.next)
if (i == s) return true;
return false;
}
public boolean del(Sache s) {
if (!member(s)) {
System.out.println(s+" ist nicht da.\n"); return false;
}
if (Kopf == s) Kopf = Kopf.next;
for (Sache i = Kopf;i != null;i = i.next)
if (i.next == s) i.next = s.next;
s.next = null; return true;
}
public String toString() {
StringBuffer st = new StringBuffer("");
for (Sache i = Kopf;i != null;i = i.next)
if (i.next == null) st.append(i); else st.append(i+", ");
return st.toString();
}
245
}
// Richtungen werden als Zeichenketten verarbeitet
class Richtung {
String name;
Richtung(String n) { name = n; }
public String toString() {
StringBuffer st = new StringBuffer(name); return st.toString();
}
}
// (offene) Tueren in der virtuellen Welt verbinden Raeume
class Tuer {
public boolean offen; String name;
Tuer (String n, Raum r1, Raum r2, Richtung d1, Richtung d2) {
name = n; offen = false;
r1.nebenan(r2,d1,this); r2.nebenan(r1,d2,this);
}
public String toString() {
StringBuffer st = new StringBuffer("");
if (offen) st.append("offene "); st.append(name);
return st.toString();
}
}
// Raeume haben in max. 6 Richtungen Tueren in Nachbarraeume
class Raum {
final static int MAX = 6;
Liste l; String name; int nachbar; Richtung D[]; Tuer T[]; Raum R[];
Raum(String n) {
nachbar = 0; name = n; l = new Liste();
D = new Richtung[MAX]; T = new Tuer[MAX]; R = new Raum[MAX];
}
public void nebenan(Raum r, Richtung d, Tuer t) {
R[nachbar] = r; D[nachbar] = d; T[nachbar++] = t;
}
246
KAPITEL 11. PRAXIS
public Raum gehe(Richtung r) {
int i=0; while (D[i] != r && i < MAX) i++;
if (i == MAX) return this;
if (T[i].offen) return R[i]; else return this;
}
public String toString() {
StringBuffer st = new StringBuffer("Ich bin "+name+".\n");
for (int i=0;i<nachbar;i++) st.append(D[i]+" ist eine "+T[i]+".\n");
st.append("Inventar: "+l+".\n");
return st.toString();
}
}
// Der Studi, der sich in der virtuellen Welt bewegt und dort agiert
// Hier sind die Funktionsruempfe zu implementieren
class Studi {
Raum in; String name; public Liste l;
Studi(String n, Raum r) { name = n; in = r; l = new Liste(); }
public void schliesse(Tuer t) ...
public void gehe(Richtung r) ...
public void oeffne(Tuer t) ...
public void nehme(Sache s) ...
public void gebe(Sache s) ...
public String toString() {
StringBuffer st = new StringBuffer("Ich heisse "+name+".\n");
st.append("Ich besitze: "+l+".\n"); st.append(in);
return st.toString();
}
}
// Die Definition der Welt und die Schnittstelle zum Benutzer
class Virtu {
public String readString() {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String s = ""; try { s = in.readLine();}
catch(Exception e) { System.err.println(e); System.exit(1); }
return s;
247
}
public void loop(String name) {
Richtung oben = new Richtung("Oben"), unten = new Richtung("Unten"),
nord = new Richtung("Im Norden"), sued = new Richtung("Im Sueden"),
west = new Richtung("Im Westen"), ost = new Richtung("Im Osten");
Raum pool = new Raum("im Pool"), flur = new Raum("im Flur"),
hoersaal = new Raum("im Hoersaal"), mensa = new Raum("in der Mensa");
Tuer holztuer = new Tuer("Holztuer",pool,flur,ost,west),
eisentuer = new Tuer("Eisentuer",flur,mensa,oben,unten),
fluegeltuer = new Tuer("Fluegeltuer",hoersaal,flur,sued,nord);
Sache computer = new Sache("Computer",pool),
projektor = new Sache("Projektor",hoersaal),
kopierer = new Sache("Kopierer",flur);
Studi M = new Studi(name,flur); String e1 = "", e2 = "";
do {
if (e1.equals("oeffne")) {
if (e2.equals("Eisentuer")) M.oeffne(eisentuer);
if (e2.equals("Fluegeltuer")) M.oeffne(fluegeltuer);
if (e2.equals("Holztuer")) M.oeffne(holztuer);
}
if (e1.equals("schliesse")) {
if (e2.equals("Eisentuer")) M.schliesse(eisentuer);
if (e2.equals("Fluegeltuer")) M.schliesse(fluegeltuer);
if (e2.equals("Holztuer")) M.schliesse(holztuer);
}
if (e1.equals("gehe")) {
if (e2.equals("nord")) M.gehe(nord);
if (e2.equals("sued")) M.gehe(sued);
if (e2.equals("west")) M.gehe(west);
if (e2.equals("ost")) M.gehe(ost);
if (e2.equals("oben")) M.gehe(oben);
if (e2.equals("unten")) M.gehe(unten);
}
if (e1.equals("nehme")) {
if (e2.equals("Computer")) M.nehme(computer);
if (e2.equals("Projektor")) M.nehme(projektor);
if (e2.equals("Kopierer")) M.nehme(kopierer);
}
if (e1.equals("gebe")) {
KAPITEL 11. PRAXIS
248
if (e2.equals("Computer")) M.gebe(computer);
if (e2.equals("Projektor")) M.gebe(projektor);
if (e2.equals("Kopierer")) M.gebe(kopierer);
}
System.out.println("\n\nHallo. "+M+"\nWas Nun?");
e1 = readString(); e2 = readString();
} while (true);
}
}
// Die virtuelle Welt wird initialisiert und die Simulation kann beginnen
public class VirtuTest {
public static void main(String args[]) {
Virtu V = new Virtu(); V.loop(args[0]);
}
}
Losung zur Aufgabe 57:
public
public
public
public
public
void
void
void
void
void
schliesse(Tuer t) { t.offen = false; }
gehe(Richtung r) { in = in.gehe(r); }
oeffne(Tuer t) { t.offen = true; }
nehme(Sache s) { if(in.l.del(s)) l.add(s);
gebe(Sache s) { if(l.del(s)) in.l.add(s); }
}
Aufgabe 58:
(a) A ndern Sie das Programm HelloWorld.java so, da es als ersten Parameter einen Namen akzeptiert und dann Hallo <Name> ausgibt. Falls kein
oder mehr als ein Parameter angegeben wird, soll stattdessen die Zeile
Benutzung: java HelloName <name> ausgegeben werden.
(b) Schreiben Sie ein Klasse Counter, die einen Zahler implementiert. Die einzige Methode getNext() gibt bei jedem Aufruf eine um 1 erhohte Zahl zuruck. Als erstes
soll 1 zuruckgegeben werden. Der Zahlerwert soll nur Ableitungen der Klasse voll
zur Verfugung stehen. Schreiben Sie ein Testprogramm fur die Klasse.
249
(c) Leiten Sie eine neue Klasse SetCounter von Counter ab, bei der im Konstruktor
der Startwert angegeben werden kann. Auerdem soll der nachste Zahlerwert auch
spater mit setNext() gesetzt werden konnen.
(d) Welche Moglichkeiten gibt es, wenn eine Funktion mehr als einen Ruckgabewert
hat? Schreiben Sie eine Funktion Kuerzen zum Kurzen von Bruchen.
(e) Schreiben Sie eine Funktion
Array zuruckgibt.
, die die ersten
primes(int)
n Primzahlen in einem
(f) Implementieren Sie eine Funktion double Kehrwert(double), die den Kehrwert
einer Zahl zuruckgibt. Bei 0 soll eine eigene Exception erzeugt werden.
Losung zur Aufgabe 58:
public class HelloName {
public static void main(String args[]) {
if (args.length != 1) {
System.err.println("Benutzung: java HelloName <name>");
System.exit(1);
}
System.out.println("Hallo "+args[0]);
}
}
public class Counter {
protected int ct=1;
public int getNext() { return ct++; }
public static void main(String[] args) {
// Testprogramm
Counter ctr=new Counter();
for(int i=0;i<10;i++)
System.out.println(ctr.getNext());
}
}
public class SetCounter extends Counter {
public SetCounter() { this(1); }
public SetCounter(int value) { ct=value; }
public void setNext(int value) { ct=value; }
KAPITEL 11. PRAXIS
250
public static void main(String[] args) {
// Testprogramm
SetCounter ctr=new SetCounter(5);
for(int i=0;i<5;i++)
System.out.println(ctr.getNext());
ctr.setNext(100);
for(int i=0;i<5;i++)
System.out.println(ctr.getNext());
}
}
//
//
//
//
//
//
Da Kuerzen zwei Werte veraendert, ist es am besten, eine
neue Klasse Bruch mit einer Methode kuerzen zu definieren.
Alternativ koennte man Bruch als Object an eine Methode
kuerzen uebergeben, so dass die Werte veraendert werden
koennen. Als weitere Moeglichkeit koennte ein neues Bruch-Objekt
als Rueckgabewert definiert werden.
class Bruch {
int zaehler=1;
int nenner=1;
public Bruch() {}
public Bruch(int zaehler, int nenner) {
this.zaehler=zaehler;
this.nenner=nenner;
}
public String toString() {
return zaehler+"/"+nenner;
}
public void kuerzen() {
// Ineffizientes Verfahren zur Bestimmung des ggT!
int min=Math.min(zaehler,nenner);
int ggt;
for(ggt=min;ggt>0;ggt--)
if(zaehler%ggt==0 && nenner%ggt==0) break;
zaehler/=ggt;
nenner/=ggt;
}
}
251
public class Kuerzen {
public static void main(String[] args) {
Bruch b=new Bruch(4,6);
System.out.print("Der Bruch "+b);
b.kuerzen();
System.out.println(" lautet gekuerzt "+b+".");
}
}
public class Primes {
public static boolean prim(int zahl) {
// Ineffizienter Primzahltest
for(int n=2;n<zahl;n++)
if (zahl%n==0) return false;
return true;
}
public static int[] primes(int n) {
// Array instanziieren
int[] ret=new int[n];
// Array fuellen
int found=0, zahl=2;
while(found<n) {
if (prim(zahl)) {
ret[found]=zahl;
found++;
}
zahl++;
}
return ret;
}
public static void main(String[] args) {
int[] pr=primes(100);
for(int i=0;i<pr.length;i++)
System.out.print(pr[i]+"\t");
System.out.println();
}
}
class KehrwertVonNullException extends ArithmeticException {
KAPITEL 11. PRAXIS
252
}
public class Kehrwert {
public static double kehrwert(double x) throws KehrwertVonNullException {
if (x==0) throw new KehrwertVonNullException();
return 1/x;
}
public static void main(String[] args) {
double a;
a=5;
System.out.println("Kehrwert von "+a+" ist "+kehrwert(a)+".");
a=0;
System.out.println("Kehrwert von "+a+" ist "+kehrwert(a)+".");
}
}
Aufgabe 59:
Das Maxsummenproblem wurde in der Vorlesung wie folgt deniert: Finde ein IndexPaar (i; j ) in einem Array a[1..n] fur das f (i; j ) = ai + : : : + aj maximal ist. Als
Rechenschritte zahlen arithmetische Operationen und Vergleiche. Implementieren Sie die
vier verschiedenen Ansatze zur Losung des Problems. Falls sie nicht weiter kommen, nden
Sie Implementationshilfen in Ottmann/Widmayer: Algorithmen und Datenstrukturen, 3.
Auage, Seite 12.
Der naive Algorithmus Es sollen alle f (i; j ) berechnet werden und dann der maximale f -Wert ermittelt werden. Oensichtlich genugen zur Berechung von f (i; j ) genau
j i Additionen. Der Algoritmus startet mit max f (1; 1) und aktualisiert max
wenn notig.
Der normale Algorithmus Der naive Ansatz berechnet a1 + a2 fur
f (1; 2); f (1; 3); : : : ; f (1; n), also (n 1)-mal.
Besser geht's mit folgender Erkenntnis. Es gilt:
f (i; j + 1) = f (i; j ) + aj
Damit braucht man fur alle f (i; )-Werte genau (n
+1
i) Additionen.
Divide-And-Conquer Annahme: n = 2k. Unterteilung in drei Klassen:
253
1 i; j n=2 (Divide-And-Conquer)
1 i n=2 < j n (Direkt)
n=2 < i j n (Divide-And-Conquer)
g(i) = ai + : : : + an=2 und h(j ) = an=2+1 + : : : + aj
Dann gilt: f (i; j ) = g(i) + h(j )
Um f zu maximieren, maximiere g und h einzeln.
Berechne nacheinander g(n=2) = a[n=2]; g(n=2 1); : : : ; g(1) und den max. g
Wert in (n=2 1) Vergleichen und (n=2 1) Additionen. Berechne h analog.
Der clevere Algorithmus Wir wollen das Problem in [1::n] losen und verwalten dabei
nach dem Lesen von ak in max den groten Wert von f (i; j ) aller Paare (i; j ) fur
1 i j k. Fur k = 1 setzen wir max auf a1.
Wenn ak+1 gelesen ist, aktualisiere maxneu = maxfmaxalt +ak+1; ak+1g.
Fur maxneu kommen folgende Paare in Frage:
1 i j k (maximaler Wert maxalt)
1 i k; j = k + 1 (maximaler Wert maxneu)
Losung zur Aufgabe 59:
(a)
public class Naiv {
int[] list;
public Naiv(int [] list) {
//Warnung: prueft die Liste nicht auf laenge!!!
this.list=list;
}
public void showMaxSubArray(){
int max_so_far=list[1];
for(int j=1;j<list.length;j++){
for(int i=2;i<list.length;i++){
if (getSum(j,i)>max_so_far) {
max_so_far=getSum(j,i);
System.out.println("Index "+j+","+i);
System.out.println("Summe(naiv):"+max_so_far);
}
}
}
}
KAPITEL 11. PRAXIS
254
private int getSum(int vonIndex,int bisIndex){
int erg=list[vonIndex++];
while(vonIndex<=bisIndex) erg+=list[vonIndex++];
return erg;
}
}
(b)
(c)
public class Normal extends Naiv{
public Normal(int[] list) {
super(list);
}
public void showMaxSubArray(){
int max_so_far=list[1];
int sum_so_far;
for(int j=1;j<list.length;j++){
sum_so_far=list[j];
for(int i=j+1;i<list.length;i++){
sum_so_far+=list[i];
if (sum_so_far>max_so_far) {
max_so_far=sum_so_far;
System.out.println("Index "+j+","+i);
System.out.println("Summe(norm):"+max_so_far);
}
}
}
}
}
public class Divide{
static int[] list=new int[] {0,0};
public Divide(int[] list) {
this.list=list;
System.out.println("Ergebnis(divide):"+getMaxSum(1,list.length-1);
}
public int getMaxSum(int vonIndex, int bisIndex){
//Basisfall: Arraylaenge=1;
if(vonIndex==bisIndex) {if(list[vonIndex]>=0) return list[vonIndex]; else ret
urn 0;}
//Divide:
int middle=(vonIndex+bisIndex)/2;
255
System.out.println("middle:"+middle);
int lmax=getMaxSum(vonIndex,middle);
int rmax=getMaxSum(middle+1,bisIndex);
//mittleres maximum
int mmax1=0;
int mmax2=0;
for (int i=middle;i>=vonIndex;i--){
if (getSum(i,middle)>mmax1) mmax1=getSum(i,middle);
}
for (int i=middle+1;i<=bisIndex;i++){
if (getSum(middle+1,i)>mmax2) mmax2=getSum(middle+1,i);
}
int erg=max(lmax,max(rmax,mmax1+mmax2));
return erg;
}
private int max(int i,int j){
if (i>j) return i; else return j;
}
private int getSum(int vonIndex,int bisIndex){
int erg=list[vonIndex++];
while(vonIndex<=bisIndex) erg+=list[vonIndex++];
return erg;
}
}
(d)
public class Scan {
int[] list;
public Scan(int[] list) {
this.list=list;
}
public void showMaxSubArray(){
int scanMax=0, bisMax=0;
int i=1;
while (i<list.length){
if (scanMax+list[i]>0) scanMax+=list[i];
else scanMax=0;
bisMax=max(bisMax,scanMax);
i++;
}
KAPITEL 11. PRAXIS
256
System.out.println("Ergebnis(scan):"+bisMax);
}
private int max(int i,int j){
if (i>j) return i; else return j;
}
}
Aufgabe 60:
In dieser Aufgabe sollen Matrixoperationen implementiert werden. Zur Erinnerung: Matrizen sind Tabellen bestehend aus Zahlen. Die Addition A + B und die Multiplikation
AB fur zwei Matrizen A = (aij ) und B = (bij )Psind wie folgt deniert: C = A + B
mit cij = aij + bij und C = AB mit cij = k aikbkj . Im ersten Fall mussen die
Dimensionen von A und B ubereinstimmen, im zweiten Fall mu die Spaltenanzahl von
A gleich der Zeilenanzahl von B sein. Ein Beispiel:
0
1
0
1
0
1
1
3
BB
CC
3
1
6
2
@
A
@
A
Fur A = B
@ 0 4 CA und B = 0 2 ist B + B = 0 4 und AB =
0
1 2 5
3 7C
B
B
C
B
@ 0 8 CA :
6 8
Erganzen Sie die folgende Klasse Matrix einer ganzzahligen Matrix an den angedeuteten
Stellen und testen Sie die Klasse mit dem angegebenen Beispiel. Hinweis: Zur Dimensionsabfrage des zwei-dimensionalen Arrays M konnen Sie die Variablen M.length und
M[0].length nutzen.
public class Matrix {
public int M[][];
Matrix (int z, int s) { M = new int[z][s]; }
public Matrix add (Matrix A) ...
public Matrix mult (Matrix A) ... }
Wenn Sie mochten, konnen Sie eine Matrix und die darauf agierenden Matrixoperationen
anstatt fur Ganzzahlen fur Korperelemente und damit auch fur Bruchzahlen (Info I, Blatt
6, Aufgabe 3) verwirklichen.
Losung zur Aufgabe 60:
257
public Matrix add (Matrix A) {
Matrix C = new Matrix(A.M.length,A.M[0].length);
for(int i=0;i<A.M.length;i++) {
for(int j=0;j<A.M[0].length;j++)
C.M[i][j] = M[i][j] + A.M[i][j]; }
return C; }
public Matrix mult (Matrix A) {
Matrix C = new Matrix(M.length,A.M[0].length);
for(int i=0;i<M.length;i++) {
for(int j=0;j<A.M[0].length;j++) {
C.M[i][j] = 0;
for(int k=0;k<M[0].length;k++)
C.M[i][j] = C.M[i][j] + (C.M[i][k] * A.M[k][j]); } }
return C; }
Aufgabe 61:
Zum Sortieren von Elementen dient als Komplexitatsma die Anzahl der Schlusselvergleiche. Analog zu dem in der Vorlesung beschriebenen Interface Orderable benotigen wir
hier ein (vereinfachtes) Interface Comparable zum Groenvergleich zweier Objekte.
interface Comparable {
public boolean greater (Comparable o);
}
Schreiben Sie eine Java-Klasse Names, die den Namen und Vornamen einer Person speichert und das Interface Comparable befriedigt. Der Vergleich zweier Personen sollte in
der Reihenfolge Name, Vorname durchgefuhrt werden. Dabei werden die Strings lexikographisch abgeglichen. Nutzen Sie dabei die Funktion public int compareTo(String
anotherString) aus der Klasse String.
Aufgabe 62:
Ein Ring ist eine Datenstruktur, die, wie der Name schon sagt, Elemente in einem Zyklus miteinander verbindet. Ringe konnen in zwei Teile aufgeteilt und auch miteinander
verschmolzen werden. Der Ring besitzt einen Einstiegspunkt first an dem (Ganzzahl-)
Elemente eingefugt werden konnen. Implementieren Sie den wie folgt spezizierten Datentyp Ring als doppelt verkettete Liste in Java.
Erzeugen Sie im Testlauf einen Ring mit den Elementen 1..8, spalten Sie die Elemente 3..6
in einen neuen Ring ab und fugen Sie den zweiten Ring, beginnend mit seinem zweiten
Element, hinter das erste Element des ersten Rings ein.
258
KAPITEL 11. PRAXIS
class Item { Item prev, next; public int value; }
public class Ring {
private Item first;
public Ring();
// Konstruktor fuer leeren Ring
public boolean isEmpty();
// Abfrage auf leeren Ring
public boolean isFirst(Item l); // Anfrage auf erstes Element
public Item getFirst();
// Ausgabe erstes Element
public Item getNext(Item l);
// Ausgabe nachfolgendes Element
public void append(int val);
// fuegt Element mit Wert val an
public Ring split(Item s, Item t);
// trennt den Teilring [s..t] vom aktuellen Ring ab
// und liefert ihn zurueck
public void merge(Item insertPos, Ring r, Item beginWith);
// fuegt den Ring r ab der Position beginWith in den aktuellen
// Ring ein, und zwar hinter der Position insertPos
public String toString();
}
Losung zur Aufgabe 62:
class Item { Item prev, next; public int value; }
public class Ring {
private Item first;
public Ring() { first = null; }
public boolean isEmpty() { return first == null; }
public boolean isFirst(Item l) { return l == first; }
public Item getFirst() { return first; }
public Item getNext(Item l) { return l.next; }
public void append(int val) {
Item l = new Item();
l.value = val;
if(first == null) { first = l.prev = l.next = l; } // ein Element
else { // Item am "Ende" des Rings, also vor first, einhaengen
first.prev.next = l; l.prev = first.prev;
l.next = first; first.prev = l; } }
public Ring split(Item s, Item t) {
if(t.next == s) {
// trenne den ganzen Ring ab
Ring neu = new Ring();
neu.first = first; first = null; return neu; }
else { // entferne [s..t] aus diesem Ring
s.prev.next = t.next; t.next.prev = s.prev;
259
Ring neu = new Ring(); // erzeuge einen neuen Ring mit den Elementen [s..t]
s.prev = t; t.next = s; neu.first = s; return neu; } }
public void merge(Item insertPos, Ring r, Item beginWith) {
insertPos.next.prev = beginWith.prev;
beginWith.prev.next = insertPos.next;
insertPos.next = beginWith;
beginWith.prev = insertPos;
r.first = null; }
public String toString() {
if(first == null) { return ""; }
else { String back = "";
Item pos = first;
do {
back = back + pos.value; pos = pos.next;
if(pos != first) back = back + " ";
} while(pos != first);
return back; } }
public static void main(String args[]) {
Ring r = new Ring();
r.append(1); r.append(2); r.append(3); r.append(4);
r.append(5); r.append(6); r.append(7); r.append(8);
System.out.println(r); System.out.println();
Ring.Link pos3 = r.getNext(r.getNext(r.getFirst()));
Ring.Link pos6 = r.getNext(r.getNext(r.getNext(pos3)));
Ring r2 = r.split(pos3, pos6);
System.out.println("Ring 1: " + r);
System.out.println("Ring 2: " + r2); System.out.println();
Ring.Link pos4 = r2.getNext(r2.getFirst());
r.merge(r.getFirst(), r2, pos4);
System.out.println("Ring 1: " + r);
System.out.println("Ring 2: " + r2); } }
Aufgabe 63:
Man kann die Implementierung einer Queue (Schlange) fur beliebige Objekte auf eine
schon vorhandene Implementierung der entsprechenden Stack-Klasse zuruckfuhren, auch
wenn das nicht sehr ezient ist. Die begonnene Java-Implementierung von Queue sei
class Queue
Stack s,
Queue ()
empty ()
{
z;
{ s = new Stack (); z = new Stack (); }
{ return s.empty (); }
KAPITEL 11. PRAXIS
260
...
}
Erganzen Sie die Implementierung der noch fehlenden Methode enqueue(Object
,
Object dequeue()
o)
und
Losung zur Aufgabe 63:
enqueue(Object o) f
g
s.push (o);
f
dequeue()
while (!s.empty ()) z.push (s.pop ());
Object o = z.pop ();
while (!z.empty ()) s.push (z.pop ());
return o;
g
Aufgabe 64:
McDiarmid and Reeds Variante des BOTTOM-UP-HEAPSORT, kurz MDR-HEAPSORT, ermoglicht es, weitere Vergleiche einzusparen, indem alte Informationen
verwendet werden. Es wird allerdings ein zusatzliches Array Info benotigt, das diese alten Informationen verwaltet. Die moglichen Belegungen von Info[j] sind unbekannt,
links und rechts (festgelegt durch die Konstanten UNKNOWN, LEFT und RIGHT) und
konnen mit zwei Bits codiert werden. Ist Info[j] = LEFT, dann enthalt das linke Kind
ein kleineres Element als das rechte, im Falle Info[j] = RIGHT ist es entsprechend genau
umgekeht. Wenn Info[j] = UNKNOWN gilt, ist keine Dominanz der Kinder untereinander
bekannt.
Erweitern Sie Bottom-Up-Heapsort um diesen Ansatz, indem Sie die Prozeduren LeafSearch und Interchange entsprechend modizieren.
Losung zur Aufgabe 64:
static int LeafSearch(int root, int size) {
int j, i=0;
Path[i++]=j=root;
while(2*j < size) {
if (Info[j] == LEFT) {
261
Path[i++]=j=2*j;
}
else if (Info[j] == RIGHT) {
Path[i++]=j=2*j+1;
}
else if (A[2*j+1].less(A[2*j])) {
Info[j] = LEFT;
Path[i++]=j=2*j;
}
else {
Info[j] = RIGHT;
Path[i++]=j=2*j+1;
}
}
if (2*j == size)
Path[i++]=j=size;
return j;
}
static void Interchange(int i,int j) {
int k; Orderable v = A[Path[0]];
for(k=0;Path[k]<j;k++) {
A[Path[k]] = A[Path[k+1]];
Info[Path[k]] = UNKNOWN;
}
A[Path[k]] = v;
}
Aufgabe 65:
WEAK-HEAPSORT benotigt im MergeForest Schritt im ungunstigen Fall dlog(m +1)e
und im gunstigen Fall dlog(m +1)e 1 viele Vergleiche. Die Gesamtanzahl der Vergleiche
ist durch n 1 plus die Schlusselvergleiche in der Sortierphase durch MergeForest (m),
fur m = n 1; : : : ; 2, bestimmt.
In dieser Aufgabe soll EXTERNAL-WEAK-HEAPSORT in Java programmiert werden,
ein Ansatz, der jeweils den best case erzwingt (und demnach mit n log n 0:9n Vergleichen) auskommt. Dabei gehen wir davon aus, da wir einen zusatzlichen Datenbereich
A[n : : : 2n 1] besitzen, in dem wir die entnommenen Wurzelelemente schreiben konnen.
Dazu nutzen wir ein Boolesches Array Active, das die derzeit aktiven Index-Positionen
verwaltet. Falls Active[i] wahr ist, dann wurde der Schlussel an Position i noch nicht in
KAPITEL 11. PRAXIS
262
den Bereich A[n : : : 2n 1] geschrieben und ist demanch noch aktiv fur den betrachteten Weak-Heap. Die Positionen A[n : : : 2n 1] werden in einer Schleifenvariablen call
verwaltet.
In Pseudo-Code gestaltet sich die auere Schleife nun wie folgt
procedure EXTERNAL-WEAK-HEAPSORT
i := n-1, call := 0
WHILE i > 1
WHILE (Active[i] = 0) i := i - 1
call := call + 1
MergeForest(i)
Wir betrachten die sich ergebenen Falle in der Sortierphase nun genauer. In den Fallen
b) und c) der folgenden Abbildung liegen m und x auf unterschiedlichen Leveln, d.h.
depth(x) = depth(m) 1 = dlog(m + 1)e 1. In Fall a) gilt hingegen depth(x) =
depth(m) = dlog(m + 1)e.
75
a)
35
32
10
x
75
b)
70
70
6
8
11
35
36
4
m
5
32
1
10
m
6
x
0
36
5
11
75
c)
70
35
x
32
6
0
11
36
4
5
m
Demnach konnen wir immer dann ein Vergleich einsparen, wenn wir anstatt dem mElement am Ende des Arrays, ein Element am Ende des speziellen Pfades wahlen und
den entsprechende Index nach dem MergeForest -Schritt deaktivieren.
Zusatzaufgabe (keine Bewertung): U berlegen Sie weiterhin, ob sich EXTERNAL-WEAKHEAPSORT analog zu QUICK-HEAPSORT in eine QUICKSORT Variante wandeln lat
und wie sich der zusatzliche Speicherbedarf reduzieren lat.
Losung zur Aufgabe 65: Hier die gewandelte Prozedur MergeForest im Pseudo-Code.
PROCEDURE MergeForest(m)
x := 1
WHILE (2x + Reverse[x] < m) x := 2x + Reverse[x]
IF (depth(x) = depth(m))
temp = x
DO Merge(temp, x DIV 2); x := x DIV 2; WHILE (x > 0)
263
swap(a[2n - call],a[temp])
Active[temp] := 0
ELSE
DO Merge(m,x); x := x DIV 2; WHILE (x > 0)
Swap(a[2n - call], a[m])
Active[m] := O
Aufgabe 66:
Innerhalb eines zufallig mit Ganzzahlelementen (Schlusseln) a[0],. . . ,a[n-1] gefullten
Arrays a soll das minimale und maximale Element gefunden werden. Schreiben Sie ein
Java Programm MinMax, das zur gleichzeitigen Suche beider Elemente moglichst wenige
(Schlussel-) Vergleiche durchfuhrt. Ihr Programm sollte die triviale Anzahl von 2(n 1)
Vergleichen wesentlich unterbieten. Nutzen Sie eine Variable call, die protokolliert, wie
haug ein Schlusselvergleich less bzw. greater durchgefuhrt wurde.
(a) Schreiben Sie das Programm ohne Divide-and-Conquer.
(b) Schreiben Sie das Programm mit Divide-and-Conquer.
Zur Vereinfachung konnen Sie sich auf n = 2k fur ein ganzzahliges k beschranken.
Losung zur Aufgabe 66:
class mm {
int max, min;
mm(int i, int j) { min = i; max = j;}
public String toString() {
return (new StringBuffer("Min:"+min+" Max:"+max)).toString(); } }
public class MinMax {
static int call;
static void shuffle(int[] a) {
call = 0;
for(int j=0;j<a.length;j++) a[j] = j;
for(int j=t.length-1;j>=1;j--) {
int ri = (int) (Math.random() * j); int t=a[ri]; a[ri]=a[j]; a[j]=temp; } }
static boolean less (int i,int j) { call++; return i<j; }
static boolean greater(int i,int j) { call++; return i>j; }
static mm loop_minmax(int[] a) {
int n = a.length, max = 0, min = n;
for(int i=0;i<n/2;i++)
KAPITEL 11. PRAXIS
264
if (greater(a[i],a[n-i-1])) { int t=a[i]; a[i]=a[n-i-1]; a[n-i-1]=t; }
for (int i=1;i<(n+1)/2;i++) if (less(a[i],min)) min = a[i];
for (int i=n/2;i<n-1;i++) if (greater(a[i],max)) max = a[i];
return new mm(min,max); }
static mm dc_minmax(int[] a, int x,int y) { // only for n=2^k
if (y-x <= 1)
if (less(a[x],a[y])) return new mm(a[x],a[y]); else return new mm(a[y],a[x]);
mm left = dc_minmax(a,x,(x+y)/2), right = dc_minmax(a,(x+y)/2+1,y);
int min = 0, max = 0;
if (greater(left.max,right.max)) max = left.max; else max = right.max;
if (less(left.min,right.min)) min = left.min; else min = right.min;
return new mm(min,max); }
static mm dc_minmax(int[] a) { return dc_minmax(a,0,a.length-1); }
public static void main(String args[]) {
int[] a = new int[Integer.valueOf(args[0]).intValue()];
shuffle(a); mm r = loop_minmax(a);
// simple loop
System.out.println(r+" Calls:"+call);
shuffle(a); r = dc_minmax(a);
// divide-and-conquer
System.out.println(r+" Calls:"+call); } }
Aufgabe 67:
Einen Stack kann man sich als Rangiergleis mit Prellbock vorstellen (siehe Skizze). Die
Operation push entspricht dem Einfahren eines Waggons von rechts auf das Rangiergleis
und die Operation pop dem Ausfahren eines Waggons vom Rangiergleis auf das linke
Gleis. Durch Mischen der push{ und pop{Operationen kann nun die Reihenfolge der
Wagen 1; : : : ; n permutiert werden. So erzeugt die Folge push, pop, push, push, pop,
push, push, pop, pop, pop die Permutation 1,3,5,4,2 der Zahlen 1,2,3,4,5.
Ausfahrt
Einfahrt
Stack
Abbildung 11.1: Einfahrt und Ausfahrt der Waggons auf dem Rangiergleis.
Programmieren Sie einen Algorithmus RangierTest, der fur eine gegebene Permutation
i1; : : : ; in der Zahlen 1; : : : ; n entscheidet, ob diese durch das obige Verfahren erzeugt
265
werden kann. Es bietet sich an, den Stapel Stack aus der Bibliothek java.util.Stack zu
benutzen. Die wichtigen Operationen sind isEmpty(), peek(), pop() und push(Object).
Losung zur Aufgabe 67:
import java.util.Stack;
class RangierTest {
Stack myStack = new Stack();
// einen Stack brauchen wir
private int[] per;
// nimmt die Zahlenfolge auf
RangierTest(int per[]) { this.per = per; } // Konstruktor
public boolean teste() {
int n = per.length, j = 1;
myStack.push(new Integer(0));
// Markierung fuer Stack-Ende
for (int i=0;i<n;i++) {
while (!(myStack.peek().equals(new Integer(per[i]))) && (j <= n))
myStack.push(new Integer(j++));
if (!(myStack.peek().equals(new Integer(per[i])))) return false;
myStack.pop(); }
if (j == (n + 1)) return true;
return false; } }
public class Test {
public static void main(String args[]) {
int[] per = new int[args.length];
for(int i=0;i<args.length;i++) per[i] = Integer.parseInt(args[i]);
RangierTest myRangierTest = new RangierTest(per); // Instanz erzeugen
if (myRangierTest.teste())
// jetzt testen
System.out.println("Die Permutation kann erzeugt werden!");
else System.out.println("Diese Permutation funktioniert nicht!"); } }
Aufgabe 68:
Arithmetische Ausdrucke lassen sich sowohl in der Inx-, in der Prex-, als auch in
der Postxdarstellung beschreiben. So ist entspricht (12-3) * ~4 der Inxdarstellung
dem Ausdruck (* (- 12 3) (~ 4)) in der Prexdarstellung und 12 3 - 4 ~ * . in
der Postxdarstellung. Zur Unterscheidung mit dem binaren Minusoperator wahlen wir
zur Vereinfachung die Tilde (~) als ein unares Minuszeichen.
Wir betrachten die Postxdarstellung, die durch einen Punkt abgeschlossen wird. Sie
kommt ohne Klammern aus und kann durch den Einsatz eines Stapels verarbeitet werden. Von links nach rechts vorranschreitend werden jeweils die Operanden auf den Stapel
266
KAPITEL 11. PRAXIS
gelegt. Wird ein Operator erreicht, so werden die obersten Elemente vom Stapel genommen (pop) und das Ergebnis wieder hinaufgelegt (push).
Schreiben Sie eine Java Klasse Postfix, die einen Postx Ausdruck auswertet. Testen Sie
Ihr Programm mit dem obigen Ausdruck.
Losung zur Aufgabe 68:
import java.util.Stack;
class Postfix {
private Stack stapel;
private String expression, err = "";
private int pos, result;
Postfix(String s) { stapel = new Stack(); expression = s; pos = result = 0; }
private char getNextChar() { return expression.charAt( pos++ ); }
public int getResult() { return result; } // Gibt das Ergebnis zurueck
public String geterr() { return err; }
// Fehlermeldung
public boolean evaluate() { // false bei Fehler, ansonsten true
char c = getNextChar();
// Naechstes Eingabezeichen
int res = 0;
// Speichert Zwischenergebnisse
while (c != '.') {
if (c == '+' || c == '-' || c == '*' || c == '/') {
if (stapel.isEmpty()) { err = "Zu wenig Operanden!"; return false; }
int op2 = ((Integer)stapel.pop()).intValue();
if (stapel.isEmpty()) { err = "Zu wenig Operanden!"; return false; }
int op1 = ((Integer)stapel.pop()).intValue();
switch ( c ) {
case '+' : { res = op1 + op2; break; }
case '-' : { res = op1 - op2; break; }
case '*' : { res = op1 * op2; break; }
case '/' : {
if ( op2 == 0 ) { err = "Division durch 0!"; return false; }
res = op1 / op2; break; } }
stapel.push( new Integer( res ) );
c = getNextChar(); }
else if (c == '~') {
int op1 = ((Integer)stapel.pop()).intValue();
res = - op1;
stapel.push( new Integer( res ) );
c = getNextChar(); }
else if ( Character.isDigit( c ) ) {
267
res = 0;
do {
res = res * 10 + Character.getNumericValue( c );
c = getNextChar();
} while ( Character.isDigit( c ) );
stapel.push( new Integer( res ) ); }
else if ( c == ' ' ) c = getNextChar();
else { err = "Falsches Eingabezeichen"; return false; } }
result = ((Integer)stapel.pop()).intValue();
if ( !stapel.isEmpty() ) { err = "Zu wenig Operatoren"; return false; }
return true; } }
class PostfixTest {
public static void main( String[] args ) {
String PostfixExpression = "12 3 - 4 ~ * .";
Postfix exp = new Postfix( PostfixExpression );
if ( exp.evaluate() ) { // Ist der Postfixausdruck gueltig?
System.out.println( "Das Ergebnis ist "+ exp.getResult() ); }
else System.out.println( "Fehler: "+exp.geterr() ); } }
Aufgabe 69:
Gegeben sei das Feld a mit der folgenden Schlussel-Belegung: 0,2,6,8,12,34,75,120.
Beschreiben Sie die Suche nach dem Schlussel 34 im obigen Feld a durch Angabe der
Folge der ausgefuhrten Schlusselvergleiche, wenn als Suchstrategie exponentielle Suche
zur Eingrenzung des Suchbereichs mit anschlieender linearer Suche angewandt wird.
Mit dieser neuen Erkenntnis implementieren Sie die Klasse ExpList, die diese Strategie
verfolgt. Der Algorithmus soll angeben, in welcher Suchphase (exponentielle od. lineare)
er gerade ist und alle Schluesselvergleiche auf der Konsole protokollieren.
Das aufrufende Testprogramm ndet sich im Netz an der gleichen Stelle wie die Sourcen
der folgenden Aufgabe und gestaltet sich wie folgt:
public class Test {
public static void main(String[] args) {
System.out.println("\nExpo-(2000)-Suche\n");
ExpList f = new ExpList(new int [] {0,2,6,8,12,34,75,120});
f.find(12);
f.find(120);
f.find(-1);
}
}
268
KAPITEL 11. PRAXIS
Losung zur Aufgabe 69:
public class ExpList {
int [] list;
public ExpList(int[] list){
//Voraussetzung: Liste sortiert und listenlaenge-1 ist eine Zweierpotenz
this.list=list;
}
public boolean find(int key){
int i=1;
while ((i<list.length)&&(key>list[i])) {
System.out.println("Exponentielle Suche: Index "+i+
": vergleiche "+key+">"+ list[i]);
i=i+i;
}
if (i>=list.length) {
System.out.println("Array-Grenze ueberschritten:
NICHT GEFUNDEN!");
return false;
}
System.out.println("Suchintervall(der Indizes!): ("+
((int)(i/2))+","+i+"]");
// also ist der Schluessel (wenn vorhanden) im Intervall (i/2,i]
// Beginn der linearen Suche
int j=(int)(i/2)+1;
while (j<=i) {
System.out.println("Lineare Suche : Index" + j + ": vergleiche "+
key + "==" + list[j]);
if (list[j]==key) {
System.out.println ("GEFUNDEN!!");
return true;
}
j++;
}
System.out.println("NICHT GEFUNDEN!!");
return false;
}
}
269
Aufgabe 70:
Um die verschiedenen Selbstanordnungsstrategien besser bewerten zu konnen, vergleichen
Sie das Verhalten der MF-, der FC, und der T-Regel fur eine Zugrisfolge s, indem sie
fur die folgende zu statistischen Zwecken aus gelegte Tabellenklasse die fehlenden Klassen
MFList, FCList, und TList implementieren.
public class Tabelle {
MFList MF; TList T; FCListe FC;
int[] folge;
int totalMF, totalT, totalFC;
public Tabelle(int [] list, int[] zugriffsfolge) {
MF = new MFList(list);
int[] listT=new int[list.length];
for (int i=0;i<list.length;i++){ listT[i] = list[i];}
T = new TList(listT);
int[] listFC=new int[list.length];
for (int i=0;i<list.length;i++){ listFC[i] = list[i];}
FC = new FCList(dummy2);
folge = zugriffsfolge;
totalMF = totalT = totalFC = 0;
}
public void starteVergleich(){
System.out.println("MFListe: "+MF+" | TListe: "+T+" | FCListe: "+FC);
for (int i=0;i<folge.length;i++){
System.out.print("Zugriff auf Schluessel "+folge[i]+": ");
int cnt = MF.access(folge[i]); totalMF += cnt;
System.out.print("MF: "+MF+" Cost:"+cnt);
cnt = T.access(folge[i]);
totalT += cnt;
System.out.println(" T: "+T+" Cost:"+cnt);
cnt = FC.access(folge[i]);
totalFC += cnt;
System.out.println("FC: "+FC+" Cost:"+cnt);
}
System.out.println("-------------------------------------------------------------");
System.out.println("Gesamtkosten: MF "+totalMF+" , T "+totalT+" , FC"+totalFC); }
}
Testen Sie ihr Programm mit der Liste L = 1; 2; 3; 4; 5; 6; 7 und die Zugrisfolge s mit
21 Zugrien: 7; 2; 7; 3; 3; 7; 4; 4; 4; 7; 5; 5; 5; 5; 7; 6; 6; 6; 6; 6; 7 wie folgt
public class Test {
public static void main(String[] args) {
Tabelle t =
KAPITEL 11. PRAXIS
270
new Tabelle(new int [] {1,2,3,4,5,6,7},
new int [] {7,2,7,3,3,7,4,4,4,7,5,5,5,5,7,6,6,6,6,6,7});
t.starteVergleich(); }
}
Losung zur Aufgabe 70:
public class MFList {
private int[] list;
public MFList(int[] list) {
this.list=list;
}
public int access(int key){
int count=0;
for (int i=0;i<list.length;i++){
count++;
if (list[i]==key) i=list.length;
}
for (int i=(count-1);i>0;i--)
swap(i,(i-1));
return count;
}
public String toString(){
String Erg="";
for (int i=0;i<list.length;i++){Erg+=" "+list[i];}
return Erg+" ";
}
private void swap(int i, int j) {
int dummy=list[i];
list[i]=list[j];
list[j]=dummy;
}
}
public class TList{
private int[] list;
private int bal;
public TList(int[] list) {
this.list=list;
271
int bal=0;
}
public int access(int key){
int count=0;
for (int i=0;i<list.length;i++){
count++;
if (list[i]==key) i=list.length;
}
if ((count-2)>=0) swap((count-1),(count-2));
return count;
}
public String toString(){
String Erg="";
for (int i=0;i<list.length;i++){Erg+=" "+list[i];}
return Erg+" ";
}
private void swap(int i, int j){
int dummy=list[i];
list[i]=list[j];
list[j]=dummy;
}
}
public class FCList {
private Item[] list;
public FCList(int[] intList) {
list=new Item[intList.length];
for(int i=0;i<intList.length;i++) list[i]=new Item(intList[i]);
}
public int access(int key){
int count=0;
for (int i=0; i<list.length;i++) {
count++;
if (list[i].key==key){
list[i].count++;
while((i!=0)&&(list[i-1].count<list[i].count)){
swap(i,i-1);
i--;
KAPITEL 11. PRAXIS
272
}
}
}
return count;
}
public String toString(){
String Erg="";
for (int i=0;i<list.length;i++){Erg+=" "+list[i];}
return Erg+" ";
}
private void swap(int i, int j){
Item dummy=list[i];
list[i]=list[j];
list[j]=dummy;
}
}
class Item {
int key;
int count;
public Item(int key){
this.key=key;
this.count=0;
}
public String toString(){
return ""+key;
}
}
Aufgabe 71:
Sortieren Sie mit naturlichen Suchbaumen!
(a) Erzeugen Sie algorithmisch alle Permutationen von [1; 2; 3; 4; 5]. Nutzen Sie zur
Aufzahlung eine rekursive Prozedur permute anstatt funf ineinandergeschachtelte
Schleifen. Wieviel Vertauschungen benotigen Sie?
(b) Gegeben eine Menge von n vergleichbaren (o.B.d.A. paarweise verschiedenen) Arrayelementen a[0] bis a[n-1]. Zu Vereinfachung beinhalte a eine Permutationen
von [1; 2; 3; 4; 5]. Fugen Sie diese Elemente in einen naturlichen Suchbaum ein
273
und durchlaufen Sie den Baum in der symmetrischen Reihenfolge. Sie erhalten ein
einfaches Sortierverfahren.
Wie gut ist das Verfahren im besten, mittleren und schlechtesten Fall? Beantworten Sie diese Frage experimentell, indem Sie die Suchbaumimplementation aus der
Vorlesung nutzen. Mitteln Sie uber alle Permutationen der Elemente in a.
Losung zur Aufgabe 71: Es gibt Algorithmen (Pseudocode), die O(n!) viele Operationen benotigen.
public void permute(int n) {
if (n==0) { process(a); return; }
for (int i=0;i<n;i++) b[i] = a[i];
permute(n-1);
for (int i=0;i<n-1;i++) { swap(a[n-1],a[0]); permute(n-1); }
for (int i=0;i<n;i++) a[i] = b[(i-1) \% n];
}
Rekursion T (1) = c und T (n) = n T (n 1) + d(n 1) mit konstantem c und d.
Weiterhin gibt es minimal-change-algorithmen die mit n! 1 swaps auskommen (siehe
B.R. Heap. Permutations by interchanges. Computer Journal 6:293-294,1963).
public void permute(int n) {
for (int i=0;i<n-1;i++) a[i] = i+1;
if (n\%2 == 1) oddpermute(n);
else evenpermute(n); }
public void oddpermute(n) {
if (n==1) { process(a); return; }
evenpermute(n-1);
for (int i=0;i<n-1;i++) {
swap(a[n-1],a[0]);
evenpermute(n-1); }}
public void evenpermute(n) {
if (n==1) process(a); return;
permute(n-1);
for (int i=0;i<n-1;i++) { swap(a[n-1],a[i]); permute(n-1); }}
public class TSort{
static int[]a;
static int allCalls; // zaehlt die Vergleiche beim Einfuegen der Knoten
static int calls,minsofar,maxsofar;
274
KAPITEL 11. PRAXIS
public static void main (String args[]){
int b=Integer.parseInt(args[0]); //Das Array von der Laenge args[0] wird
a=new int[b];
//mit einer aufsteigenden
for (int i=0;i<b;i++) a[i]=i+1; //Folge von integers initialisiert.
minsofar=999999;
//999999 reicht hoffentlich
permute (0);
System.out.println("Gesamtvergleiche: "+allCalls+
" Anzahl der Permutationen:"+fak(b));
System.out.println("durchschnittliche Vergleichsanzahl: "+
((double)allCalls/fak(b)));
System.out.println("worst case Vergleiche: "+maxsofar);
System.out.println("best case Vergleiche: "+minsofar); }
public static void permute(int fromIndex){ //permutiere rechts von fromIndex
if(fromIndex==a.length) {
//falls nur eine Zahl zu permutieren ist
for (int j=0;j<a.length;j++) // das aktuelle Array ist eine Permutation
System.out.print(a[j]); System.out.print("-->"); calls=0;
tSort(a);
if (calls>maxsofar) maxsofar=calls;
if (calls<minsofar) minsofar=calls;
System.out.println(); }
else{
// vertausche nacheinander alle Zahlen rechts von fromIndex
for (int i=0;i<a.length-fromIndex;i++){ //mit der Zahl auf a[fromIndex]
swap(fromIndex,fromIndex+i);
permute(fromIndex+1);
// permutiere den Rest
swap (fromIndex+i,fromIndex); }}} //tausche die 2 Zahlen wieder zurueck
public static void swap(int x,int y){ int dummy=a[x];a[x]=a[y];a[y]=dummy;}
public static int fak(int n){ if (n<=1) return 1; else return n*fak(n-1); }
public static void tSort(int a[]){
SearchTree T=new SearchTree();
for (int j=0; j<a.length;j++) T.insert (a[j]);
T.inOrder(); }}
class SearchTree extends TSort {
SearchNode root;
SearchTree(){root=null;}
void insert(int c){
if (root==null) root=new SearchNode(c);
else insert(root,c);}
void insert(SearchNode current,int c){ //kuerzere Version
if (current.key==c) return;
//der Fall current.key==c
else {
//und wird deshalb als Vergleich nicht mitgezaehlt
275
allCalls++;calls++;
//vergleiche werden gezaehlt
if (c<current.key) {
if (current.left!=null) insert(current.left,c);
else current.left=new SearchNode(c); }
else {
//also c> current.key
if (current.right!=null) insert(current.right,c);
else current.right=new SearchNode(c); }}}
void inOrder() {inOrder (root);System.out.println();}
void inOrder(SearchNode n){
if (n==null) return;
inOrder (n.left);
System.out.print(n.key+" ");
inOrder (n.right); }}
class SearchNode{
int key;
SearchNode left;
SearchNode right;
SearchNode(int n) {key=n;left=right=null;}}
Aufgabe 72:
Wir wollen in dieser Aufgabe die aus der Durchlaufordnung bestimmten Binarbaume explizit konstruieren (vergl. Aufgabe 3, Blatt 4). Dabei gehen wir von paarweise verschiedenen
Elementen aus.
(a) Gegeben sei die Levelorder. Schreiben Sie ein Programm Level, das den korrespondierenden Binarbaum konstruiert. Testen Sie Ihr Programm mit der Beispieleingabe
1 3 5 4 5 3 6 7 6 3 1 2 1.
(b) Gegeben sei Preorder. Konstruieren Sie den korrespondierenden Suchbaum (!) in
einem Programm Preorder. Testen Sie das Programm mit dem Durchlauf 12 6 2
1 4 3 5 8 7 9 10 11 14 13.
Losung zur Aufgabe 72: Nicht vorhanden.
Aufgabe 73:
Das Spiel Nim ist ein beliebter Zeitvertreib. Gegeben sind dabei m Stapel von ni Holzern,
1 i m. Abwechselnd werden von den beiden Spielern von nur einem Stapel beliebig
viele Holzer entnommen. Gewonnen ist das Spiel, falls man das letzte Holzchen wegnehmen
kann. Als Beispiel betrachten wir m = 4 und die Eingabe [n1; n2; n3; n4] = [7; 5; 3; 1]:
276
KAPITEL 11. PRAXIS
Die Strategie ist es, immer eine Gewinnposition zu erreichen, in der man bei einem beliebigen Zug des Gegners wieder eine Gewinnposition erreichen kann. Alle anderen Positionen sind Verlustpositionen. Nach einigen Spielversuchen stellt sich heraus, da neben
[0; 0; 0; 0] einfache Gewinnpositionen durch [1; 1; 0; 0], [2; 2; 0; 0], [3; 3; 0; 0] oder auch
durch [3; 2; 1; 0] gegeben sind.
(a) Schreiben Sie Java-Programm, das eine 4-dimensionale Tabelle T fullt und bestimmt,
ob die gegebene Anfangsstellung [7; 4; 3; 1] eine Gewinn- oder eine Verlustposition
ist. Hier durfen Sie vier ineinander verschachtelte Schleifen verwenden.
(b) Es gilt der folgende Satz: Eine Position ist genau dann eine Gewinnposition, wenn
man die Binardarstellungen der Werte ni untereinander schreibt und zahlt, ob die
Anzahl der Einsen in jeder Spalte gerade ist. Schreiben Sie ein Java-Programm,
das mit Hilfe dieser Regel bei einer gegebenen Spielsituation den optimalen Zug
ausfuhrt.
Losung zur Aufgabe 73:
class Node { public int p[4]; }
class Nim {
int T[8][6][4][2];
Nim() { T[0][0][0][0]=1; }
public void dp(Node n) {
int win = 1;
for(int i=0;i<n.p[3];i++) win = win && T[n.p[0]][n.p[1]][n.p[2]][i];
for(int i=0;i<n.p[2];i++) win = win && T[n.p[0]][n.p[1]][i][n.p[3]];
for(int i=0;i<n.p[1];i++) win = win && T[n.p[0]][i][n.p[2]][n.p[3]];
for(int i=0;i<n.p[0];i++) win = win && T[i][n.p[1]][n.p[2]][n.p[3]];
T[n.p[0]][n.p[1]][n.p[2]][n.p[3]] = (1-win); }
public void traverse(Node m) {
Node n;
for(int i0=0;i0<=m.p[0];i0++)
for(int i1=0;i1<=m.p[1];i1++)
for(int i2=0;i2<=m.p[2];i2++)
for(int i3=0;i3<=m.p[3];i3++)
if (i0+i1+i2+i3 >0) {
n.p[0] = i0; n.p[1] = i1; n.p[2] = i2; n.p[3] = i3;
dp(n); } }
public static void main(String args[]) {
Node m; Nim nim;
m.p[0] = 7; m.p[1] = 5; m.p[2] = 3; m.p[3] = 1;
nim.traverse(m); System.out.println(nim.T[7][5][3][1]); } }
277
Auszug aus StrategieZug im Java-Applet Nim-Spiel
http://www.informatik.uni-freiburg.de/~hipke/NIM
public static void ziehe(NIMSpiel spiel) {
int xor=0;
// Aktuellen XOR-Wert bestimmen
for(int s=1;s<=spiel.anzahlStapel();s++) xor^=spiel.anzahl(s);
if (xor==0) { NIMZufallZug.ziehe(spiel); return; }
int maxbit=xor;
// Bit am weitesten links bestimmen
int shift=0;
while(maxbit!=0) { maxbit>>=1; shift++; }
maxbit=1<<(--shift);
int stapelNr=1;
// Stapel mit gesetztem maxbit suchen
for(;stapelNr<=spiel.anzahlStapel();stapelNr++)
if ((spiel.anzahl(stapelNr) & maxbit) != 0) break;
int anzahl=spiel.anzahl(stapelNr);
// Anzahl bestimmen
int wegnehmen=anzahl-(anzahl^xor);
spiel.nimmWeg(wegnehmen,stapelNr); }
// Zug ausfuehren
Aufgabe 74:
Worterbucher bieten die Operation des Suchens, des Einfugens, und des Loschens eines
Datensatzes aufgrund seines Schlussels an. AVL-Baume fuhren diese Operationen in logarithmischer Zeit zur Anzahl der Datenelemente durch. Wenn wir ganzzahlige Schlussel
aus dem Intervall [0 : : : m] ablegen wollen, so konnen wir diese Operationen durch ein
Array (Schubladen) von AVL-Baumen A0; : : : ; Ak 1 noch weiter beschleunigen. Hierbei
wird der Schlussel i in den AVL-Baum mit Nummer (i mod k) gesucht, eingefugt oder
geloscht.
Implementieren sie die Klasse SchubladenAVL mit den Methoden insert, delete, search
und print. Fur die Aufgabe verwende man die AVL-Implementation von Seite
www.informatik.uni-freiburg.de/~edelkamp/info2/baeume/AVLTreeTest.java.
Als Test wahle man k = 7, fuge die Zahlen von 0 bis 90 ein und losche die Zahlen
von 21 bis 69 gema dem folgenden Schema.
public class SchubladenTest {
public static void main(String args[]) {
System.out.println ("SchubladenTest");
System.out.println ("Erzeuge 7 Schubladen von AVL-Baeumen");
SchubladenAVL T = new SchubladenAVL (7);
System.out.println ("Fuege Zahlen 0 .. 90 ein:");
for (int i=0; i<=90; ) T.insert (i++);
System.out.println ("Loesche Zahlen 69 .. 21:");
KAPITEL 11. PRAXIS
278
for (int i=69; i>=21; ) T.delete (i--);
System.out.println ("Ist 90 enthalten? "+T.search(90));
System.out.println ("Ist 21 enthalten? "+T.search(21));
}
}
Losung zur Aufgabe 74:
import AVLTree;
/* Hashtabelle fuer int-Zahlen mit Divisions-Rest-Methode und
direkter Verkettung der Ueberlaeufer in AVL-Baeumen */
class SchubladenAVL {
AVLTree harray[];
// Tabelle mit Referenzen auf AVL-Baeume
SchubladenAVL (int length) {
// Konstruktor
harray = new AVLTree [length];
// Initialisieren der Tabelle
for (int i = 0; i < length; i++)
// mit leeren AVL-Baeumen
harray [i] = new AVLTree ();
}
/* Einfuegen, Loeschen und Suchen zurueckfuehren auf AVL-Baeume */
boolean insert (int c) { return harray [c \% harray.length].insert (c); }
boolean delete (int c) { return harray [c \% harray.length].delete (c); }
boolean search (int c) { return harray [c \% harray.length].search (c); }
void print () {
// Ausgabe aller AVL-Baeume
for (int i=0; i < harray.length; i++) {
System.out.println ("AVL-Baum Nr. "+i+" (Hoehe "+
harray[i].height()+", interne Pfadlaenge "+harray [i].I()+"):");
harray [i].print ();
}
}
}
Aufgabe 75:
In dieser Aufgabe sollen AVL-Baume mit den Java Graphik-Routinen gezeichnet werden.
Als Beispiel dient die Abbildung 11.2. Erweitern Sie hierfur die im folgenden angegebene
Klasse AVLTest. (Hilfreiche Programmtexte zur Bearbeiten der Aufgaben nden Sie unter
www.informatik.uni-freiburg.de/~edelkamp/info2/baeume/visual).
Um das Layout nicht allzu verkomplizieren, konnen Sie eine maximale Tiefe des AVLBaumes von drei oder vier annehmen.
279
Abbildung 11.2: AVL-Baum Visualisierung
class AVLTest extends Frame {
static AVLTree Baum;
public AVLTest(){ //Konstruktor des Info-Fensters
super("AVLTest");
resize(1000,270);
show();
}
public void update(Graphics g){paint(g);}
public void paint(Graphics g){ // Zeichen des AVL-Baumes
if (Baum==null) return;
g.setColor(new Color(0,0,0));
g.fillRect(0,0,1000,500);
g.setColor(new Color(255,255,255));
g.drawString("AVL-BAUM-VISUALISIERUNG (bis zur Tiefe 4)",20,40);
g.setColor(new Color(255,0,0));
g.drawString("Balance-Werte",20,70);
g.setColor(new Color(0,255,0));
g.drawString("Schluessel",20,85);
... // die Knoten zeichen
}
public void zeichneKnoten( // zeichnet einen AVL-Knoten
AVLKnoten k,Graphics g,int x,int y,
// x und y
int vx,int vy) {
// Vorgaenger x und y
...
}
280
KAPITEL 11. PRAXIS
Losung zur Aufgabe 75:
public void zeichneKnoten
(AVLKnoten k,Graphics g,
int x,int y,
int vx,int vy){
if (k==null) return;
g.setColor(new Color(255,255,255));
g.drawOval(x-3,y,30,20);
g.drawLine(x+10,y,vx+10,vy+20);
g.setColor(new Color(0,255,0));
g.drawString(new Integer(k.key).toString(),x+5,y+13);
g.setColor(new Color(255,0,0));
g.drawString(new Integer(k.b).toString(),x+5,y-7);
}
public void paint(Graphics g){
if (Baum==null) return;
g.setColor(new Color(0,0,0));
g.fillRect(0,0,1000,500);
g.setColor(new Color(255,255,255));
g.drawString("AVL-BAUM-VISUALISIERUNG (bis zur Tiefe 4)",20,40);
g.setColor(new Color(255,0,0));
g.drawString("Balance-Werte",20,70);
g.setColor(new Color(0,255,0));
g.drawString("Schluessel",20,85);
//die Knoten zeichen:
zeichneKnoten(Baum.Wurzel,g,500,50,500,30);
zeichneKnoten(Baum.Wurzel.l,g,250,100,500,50);
zeichneKnoten(Baum.Wurzel.r,g,750,100,500,50);
if (Baum.Wurzel.l!=null){
281
zeichneKnoten(Baum.Wurzel.l.l,g,125,150,250,100);
zeichneKnoten(Baum.Wurzel.l.r,g,375,150,250,100);
if (Baum.Wurzel.l.l!=null){
zeichneKnoten(Baum.Wurzel.l.l.l,g,75,200,125,150);
zeichneKnoten(Baum.Wurzel.l.l.r,g,175,200,125,150);
}
if (Baum.Wurzel.l.r!=null){
zeichneKnoten(Baum.Wurzel.l.r.l,g,315,200,375,150);
zeichneKnoten(Baum.Wurzel.l.r.r,g,415,200,375,150);
}
}
if (Baum.Wurzel.r!=null){
zeichneKnoten(Baum.Wurzel.r.l,g,625,150,750,100);
zeichneKnoten(Baum.Wurzel.r.r,g,875,150,750,100);
if (Baum.Wurzel.r.l!=null){
zeichneKnoten(Baum.Wurzel.r.l.l,g,575,200,625,150);
zeichneKnoten(Baum.Wurzel.r.l.r,g,675,200,625,150);
}
if (Baum.Wurzel.r.r!=null){
zeichneKnoten(Baum.Wurzel.r.r.l,g,815,200,875,150);
zeichneKnoten(Baum.Wurzel.r.r.r,g,915,200,875,150);
}
}
}
Aufgabe 76:
Programmieren Sie Brent's Algorithmus zum ezienten Hashing mit oener Adressierung
als Klasse BrentOpenHashTable extends OpenHashTable. Implementieren Sie insbesondere die Funktion public void insert (Object key,Object value). Testen Sie Ihr
Programm in der Klasse BrentOpenHash mit dem Standart-Array 19, 53, 72, 5, 12. Nutzen Sie dabei den Rahmen fur Hashtabellen aus der Vorlesung (im Netz zu nden).
Losung zur Aufgabe 76: Losung korrespondiert zum alten Hashtabellenrahmen, (z.B.
heit comparable jetzt Orderable, eigene Klassen werden nunmehr gro geschrieben u.a.)
ist aber leicht zu ubertragen. Die Methode vertausche ist nicht sehr ubersichtlich. Besser
ist die direkte Einbeziehung der Funktion in insert.
class brentOpenHashTable extends openHashTable {
brentOpenHashTable (int capacity) {
KAPITEL 11. PRAXIS
282
super (capacity);
}
protected int hprime (Object key) {
return 1+(key.hashCode()\%(capacity-2));
}
protected int s (int j, Object key) {
return j * hprime (key);
}
protected void vertausche (openTableEntry entry1,
openTableEntry entry2) {
Object tempItem = entry1.item;
entry1.item = entry2.item;
entry2.item = tempItem;
Object tempKey = entry1.key;
entry1.key = entry2.key;
entry2.key = tempKey;
}
public void insert (Object key, Object item) {
openTableEntry newEntry = new openTableEntry (key,item);
int i = h(key);
System.out.print("Betrachtete Speicherplaetze: ("+
i+ "," + key.hashCode ()+")");
while (hashEntry[i].marke == BELEGT) {
int i1 = (i-s(1,newEntry.key))\%capacity;
if (i1 < 0) i1 = i1+capacity;
int i2 = (i-s(1,hashEntry[i].key))\%capacity;
if (i2 < 0) i2 = i2+capacity;
if ((hashEntry[i1].marke == BELEGT) &&
(hashEntry[i2].marke != BELEGT)) {
vertausche(hashEntry[i], newEntry);
i = i2;
}
else i = i1;
System.out.print(" (" + i+ "," + newEntry.key.hashCode ()+")"); }
System.out.println ();
hashEntry[i].key = newEntry.key;
hashEntry[i].item=newEntry.item;
hashEntry[i].marke = BELEGT;
}
283
}
public class brentOpenHash { // Programm zum Testen von Hashverfahren
public static void main(String args[]){
int vec[] = {19, 53, 72, 5, 12}; // "Standard"-Array
if (args.length != 0) {
vec = new int [args.length];
for (int j = 0 ; j < args.length ; j++)
vec[j] = Integer.valueOf(args[j]).intValue();
}
Integer [] t = new Integer [vec.length];
for (int i = 0; i <= vec.length - 1; i++) {
t[i] = new Integer(vec[i]);System.out.print(vec[i] + " ");
}
System.out.println();
brentOpenHashTable h = new brentOpenHashTable (7);
h.printTable ();
for (int i = 0; i <= t.length - 1; i++) {
h.insert(t[i], t[i]);
h.printTable ();
}
h.delete(t[0]);
h.printTable();
System.out.println(h.search(t[4]).hashCode ());
h.delete(t[3]); h.printTable();
h.insert(new Integer (54), new Integer (54));
h.printTable ();
}
}
Aufgabe 77:
In der Vorlesung wurden unterschiedliche Hashverfahren besprochen. Programmieren Sie
jeweils eine Klasse fur die angegebene Methode und testen Sie ihre Programme mit verschiedenen Eingabefolgen. Nutzen Sie auch hier den Rahmen aus der Vorlesung.
(a) Die multiplikative Methode wahlt eine irrationale Zahl zwischen Null und Eins aus,
multipliziert den Schlussel mit dieser Zahl und entnimmt die Nachkommastellen.
Diese werden wiederum mit der Tabellengroe multipliziert und letztendlich
der
p
Nachkommabereich weggestrichen. Wahlen Sie fur den goldenen Schnitt ( 5
1)=2.
284
KAPITEL 11. PRAXIS
(b) Universelles Hashing umgeht den worst-case im Hashing, der sich durch eine
ungunstige Eingabe immer ergeben kann, mit Randomisierung. In der Vorlesung
wurde gezeigt, da die Klasse H = f((ax + b) mod p) mod m mit 1 a < p
und 0 b < pg fur eine Primzahl p universell ist. Dabei sei m die Groe der
Hashtabelle und das Universum aller Schlussel bestehe o.B.d.A. aus p Elementen.
Losung zur Aufgabe 77:
public class MultiOpenHash extends OpenHashTable{
public MultiOpenHash () { super (); }
public MultiOpenHash (int capacity) { super(capacity); }
public int h (Object key) {
double teta = (Math.sqrt(5)-1)/2;
double hk= (key.hashCode()*teta)\%1;
int hashValue= (int) (getCapacity ()*hk);
return hashValue;
}
}
public class MultiOpenHashTest {
public static void main(String args[]){
int vec[] = {19, 53, 72, 5, 12};
System.out.println();
if (args.length != 0) {
vec = new int [args.length];
for (int j = 0 ; j < args.length ; j++){
vec [j] = Integer.valueOf(args[j]).intValue();
}
}
System.out.println();
Integer [] t = new Integer[vec.length];
for (int i = 0; i <= vec.length - 1; i++) {
t[i] = new Integer(vec[i]);
System.out.print(vec[i] + " "); }
System.out.println();
MultiOpenHash h = new MultiOpenHash (7);
for (int i = 0; i <= t.length - 1; i++) {
h.insert(t[i], null);
h.printTable ();
285
}
}
}
public class UniOpenHash extends OpenHashTable {
boolean init=true;
int a[];
int b[];
int p=11; //Primzahl
public UniOpenHash () { super (); }
public UniOpenHash (int capacity) { super(capacity);}
public int h (Object key) {
if(init){
init=false;
a=new int[p];
b=new int[p];
Random zufall=new Random();
for(int i=0;i<p;i++){
a[i]=(int) (p*(zufall.nextDouble()\%1));
//% bedeutet
b[i]=(int) (p*(zufall.nextDouble()\%1)); }}//mod (Modulo)
int hashValue=((a[key.hashCode()\%p]*key.hashCode()+
b[key.hashCode()\%p])\%p)\%getCapacity ();
if (hashValue < 0) hashValue = hashValue + getCapacity ();
return hashValue; }}
public class UniOpenHashTest {
public static void main(String args[]){
int vec[] = {19, 53, 72, 5, 12};
System.out.println();
if (args.length != 0) {
vec = new int [args.length];
for (int j = 0 ; j < args.length ; j++) {
vec [j] = Integer.valueOf(args[j]).intValue();
}
}
Integer [] t = new Integer[vec.length];
for (int i = 0; i <= vec.length - 1; i++) {
t[i] = new Integer(vec[i]);
System.out.print(vec[i] + " ");
KAPITEL 11. PRAXIS
286
}
System.out.println();
UniOpenHash h = new UniOpenHash (7);
for (int i = 0; i <= t.length - 1; i++) {
h.insert(t[i], null);
h.printTable ();
}
}
}
Aufgabe 78:
Schweden- bzw. Kreuzwortratsel sind ein beliebter Zeitvertreib. Die automatische Generierung dieser Ratsel eine echte Herausforderung. Anbei ein Gerust, das das Problem
mittels Backtracking lost. Programm und Wortschatz nden Sie im Vorlesungsverzeichnis
im Netz. Leider ist das Programm noch zu langsam.
(a) Die Reihenfolge der Wortbelegungen ist entscheidend. Programmieren Sie eine Methode permute, die das Array All mischt, und nden Sie eine Loesung durch Neustarts.
(b) Nutzen Sie Hashing, um die Suchzeit im Wortschatz (Methode fits) zu verringern.
Suchen Sie dabei z.B. alle Worter der Lange k, die an Stelle j den Buchstaben
l haben. Die Tabelle wird recht gro, da fur jeden Buchstaben ein Eintrag abgelegt wird. Konzentrieren Sie sich deshalb auf Indizes zu den Wortern und Teilen
des Wortschatzes, die durch das festverdrahtete Ratsel auch tatsachlich angefragt
werden. Es bietet sich eine Partitionierung der Worter nach Ihrer Lange an.
(c) Entwickeln Sie eigene Ideen und versuchen Sie sich an groeren Ratseln (ein weiteres
Beispiel ist im Netz zu nden). Die besten Abgaben werden pramiert.
import java.io.*;
class Word {
int xpos, ypos, length;
public String st;
public boolean hor;
Word(int x,int y,int l)
class CrossWord {
String St[];
Word All[];
//
//
//
{
Koordinaten und Laenge des einzusetzenden Wortes
einzusetzendes Wort
horizontale oder vertikale Ausrichtung
xpos=x; ypos=y; length=l; st = ""; }}
// Array aller Woerter im Wortschatz
// Array aller einzusetzenden Woerter
287
char Board [][];
// Darstellung des Kreuzwortraetsels
static final int rows = 6, cols = 6, size=25017;
Word Hors[] = { new Word(1,0,6), new Word(2,1,3),
new Word(3,0,5), new Word(4,3,3), new Word(5,0,3) };
Word Verts[] = { new Word(1,1,5), new Word(1,2,3), new Word(0,3,5),
new Word(3,4,3), new Word(0,5,3)};
CrossWord() {
St=new String[size];
All=new Word[Hors.length+Verts.length];
Board=new char[rows][cols]; int line=0,k=0;
try { System.out.println("Reading File");
// Einlesen des Wortschatzes
FileReader f = new FileReader("in"+"");
StreamTokenizer tok=new StreamTokenizer(f);
while (tok.nextToken() != tok.TT_EOF) St[line++] = tok.sval; f.close(); }
catch(IOException e) {
System.err.println(e); System.exit(1);
}
System.out.println("Done.");
for(int i=0;i<Hors.length;i++)
for(int j=0;j<Hors[i].length;j++)
Board[Hors[i].xpos][Hors[i].ypos+j]=' ';
for(int i=0;i<Verts.length;i++)
for(int j=0;j<Verts[i].length;j++)
Board[Verts[i].xpos+j][Verts[i].ypos]=' ';
for(int i=0;i<Hors.length;i++) {
All[k] = Hors[i]; All[k++].hor = true;
}
for(int i=0;i<Verts.length;i++) {
All[k] = Verts[i]; All[k++].hor = false;
}
}
private void set() {
// plaziere alle gefunden Woerter in Raetsel
for(int i=0;i<rows;i++) for(int j=0;j<cols;j++) Board[i][j] = '#';
for(int i=0;i<Hors.length;i++)
for(int j=0;j<Hors[i].length;j++)
if (Hors[i].st != "")
Board[Hors[i].xpos][Hors[i].ypos+j] = Hors[i].st.charAt(j);
else if (Board[Hors[i].xpos][Hors[i].ypos+j] == '#')
Board[Hors[i].xpos][Hors[i].ypos+j] = ' ';
288
KAPITEL 11. PRAXIS
for(int i=0;i<Verts.length;i++)
for(int j=0;j<Verts[i].length;j++)
if (Verts[i].st != "")
Board[Verts[i].xpos+j][Verts[i].ypos] = Verts[i].st.charAt(j);
else if (Board[Verts[i].xpos+j][Verts[i].ypos] == '#')
Board[Verts[i].xpos+j][Verts[i].ypos] = ' '; }
private String get(Word w) {
// entnehme Muster in derzeitigen Raetsel
char c[] = new char[w.length]; // erzeuge Zeichenkette
for(int j=0;j<w.length;j++)
if (w.hor==true)
c[j]=Board[w.xpos][w.ypos+j];
else
c[j]=Board[w.xpos+j][w.ypos];
return new String(c); }
// gib Ergebnis zurueck
private int fits(Word w, int start) { // pruefe ob Wort in Wortschatz passt
String pat = get(w); int j;
// Hole das Muster fuer Wort w
for (int i=start;i<size;i++)
if (pat.length() == St[i].length()) { // Laenge stimmt ueberein
for(j=0;j<pat.length();j++)
if (pat.charAt(j) != ' ' && pat.charAt(j) != St[i].charAt(j)) break;
if (j == pat.length()) return i; } // Es gibt ein Wort fuer Muster
return -1; }
// Kein Wort fuer Muster vorhanden
private boolean backtrack(int i) {
// Suche durch Backtracking
if (i == All.length) return true; // Abbruch, da Raetsel geloest
int found=-1;
// Position eines gefundenen Wortes im Wortschatz
while ((found=fits(All[i],found+1)) != -1) { // solange naechstes Wort ex.
All[i].st = St[found]; set(); i++; // Einfuegen des gefundenen Wortes
if (backtrack(i)) return true; // rekursiver Aufruf
i--; All[i].st = ""; set(); }
// Entfernen des gefunden Wortes
return false; }
// Abbruch, kein passendes Wort gefunden
public boolean search() { return backtrack(0); } // Schnittstelle
public String toString() {
StringBuffer s = new StringBuffer(); // Ausgabe des Kreuzwortraetsels
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++)
s.append(Board[i][j]); s.append("\n");}
return s.toString();
}
}
289
class Cross {
// Haupt- bzw. Testklasse
public static void main(String args[]) {
CrossWord C = new CrossWord();
if (C.search()) System.out.println(C);
}
}
Losung zur Aufgabe 78:
private void permute() {
for(int i=0;i< All.length;i++) {
int r = (int) (Math.random() * All.length);
Word temp = All[r];
All[r] = All[i];
All[i] = temp;
}
}
Aufgabe 79:
Ein Wurfelwusel ist aus gleichartigen Wurfeln zusammengesetzt, welche auf geheimnisvolle Weise aneinander haften. Der Wusel bendet sich entweder in Ruhe oder in Bewegung.
Er besteht aber immer aus einem Stuck. In Ruhe haften immer jeweils ganze Wurfelachen
aufeinander. In Bewegung wird eine Gruppe von Wurfeln (Anzahl 1) in Richtung einer
der Wurfelachsen um eine Wurfellange bewegt. Mogliche Bewegungsrichtungen sind also
links, rechts, auf, ab, vor und zuruck. Wahrend einer Bewegung haftet die verschobene
Gruppe immer mit mindestens der Groe einer Wurfelache am Rest.
Mogliche Bewegungen:
Wurfel, die die Standache beruhren, konnen nur aufwarts bewegt werden. (Ein Wurfelwusel hebt die Fue beim Laufen namlich an und schlurft nicht uber den Boden.) Wenigstens ein Wurfel mu die Standache beruhren, wobei der Wusel nicht an der Standache
haftet. Die ubrigen Wurfel mussen so verteilt sein, da der Wusel nicht umkippt.
Beispielwusel:
290
KAPITEL 11. PRAXIS
Zur Statik des Wurfelwusels: Der Schwerpunkt des Wusels lat sich leicht berechnen. Der
Wusel fallt um, wenn der Schwerpunkt { bzw. die Projektion des Schwerpunkts auf den
Boden { sich auerhalb der durch die Beine denierten Grundache bendet.
Ziel ist es, von einer gegebenen Anfangsstellung heraus in eine vordenierte Zielposition
zu gelangen. Weiterhin mussen dabei unter Umstanden kleine Hindernisse uberwunden
werden konnen.
Uns ist bewut, da das gestellte Problem eine harte Nu ist und in einem einfachen
Ansatz fur groere Wusel nicht immer zu losen ist. Hier sollen Ihre Verbesserungen greifen.
Index
EXTERNAL-HEAPSORT, 89
WEAK-HEAPSORT, 84
Allgemeine Sortierverfahren, 48
Alternative mit Zeigern, 29
Amortisierung, 116
Analyse, 80, 97, 110
Analyse der direkten Verkettung, 160
Analyse ideales Hashing, 168
Analyse von Mergesort, 57
Analyse von Quicksort, 62
Analyse von Radix-exchange-sort, 93
Anwendung: Topologische Sortierung, 42
Arraydarstellung, 31
Arrayeinbettung, 86
Arrays, 28
Aufgaben, 17
Aufrufbaum, 38
Aufteilen (2-3 Baum), 140
Auswahlsort, 52
Average Case, 64
Average Case Analyse Quicksort, 70
Beispiel, 15, 121, 155, 170, 173
Beispiel zu Shellsort, 54
Beispiel:, 43
Beispiel: Ordered double Hashing, 174
Beispiel: Tower-of-Hanoi Problem, 36
Beispiele, 22
Bemerkungen, 16
Bottom-up-Heapsort, 81
Bottom-Up-Heapsort: Implementation,
82
Clever-Quicksort, 73
Das Auswahlproblem, 100
Das Maxsummenproblem, 22
Das Sortierproblem, 48
Der clevere Algorithmus, 23
Der normale Algorithmus, 22
Divide-And-Conquer, 23
Doppelte Verkettung, 34
Double Hashing, 170
Durchlaufreihenfolgen, 125, 130
Dynamisch, 32
Eigenschaften, 130, 143
Elementare Sortierverfahren, 49
Entfernen, 133
Entfernen des Maximums, 76
Ergebnisse, 83
Erstellen eines Heaps, 78
Exponentielle Algorithmen, 20
Exponentielle Suche, 114
Fallunterscheidung, 14
Fibonacci-Suche, 112
Frequency Count, 116
Funktionenklassen, 16
Generierung eines Weak-Heaps, 85
Geschlossene Hashverfahren, 150
Graphen, 178
Grundlegende Implementation, 50
Grundtypen und Knoten, 30
Hashfunktion, 152
291
292
Hashverfahren, 150, 152
Hashverfahren|Beispiele, 150
Heapsort, 75
Implementation, 59, 73, 87, 112, 125, 131,
134, 178
Implementation (Rahmen), 100
Implementation Adjazenzliste, 180
Implementation Adjazenzmatrix, 179
Implementation in Java, 153, 158
Implementation: Aufteilungsschritt, 61
Implementation: Decomposable, 94
Implementation: Fachverteilung, 98
Implementation: Ganzzahlen, 94
Implementation: Oene Hashtabelle, 163
Implementation: Radix-Exchange, 95
Implementation: Shellsort, 54
Implementation: Suchen, 175
Implementation: Zeichenketten, 95
Implementation: QUICK-HEAPSORT,
90
Implementationen, 29
Interpolationssuche, 115
Iteriertes Versickern, 80
Kriterien, 163
Landau'sche O-Notation, 16
Laufzeitanalyse, 88
Leistungsverhalten von Algorithmen, 14
Lineares Sondieren, 167
Listen, 29
Maximaler Zeitaufwand cost(A) in Java, 21
Median-of-Median, 102
Mengen, 45
Mergesort, 56
Messung des Leistungsverhaltens, 14
Motivation, 27
INDEX
Move-To-Front Analyse, 118
Multiplikative Methode, 154
Oene Hashverfahren, 162
Operationen, 41
Optimale Nutzung der Vorsortierung, 104
Probleme, 151
Problemstellung, 108
Quadratisches Sondieren, 167
Quick-Heapsort, 89
Quicksort, 59
Radix-exchange-sort, 92
Radixsort, 92
Reines 2-Wege-Mergesort, 58
Rotationen, 135
Schlange/Queue:, 41
Selbstanordnende lineare Listen, 116
Shellsort, 54
Situation im Oenen Hashing, 162
Skalierbarkeiten, 19
Sondierungsfolgen, 162
Sortieren durch Fachverteilung, 97
Sortieren vorsortierter Daten, 103
Sortierungsphase, 86
Stapel/Stack, 36
State-of-the-Art, 69
Suchen, 164
Test-Programm, 159, 166
Testen: Tower-of-Hanoi-Stack, 40
Tiefe, 138
Tiefensuche, 181
Topologische Sortierung, 181
Uniformes Sondieren, 168
Universelles Hashing, 155, 156
Veranschaulichung, 76
INDEX
Verbesserung der erfolglosen Suche, 174
Verbesserung der erfolgreichen Suche,
171, 172
Verfeinerung, 32
Verschmelzen, 84
Verschmelzen zweier Teilfolgen, 56
Versickern, 77
Verwendete Datenstrukturen, 43
Visualisierung, 17
Visualisierungen, 33
Wahl der Hash-Funktion, 153
Wahl des Pivotelements, 102
Weitere Eigenschaften, 18
Zusammestellung der Ergebnisse, 24
293
Herunterladen