Informatik 2 - Script 2. Teil

Werbung
Informatik 2 - Script 2. Teil
Inhaltsverzeichnis
3 Analyse von Algorithmen
50
3.1
O-Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
3.2
Beispiel: Sortieren durch Einfügen (Insertion Sort) . . . . . . . . . . . . . . . . . . . . . .
50
3.3
Polynomauswertung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
3.4
Verschiedene Arten der Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
3.5
Untere Schranke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
3.6
Exakte Schranke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
3.7
Sortieren durch Auswählen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
3.8
Bubblesort
55
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4 Algorithmische Methoden
4.1
55
Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
4.1.1
Berechnung der Fakultät . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
4.1.2
Berechnung des ggT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
4.1.3
Türme von Hanoi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
4.1.4
Fibonacci-Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
4.2
Dynamische Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
4.3
Divide and Conquer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
4.3.1
Mergesort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
4.3.2
Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
4.3.3
Randomisierte Version von Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . .
70
Backtracking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
4.4.1
73
4.4
N-Damen Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5 Weitere Ergebnisse zum Sortieren
5.1
5.2
76
Heapsort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
5.1.1
Heapsort-Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83
5.1.2
Prioritäts-Warteschlange . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
85
Sortieren von natürlichen Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
87
48
5.2.1
5.3
Bucketsort
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
88
Radixsort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
88
49
3
Analyse von Algorithmen
3.1
O-Notation
f: N → R+ . Dann ist
O(f) = {g: N → R+ | ∃ c > 0, ∃n0 ≥ 1 so, dass g(n) ≤ c * f(n) ∀ n ≥ n0 }
Für g ∈ O(f) sagen wir g ist O(f ) oder g in O(f) (kurz: g = O(f)).
Beispiele:
1. 7u - 3 = O(n)
(g(n) = 7n - 3, f(n) = n)
2. 20n3 + 10n logn = O(n3 )
(g(n) = 20n3 + 10n logn ≤ 20n3 + 10n2 ≤ 30n3 , f(n) = n3 )
3.2
Beispiel: Sortieren durch Einfügen (Insertion Sort)
Gegeben: Array A bestehend aus n ganzen Zahlen
Ziel:
sortiere A so um, dass A[i] ≤ A[i+1] ∀ i ∈ {0,...,n-2}
Idee:
halte einen sortierten und einen nicht-sortierten Bereich in A vor. In jedem
Durchlauf würde das nächste Element aus den nicht-sortierten Bereich und
füge es an der richtigen Stelle im sortierten Bereich ein.
≤
0
≤
i-1
x
nicht sortiert
i i+1
n-1
Durchführung:
7
5
1
1
1
|
7
5
4
2
5
|
7
5
4
1
1
|
7
5
4
4
4
|
7
2
2
2
2
|
6
6
6
6
6
3
3
3
3
3
8
8
8
8
8
A[0],...,A[i-1] sortiert.
50
int i,j;
for(int i = 0;i < n;i++){
x = A[i]; j = i - 1;
while ((j >= 0) && (A[j] > x)){
A[j+1] = A[j];
j- }
A[j+1] = x; }
Es sei t1 die Anzahl der Elemente in A[0],...,A[i-1] in Durchlauf i, die größer als x sind.
Ergibt: Laufzeit T(n) = 5n-3 + 4
Pn−1
i=1
ti + 2(n-1) = 7n-5 + 4
Pn−1
i=1
ti
Best-Case: Array bereits sortiert
⇒ ti = 0 ⇒ T(n) = 7n-5 = O(n)
Worst-Case: Array Elemente in umgekehrter Reihenfolge
⇒ ti = i
P
4(n−1)∗n
T(n) = 7n-5 + 4 n−1
= 7n-5 + 2(n-1) * n
i=1 ti = 7n-5 +
2
= 2n2 + 5n - 5 = O(n2 )
Best-Case: linear in n O(n)
Worst-Case: quadratisch in n O(n2 )
P
Lemma: Ist g(n) = ni=1 ci ∗ ndi ein Polynom m * n mit ci , di ∈ R+ ,
dann ist g(n) = O(nk ) für k ≥ max di , 1 ≤ i ≤ m
di
k
Beweis:
P
Pm di ≤ kk
Pm n ≤di n falls
⇒ i=1 ci ∗ n ≤ i=1 ci ∗ n = nk m
i=1 ci
Setze c =
Pm
i=1 ci
Dann gilt
P
g(n) =
ci ndi ≤ c ∗ nk
d.h. g(n) = O(nk )
∀n ≥ 1
51
Weitere Beispiele:
1. 3 log n + log log n = O(log n)
2. 21 00 = O(1)
3. n log n = 0( n2 log n2 )
4.
5
n
= O( n1 )
5. log(2n2 ) = O(log n)
Wichtige Funktionen:
log log n
n
log2 n
log n
n log n
n 23
n2
√
n3 (0 < < 12 )
n3
2n
Gegeben: Array A mit n Zahlen
Ziel:
berechne Präfix-Durchschnitte
Pi
1
S[i] = i+1
j=1 A[j], i = 0,...,n-1
Algorithmus 1:
double S[] = new double[n];
for(int i = 0;i <= n-1;i++){
double a = 0.0;
for(int j = 0;j <= i; j++){
a = a + A[j];
}
S[i] = a/(i+1);
}
Laufzeit: T1 (n) = O(n2 )
Algorithmus 2:
double S[] = new double[n];
double a = 0.0;
for(int i = 0;i <= n-1;i++){
a = a + A[i];
S[i] = a/(i+1);
}
Laufzeit: T2 (n) = O(n)
52
3n
n
nn
...
3.3
Polynomauswertung
p(x) =
Pn
i=0 ci x
i
Horner-Schema: p(x) = c0 + x(c1 + ... + x(cn−2 + x(cn−1 + cn x))...)
Gegeben: Feld C, dass die Koeffizienten c0 , ..., cn enthält.
double p = c[n];
for(int i = n-1;i >= 0;i- -)
p = p * x + c[i];
Laufzeit: O(n)
3.4
Verschiedene Arten der Analyse
• Best-Case: zu optimistisch
• Worst-Case: Analyse gilt für jede Eingabe. In vielen algorithmischen Problemen
wird der worst-case angenommen.
• Average-Case: Eingabe von Wahrscheinlichkeitsverteilung erzeugt. Theoretisch interessant, aber in der Praxis oft schwierig zu analysieren bzw. realistische Verteilung
zu simulieren / modellieren.
3.5
Untere Schranke
f: N → R+
Ω(f ) = {g : N → R+ |∃c > 0, ∃n0 ≥ 1 : g(n) ≥ c ∗ f (n), ∀n ≥ n0 }
Beispiele:
• 3 log n + log log n ist Ω(log n)
• n2 + n3 ist Ω(n3 )
3.6
Exakte Schranke
Θ(f ) = {g : N → R+ | ∃c1 , c2 > 0, ∃n0 ≥ 1 : c1 f (n) ≤ g(n) ≤ c2 f (n)
Bemerkung: g ∈ Θ(f ), wenn g ∈ O(f ) und g ∈ Ω(f )
53
3.7
Sortieren durch Auswählen
Idee: halte sortierten und nicht-sortierten Bereich in A. In jeder Iteration wählt man das
minimale Element aus dem nicht-sortierten Bereich und fügt es am Ende des sortierten
Bereichs ein.
≤
0
≤
i-2
i-1
Beispiel:
|
1
1
1
1
7
|
2
2
2
5
7
|
3
3
1
5
7
|
4
4
4
5
7
|
..
.
1 2 3 4 5
2
2
4
5
7
6
6
6
4
5
3
3
3
6
6
8
8
8
8
8
6 7 8 |
for(int i = 0;i < n-1;i++){
int index = i;
int min = A[i];
for(int j = i+1;j < n;j++){
if (A[i] < min) {
min = A[j];
index = j;
}
}
int temp = A[i];
A[i] = A[index];
A[index] = temp;
}
Laufzeit:
Pn−1
i=0 (c1 + c2 (n − i))
c1 , c2 sind Konstanten!
P
= n−1
i=0 (c1 + c2 i)
P
= c1 ∗ n + c2 ni=1 i
= c1 ∗ n + c2 ∗ n (n + 1)/2 = O(n2 )
54
nicht sortiert
i
n-1
3.8
Bubblesort
Idee: laufe wiederholt von links nach rechts durch das zu sortierende Array. In jedem
Durchlauf vergleiche nacheinander benachbarte Paare. Sind sie in der falschen Reihenfolge,
so vertausche diese.
Beispiel:
7
5
1
1
5
1
4
2
1
4
2
4
4
2
5
3
2
6
3
5
6
3
6
0
3
7
0
|
8
0
|
6
0
|
7
7
|
8 (nach 1. Durchlauf)
8 (nach 2. Durchlauf)
8 (nach 3. Durchlauf)
Bemerkung: Nach dem i-ten Durchlauf sind die größten i Elemente an ihrer endgültigen
Position.
for(int i = 0; i < n-1; i++){
for(int j = 0;j <= n-2-i; j++){
if (A[j] > A[j+1]){
int temp = A[j];
A[j] = A[j+1];
A[j+1] = temp;
}
}
}
Laufzeit:
Pn−1
P
T(n) ≤ n−2
i=1 i = c *
i=0 c * (n - i - 1) = c *
4
4.1
(n−1)n
2
= Θ(n2 )
Algorithmische Methoden
Rekursion
Ein rekursiver Algorithmus löst ein Problem, indem er eine oder mehrere kleinere Instanzen des gleichen Problems löst.
4.1.1
Berechnung der Fakultät
0! = 1, n! = n(n-1)! für n ≥ 1.
55
public static int factorial(int n){
if (n == 0)
return 1;
else
return (n * factorial(n-1);
}
Das kann ersetzt werden durch ein nicht-rekursives Programm.
public static int factorial(int n){
int f = 1;
for(int i = 1;i < = n; i++)
f = f*i;
return f;
}
Jedes rekursive Programm kann durch ein nicht-rekursives Programm
ersetzt werden, das die gleichen Berechnungen durchführt.
4.1.2
Berechnung des ggT
public static int ggT(int x, int y){
if (x == y)
return x;
else if (x > y)
return ggT(x - y, y;
else
return ggT(x, y - x;
}
4.1.3
Türme von Hanoi
3 Positionen. Position 1 hat anfangs eine Anzahl von Scheiben, die der größe nach geschichtet sind. Die größte Scheibe liegt unten.
Ziel: die Scheiben sollten von Position 1 nach Position 2 bewegt werden. Dabei darf zu
jedem Zeitpunkt nur eine Scheibe bewegt werden und eine größere Scheibe darf nie auf
einer kleineren liegen.
56
Idee: bewege n Scheiben von Position 1 nach Position 2.
1. bewege n-1 Scheiben von Pos 1 nach Pos 3 (über Pos 2)
2. bewege die größte Scheibe von Pos 1 nach Pos 2
3. bewege n-1 Scheiben von Pos 3 nach Pos 2 (über Pos 1)
public static void BewegeScheibe(int n, int from, int to, int via){
if (n > 0){
BewegeScheibe(n-1,from,via,to);
System.out.println(”Bewege Scheibe von ” + from + ” nach ” + to);
BewegeScheibe(n-1,via,to,from);
}
}
n = 3:
BS(3,1,2,3)
BS(2,1,3,2)
Bewege Scheibe von 1
BS(2,3,2,1)
BS(2,3,2,1)
BS(1,3,1,2) //Bewege
Bewege Scheibe von 3
BS(1,1,2,3) //Bewege
BS(2,1,3,2)
BS(1,1,2,3) //Bewege
Bewege Scheibe von 1
BS(1,2,3,1) //Bewege
nach 2
Scheibe von 3 nach 1
nach 2
Scheibe von 1 nach 2
Scheibe von 1 nach 2
nach 3
Scheibe von 2 nach 3
57
Sei B(n) die Anzahl der Bewegungen für ein Problem mit n Scheiben.
B(n) = 2B(n-1) + 1
B(1) = 1
Lemma: Der Algorithmus benötigt B(n) = 2n -1 Züge.
Idee: B(n) = 2B(n - 1) + 1 = 2(2B(n - 2) + 1) + 1 = 2( 2( 2B(n - 3) + 1) + 1) + 1
= 23 B(n-3) + 4 + 2 + 1 = 23 2B(n-4) + 23 + 22 + 21 + 20
n−1
= 2P
B(1) + (2n−2 + 2n−3 + . . . + 20 )
n−1 i
= i=0 2 = 2n -1.
58
Beweis: Induktion über n
n = 1: B(1) = 1 = 21 -1 → stimmt!
n y n + 1: Es sei B(n) = 2n -1
Zeige: B(n+1) = 2n+1 - 1
B(n+1) = 2 B(n) + 1 = 2 (2n - 1) + 1
= 2n+1 - 2 + 1 = 2n+1 - 1 → stimmt!
4.1.4
Fibonacci-Zahlen
F0 = 0, F1 = 1, Fn = Fn−1 + Fn−2 für n ≥ 2
public static int fib(int n){
if (n <= 1) return n;
else
return (fib(n-1) + fib(n-2));
}
Laufzeit:
T(0) = T(1) = 1
T(n) = T(n-1) + T(n-2) + c≥1
⇒ T(n) ≥ Fn , n ≥ 0 (per Induktion)
√ n−2
Fn ≥ 1+2 5
für n ≥ 2 (⇒ exponentielle Laufzeit)
n = 2: F2 = 1 =
√ 0
1+ 5
2
n y n + 1: Fn+1 = Fn + Fn−1 ≥IV
=
√ n−2
1+ 5
2
√ √ n−3 1+ 5
1+ 5
1+ 2
2
√ 2
√
3+ 5
≥! (1+4 5)
2
=
√
√
=6+2 5≥1+2 5+5
√ n−3 √ 2
1+ 5
⇒ Fn+1 ≥ 1+2 5
2
√ n−1
→ stimmt!
= 1+2 5
59
+
√ n−3
1+ 5
2
Problem: Zahlen werden mehrfach berechnet.
F6 → F5 + F4 → [(F4 + F3 ) + (F3 + F2 )] → . . .
Es baut sich ein sehr großer Binärbaum auf, bei dem beinahe jeder Wert zwei Nachfolger
hat.
Vermeidbar: durch iterative Lösung [→ Präsenzaufgabe]
Algorithmus angeben mit T(n) = O(n).
4.2
Dynamische Programmierung
Idee: definiere Lösung eines Problems rekursiv und fülle schrittweise eine Tabelle auf.
Beginne dabei mit den Elementen am Rekursionsende und verwende die schon berechneten
Werte als Grundlage zur Berechnung neuer Werte.
Beispiele:
1. Fibonacci-Zahlen
2. Binomialkoeffizienten
n
n!
= k!(n−k)!
k
n
= n−1
+ n−1
k
k−1 k
n
= 1, nn = 1
0
2
1
1
1
2
2
3
3
3
1 2 3 4
4
4
4
1
2
3
4
Idee:
2
2
n
beginne mit 11 , dann berechne
nächste
Zeile
usw.
bis
wir
beim
Wert
1
2
k
ankommen. Der Wert von ab ergibt sich aus der Summe der beiden Werte oberhalb
im Pascal’schen Dreieck.
3. Rucksack-Problem
geg.:
• n Gegenstände mit Größen w0 , ..., wn−1 ∈ N und Gewinne P0 , ..., Pn−1 ∈ N
• Rucksack der Kapazität B ∈ N
ges.: optimale Packung des Rucksacks, d. h. eine Teilmenge I ⊂ { 0,...,n-1} mit
P
maximalen Gewinn
Pi∈I wi ≤ B undP
P
P
=
max{
i
i∈I
i∈I 0 Pi | I’ ⊂ { 0,...,n-1},
i∈I 0 wi ≤ B}
60
Beispiel: Ein Dieb, der einen Safe ausräumt, findet in ihm N Gegenstände unterschiedlicher Größe und unterschiedlichen Werts vor, hat aber nur einen kleinen
Rucksack mit Fassungsvermögen B um die Beute zu transportieren.
Das Rucksackproblem besteht darin eine Kombination der Gegenstände zu suchen,
die der Dieb für den Rucksack auswählen sollte, um den Gesamtwert der Beute zu
maximieren.
Gegenstände Größe wi Gewinne Pi
0,...,5
3
4
6,...,9
4
5
10,11
7
10
12,13
8
11
14
9
13
Mögliche Lösung:
P
P
I={ 0,...,4} P wi = 15 ≤ 17, P pi = 5 ∗ 4 = 20
I={ 12,14}
wi = 17 ≤ 17,
pi = 24
Idee: verwende ein Feld A, wobei A[i][t] = normales Gewicht einer Packung von
Gegenständen aus {0,...,i} mit Gewinn = t. Wir setzen A[i][t] = ∞ (bzw.
B+1), falls es keine Packung mit Gewinn = t und Gegenständen aus { 0,...,i} gibt.
Lemma: A[i][t] = min{A[i-1][t], wi + A[i-1][t-Pi ]}
Beweis: es gibt zwei Möglichkeiten. Der i-te Gegenstand gehört zu einer Teilmenge
mit minimalem Gewicht und Gewinn = t oder nicht!
Im ersten Fall ist das Gewicht
A[i-1][t-Pi ] + wi
und im zweiten Fall
A[i-1][t]
↓i | t→
0
1
2
3
4
5
6
7
0
0
0
0
0
0
0
0
0
1
/
/
/
/
/
/
/
/
2
/
/
/
/
/
/
/
/
3
/
/
/
/
/
/
/
/
4
3
3
3
3
3
3
3
3
5
/
/
/
/
/
/
4
4
6
/
/
/
/
/
/
/
/
7
/
/
/
/
/
/
/
/
8
/
6
6
6
6
6
6
6
61
9
/
/
/
/
/
/
7
7
10
/
/
/
/
/
/
/
8
11
/
/
/
/
/
/
/
/
12
/
/
9
9
9
9
9
9
13
/
/
/
/
/
/
10
10
14
/
/
/
/
/
/
/
11
15
/
/
/
/
/
/
/
/
16
/
/
/
12
12
12
12
12
17
/
/
/
/
/
/
13
13
18
/
/
/
/
/
/
/
14
19
/
/
/
/
/
/
/
/
Algorithmus:
int P[] = new int[n];
int w[] = new int[n];
//Eingabe...
..
.
//Alg:
int s = 0;
for(int i = 0;i <= n-1; i++)
s = s + P[i];
//Initialisieren, A[0][.]
int A[][] = new int[n][s+1];
for(int t = 0;t <= s; t++){
if (P[0] != t)
A[0][t] = B + 1;
else
A[0][t] = w[0];
}
A[0][0] = 0;
//Berechnung der Werte A[1][.], A[2][.],...,A[n-1][.]
for(int t = 0;t <= s; t++){
if (t < P[i])
A[i][t] = A[i-1][t];
else if (A[i-1][t] <= A[i-1][t-P[i]] + w[i])
A[i][t] = A[i-1][t];
else
A[i][t] = A[i-1][t-P[i]] + w[i];
}
}
//Bestimme max j mit A[ne-1][j] ≤ B
int j = 0;
for(int t = 1;t <= s; t++){
if (A[n-1][t] <= B){
j = t;
}
}
Satz: Der Algorithmus hat Laufzeit O(n∗s) = O(n2 Pmax ) wobei s =
Pmax = max { Pi | 0 ≤ i ≤ n-1 }
Pn−1
i=0
Pi und
Bemerkung: Der Algorithmus lässt sich modifizieren, sodass auch die zugehörige
Teilmenge der Gegenstände mitberechnet wird (siehe ebenfalls Übung!)
62
4.3
Divide and Conquer
Idee: ”Teile und Herrsche”
Teile großes Problem in mindestens 2 kleinere Probleme. Dann verknüpfe die Teillösungen
zu einer Gesamtlösung.
4.3.1
Mergesort
Sequenz S von Zahlen ist zu sortieren.
(a) Divide
enthält S mindestens 2 Elemente, so teile S in zwei Sequenzen S1, S2.
S1 bestehend aus b n2 c Zahlen.
S2 bestehend aus den verbleibenden d n2 e zahlen
Sortiere S1 und S2 rekursiv
(b) Conquer
mische die sortierten Sequenzen S1, S2 zu einer Gesamtfolge S
Beispiel:
85
24
63
45 17 31 96 50
85 24 63 45 17 31 96 50
..
85 24 63 45
.
85 24 63 45
Die entstandenen Listen haben nur ein Element. Sie können nun aufwärts mit den darüber
liegenden Listen vermischt werden (siehe auch Kapitel 1.6!).
Implementierung:
Voraussetzung: Sequenz S sein ein Array.
public static void mergeSort(int[] s){
int n = s.length;
inf (n <= 2) //sortiert
return s;
int m = n/2;
int[] s1 = new int[m];
int[] s2 = new int[n-m];
63
//Belegung
for(int i = 0;i < m; i++){
s1[i] = s[i];
}
for(int i = 0;i < n-m;i++){
s2[i] = s[m+i];
}
//Rekursiver Aufruf
mergeSort(s1);
mergeSort(s2);
merge(s1,s2,s); //siehe Kapitel 1.6
/*Mischt s1, s2 zu einer Sequenz s*/
}
Laufzeit: T(n): Worst-Case Laufzeit für ein Array der Länge n
T(n) = T(b n2 c) + T(d n2 e + c1 n + c2
T(1) = c0 , wobei c0 , c1 und c2 Konstanten.
Falls n = 2k :
T(2k ) = 2 T(2k−1 ) + c1 2k + c2 = 2 (2T(2k−2 ) + c1 2k−1 + c2 )
= 4 (2T(2k−3 ) + c1 2k−1 + c2 ) + 2c1 2k + (1 + 2)c2
= 8 T(2k−3 )+ 3 c1 2k + (1 + 2 + 4)c2
= 2i T(2k−i ) + i c1 2k + (1 + 2 + 2i−1 ) c2
|
{z
}
2i −1
= 2k c0 + kc1 2k + (2k − 1) c2
= 2k (kc1 + c2 + c0 ) - c2
=n=2k n(c1 log n + (c0 + c2 )n - c2
⇒k=logn für n = 2k ist T(n) = O(n log n)
Allgemeines n
Beh.: T(n) ist monoton wachsend
Bew.: per Induktion nach n
I.A.: T(2) = 2c0 + 2c1 + c2 ≥ c0 = T(1)
I.V.: Beh. gilt für alle i = 2,...,n, d. h. T(i) ≥ T(i-1) für i = 2,...,n
I.S.:
T(n+1) = T(b n+1
c) + T(d n+1
e) + c1 (n+1) + c2
2
2
≥I.V. T(b n2 c) + T(d n2 e) + c1 (n) + c2
= T(n)
64
Für allgemeine n schätzen wir
T(n) durch T(n0 ) ab, wobei n’ die kleinste Zweierpotenz größer gleich n ist.
n = 2log n
T(n) ≤ T(2dlog ne ) = T(n0 )
= c1 2dlog ne dlog ne + (c0 + c2 ) 2dlog ne
≤ c1 21+log n 1 + log n + (c0 + c2 ) 21+log n
log n
= 2c1 2|{z}
(1 + log n) + (c0 + c2 ) 2-2log n
n
= 2c1 n log n + (2c1 + 2c0 + 2c2 ) n
= c1 n log n + c1 n
= O(n log n)
Iterative Version von Mergesort
Problem: hoher Speicheraufwand beim Array kopieren
Idee: Mische bottom-up jeweils 2 sortierte Sequenzen der Länge l in einer neuen Sequenz
der Länge 2l zusammen.
(L = 1)
(L = 2)
(L = 4)
(L = 8)
Kosten pro Iteration O(n) für mischen. Anzahl der Iterationen log2 n.
Nach i Iterationen ist L = 2i .
2i = n ⇒ i = log2 n
⇒ Gesamtlaufzeit O(n log n)
Implementierung:
public static void mergesort2(int[] a){
int l = 1;
while (l < a.length){
/* Mische aufeinanderfolgende Teilfolgen
der Länge l zu sortierten Teilfolgen der
Länge 2l*/
}
l = 2l;
}
65
...
k
k + l-1
...
k+l
k + 2l - 1
int k = 0;
while(k + 2l - 1 < n){
merge(a, k, k+l-1, k+2l-1);
k = k + 2l;
}
if (k + k-1 < n-1){ //sonst fertig
merge(a, k, k+l-1, n-1);}
public static void merge(int[] a, int k, int i, int j){
//mischt Sequenz a[k], a[k+1],...,a[i]
//mit Sequenz a[i+1], a[i+2],...,a[j]
}
4.3.2
Quicksort
(C. A. R. Houre, 1960)
Sortierungsverfahren, dass in der Praxis häufig verwendet wird.
Worst-Case-Laufzeit: = O(n2 )
Average-Case-Laufzeit: = O(n log n)
(Wenn alle Eingabepermutationen gleich wahrscheinlich sind).
Im Gegensatz zu Mergesort (einfache Version) benötigt Quicksort keinen zuätzlichen Speicherplatz.
geg.: Array von Zahlen, dass zu sortieren ist.
Idee:
• wähle ein Pivotelement x aus A
• ordne Array so um, dass zwei Teilfelder entstehen
– wobei ein Teil ≤ x und
– der andere Teil ≥ x
A:
⇓
x
Elemente ≤ x
Elemente ≥ x
66
Dann sortiere die beiden Teilfelder rekursiv.
Hoffnung: beide Teilfelder etwa gleich groß.
Quicksort: für A[l],A[l+1],...,A[r], (l < r)
• wähle x aus A[l],...,A[r]
• teile und ordne so um, dass ∃ i ∈ { l,...,r} mit
– A[l],...,A[i-1] ≤ x
– A[i+1],...,A[r] ≥ x
– A[i] = x
• sortiere A[l],...,A[i-1] rekursiv
• sortiere A[i+1],...,A[r] rekrusiv
Wir wählen als Pivotelement das Element aus dem zu sortierenden
Teilarray (d.h. x = A[0]).
Im
folgenden sind die Elemente X (eckiger Kasten) Pivotelemente und die Elemente
X (runder Kasten) Elemente, die Ihre endgültige Position im sortierten Array erreicht
haben.
1. Schritt
2. Schritt
3. Schritt
4. Schritt
5. Schritt
6. Schritt
7. Schritt
8. Schritt
Endarray
85
24
24
17 17 24
63
63 24 17
24
63
45
45
63
63
45
45
31
31
45
17
17
45
45
31
31
45
45
17
31
31
31
31
50
50
50
50
31
50
50
50
50 63 63
96 50
85 96 96 85
96
Implementierung:
public static void quicksort(int[] A, int l, int r){
int x;
if (l < r){
x = P[l];
/*Finde i ∈ { 1,...,r} und ordne um, so dass ≤ x
quicksort(A,l,r-1);
quicksort(A,l+1,r);
}
}
67
x
≥ x */
Umordnung des Feldes:
a
b c
d e
f
g
h
⇓
b c
d e
a
f
g
h
Dies Lösung ist nicht gut, da extra Speicherplatz benötigt wird.
Idee:
• halte zwei Indizes i und j anfangs i = l + 1 und j = r
• solange l < i ≤ j ≤ r
– lasse i von links nach rechts laufen bis A[i] > x
– lasse j von rechts nach links laufen bis A[j] < x
– vertausche A[i] und A[j]
• füge Pivotelement an Position i - 1 = j ein
Beispiel 1:
85
= x (l)
24
i
63
45
17
31
96
50
85
⇒
j
= x (l)
24
63
45
17
31
50
j
96
i
• i>j
– vertausche l und i - 1: ⇒
50
24
63
45
17
31
85
96
= x(l) i
24
63
45
17
Beispiel 2:
85
l
24
i
63
45
17
31
96
99
⇒
j
85
l
31
j
96
i
99
Anm. d. Autors: Fragt mich nicht wo die 99 da oben herkommt! Bin mir generell nicht so sicher, was uns der
Künstler damit sagen will... sollte jemand Aufzeichnungen besitzen, die mehr Sinn ergeben: Immer her damit! Ich
werde es dann sofort ändern!
• i>j
– vertausche l und i - 1 = j:
31 24 63 45 17 85
96
99
68
public static void quicksort(int[] A, int l, int r){
int x, i , j, temp;
if (l < r){
x = A[l]; i = l + 1; j = r;
while (i <= j){
while((i <= j) && (A[i] <= x)) i++;
while((i <= j) && (A[j] >= x)) j--;
if (i < j){
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
i--;
A[l] = A[i];
A[i] = x;
quicksort(A,l,i-1);
quicksort(A,i+1,r);
}
}
Laufzeitanalyse von Quicksort:
Umordnen von A[l],...,A[r] benötigt O(r-l) Zeit (Anweisungen i++, j- - werden
insgesamt (r-l) mal ausgeführt).
Lauzeit ≤ c1 (r-l) + c2
Best-Case Analyse
T(n) = Best-Case Laufzeit zum sortieren eines Arrays mit n Elementen mittels Quicksort.
T(0) = i0
T(n) = c1 · n + c2 + T b n−1
c + T d n−1
e
2
2
⇒ T(n) = O(n log n)
Worst-Case Laufzeit
T(n) = Worst-Case Laufzeit mittels Quicksort
T(0) = c0
T(n) = c1 · n + c2 + max (T(nl ) + T(nr )), nl + nr = n - 1
+ c2 n + c0
Beh.: T(n) = n(n+1)
2
Bew.: Induktion nach n
I.A.: T(0) = c0
I.V.: Beh. gilt für alle i = 0,...,n
I.S.: n y n + 1
69
z.Z.: Behauptung gilt für n + 1
Hilfs Behauptung: T(i) - T(n-1) ≤ T(0) + T(n)
Bew. d. linken Seite:
+ c2 i + c0 (i + 1) + c1 (n−i+1)(n−1)
+ c2 (n - i) + c0 (n - i + 1)
T(i) + T(n-1) =IV c1 i(i+1)
2
2
c1
= 2 (i (i + 1) + (n - i)(n - i + 1) + c2 n + c0 (n + 2)
Bew. d. rechten Seite:
+ c2 n + c0 (n + 1)
T(0) + T(n) =IV c0 + c1 n(n+1)
2
c1
= 2 n(n + 1) + c2 n + c0 (n + 2)
Zu zeigen also:
i(i + 1) + (n - i )(n - i + 1) ≤ n(n + 1)
⇔ i2 + i + n2 - 2ni + i2 +n - i ≤ n2 + n
⇔ 2i2 - 2ni ≤ 0
√
⇔i-n≤0⇔i≤n
Aus der Hilfsbehauptung folgt:
T(n+1) ≤ c1 (n + 1) + c2 + T(0) + T(n)
+ c2 n + c0 (n + 1)
=IV c1 (n + 1) + c2 + c0 + c1 n(n+1)
2
n(n + 1)
= c1
+ (n + 1) + c0 (n + 2) + c2 (n + 1)
2
|
{z
}
n(n+1)+2n+2
(n+1)(n+2)
=
2
2
= c1
4.3.3
(n+1)(n+2)
2
+ c2 (n + 1) + c0 (n + 2)
√
Randomisierte Version von Quicksort
Ein Algorithmus ist randomisiert, wenn sein Verhalten nicht nur durch die Eingabe sondern auch durch Werte , die von einem Zufallsgenerator produziert werden, bestimmt
ist.
Praxis: Pseudozufallszahlen-Generatoren erzeugen Zahlen deterministisch, die lediglich
zufällig aussehen.
Randomized Quicksort (RQ)
Pivotelement wird zufällig aus dem Teilfeld A[l],...,A[r] ausgewählt.
k = zufällig gewähltes Elementaus { l,...,r } ⇒ A[k] wird Pivotelement.
Vertausche A[l] und A[k].
Hoffnung: erwarte Größe der beiden Teilprobleme ist etwa gleich!
≤x
x
'
70
≥x
Zeige: erwartete Laufzeit von RQ ist O(n log n).
zur Verinfachung: Alle Eingabewerte seien verschieden.
O(n log n) Schranke gilt auch für beliebige Eingaben.
Sei A ein Feld mit n Zahlen x1 ,...,xn . Für ein x aus A sei rang(x) = |{ y aus A | y ≤ x } |.
Da die Eingabewerte verschieden sind, gilt folgendes:
∀ i = 1,...,n ∃ genau ein x aus A mit rang(x) = i
Wählt RQ ein Element mit Rang i, so haben wir Teilprobleme der Größe i - 1 und ein
Problem der Größe n - i.
T(n) = erwartete Laufzeit von RQ auf ein Feld der Größe n.
T(0) = c0
T(n) = c1 n + c2 + n1 (T(0) + T(n-1)) + n1 (T(1) + T(n-2)) + ... + n1 (T(n-1) + T(0))
P
= c1 n + c2 + n1 n−1
i=0 (T(i) + T(n-1-i))
P
= c1 n + c2 + n2 n−1
i=0 T(i)
(
a· n log n + b n ≥ 1
Beh.: T(n) =
b
n=0
[wobei a, b Konstanten sind und im Beweis bestimmt werden]
Bew.:
n = 0: T(0) = c0 ≤ b (erfüllt für alle b ≥ c0 )
n = 1: T(1) = c1 + c2 + 2c0 ≤ b (erfüllt für b ≥ c1 + c2 + 2c0 )
I.S.: n-1 y n
P
T(n) = n2 n−1
i=0 T(n) + c1 n + c2
P
2
≤I.V. n2 n−1
i=1 (a · i log i + b) + n b + c1 n + c2
Pn−1
2b
= 2a
i=1 i log i + 6n · 6 n + c1 n + c2
n
Es gilt nun:
Pn−1
i=1 i log i ≤
⇒ T(n) ≤
2a 1
(
n 2
1
2
n2 log n - 18 n2
n2 log n - 18 n2 ) + 2b + c1 n + c2
≤ a n log n - a4 n + 2b + c1 n + c2
a
= an log n + b + (b + c1 n + c2 − n) ≤ an log n + b
4
|
{z
}
gilt für b+c1 n+c2 − a4 ≤0
71
Einschub:
⇔ a4 n ≥ b + c1 n + c2
⇔ a4 n ≥ c1 +
b+c2
n
b + c2
bzw. a ≥ 4c1 + 4
| {zn }
≤4(b+c2 )
2
d.h. falls a ≥ 4c1 + 4(b + c2 ) folgt a ≥ 4c1 + 4 b+c
n
und damit durch Äquivalenzumformung:
b + c1 n + c2 - a4 n ≤ 0 bzw. T(n) ≤ a · n log n + b
P
1 2
1 2
Es fehlt noch: n−1
i=0 i log i ≤ 2 n log n - 8 n
P
2
Es gilt sofort n−1
i=0 i log i ≤ n log n.
Dies reicht aber nicht in der obigen Abschätzung.
Daher:
Pd n2 e−1
Pn−1
Pn−1
i
log
i
=
i
log
i
+
i=1
i=0
d n e−1 i log i
2
[ log (d n2 e − 1) ≤ log ( n2 = logn - log2 = log n - 1, ”≤” weil d n2 e − 1 ≤
wachsend]
Pd n2 e−1
P
≤ (log n - 1) i=1
i + n−1
d n e−1 i
n
2
2
= log n
Pn−1
[Beachte:
≤
1
2
i=1
i-
Pd n2 e−1
i=1
Pd n2 e−1
i=1
i
i = 21 (d n2 e − 1) (d n2 e) ≥ 12 ( n2 -1) ( n2 )]
log n (n(n-1)) -
1
2
( n2 -1) n2
= 12 n2 log n - 21 n log n - 18 n2 + 41 n
≤ 21 n2 log n - 18 n2
↑
falls 14 n - 12 n log n ≤ 0 ⇔ 14 n ≤ n1 n log n ⇔
1
2
≤ log n; gilt für n ≥ 2
Damit ist die erwartete Laufzeit von RQ O(n log n).
72
und log() monoton
4.4
Backtracking
Methode, um den gesamten Lösungsraum nach einer zulässigen oder optimalen Lösung
abzusuchen.
Idee: Stelle den Lösungsraum als Baum dar und durchsuche den Baum systematisch.
Kann man die Lösung in einem Zweig nicht finden, so geht man zurück und durchsucht
einen anderen Zweig.
4.4.1
N-Damen Problem
platziere N Damen auf einem N x N großen Schachbrett, so dass sie sich gegenseitig nicht
schlagen können.
formal: finde N Positionen auf einem (N x N) Schachbrett
{ (i , j)|0 ≤ i ≤ N-1, 0 ≤ j ≤ N-1 }, so dass auf jeder Zeile, Spalte, Diagonalen und
Co-Diagonalen nur eine Position ausgewählt ist.
(i , j) = Position an der i-ten Zeile und j-ten Spalte.
0
0
1
2
3
1
2
X
3
X
X
X
Wir haben N Zeilen und N Spalten.
2N-1 Diagonalen d ∈ { 0,...,2N-2 }
jeweils Paare (i , j) mit i + j = d
0
1
2
3
0
d=0
d=1
d=2
d=3
1
d=1
d=2
d=3
d=4
2
d=2
d=3
d=4
d=5
3
d=3
d=4
d=5
d=6
2N-1 Co-Diagonalen c ∈ { 0,...,2N-2 }
jeweils Paare (i , j) mit j - i + N-1 = c bzw. j = c + i - N+1
0
1
2
3
0
c=3
c=2
c=1
c=0
1
c=4
c=3
c=2
c=1
2
c=5
c=4
c=3
c=2
3
c=6
c=5
c=4
c=3
73
Idee zum Algorithmus:
Beginnend mit Zeile 0 platzieren wir Damen Zeile für Zeile in der Reihenfolge 0,1,...,N-1,
so dass sich keine zwei Damen schlagen können. Ist dies in Zeile i nicht möglich, so gehen
wir zurück zu Zeile i-1 und setzen die Dame dort um.
Innerhalb einer Zeile probieren wir die Positionen von links nach rechts durch.
Beispiel:
Allgemeine Situation:
Bei jedem Knoten des Suchbaums sind Damen bereits konfliktfrei in den Zeilen 0,...,i-1
plaziert und diese Plazierung soll (falls möglich) vervollständigt werden.
74
Speicherung einer aktuellen Plazierung:
static int[] rowplace = new int[N];
//rowplace[i] = j, j ∈ { 0,...,N-1 }
//d.h. in Zeile i steht eine Dame auf Position (i,j)
//rowplace[i] = N falls keine Dame un Zeile i
static boolean[] col = new boolean[N];
//col[j] = true, falls Spalte j eine Dame hat
//col[j] = false, falls Spalte j keine Dame hat
static boolean[] diag = new boolean[2N-1];
//diag[d] = true, falls Diagonale d eine Dame hat
//diag[d] = false, falls Diagonale d keine Dame hat
static boolean[] codiag = new boolean[2N-1];
//codiag[c] = true, falls Co-Diagonale c eine Dame hat
//codiag[c] = false, falls Co-Diagonale c keine Dame hat
Einige Methoden zum Platzieren, Löschen von Damen:
stativ void placeQueen(int i, int j){
rowplace[i] = j;
col[j] = true;
diag[i+j] = true;
codiag[j-i+N-1] = true;
}
public static void moveQueen(int i, int j){
rowplace[i] = N;
col[j] = false;
diag[i+j] = false;
codiag[j-i+N-1] = false;
}
Methode zum testen, ob eine Dame plazierbar ist:
static boolean safe(int i, int j){
return
((rowplace[i] == N) && (!col[j]) && (!diag[i+j]) && (!codiag[j-i+N-1]));
}
75
Backtracking-Algorithmus:
static boolean complete(int i){
if (i == N) return true;
else {
boolean success = false;
int j = 0;
while ((j < N) && (!success)){
if (safe(i,j)){
placeQueen(i,j);
success = complete(i+1);
if (!success)
removeQueen(i,j);
}
j++;
}
return success;
}
}
5
Weitere Ergebnisse zum Sortieren
5.1
Heapsort
Laufzeit: O(n log n)
Heap: Datenstruktur, die als vollständiger Binärbaum aufgefasst werden kann:
1. jeder Knoten im Baum enthält ein Objekt
2. abgesehen von der untersten Ebene sind alle Ebenen vollständig gefüllt
3. auf der untersten Ebene sind die Knoten von links nach rechst bis zu einem Punkt
vollständig vorhanden
76
Binärbaum: Struktur T = (V,E), wobei V Knotenmenge und E Kantenmenge, die gemäß
einer ”Vater-Kind”-Relation verbunden sind.
1. T hat einen ausgezeichneten Knoten r ∈ V (root, Wurzel)
2. jeder Knoten v ∈ V hat maximal zwei Kinder (rechtes bzw. linkes Kind)
3. für jeden Knoten v ∈ V gibt es einen Pfad von der Wurzel r zu v
Begriffe:
(a) für Knoten v ∈ V ist Vater(v) der unmittelbar nächste Knoten auf Pfad zur Wurzel
(Vater(8) = 3)
(b) Knoten w ist Kind von Knoten v, wenn v Vater von w ist
(c) Blatt ist ein Knoten ohne Kinder (z.B. Knoten 7)
(d) Höhe eines Knoten ist die maximale Anzahl von Kanten auf einem Pfad (im induzierten Teilgraphen) von dem Knoten zu einem Blatt
• Höhe(7) = 0
• Höhe(3) = 1
• Höhe(1) = 2
(e) Höhe eines Baums ist die Höhe der Wurzel
77
Ein Heap hat die Heapeigenschaft, wenn ∀ Knoten v 6= r gilt:
Element in Vater(v) ≥ Element in v
(⇒ größtes Element steht an der Wurzel!)
Heap mit Heapeigenschaft:
Speicherung eines Heaps in einem Array A:
Also: für i ∈ { 0,...,n-1 } gilt:
left(i) = 2i + 1
right(i) = 2i + 2
vater(i) = b i−1
c (i 6= 0)
2
Speichere nun Knoten (bzw. deren Elemente) von oben nach unten Ebene für Ebene ab:
Wert 16
Index 0
14
1
10
2
8
3
7
4
9
5
3
6
2
7
4
8
1
9
78
Bemerkung: Ein Heap mit n Elementen hat Höhe blog(n)c.
Beweis: sei H ein Heap der Höhe h.
⇒ H enthält mindestens |1 + 2 + 4 +{z
... + 2h−1 + 1} = 2h Elemente.
Schichten 0 bis h - 1
Außerdem enthält H höchstens 1 + 2 + 4 + ... + 2h−1 + 2h = 2h+1 - 1 Elemente.
Gesucht ist zu gegebener Anzahl von n Elementen ein h mit 2h ≤ n ≤ 2h+1 - 1
||log...
⇒ h ≤ logn und h+1 ≥ log(n+1)
⇒ log(n)-1 < log(n+1)-1 ≤ h ≤ log(n)
⇒ h = blog(n)c
Erstellen der Heap-Eigenschaft
heapify(A,i,size), wobei A = Array, i = int und size = Größe des Heaps gegeben.
• Teilbäume mit Wurzel, left(i) und right(i) erfüllen Heapeigenschaft
• Knoten i verletzt Heapeigenschaft, also
A[i] < A[left[i]] und/oder
A[i] < A[right[i]]
Gesucht: ungeordneter Teilbaum mit Wurzel i, der die Heapeigenschaft erfüllt.
Idee: Vertausche rekursiv Elemente mit dem größeren der beiden Kinder.
79
Beispiel:
Funktionsweise von heapify
static void heapify(int[] A, int i, int size){
int l = 2i + 1;
int r = 2i + 2;
int largest = i;
if ((l < size) && (A[i] < A[l]))
largest = l;
if ((r < size) && (A[largest] < A[r]))
largest = r;
if (largest != i) {
//vertausche A[i] und A[largest]
heapify(A,largest,size);
}
}
80
Laufzeitanalyse:
heapify(A,i,size) benötigt
O(hi ), wobei hi die Höhe des Knotens i ist.
O(log ni ) wobei ni die Anzahl der Knoten im Teilbaum mit Wurzel i
Schema von heapify
Idee:
• baue den Heap bottom-up auf
• starte bei Knoten i = b n2 c-1
(Vater(n-1) = b (n−1)−1
c = b n2 − 1c = b n2 c − 1)
2
• durchlaufe den Baum von unten nach oben pro Ebene von rechts nach links
(d.h. vermindere i jeweils um 1) und rufe heapify auf.
static void buildHeap(int[] A){
int n = A.length;
for(int i = n/2 - 1;i >= 0;i- -)
heapify(A,i,n);
}
81
Beispiel:
Schema eines Heaps (mit Indexmarkierungen)
Anm. d. Autors: Wenn man lediglich heapify anwendet, dann bekommt man zwar eine gewisse Ordnung in den
Heap, allerdings treten auch wieder Elemente auf, die die Heapeigenschaften nicht erfüllen (in diesem Beispiel im
vorletzten Schritt z. B. die 4, deren Unterbaum die Heapeigenschaft nicht erfüllt. In diesem Fall muss man den
Baum nochmals untersuchen. Allerdings hat sie (die heutige Vorlesung wurde nicht von Prof. Jansen gehalten)
sich diesbezüglich nicht so ganz deutlich geäußert.
82
Laufzeitanalyse: (von buildHeap)
Der Heap hat insgesamt die Höhe blog nc.
Wir haben höchstens
1 Knoten mit der Höhe blog nc
2 Knoten mit der Höhe blog nc-1
4 Knoten mit der Höhe blog nc-2
..
.
d n2 e Knoten mit der Höhe 1
Für jeden Knoten mit Höhe h ist der Aufwand von heapify O(h) bzw. c̄ · h
(wobei c̄ Konstante ist)
P nc a
⇒ T(n) ≤ blog
2 · c̄· blog nc − a
a=0
| {z }
Höhe d. Teilbaums
Pblog nc
2a · (blog nc − a)
P nc
i·
=Indexmodifikation c̄ blog
i=0
= c̄
a=0
2| blog{znc−i}
log n
2blog nc
≤ 2 i = ni
2i
2
2
= c̄ ·
≤ c̄
n
2
= c̄ ·
1
n
2
Pblog nc
i
2i−1
i=1
P∞
i
i=1 2i−1
n
2
Pblog nc
i=1
i 12
⇒x= 2 T(n) ≤ c̄
5.1.1
i−1
n
1
2 (1− 12 )2
|| f(x) =
P∞
i=0
xi =
1
1−x
für |x| < 1 , f 0 (x) =
P∞
i=1
ixi−1 =
1
(1−x)2
= 2c̄n ∈ O(n)
Heapsort-Algorithmus
Idee:
• baue sortierte Folge von hinten nach vorne auf.
• Vertausche wiederholt die Wurzel des Heaps mit dem letzten Element des Heaps
• rufe heapify auf um jeweils die Heapeigenschaft widerherzustellen
83
Heapsort-Algorithmus:
public void heapsort(int[] A) {
int size = A.length;
buildHeap(A); ← O(n)
for(int i = size-1; i > 0; i–){
vertausche A[0] und A[i] ;
size–;
heapify(A,0,size);← O(log n)
}
}
Laufzeit: O(n log n)
84
5.1.2
Prioritäts-Warteschlange
Datenstruktur, die eine Menge S verwaltet und die folgenden Operationen unterstützt:
• Insert(S,x): fügt Element x in S ein
• Maximum(S): gibt größtes Element aus
• ExtractMax(S): entfernt größtes Element und gibt es aus
Ein Heap kann eine Prioritäts-Warteschlange (priority queue) effizient realisieren.
Insert(S,x):
Füge neues Element x auf unterster Ebene ein (am Ende des Arrays). Vertausche dann
neues Element mit dem Vater bis ein Vaterknoten ≥ dem neuen Element ist
Untere Schranken
Satz: Ein Algorithmus , der n Elemente mit Hilfe von Vergleichen sortiert, hat eine Laufzeit
von Ω(n log n).
Beweis: Wir betrachten Eingaben, in denen alle Zahlen verschieden sind und o.B.d.A.
konzentrieren wir uns auf die Operation < . Der Algorithmus bekommt Eingabezahlen
x1 ,...,xn und entscheidet welche Permutation der Zahlen die richtige Sortierung liefert.
Insgesammt gibt es n! Permutationen.
Betrachte einen Entscheidungsbaum
- mit n! Permutationen an der Wurzel
- durch einen Vergleich ”xi < xj ” werden zwei Kinder erzeugt:
* linkes Kind enthält alle Permutationen, die konsistent mit xi < xj sind
* rechtes Kind enthält alle Permutationen, die konsistent mit xi > xj sind
85
Schema eines Entscheidungsbaums
Diesen Schritt wiederholt man in jedem Knoten, bis wir zu Knoten mit jeweils nur einer
Permutation kommen.
Bemerkung:
(a) Ein Pfad im Baum von der Wurzel aus entspricht einer Folge von Vergleichen, die
der Algorithmus ausführt
(b) enthält ein Knoten ≥ zwei Permutationen, so ist die richtige Permutation bzw. Sortierung noch nicht eindeutig festgelegt
Beispiel für einen Entscheidungsbaum
Behauptung: Jeder Entscheidungsbaum enthält mindestens einen Pfad mit ≥
n
Kanten (für alle n ≥ 4).
2
86
n
2
log n -
Beweis:
1. ein Binärbaum, in dem alle Pfade ≤ k Kanten besitzen, hat höchstens 2k Blätter.
Beispiel: k = 3:
23 = 8 Blätter
Es gilt also 2k ≥ n!, wobei k die maximale Pfadlänge ist (alle Permutationen müssen
auf die Blätter verteilt werden)
n
⇒ 2k ≥ n! ≥ n(n-1)(n-2)...(n- n2 ) ≥ ( n2 2 )
n
⇒ k ≥ log( n2 ) 2 =
n
2
log( n2 ) =
n
2
log(n) -
n
2
log(2)
| {z }
=1
=
n
2
log(n) -
n
2
2. Es gilt nun:
n
log(n) - n2 ≥ n4 log(n) ∀n ≥ 4,
2
weil n4 log(n) ≥ n2 ⇔ log(n) ≥ 2 ⇔ n ≥ 4
5.2
Sortieren von natürlichen Zahlen
gegeben: n Zahlen von x0 ,...,xn−1 ∈ { 0,...,n-1 }
(die Zahlen seien anfangs im Array A[0],...,A[n-1] gespeichert.
87
5.2.1
Bucketsort
Verwende ein Array von m Buckets B[0],...,B[n-1]. Jeder Bucket enthält eine Liste
von Elementen, die anfangs leer ist. Jeder Bucket enthält einen Verweis auf das erste und
letzte Element der Liste:
Bucket-Schema
Algorithmus (Pseudo-Code)
for(int i = 0;i <= n-1; i++){
füge A[i] am Ende der Liste in Bucket B[A[i]] ein;
for(int j = 1, j <= n-1; j++)
hänge Liste in B[j] am Ende von B[0] an;
Am Ende steht die sortierte Liste in B[0].
Laufzeit: O(n + m)
Bemerkung: Ist m ==(n), so ist die Gesamtlaufzeit O(n).
5.3
Radixsort
Sortiere n natürliche Zahlen, die aus jeweils d Ziffern bestehen und jede Ziffer einen Wert
aus { 0,1,...,k-1 } hat.
Die Ziffernpositionen seien von rechts nach links durchnummeriert.
Position
d
d-1 ... 2
Ziffern
ad
ad−1 ... a2
Werte k d−1 k d−2 ... k 1
P
⇒ di=1 ai · k i−1
= a1 k 0 + a2 k 1 + ... + ad k d−1
1
a1
k0
88
Pseudo-Code:
for (int l = 1; l <= d; l++){
sortiere die n Zahlen gemäß Ziffer l mit Bucketsort ;
Dezimalzahlen k = 10
025
329
004
839
720
355
155
720
004
025
355
155
329
839
—↑
004
720
025
329
839
255
155
-↑-
004
025
155
329
355
720
839
↑—
Laufzeit: O(d · (n + k))
Bemerkung: Ist d = O(1) und k = O(n), so ist die Gesamtlaufzeit O(n).
Korrektheit des Algorithmus:
Beh.: nach l Iterationen, 1 ≤ l ≤ d, sind die n Zahlen gemäß den letzten l Ziffern
sortiert
Bew.: √
per induktion
l=1
l-1 y l: Seien x,y zwei beliebige Zahlen aus der zu sortirenden Sequenz. O.B.d.A. sei
x auf den letzten l Ziffern kleiner als y.
Zeige: x steht vor y nach l Iterationen
Es sei xk , yk die k-te Ziffer von x bzw. y.
(1) xl ≤ yl → x landet im Bucket mit kleinerem Index als y
(2) xl = yl
→ x ist auf den letzten l-1 Ziffern kleiner als y
→IV x steht vor y nach l-1 Iterationen.
x,y gelangen in demselben Bucket in Iteration l.
x steht aber vor y, weil y später eingefügt wird
89
√
Herunterladen