Hilfsunterlagen zu Datenstrukturen und Algorithmen 708.031/032

Werbung
Hilfsunterlagen zu
Datenstrukturen und Algorithmen
708.031/032
Helmut Hauser, Jan.2006
1
Inhaltsverzeichnis
1 Einführung
5
1.1
Ein erstes Beispiel
. . . . . . . . . . . . . . . . . . . . . . . . . .
1.2
Analyse der Laufzeit . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.2.1
Bester Fall (best case) . . . . . . . . . . . . . . . . . . . .
7
1.2.2
Schlechtester Fall (worst case) . . . . . . . . . . . . . . . .
7
1.2.3
Durchschnittlicher Fall (averaged case) . . . . . . . . . . .
7
2 Asymptotische Schranken
5
8
2.1
O-Notation
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.2
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.3
Ω-Notation
Θ-Notation
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.4
Rechen-Regeln für die O-Notation . . . . . . . . . . . . . . . . .
9
2.5
Häug vorkommende Funktionen . . . . . . . . . . . . . . . . . .
10
2.6
Anmerkung zum Logarithmus . . . . . . . . . . . . . . . . . . . .
10
3 Sortieren durch Verschmelzen - Mergsort
11
4 Elementare Datenstrukturen
13
4.1
Lineares Feld
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
4.2
Lineare Liste
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
4.3
Stapel (Stack) . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
4.4
Schlange (Queue) . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
5 Rekursionen
5.1
5.2
5.3
Erstes Beispiel
15
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1.1
Iterative Version
5.1.2
Rekursive Version
15
. . . . . . . . . . . . . . . . . . . . . . .
15
. . . . . . . . . . . . . . . . . . . . . .
15
Methoden zur Lösung von Rekursionsgleichungen . . . . . . . . .
16
5.2.1
Lösen durch Einsetzen . . . . . . . . . . . . . . . . . . . .
16
5.2.2
Lösen durch Induktion . . . . . . . . . . . . . . . . . . . .
16
Zweites Beispiel - Fibonacci-Zahlen . . . . . . . . . . . . . . . . .
16
5.3.1
Iterative Version
. . . . . . . . . . . . . . . . . . . . . . .
17
5.3.2
Rekursive Version
. . . . . . . . . . . . . . . . . . . . . .
17
5.4
Die Ackermann-Funktion
. . . . . . . . . . . . . . . . . . . . . .
17
5.5
Türme von Hanoi . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
6 Halde (Heap)
19
6.1
Verhalden (Heapify)
. . . . . . . . . . . . . . . . . . . . . . . . .
6.2
Aufbau einer Halde . . . . . . . . . . . . . . . . . . . . . . . . . .
20
6.3
Sortieren mit Halden (Heap Sort) . . . . . . . . . . . . . . . . . .
20
6.4
Wartschlange (Priority Queue)
21
2
. . . . . . . . . . . . . . . . . . .
20
7 Quicksort
22
7.1
Partition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
7.2
Quicksort
22
7.3
Randomisierter Quicksort
. . . . . . . . . . . . . . . . . . . . . .
23
7.4
Quicksort (iterativ) . . . . . . . . . . . . . . . . . . . . . . . . . .
23
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8 Untere Schranke für vergleichende Sortierverfahren
8.1
Herleiten der unteren Schranke
8.2
worst-case optimal
Ω (n log n)
24
. . . . . . . . . . . . .
24
. . . . . . . . . . . . . . . . . . . . . . . . . .
25
9 Sortieren in linearer Zeit
26
9.1
RadixSort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
9.2
Weitere lineare Sortieralgorithmen
26
. . . . . . . . . . . . . . . . .
10 Gestreute Speicherung (Hashing)
27
10.1 Grundidee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
10.2 Gute Hashfunktionen . . . . . . . . . . . . . . . . . . . . . . . . .
27
10.2.1 Divisionsmethode . . . . . . . . . . . . . . . . . . . . . . .
27
10.2.2 Multiplikationsmethode
27
. . . . . . . . . . . . . . . . . . .
10.3 Behandlung von Kollisionen . . . . . . . . . . . . . . . . . . . . .
27
10.3.1 Kollisionsbehandlung mit Überlauferliste (Chaining) . . .
28
10.3.2 Oene Adressierung
28
. . . . . . . . . . . . . . . . . . . . .
11 Suchen in linearen Feldern
31
11.1 Ohne Vorsortierung . . . . . . . . . . . . . . . . . . . . . . . . . .
11.1.1 Sequentielle Suche
. . . . . . . . . . . . . . . . . . . . . .
31
31
11.1.2 Sequentielle Suche bei bestimmter Wahrscheinlichkeitsverteilung 31
11.2 Mit Vorsortierung
. . . . . . . . . . . . . . . . . . . . . . . . . .
11.2.1 Binärsuche (Binary Bisection Search)
. . . . . . . . . . .
31
32
11.2.2 Interpolationssuche . . . . . . . . . . . . . . . . . . . . . .
32
11.2.3 Fastsearch . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
11.2.4 Zusammenfassung
34
. . . . . . . . . . . . . . . . . . . . . .
12 Binärbäume
35
12.1 Grundlagen - Denitionen . . . . . . . . . . . . . . . . . . . . . .
12.2 Nötige Funktionen
. . . . . . . . . . . . . . . . . . . . . . . . . .
12.3 Reihenfolge der Knoten
35
35
. . . . . . . . . . . . . . . . . . . . . . .
36
12.3.1 Symmetrische Reihenfolge (SR) . . . . . . . . . . . . . . .
36
12.3.2 Haupreihenfolge (HR)
. . . . . . . . . . . . . . . . . . . .
36
12.3.3 Nebenreihenfolge (NR) . . . . . . . . . . . . . . . . . . . .
36
12.4 Sortierte Binärbäume
. . . . . . . . . . . . . . . . . . . . . . . .
12.4.1 Suchen in Binärbäumen
. . . . . . . . . . . . . . . . . . .
12.4.2 Finden des Maximus in einen Binärbaum
12.4.3 Finden des Minimums in einen Binärbaum
36
36
. . . . . . . . .
37
. . . . . . . .
37
12.4.4 Finden des Vorgängers eines Knotens
. . . . . . . . . . .
37
12.4.5 Finden des Nachfolger eines Knotens
. . . . . . . . . . .
38
3
12.4.6 Einfügen in den Binärbaum . . . . . . . . . . . . . . . . .
38
12.4.7 Löschen eines Werts
38
. . . . . . . . . . . . . . . . . . . . .
13 (2-4)-Bäume
40
13.1 Implementierbare Funktionen . . . . . . . . . . . . . . . . . . . .
40
13.1.1 Suchen in (2-4)-Bäumen . . . . . . . . . . . . . . . . . . .
40
13.1.2 Einfügen in (2-4)-Bäumen . . . . . . . . . . . . . . . . . .
41
. . . . . . . . . . . . . . . . .
41
13.2 Beweis der logarithmischen Höhe . . . . . . . . . . . . . . . . . .
13.1.3 Entfernen in (2-4)-Bäumen
42
. . . . . . . . . . . . . .
42
13.4 Sortieren mit (2-4)-Bäumen . . . . . . . . . . . . . . . . . . . . .
13.3 Anwendung: Mischbare Warteschlangen
43
13.4.1 Analyse des Sortierens mit (2,4)-Bäumen
. . . . . . . . .
14 Amortisierte Kosten bei (2,4)-Bäumen
45
14.1 Denitionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14.1.1 minimaler, maximaler Kontostand
14.1.2 Einfügen, Entfernen
14.1.3 Spalten
43
b(T )
45
. . . . . . . . . .
46
. . . . . . . . . . . . . . . . . . . . .
46
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
14.1.4 Stehlen und Verschmelzen . . . . . . . . . . . . . . . . . .
46
14.2 Abrechnung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
15 Optimales Kodieren
48
15.1 Nötige Denitionen . . . . . . . . . . . . . . . . . . . . . . . . . .
48
15.2 Darstellung des Kodierers
49
. . . . . . . . . . . . . . . . . . . . . .
15.3 Shannon-Fano . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49
15.4 Human
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
15.4.1 Implementierung . . . . . . . . . . . . . . . . . . . . . . .
50
4
1
Einführung
1.1
Ein erstes Beispiel
Eine ungeordnete Folge von Zahlen soll aufsteigend sortiert werden. Ein erster
intuitiver Ansatz wäre, von links nach rechts gehend, jede einzelne Zahl so weit
nach links zu verschieben, bis sie am richtigen Platz angelangt ist.
Die verbale Formulierung kann in Form eines Pseudocodes ausgedrückt werden. Ein Pseudocode ist eine an höhere Programmiersprachen angelehnte Formulierung. Der Algorithmus wird in der Fachliteratur
durch Einfügen ) genannt.
Insertion-Sort (Sortieren
INSERTION_SORT (A)
1: FOR i=2 TO n DO
2:
h=A[i]
3:
j=i-1
4:
WHILE A[j]>h AND j>0 DO
5:
A[j+1]=A[j]
6:
j=j-1
7:
A[j+1]=h
1.2
Analyse der Laufzeit
Wir suchen nun die Abhängigkeit der Laufzeit
der zu sortierenden Zahlen)
n.
T
von der Inputgröÿe (Anzahl
Im obigen Zahlenbeispiel ist
n = 6.
Die Grundidee ist einfach die einzelnen Schritte zu zählen, sie mit konstanten
Zeitfaktoren
ci
(als Platzhalter für die Zeit die sie benötigen) zu versehen und
sie in Abhängigkeit von der Inputgröÿe
n
können aber dann immer als konstant angesehen werden.
5
ci
sind
unterschiedlich.
Sie
darzustellen. Die Zeitfaktoren
je nach Implentierung (Datenstruktur, Hardware, etc.)
Pseudocode
Insertion_Sort (A)
Zeitkonstante
Anzahl der Wiederholungen
c0
c1
c2
c3
c4
c5
c6
c7
1
1: FOR i=2 TO n DO
2:
h=A[i]
3:
j=i-1
4:
WHILE A[j]>h AND j>0 DO
5:
A[j+1]=A[j]
6:
j=j-1
7:
A[j+1]=h
(n − 1)
(n − 1)
(n − 1)
P
n
Pn i=2 ti
(ti − 1)
Pi=2
n
i=2 (ti − 1)
n−1
Bemerkungen: Die Zeit um den Algorithmus aufzurufen wird als unabhängig von
der Inputgröÿe angenommen.
In den zukünftigen Betrachtungen wird sie de-
shalb nie berücksichtigt. Als optisches Kennzeichen trägt der Algorithmuskopf
daher auch keine eigene Zeilennummer.
Weitere Bemerkungen zu den einzelnen Zeilen:
1. In der Schleife werden die einzelnen Elemente abgearbeitet. Pro Schleife
i
wird geschaut, ob der Index
wird
i
schon
n
um eins erhöht. Benötigte Zeit:
erreicht hat oder nicht. Falls nicht
c1
2. Die Zuweisung benötigt eine konstante Zeit
c2 .
3. Ebenfalls eine Zuweisung mit einer Subtraktion:
4. Die Konstante
c4
c3
beinhaltet die beiden Vergleiche als auch deren logische
Verknüpfung. Wenn man sich ein Zahlenbeispiel genauer anschaut, sieht
man, das es von der Verteilung der Zahlen abhängig ist, wie oft man die
While-Schleife durchlaufen und damit die Abfrage durchführen muss. Man
deniert eine allgemeine Variable ti :
ti
ist die Anzahl der while-Abfragen,
1
die der Algorithmus für das i-te Element des Inputs machen muss. .
5. ,6. und 7. sind ebenfalls Zuweisungen:
c5 , c6
und
c7
Wenn man nun einfach alle Zeiten zusammenzählt, erhält man die Laufzeit
in Abhängigkeit der Inputgröÿe
T
n:
n
n
n
X
X
X
T (n) = c1 (n−1)+c2 (n−1)+c3 (n−1)+c4 ·
ti +c5 · (ti −1)+c6 · (ti −1)+c7 (n−1)
i=2
Oensichtlich hängt die Laufzeit
Zeitkonstanten
Folge
A
ci
T
i=2
nicht nur von der Inputgröÿe
n
und den
ab, sondern auch davon wie die Zahlen in der ungeordneten
vorliegen (= Einuss von
ti ).
Dadurch kann man drei wichtige Fälle
beste Fall, der schlechteste Fall
Fall (best, worst and averaged case ).
unterscheiden:
i=2
der
und der
durchschnittliche
1 Bemerkung: Man könnte auch eine Variable w denieren, welche zeigt wie oft die Schleife
i
für das i-te Element durchlaufen werden muss. Der Zusammenhang wäre dann wi = ti − 1
6
1.2.1 Bester Fall (best case)
Der beste (schnellste) Fall liegt vor, wenn der Algorithmus
nie
in die while-
A[j] > h and j > 0 in
Zeile 5 wird jeweils nur einmal pro Element durchgeführt: ti = 1, ∀i = 2, 3..., n.
Pn
Pn
Mit
i=2 ti =
i=2 1 = (n − 1) und c1 + c2 + c3 + c4 + c7 = cGesamt ergibt sich
Schleife hineingehen muss. Das heiÿt, die logische Abfrage
für die Laufzeit:
T (n) = (n − 1) · (c1 + c2 + c3 + c4 + c7 ) = (n − 1) · cGesamt = n · cGesamt − cGesamt
Das heiÿt, die Laufzeit nimmt
linear mit der Inputgröÿe zu.
1.2.2 Schlechtester Fall (worst case)
i-te
ti = i
Der schlechteste (langsamste) Fall liegt vor, wenn der Algorithmus das
Elemente um
∀i = 2, 3, ..n.
i−1
2
Plätze nach vorne verschieben muss .
Das heiÿt
Die Laufzeit beträgt:
n
n
n
X
X
X
T (n) = c1 (n−1)+c2 (n−1)+c3 (n−1)+c4 ·
i+c5 · (i−1)+c6 · (i−1)+c7 (n−1)
i=2
Mit
Pn
i=2
i=
n·(n+1)
2
−1
und
Pn
i=2 (i − 1)
=
i=2
i=2
n·(n−1)
erhält man folgende Form:
2
T (n) = cquad n2 + clin n + crest
Das heiÿt, die Laufzeit nimmt
quadratisch mit der Inputgröÿe zu.
1.2.3 Durchschnittlicher Fall (averaged case)
Alle
n
Zahlen sind zufällig verteilt (gleichtverteilt).
Im Durchschnittt ist die
A[1..i − 1] gröÿer als das i-te Element. Das heiÿt ti ≈ 2i .
Pn
Pn i
n·(n+1)
1
1
Mit
erhält man wieder eine quadratische
i=2 (i) = − 2 +
i=2 ( 2 ) = 2
4
Hälft der Elemente in
Abhängigkeit der Laufzeit von der Inputgröÿe.
2 Die
Eingabefolge ist umgekehrt sortiert!
7
2
Asymptotische Schranken
Sowohl die Laufzeit
T (n)
asymptotische Schranken
ci ,
Die Konstanten
als auch der Speicherbedarf
S(n)
werden meist durch
angegeben.
welche in der Einführung deniert wurden, sind direkt von
der Implementation (Taktfrequenz, Datenstruktur, Hardware allgemein,....) abhängig.
Man will aber ein davon unabhängiges Kriterium nden, um Algorithmen von
3
der Implementation unabhängig zu vergleichen .
Die Grundidee ist, dass bei einem immer gröÿer werdenden Input
n
die Kon-
stanten vernachlässigbar werden. Weiters können Terme mit niederer Ordnung
als verschwindend angesehen werden.
T (n) = c1 n3 + c2 n2 + c3 n + c4 kann man,
Konstanten c1 , c2 und c3 als auch die Terme
Zum Beispiel mit einer Laufzeit von
bei sehr groÿem
c2 n2 + c3 n + c4
n
, sowohl die
vernachlässigen.
Wenn man das ganze in mathematische Denitionen verfasst, erhält man die
sogenannte O-,
2.1
Θ-
und
Ω-Notation.
O-Notation
f (n), g(n) : N → R+
f (n) = O (g(n)) ⇔ c ∈ R, c > 0, n0 ∈ N : f (n) ≤ c · g(n)
3 Deshalb
∀n ≥ n0
(Ohh)
benützt man auch einen Pseudocode, um den Algorithmus zu analysieren.
8
2.2
Ω-Notation
f (n), g(n) : N → R+
f (n) = Ω (g(n)) ⇔ c ∈ R, c > 0, n0 ∈ N : f (n) ≥ c · g(n)
2.3
∀n ≥ n0
(Omega)
Θ-Notation
f (n), g(n) : N → R+
f (n) = Θ (g(n)) ⇔ c1 , c2 ∈ R, c1 , c2 > 0, n0 ∈ N : c1 · g(n) ≤ f (n) ≤ c2 · g(n)
(Theta)
2.4
Rechen-Regeln für die O-Notation
O (f (n)) + O (g(n)) = O (max {f (n), g(n)})
O (f (n)) · O (g(n)) = O (f (n) · g(n))
f (n) = O (g(n)) ∧ g(n) = O (h(n)) ⇒ f (n) = O (h(n))
9
∀n ≥ n0
O (f (n)) − O (f (n)) = O (f (n))
2.5
Häug vorkommende Funktionen
Name
O-Notation
konstant
O(1)
O(log n)
O(nc ),0 < c < 1
O(n)
O(n · log n)
O(nc ), c > 1
O(cn ), c > 1
O(n!)
logarithmisch
Wurzelfunktion
linear
linearlogarithmisch
polynominal
exponential
Fakultät
2.6
Bsp.-Funktion
Bsp.-Algorithmus
3
Addition
ld (n), ln (n)
√
Suchen
1
n, n 3
3n + 4
2n · ld(n)
4n2 + 7n + 10
2n , 10n
n!
Primzahltest
Maximum nden
Sortieren
Matrizenoperationen
NP-vollständige Alg.
Anmerkung zum Logarithmus
Es kommt sehr häug vor, dass bei der Laufzeitanalyse der Logarithmus zur
Basis 2 verwendet wird.
liebigen Basis
b
Es lässt sich aber jeder Logarithmus mit einer be-
in einen anderen Logarithmus mit der Basis
b0
folgendermaÿen
umrechnen:
logb0 (x) =
1
· logb (x)
logb (b0 )
Die beiden Versionen unterscheiden sich nur durch einen konstanten Faktor, der
natürlich asymptotisch gesehen vernachlässigbar wird. Das heiÿt:
O(logb0 n).
10
O(logb n) =
3
Sortieren durch Verschmelzen - Mergsort
Das Sortierverfahren arbeitet auf der Basis der
gehend von einem linearen Feld
1.
2.
3.
A
der Gröÿe
divide&conquer -Strategie.
Aus-
n
Teile das Feld in 2 gleich groÿe Hälften
Sortiere die Teilfelder rekursiv mit demselben Verfahren
Verschmelze die sortierten Teilfelder zu einem sortierten Gesamtfeld
Der Pseudocode dazu (wobei die Indizes
i
und
j
jeweils auf die Grenzen des
aktuellen Teilfeldes verweisen):
MERGESORT(A,i,j)
1: IF i<j THEN
2:
k = (i+j)/2
3:
MERGESORT (A,i,k)
4:
MERGESORT (A,k+1,j)
5:
VERSCHMELZE (A,i,k,j)
Der Aufruf erfolgt mit
MERGESORT(A,1,n).
4
Der Verschmelzungsprozess schaut folgendermaÿen aus :
VERSCHMELZE(A,i,k,j)
1: FOR l=i TO k DO B[l]=A[l]
2: FOR r=k+1 TO j DO C[r]=A[r]
3: B[k+1]=∞
4: C[j+1]=∞
5: l=i r=k+1
6: FOR m=i TO j
7:
IF B[l]<C[r] THEN
8:
A[m]=B[l] , l=l+1
9:
ELSE A[m]=C[r] , r=r+1
Der Verschmelzungsprozess besitzt eine Laufzeit von
T (n) = O(j−i+1) = O(n).
MERGESORT : T (n) =
Daraus ergibt sich folgende Rekursionsgleichung für
2T ( n2 ) + O(n),
4∞
deren Lösung
T (n) = O(n · log n)
ist.
wird in der Praxis mit der gröÿten darstellbaren Zahl implementiert.
11
Ein Beispiel mit der Zahlenfolge
< 5, 2, 4, 6, 1, 3 >
baum.
12
ergibt folgenden Rekursions-
4
Elementare Datenstrukturen
4.1
Lineares Feld
Benachbarte Elemente stehen im Speicher direkt nebeneinander.
auf das
i-te
Element erfolgt in konstanter Zeit:
angegeben kann man die Gröÿe
4.2
n
A[i]
in
O(1).
Der Zugri
Falls nicht anders
des Feldes als gegeben annehmen.
Lineare Liste
Benachbarte Elemente sind miteinander verkettet.
Die Datenstruktur eignet
sich besonders, wenn die Länge in vorhinein nicht bekannt ist.
Die Verkettung erfolgt durch Pointer auf die Nachfolger.
Funktionen
NACH(p), VOR(p)
und
WERT(p)
Es existieren die
um auf das nachfolgende, das
vorhergehende Elemente oder den Wert direkt zuzugreifen. Weiters gibt es die
Möglichkeit die Liste doppelt zu verketten.
4.3
Stapel (Stack)
Der Stapel ist ein spezielles lineares Feld, bei dem das Einfügen und Entfernen
nur an der Spitze erlaubt ist.
Dadurch wird die LIFO-Strategie (last-in, rst-out) implementiert. Die Funktionen STAPEL_LEER, PUSH und POP werden verwendet um zu überprüfen,
ob der Stapel leer ist, um ein Element auf den Stapel zu werfen bzw. vom Stapel
S
zu entfernen.
13
STAPEL_LEER(S)
1: IF t=0
2:
RETURN TRUE
3: ELSE FALSE
PUSH (S,x)
1: IF t=n THEN 'overflow'
2: ELSE
3:
t=t+1
4:
S[t]=x
POP (S)
1: IF STACK_LEER THEN 'underflow'
2: ELSE
3:
x=S[t]
4:
t=t-1
Alle Operationen benötigen nur
O(1)
Zeit!
Anwendung: Abarbeiten von Rekursionen, umkehren einer Reihenfolge, Umwandlung von Inx-Ausdrücken in Postx-Ausdrücken,...
4.4
Schlange (Queue)
Implementiert die FIFO-Strategie (rst-in, rst-out). Die Schlange
die Funktionen
PUT
und
Q verwendet
GET, um Werte in die Schlange zu schreiben und sie
wieder auszulesen.
PUT (Q,x)
1: IF anzahl=n THEN 'overflow'
2: ELSE
3:
anzahl=anzahl+1
4:
IF ende<n THEN ende=ende+1
5:
ELSE ende=1
6:
Q[ende]=x
GET (Q)
1: IF anzahl=0 THEN 'underflow'
2: ELSE
3:
anzahl=anzahl-1
4:
x=Q[anfang]
5:
IF anfang<n THEN anfang=anfang+1
6:
ELSE anfang=1
7:
RETURN x
Alle Operationen benötigen
O(1)
Zeit!
14
5
Rekursionen
Viele Algorithmen besitzen sowohl eine iterative als auch eine rekursive Lösung.
Sie unterscheiden sich darin, dass die iterative Version meist einen etwas längeren Kode besitzt, während die rekursive Version mehr Speicher benötigt, um
alle Rekursionsschritte zwischen zu speichern.
5.1
Erstes Beispiel
Der Algorithmus soll die Fakultät
n!
berechnen.
5.1.1 Iterative Version
FAKULTÄT_ITERATIV(n)
1: f=1
2: FOR i=1 to n
3:
f=f*i
4: RETURN f
Die Laufzeit beträgt
T (n) = O(n), da nur eine Schleife durchlaufen werden
O(1) unabhängig von der Inputgröÿe.
muss. Der Speicherbedarf ist mit
5.1.2 Rekursive Version
FAKULTÄT_REKURSIV(n)
1: IF n=1 THEN
2:
RETURN 1
3: ELSE
4:
RETURN (n*FAKULTÄT_REKURSIV(n-1))
In Zeile 1 bendet sich die
Abbruchbedingung
für die Rekursion. Zur Analyse
benötigt man die Laufzeit für den Fall, wenn abgebrochen wird. Hier benötigt
der Algorithmus im Abbruchfall
n=1
eine Laufzeit von
O(1).
Im Normalfall (während der Rekursionen) kann man folgende Rekursionsgleichung aufstellen:
T (n) = O(1) + T (n − 1)
| {z } | {z }
1
2
1. Beinhaltet die konstante Zeit, die die Zeilen 1-3 benötigen
2. Ist die Laufzeit um ein Element verringert, da
FAKULTÄT_REKURSIV(n-1)
aufgerufen wird.
15
5.2
Methoden zur Lösung von Rekursionsgleichungen
5.2.1 Lösen durch Einsetzen
Es wird ein Rekursionsschritt tiefer
T (n − 1) = O(1) + T (n − 2)
in die Orginalgleichung
T (n) = O(1) + T (n − 1)
eingesetzt:
T (n) = O(1) + O(1) + T (n − 2)
Analog erhält man durch weiteres einsetzten eine allgemeine Formel in Abhängigkeit der Rekursionstiefe
k
= O(1) + O(1) + T (n − 2)
T (n)
= O(1) + O(1) + O(1) + T (n − 3)
= ...
= k · O(1) + T (n − k)
Die Rekursion verläuft bis zur Abbruchbedingung
n = 1, dass heiÿt, bis T (1)
k . Mit T (1) = T (n − k)
auftritt. Man will nun eine Lösung unabhängig von
bekommt man
n−k =1⇒k =n−1
T (n)
und setzt in die Gleichung ein:
=
(n − 1) · O(1) + T (n − n + 1)
=
(n − 1) · O(1) + T (1)
= (n − 1) · O(1) + O(1)
T (n) = O(n)
5.2.2 Lösen durch Induktion
Zur Lösen der Rekursionsgleichung durch Induktion muss zuerst eine Lösung
geschätzt und diese Annahme durch Induktion bewiesen werden.
5.3
Zweites Beispiel - Fibonacci-Zahlen
Die Denition der Fibonacci-Zahlen:
fn = fn−1 + fn−2 ; n ≥ 3 ; f1 = 1, f2 = 1
Die ersten Zahlen lauten
1, 1, 2, 3, 5, 8, 13, 21, ...
Die Folge ist monton wachsend und besitzt folgende Schranken:
fn = fn−1 + fn−2 = fn−2 + fn−3 +fn−2
| {z }
n
≥ 2 · fn−2 ⇒ fn = Ω(2 2 )
n
≤ 3 · fn−2 ⇒ fn = O(3 2 )
≤fn−2
Mit den Anfangswerten
2
n−1
2
≤ fn ≤ 3
f1 = f2 = 1 folgt:
√
√
⇔ c1 · ( 2)n ≤ fn ≤ c2 · ( 3)n ⇒ fn = Θ (cn )
n−1
2
16
5.3.1 Iterative Version
Es wird die
n-te
Fibonacci-Zahl gesucht:
FIBONACCI(n)
1: fib = 1
2: fib_1 = 1
3: FOR i = 3 to n
4:
fib_2 = fib_1
5:
fib_1 = fib
6:
fib = fib_1 + fib_2
7: RETURN fib
5.3.2 Rekursive Version
Es wird die
n-te
Fibonacci-Zahl gesucht:
FIBONACCI_R(n)
1: IF n≤2 THEN
2:
RETURN 1
3: ELSE
4:
RETURN (FIBONACCI_R(n-1)+FIBONACCI_R(n-2))
Die Rekursiongleichung lautet T (n) = O(1) + T (n − 1) + T (n − 2) mit T (1) =
O(1). Die Lösung ist Θ(cn ). Der Speicheraufwand beträgt S(n) = O(1) +
max{S(n − 1), S(n − 2)} = S(n).
Die rekursive Version ist einfacher zu implementieren, besitzt jedoch exopentielle
Laufzeit und linearen Speicherbedarf.
5.4
Die Ackermann-Funktion
Es gibt zahlreiche Arten dieser rekursiven Funktion. Zum Beispiel:
ACKER(n,x,y)
1: IF n=0 THEN
2:
RETURN x+1
3: ELSE IF y=0 THEN
4:
IF n=1 THEN RETURN x
5:
IF n=2 THEN RETURN 0
6:
IF n=3 THEN RETURN 1
7:
IF n≥4 THEN RETURN 2
8: RETURN ACKER(n-1, ACKER(n,x,y-1),x)
17
5.5
Türme von Hanoi
Regeln:
•
3 Stäbe A, B und C
•
Am Stab A liegen zu Begin n unterschiedliche groÿe Scheiben
•
Diese sollen am Ende auf Stab B liegen
•
Es darf jeweils nur eine Scheibe bewegt werden
•
Es darf nie eine göÿere auf einer kleineren Scheibe liegen
Zum Beispiel mit 7 Scheiben:
Rekursiver Algorithmus zur Lösung:
HANOI(n,x,y,z)
1: IF n=1 THEN
2:
Lege Scheibe von 'x' nach 'y'
3: ELSE
4:
HANOI(n-1,x,z,y)
5:
HANOI(1,x,y,z)
6:
HANOI(n-1,z,y,x)
Der Aufruf erfolgt zum Beispiel mit
HANOI (7,'A','B','C')
und fordert den Al-
gorithmus auf die 7 obersten Scheiben von A auf B unter Verwendung von C zu
legen.
18
6
Halde (Heap)
Die Halde ist eine lineares Feld, welches die sogenannte
Haldenbedingung:
Haldenbedinung
A[i]≥ max {A[2i], A[2i + 1]}, ∀i = 1, 2, . . . ≈
Direkt aus der Denition ergibt sich, dass
erfüllt:
n
2.
A[1] das Maximum des linearen Feldes
ist. Beispiel einer Halde:
Die Halde lässt sich auch als voller binärer Baum darstellen.
Mit den Funktionen
LINKS(i)
und
RECHTS(i)
kann auf das linke bzw.
rechte Nachfolger des i-ten Elements in der Baumstruktur zugegrien werden.
Mit VATER(i) auf den Vorgänger.
LINKS (i)
1: RETURN 2i
RECHTS(i)
1: RETURN 2i + 1
VATER (i) 1: RETURN 2i
Wichtige Beobachtungen:
•
Das Maximum sitzt in der Wurzel des Baums
•
In jedem Knoten ist die Haldenbedingung erfüllt
•
Die Maximale Tiefe des Baums ist
•
In einem Teilbaum sind alle darunter liegenden Knoten kleiner oder maximal gleich groÿ
19
blog nc
6.1
Verhalden (Heapify)
Verhalden ist der zentrale Prozess für alle Anwendungen der Haldenstruktur.
Mit
n
als die Gröÿe der Halde (Die Länge des linaren Feldes).
VERHALDE (A,i)
1: l=LINKS(i), r=RECHTS(i)
2:
index = i
3:
IF l≤n AND A[l]>A[i] THEN index=l
4:
IF r≤n AND A[r]>A[index] THEN index=r
5:
VERTAUSCHE (A[i], A[index])
6:
VERHALDE (A,index)
Analyse:
ist
Die Rekursiongleichung dazu lautet
T (n) ≤ O(1)+T ( n2 ).
Die Lösung
T (n) = O(log n).
6.2
Aufbau einer Halde
Eine Halde kann aus einem beliebigen linearen Feld
A
aufgebaut werden:
BAUE_HALDE (A)
1: FOR i=n/2 DOWNTO 1
2: VERHALDE (A,i)
Analyse:
n
2 -mal durchgeführt. D.h. T (n) = O(n·log n).
Bei genauerer Betrachtung sieht man aber, dass eben nicht alle Elemente durch
Das Verhalden wird
die ganze Baumhöhe durch verhaldet werden müssen. Die Schranke
ist nicht scharf.
Pbldnc
Pbldnc h
n
)=
h=0 2h O(h) = O(n
h=0
P∞2h h
(Die Summe kann durch eine unendliche Summe
h=0 2h
werden.)
Ein genauerer Ansatz ist
O(n)
= 2
O(n · log n)
abgeschätzt
Damit kann eine Halde in linearer Zeit aus einem linearen Feld gebaut werden.
6.3
Sortieren mit Halden (Heap Sort)
Mit Hilfe des
VERHALDE -Algorithmus kann man auch einen Sortieralgoritmus
konstruieren.
HEAP_SORT(A)
1: FOR i = n DOWNTO 2 DO
2:
VERTAUSCHE (A[1],A[i])
3:
n = n-1
4:
VERHALDE (A,1)
Die Laufzeit beträgt
T (n) = O(n · log n).
20
Der Algorithmus arbeitet
in-place.
6.4
Wartschlange (Priority Queue)
Eine weitere Anwendung dieser Datenstruktur ist die Warteschlange mit Prioritäten: Eine Warteschlange liegt vor, wenn folgende drei Operationen durchgeführt werden können:
•
EINFÜGEN (A,x)
•
MAX (A)
•
ENTFERNE_MAX (A)
Die Funktionen mit Hilfe einer Halde umgesetzt in Pseudocode:
MAXIMUM (A)
1: RETURN A[1]
ENTFERNE_MAX (A)
1: A[1] = A[n]
2: n = n-1
3: VERHALDE (A,1)
EINFÜGEN (A,x)
1: n = n+1 , A[n]=x , i=n
2: WHILE i>1 AND A[i]>A[VATER(i)] DO
3:
VERTAUSCHE (A[i],A[VATER(i)])
4:
i = Vater(i)
Die Laufzeiten betragen für MAXMIMUM, ENTFERNE_MAX und EINFÜGEN respektiv
O(1), O(log n)
und
O(log n).
21
7
Quicksort
Quicksort ist einer der meistverwendesten Sortieralgorithmen. Die Idee beruht
divide&conquer -Prinzip5 .
auf dem
Der Kern des Algorithmus ist die Zerlegung
des Feldes (Partition).
Zerlegen (Partition):
in
A[1..k]
7.1
Speichere das lineare Feld
kleiner als alle Elemente in
A[k + 1..n]
A so um,
Partition
Das Teilfeld ausgehend vom linken (unteren) Index
Index
dass alle Elemente
sind.
r
wird partitioniert.
A
l
bis zum rechten (oberen)
bezeichnet das bearbeitete lineare Feld.
PARTITION (A,l,r)
1: p = A[l]
2: j=l-1 , k=r+1
3: LOOP
4:
REPEAT j=j+1 UNTIL A[j]≥p
5:
REPEAT k=k-1 UNTIL A[k]≤p
6:
IF j<k THEN
7:
VERTAUSCHE (A[j],A[k])
8:
ELSE
9:
RETURN k
Die Laufzeit beträgt
7.2
O(r − l) = O(n).
Quicksort
Auf der Basis von
PARTITION
kann der Sortieralgorithmus
QUICKSORT
kon-
struiert werden.
QUICKSORT (A,l,r)
1: IF l<r THEN
2:
k = PARTITION(A,l,r)
3:
QUICKSORT (A,l,k)
4:
QUICKSORT (A,k+1,r)
Die Rekusionsgleichung dazu ist
T (n) = T (k) + T (n − k) + O(n).
Im besten
Fall (balancierte Aufteilung des Rekursionsbaums) beträgt die Laufzeit
5 Dabei
wird das Problem in kleinere Teilprobleme zerlegt und rekursiv aufgelöst.
22
T (n) =
O(n · log n). Im schlechtesten Fall (das Feld ist aufsteigend sortiert - der Rekur2
sionsbaum ist entartet) beträgt sie T (n) = O(n ).
7.3
Randomisierter Quicksort
Durch eine randomisierte Version von Quicksort kann man eine gute durchschnittliche Performance erreichen.
Das Pivotelement wird dabei zufällig mit
6
einer Gleichverteilung aus allen möglichen Werten ausgewählt . Das heiÿt, die
erste Zeile
p = A[l]
ind = RAN DOM (l, r), p = A[ind] ersetzt.
T (n) = O(n · log n).
2
Fall O(n ) kann immer noch auftreten!
wird durch
Die
erwartete Laufzeit beträgt dann
Bemerkung: Der schlechteste
7.4
Quicksort (iterativ)
Der Algorithmus steigt immer in die
die
gröÿere
Hälfte auf einem Stack
kleinere
S7.
der beiden Hälften ein und stapelt
QUICKSORT_ITERATIV (A)
1: l=1 , r=n
2: PUSH(1,n)
3: REPEAT
4:
IF l<r THEN
5:
k = PARTITION(A,l,r)
6:
IF (k-l) < (r-k) THEN
7:
PUSH(k+1,r)
8:
r = k
9:
ELSE PUSH(l,k)
10:
l = k+1
11:
ELSE POP(l,r)
12: UNTIL STAPEL_LEER(S)
Der Speicherbedarf
mehr
S(n) beträgt im Gegensatz zum rekursiven Algorithmus nur
O(log n).
6 Der worst-case ist mit 1 extrem unwahrscheinlich !
n!
7 PUSH(a,b) entspricht dabei der Funktion PUSH(S,x={a,b}),
welche bei den elementaren
Datenstrukturen deniert worden ist. Analoges gilt für POP(l,k), wobei auch gleichzeitig die
vom Stapel geholten Werte die aktuellen l und k Werte überschreiben.
23
8
Untere Schranke für vergleichende Sortierverfahren
Viele verwendete schnelle Sortierverfahren (z.B.:
besitzen eine Laufzeit von
O(n log n).
MERGESORT, HEAPSORT )
Die Frage, die sich dabei stellt lautet:
Geht es besser?
Man kann beweisen, das jedes Sortierverfahren, das mittels Vergleichen ar-
8
beitet , braucht mindestens
c · n log n
Vergleiche im
worst case (wobei c > 0
und konstant ist).
8.1
Herleiten der unteren Schranke
Ω (n log n)
Der Kontrolluÿ eines vergleichenden Sortierverfahrens kann mittels eines sogenannten
Entscheidungsbaummodells
dargestellt werden. Darin scheinen alle
möglichen und nötigen Entscheidungen auf, um ein sortiertes lineares Feld zu
erhalten. Beispiel: Es liegt eine Sequenz von 3 Zahlen vor
< 5, 8, 2 >).
< a1 , a2 , a3 >(z.B.
Dann schaut der zugehörige Entscheidungsbaum folgendermaÿen
aus:
9
Die Blätter stellen alle möglichen Permutationen dar . Im allgemeinen Fall ist
die Anzahl der Blätter gleich
n!.
Das worst-case Verhalten entspricht dem längsten Ast im Entscheidungsbaum
(Anzahl der inneren Knoten = Anzahl der Vergleiche). Der ideale Sortieralgorithmus enspricht einem vollständigen, da ausgeglichen, Baum. Dadurch wird
der längste Ast ( = worst case) minimiert. Die Höhe beträgt
Hilfe der Stirling-Approximation
h ≥ log
n n
e
n! > ( ne )n
h ≥ log(n!).
erhält man:
= n · log n − n · log e = Ω(n · log n)
8 D.h. beim dem jeweils einzelne Elemente direkt
9 Die Permutation über alle möglichen Inputs.
z.B.:<a1 , a2 , a3 > ,<a1 , a3 , a2 >,<a2 , a1 , a3 > , usw.
24
miteinander verglichen werden.
Mit
Ω(n · log n) ist die untere Schranke für die Anzahl der zum Sortieren notwendigen Vergleiche (und somit für die Laufzeit vergleichsorientierter Sortierverfahren).
8.2
worst-case optimal
Im Zusammenhang der unteren Schranke kann einen Sortieralgorithmus als
worst-case optimal bezeichnen, wenn er für jede Eingabefolge in O(n · log n)
sortiert. Zum Beispiel
MERGESORT
und
mal.
25
HEAPSORT
sind worst-case opti-
9
Sortieren in linearer Zeit
Neben den vergleichenden Sortieralgorithmen existierten auch Verfahren, welche
in linearer Zeit sortieren können. Was sie alle gemein haben ist, dass sie sich
auf Annahmen stützen, welche naturgemäÿ erfüllt sein müssen, damit der Algorithmus funktioniert
9.1
10 .
RadixSort
Annahme: Man sortiert
n
Zahlen, welche mit einer xen Basis
bekannten Anzahl von Ziern (digits)
b=10
und
d =3
lassen sich Zahlen von
d dargestellt werden.
000 bis 999 darstellen.
b
und einer
Zum Beispiel mit
Pseudocode:
RADIXSORT(A)
1: FOR i= d DOWNTO 1
2:
'Ordne nach der i-ten Ziffer in Fächer (Streuphase)'
3:
'Fasse Fächer in aufsteigender Reihenfolge in A
zusammen (Sammelphase)'
Die Laufzeit beträgt
O(d · n).
9.2
d als konstant
T (n) = O(n).
Wenn
werden kann, dann ist die Laufzeit
gegenüber
n
angenommen
Weitere lineare Sortieralgorithmen
Weitere lineare Sortieralgorithmen sind zum Beispiel
Sort.
10 D.h.
CountingSort
und
man besitzt zusätzliche Information über die möglichen Eingabefolgen.
26
Bucket
10
Gestreute Speicherung (Hashing)
Die Datenstrukur Hashtabelle wird zur Lösung des Wörterbuchproblems verwendet. Das Wörterbuchproblem ist durch die drei Funktionen
und
Lösche n deniert.
10.1
Einfügen, Suchen
Grundidee
Die Idee ist direkt aus dem Schlüssel mithilfe einer
Hashfunktion
die Adresse zu
seiner Speicherung zu errechnen.
10.2
Gute Hashfunktionen
Die Hashfunktion
sum
U)
auf
m
h
bildet dabei Werte
w
aus der Menge der Schlüssel (Univer-
verschiedene Indizes der Hashtabelle
T
ab.
h : U → {0, 1, . . . m − 1}
Die ideale Hashfunktion sollte für alle Werte
alle Indizes
j = 0, 1, . . . m − 1
w∈U
eine Gleichverteilung über
11 .
der Hashtabelle haben
10.2.1 Divisionsmethode
m
Dividiere den Wert durch
und nimm den Rest:
h(w) = w mod m
Vorteil: Schnell berechenbar
Nachteil: nicht für alle
m
geeignet.
10.2.2 Multiplikationsmethode
Multipliziere den Wert
w
mit einer xen Konstante
multipliziere den gebrochenen Teil
f rac(.)
A,
0 < A < 1,
m.12
wobei
des Resultats mit
und
h(w) = bm · f rac(w · A)c
Vorteil:
m
10.3
Behandlung von Kollisionen
ist unkritisch
h(w) für zwei verschiedene Werte
h(w) = h(w0 )
Eine Kollision liegt vor, wenn die Hashfunktion
w
und
w0
denselben Index
j
liefert:
11 P = P rob {h(w) = j} = 1
j
m
j
d.h. die Wahrscheinlichkeit, dass ein Wert w genau den Index
1
zugewiesen bekommt ist m
für eine Hashtabelle der Gröÿe m.
12 Anmerkung: f rac(.) ∈[0, 1)
27
10.3.1 Kollisionsbehandlung mit Überlauferliste (Chaining)
Die Idee liegt darin, Werte, die zum gleichen Index führen, als verkette Liste
zu organisieren.
Das heiÿt, dass in der Hash-Tabelle jeweils nur eine Pointer
(Adresse) steht, welcher auf den ersten Datensatz in der Liste zeigt. Die drei
nötigen Funktionen zur Lösung des Wörterbuchproblems können folgendermaÿen
implementiert werden:
Einfügen:
•
w
Der Index
j
wird mithilfe der Hashfunktion aus dem Wert
des Schlüssels berechnet. Falls es zu einer Kollision kommt, wird der
Datensatz an die erste Stelle der Liste eingefügt (Laufzeit
Suchen:
•
Der Index
j
wird mithilfe der Hashfunktion aus dem Wert
des Schlüssels berechnet.
T (n)
O(li ).
sucht. Die Laufzeit
Überläuferliste
Löschen:
•
T (n) = O(1)).
w
Die lokale Liste wird nach dem Wert durchist dabei abhängig von der Länge lj der lokalen
Das Löschen basiert auf dem Suchen. Sobald das Element ge-
funden wurde, werden einfach der davor liegende und der danach liegende
Datensatz dementsprechend verknüpft.
Die Laufzeit beträgt ebenfalls
O(lj ).
Falls die Hashfunktion ideal arbeitet, dann werden sich alle eingefügten Werte
gleichmäÿig über die Hashtabelle verteilen, d.h. alle Überläuferlisten sind gleich
lang. Die Länge ist abhängig von der Anzahl
Gröÿe
m
n
der eingefügten Werte und der
der Hashtabelle. Sie wird durch den Belegungsfaktor
α=
α
ausgedrückt:
n
m
Die Laufzeit zum Suchen eines Elements beträgt im schlimmsten Fall (worst
case)
O(n),
falls alle
Dagegen ist die
n
eingefügten Werte sich in einer Überläuferliste benden.
erwartete Suchzeit
Θ(1 + α),
n
entspricht, wenn man das Verhältnis zwischen
was einer Laufzeit von
und
m
O(1)
als konstant ansehen
kann.
10.3.2 Oene Adressierung
Es werden bei der oenen Adressierung keine Pointer verwendet. Der dadurch
frei gewordene Speicher kann dazu verwendet werden die Hashtabelle gröÿer
zu gestalten.
Die Werte werden dabei direkt in die Hashtabelle gespeichert.
Bei einer Kollision wird der "nächste" freie Platz gesucht.
Dabei wird zuerst
immer mithilfe einer herkömmlichen Hashfunktion eine Index berechnet, um
dann, falls diese Stelle schon besetzt ist (Kollision!), mit weiteren Versuchen
(Probing,
i > 0)
einen freien Platz zu nden. Folgende drei Methoden werden
dazu verwendet, wobei jeweils eine erweiterte Hashfunktion
13 :
h(w, i)
wird
13 i
ist die sogenannte Versuchs- oder Sondierungszahl mit i = 0, 1, 2, . . . m − 1
28
verwendet
•
Linear Probing:
Falls es zu einer Kollision kommt, wird einfach der
danach liegende Wert genommen:
h(w, i) = [h0 (w) + i] mod m
Nachteil: Es kommt zu einem primären Ballungseekt (primary clustering).
•
Quadratic Probing:
Indem man die Sprünge beim Versuch eine freie
Stelle zu nden polynomiell ansteigen lässt, kann man den primären Ballungseekt verhindern.
h(w, i) = [h0 (w) + f (i)] mod m
Wobei
f (i)
zum Beispiel eine quadratische Funktion (f (i)
= c1 i2 + c2 i)
sein kann.
Nachteil: Sobald es zu einer Kollision zwischen Werten
w
und
w0
kommt,
sind auch die nachfolgenden Indizes alle gleich. Das wird der sekundären
Ballungseekt (secondary clustering) genannt.
•
Double Hashing:
Um auch den sekundären Ballungseekt zu verhin-
dern, werden die Sprünge mithilfe einer zweiten Hashfunktion vom Wert
w
abhängig gemacht.
h(w, i) = [h1 (w) + i · h2 (w)] mod m
Der Pseudocode ist für diese drei Methoden analog aufgebaut.
EINFÜGEN(T,w)
1: i=0
2: REPEAT
3:
ind = h(w,i)
4:
IF T[ind] = frei THEN
5:
T[ind] = w
6:
RETURN 'Okay'
7:
i = i+1
8: UNTIL i=m
9: RETURN 'Tabelle voll'
SUCHE(T,w)
1: i=0
2: REPEAT
3:
ind = h(w,i)
4:
IF T[ind] = w THEN RETURN ind
5:
i = i+1
6: UNTIL T[ind]=frei OR i=m
7: RETURN 'nicht gefunden'
29
erwartete Suchaufwand in einer Hash-Tabelle T mit oener Adressierung
1
Θ( 1−α
), falls der gesuchte Wert w nicht in der Tabelle vorhanden ist (w ∈
/ T ).
Im Fall, dass der gesuchte Wert w in der Hash-Tabelle T vorhanden ist (w ∈ T ),
h
i
1
1
beträgt der erwartete Suchaufwand Θ
ln
+
1
.
α
1−α
Der
ist
30
11
Suchen in linearen Feldern
Will man in einer Datenmenge (sie liegt als lineares Feld
A[1..n] vor) nur
suchen,
dann gibt es mehrere Möglichkeiten. Generell kann unterschieden werden, ob
das Feld in
sortierter
oder in
unsortierter
Form vorliegt.
Beim Suchen kann
der schlechteste Fall, dass sich das gesuchte Element gar nicht in den Daten
bendet, relativ oft vorkommen.
11.1
Ohne Vorsortierung
11.1.1 Sequentielle Suche
Erste Idee: Einfach von Anfang bis Ende alles durchsuchen.
SUCHE (A,x)
1: i = 0
2: WHILE i<n
3:
i = i+1
4:
IF A[i]=x THEN RETURN i
5: ELSE RETURN -1
Die Laufzeit
case O(n).
T (n) beträgt im worst case O(n), im best case O(1) und im averaged
11.1.2 Sequentielle Suche bei bestimmter Wahrscheinlichkeitsverteilung
14 bekannt sind, dann kann die
Wenn die Zugriwahrscheinlichkeitsverteilung
Suche schneller gehen. Voraussetzung ist, dass die Werte nach diesen Wahrscheinlichkeiten geordnet sind. Das heiÿt, der am häugst gesuchte Wert steht am Anfang des Feldes, während der am wenigsten oft gesuchte Wert am Ende steht.
Wenn
pi
1 ≤ i ≤ n ist, und wir eine
1
),
dann verläuft die Suche in
2i
1
) erhält man O(n).
n−1
die Zugriswahrscheinlichkeit auf
A[i]
=
für
exponentielle Verteilung vorliegen haben (pi
O(1).
Bei einer Gleichverteilung (pi
11.2
Mit Vorsortierung
=
Für die folgenden Suchalgorithmen gilt jeweils, dass sie mit
Funktionsname(A,1,n)
aufgerufen werden, und als Rückgabewert den Index des zu ndenden Elements
zurückgeben. Falls das Element nicht in der Datenmenge vorhanden ist, wird
−1
retourniert. Weiters geht man davon aus, dass die Werte in aufsteigender
Reihenfolge sortiert sind.
14 Die Zugriswahrscheinlichkeit ist die Wahrscheinlichkeit,
dass auf ein bestimmtes Element
zugegrien wird, bzw. nach einem bestimmten Element gesucht wird.
31
11.2.1 Binärsuche (Binary Bisection Search)
Idee: Teile das Feld in zwei gleich groÿe Hälften. Vergleiche den gesuchten Wert
mit dem mittleren Element. Falls sie gleich sind, dann ist man fertig. Wenn der
gesuchte Wert kleiner ist, dann suche in der unteren Hälfte, sonst in der oberen
Hälfte.
Die rekursive Version:
BINSUCH (von,bis,x)
1: IF von≤bis THEN
2:
m = b(von + bis)/2c
3:
IF x=A[m] THEN
4:
RETURN m
5:
ELSE
6:
IF x<A[m] THEN m=BINSUCH(von,m-1,x)
7:
ELSE THEN m=BINSUCH(m+1,bis,x)
8: ELSE m = -1
Die Laufzeit beträgt
T (n) ≤ O(1) + T ( n2 ) = O(log n).
Zum Beispiel bei einer
Gröÿe von 1 Million Werte, benötigt der Algorithmus im schlechtesten Fall (der
Wert ist nicht enthalten) nur 20 Schritte!
Die iterative Version:
BINSUCH_ITERATIV (von,bis,x)
1: pos = -1
2: REPEAT
3:
m = b(von + bis)/2c
4:
IF x=A[m] THEN pos = m
5:
ELSE
6:
IF x<A[m] THEN bis=m-1
7:
ELSE von=m+1
8: UNTIL (pos 6=-1) OR (von>bis)
9: RETURN pos
11.2.2 Interpolationssuche
Idee: Man sucht nicht jeweils in der Mitte, sondern dort, wo das Element sein
sollte. Die Annahme ist, dass die Werte linear ansteigen. Mit Hilfe der Werte
des ersten
A[1]
und des letzten Elements
Index der gesucht Wert haben sollte.
32
A[n]
wir linear interpoliert, welchen
Die rekursive Version:
INTSUCH(von,bis,x)
1: IF A[von] < A[bis]
THEN
j
k
x−A[von]
2:
t = von + (bis − von) · A[bis]−A[von]
3:
IF x=A[t] THEN RETURN t
4:
ELSE IF x<A[t] THEN
5:
RETURN INTSUCH(von,t-1,x)
6:
ELSE
7:
RETURN INTSUCH (t+1,bis,x)
8:
IF x=A[von] THEN RETURN von
9: ELSE RETURN -1
Die erwartete Suchzeit ist extrem gut:
Zum Beispiel bei
5 !!
232 ≈ 4.3 · 109
O(log log n)
Werten benötigt dieser Algorithmus maximal
Schritte, wenn man eine vernünftige Verteilung annehmen kann.
worst case
wäre eine extrem stark nichtlineare Verteilung: z.B.:
Laufzeit beträgt dann
A[k] = k!.
Der
Die
Θ(n).
11.2.3 Fastsearch
Idee: Man könnte die beiden Vorteile aus Binärsuche und Interpolationssuche
kombinieren. Der neue Algorithmus hat dann
O(log n)
O(log log n) im mittleren Fall und
im schlechtesten Fall.
Die rekursive Version:
FASTSEARCH (von,bis,x)
1: IF von≤bis THEN
2:
mB = b(von +j bis)/2c
k
x−A[von]
3:
mI = von + (bis − von) · A[bis]−A[von]
4:
IF mB >mI THEN VERTAUSCHE (mB ,mI )
5:
IF x = A[mB ] THEN RETURN mB
6:
ELSE IF x = A[mI ] THEN RETURN mI
7:
ELSE IF x<A[mB ] THEN RETURN FASTSEARCH(von,mB -1,x)
8:
ELSE IF x<A[mI ] THEN RETURN FASTSEARCH(mB +1,mI -1,x)
9:
ELSE RETURN FASTSEARCH(mI +1,bis,x)
8: RETURN = -1
33
11.2.4 Zusammenfassung
Laufzeitverhalten der Suchverfahren in sortierten Feldern:
Binärsuche
Interpolationssuche
Fastsearch
Beispiel mit
109
Mittlere Fall
Schlechtester Fall
O(log n)
O(log log n)
O(log log n)
O(log n)
O(n)
O(log n)
15
Werte - Anzahl der Vergleiche:
Mittlere Fall
Binärsuche
Interpolationssuche
Fastsearch
15 Es
Schlechtester Fall
30
30
5
1.000.000.000
10
60
wird der Logarithmus zur Basis 2 zur Berechnung verwendet. Die Werte sind gerundet.
34
12
12.1
Binärbäume
Grundlagen - Denitionen
Denition:
Ein
Baum
ist eine Menge, die durch eine sog. Nachfolgerrelation
strukturiert ist.
In einem Baum gilt:
1
w
ohne VATER(w ), das ist die Wurzel
(I)
∃
(II)
∀ Knoten k 6= w ∃ Knotenfolge k0 , k1 , . . . , kt mit k0 = k , kt = w und
ki =VATER(ki−1 ) für i = 1, 2, ..., t (Ast zwischen k und w, Länge t)
Knoten
1
Ein Binärbaum ist ein spezieller Baum, in dem jeder Knoten maximal zwei
Söhne besitzt.
12.2
Nötige Funktionen
Ausgehend von einem Pointer
k auf einen beliebigen Knoten sind folgende FunkB implementiert werden kann:
tionen sind nötig, damit eine Binärbaum
•
LINKS(k): zeigt auf den linken Sohn des
•
RECHTS(k): zeigt auf den rechten Sohn des
•
VATER(k): zeigt auf den Vaterknoten des
•
WERT(k): liefert den Wert des Knotens
•
WURZEL(B): zeigt auf die Wurzel des Baumes
k -ten
k
Knotens
k -ten
k -ten
Knotens
Knotens
zurück
Anmerkung: Falls keine Sohnknoten oder Vaterknoten existiert wird nil zurückgeliefert.
35
12.3
Reihenfolge der Knoten
Die Knoten können in verschiedener Art und Weise eingefügt bzw. ausgelesen
werden.
12.3.1 Symmetrische Reihenfolge (SR)
Die Knoten werden in folgender Reihenfolge ausgelesen: Linker Teilbaum rekursiv in SR, Wurzel, rechter Teilbaum rekursiv in SR. Arithmetische Ausdrücke
werden als
SR(k)
1: IF
2:
3:
4:
Inx -Notation ausgelesen.
k6=nil THEN
SR(LINKS(k))
SCHREIBE k
SR (RECHTS(k))
12.3.2 Haupreihenfolge (HR)
Analog die Auslesefolge: Wurzel, linker Teilbaum rekursiv in HR und rechter
Teilbaum rekursiv in HR.
12.3.3 Nebenreihenfolge (NR)
Analog die Auslesefolge:
linker Teilbaum rekursiv in NR, rechter Teilbaum
rekursiv in NR und die Wurzel.
x -Notation ausgelesen.
12.4
Arithmetische Ausdrücke werden als
Post-
Sortierte Binärbäume
Im weiteren Abschnitt wird die SR verwendet.
Der Binärbäum ist eine sehr
mächtige Datenstruktur, mit der viele Funktionen eektive implementiert wer-
SUCHEN, EINFÜGEN, LÖSCHEN, MINIMUM, MAXIMUM,
VORGÄNGER, NACHFOLGER.
den können:
Alle Funktionen können in
O(Höhe
des Baumes) implementiert werden.
Vorteil: Es kann das Wörterbuchproblem dynamisch gelöst werden
Nachteil: Laufzeiten bis
Θ(n)
12.4.1 Suchen in Binärbäumen
Suche den Wert
SUCHE
w
im Binärbaum
B.
(b,Wurzel) gestartet.
36
Der rekursive Algorithmus wird mit
SUCHE(w,k)
1: IF k=nil OR w=WERT(k)THEN
2:
RETURN k
3: ELSE
4:
IF w<WERT(k) THEN
5:
SUCHE(w,LINKS(k))
6:
ELSE
7:
SUCHE(w,RECHTS(k))
12.4.2 Finden des Maximus in einen Binärbaum
Liefert den gröÿten Wert des Binärbaumes zurück. Der Algorithmus wird mit
Baum_Maximum(Wurzel(B))
aufgerufen.
BAUM_MAXIMUM(k)
1: WHILE RECHTS(k)6=nil
2:
k=RECHTS(k)
3: RETURN WERT(k)
12.4.3 Finden des Minimums in einen Binärbaum
Liefert den kleinsten Wert des Binärbaumes zurück. Der Algorithmus wird mit
Baum_Minimum(Wurzel(B))
aufgerufen.
BAUM_MINIMUM(k)
1: WHILE LINKS(k)6=nil
2:
k=LINKS(k)
3: RETURN WERT(k)
12.4.4 Finden des Vorgängers eines Knotens
Es wird der nächst kleinere Wert zum
WERT(k)
VORGÄNGER(k)
1: IF LINKS(k)6=nil
2:
RETURN BAUM_MAXIMUM(LINKS(k))
3: y=VATER(k)
4: WHILE y6=nil AND k=LINKS(y)
5:
k=y
6:
y=VATER(y)
7: RETURN(WERT(y))
37
gefunden.
12.4.5 Finden des Nachfolger eines Knotens
Es wird der nächst höhere Wert zum
WERT(k)
gefunden.
NACHFOLGER(k)
1: IF RECHTS(k)6=nil
2:
RETURN BAUM_MINIMUM(RECHTS(k))
3: y=VATER(k)
4: WHILE y6=nil AND k=RECHTS(y)
5:
k=y
6:
y=VATER(y)
7: RETURN(WERT(y))
12.4.6 Einfügen in den Binärbaum
In den Binärbaum
B
wird der Wert
w
eingefügt.
EINFÜGEN(B,w)
1: y=nil
2: x=WURZEL(B)
3: WHILE x6=nil
4:
DO y = x
5:
IF w<WERT(x) THEN
6:
x=LINKS(x)
7:
ELSE x=RECHTS(x)
8: VATER[w]=y
9: IF y=nil
10:
THEN WURZEL(B)=w
11: ELSE IF w<WERT(y)
12:
THEN LINKS(y)=w
13: ELSE RECHTS(y)=w
12.4.7 Löschen eines Werts
Der Knoten
k
soll gelöscht werden.
Dabei müssen drei Fälle unterschieden
werden, da die Sortierung in SR erhalten bleiben muss:
1.
k
ist ein Blatt: einfach entfernen
2.
k
hat nur einen Sohn: Den Sohn von
k
3.
k
hat zwei Söhne: Finde den Knoten
k0
38
an den Vater von
k
anhängen
mit dem nächst gröÿten Wert
LÖSCHEN(k)
1: IF LINKS(k)=nil OR RECHTS(k)=nil
2:
THEN y=k
3:
ELSE y = NACHFOLGER(k)
4: IF LINKS(y)6=nil
5:
THEN x=LINKS(y)
6:
ELSE x=RECHTS(y)
7: IF x6=nil
8:
THEN VATER(x)=VATER(y)
9: IF VATER(y)=nil
10:
THEN WURZEL(B)=x
11:
ELSE IF y = LINKS(VATER(y))
12:
THEN LINKS(VATER(y))=x
13:
ELSE RIGHT(VATER(y))=x
14: IF y6=k
15:
THEN WERT(k)=WERT(y)
16:
IF ('andere Felder vorhanden, mitkopieren')
17: RETURN y
39
13
(2-4)-Bäume
(2-4)-Bäume sind durch folgende Eigenschaften deniert:
1. Alle Äste sind gleich lang
2. Die Ordnung (maximale Anzahl der Söhne eines Knotens) ist gleich 4
3. Innere Knoten haben
≥
2 Söhne
4. Die Blätter enhalten von links nach rechts die Werte aufsteigend sortiert
5. Jeder innere Knoten mit
tionen
x1 , ..., xt−1
wobei
t Söhnen (2 ≤ t ≤ 4) speichert t − 1 Hilfsinformaxi = gröÿter Wert im Teilbaum des i-ten Sohnes
von links
Dadurch kann eine logarithmische Höhe garantiert werden (13.2).
Beispiel eines (2,4)-Baums:
Weiters wird die Ordnung
α(k)
eines Knotens
k
als die Anzahl der Söhne
deniert.
13.1
Implementierbare Funktionen
Die Funktionen SUCHEN, EINFÜGEN und ENTFERNEN entsprechen dem
Wörterbuchproblem.
Man kann mithilfe von (2,4)-Bäumen das Wörterbuch-
problem bestehende aus
n Elementen in O(log n) pro Funktion und S(n) = O(n)
Speicher lösen.
13.1.1 Suchen in (2-4)-Bäumen
Mit Hilfe der Information in den inneren Knoten kann von der Wurzel ausgehend
nach unten der entsprechende Wert gesucht werden. Pro Knoten benötigt der
O(1). Die gesamte
Tsuchen (n) = O(Höhe)
Suchprozess eine konstante Zeit
der Höhe des Baumes ab:
40
Laufzeit hängt direkt von
13.1.2 Einfügen in (2-4)-Bäumen
Zuerst wird der richtige Knoten gesucht und dort das neue Element eingefügt.
Dabei müssen zwei Fälle unterschieden werden:
1. Fall:
α(k) ≤ 4
nach dem Einfügen: Ergebnis ist wieder ein (2-4)-Baum
α(k) = 5
2. Fall:
nach dem Einfügen: Es liegt
SPALT -Operation
muss eine sog.
kein
(2,4)-Baum vor.
Es
durchgeführt werden, um wieder einen
(2,4)-Baum zu erhalten.
SPALTEN: Der Knoten
k.
von
k erhält einen Bruderknoten k 0
unmittelbar rechts
Dabei werden die zwei rechtesten (gröÿten) Söhne von
k
auf
k0
umgehängt.
Bemerkung: Dabei muss die
führt werden (siehe
v, v 0 ),
SPALT -Operation
auch potentiell öfters durchge-
maximal aber nur logarithmisch oft.
13.1.3 Entfernen in (2-4)-Bäumen
Auch hier wird zuerst der entsprechende Knoten gesucht. Nach dem Entfernen
muss man wieder zwei Fälle unterscheiden:
1. Fall:
α(k) ≥ 2
nach dem Entfernen: Ergebnis ist wieder ein (2-4)-Baum.
2. Fall:
α(k) = 1
nach dem Entfernen: Es liegt nun
vor.
STEHL-
Es muss entweder eine
oder eine
kein (2,4)-Baum mehr
VERSCHMELZUNGS -
Operation durchgeführt werden, um wieder einen (2,4)-Baum zu erhalten.
Sei
k0
ein direkter Bruder von
k
(a)
α(k 0 ) ≥ 3,
dann muss man einen Sohn von
(b)
α(k 0 ) = 2,
dann muss
k
mit
k 0 STEHLEN
k 0 VERSCHMOLZEN
41
werden
Bemerkung: Auch hier muss die
VERSCHMELZUNGS -Operation auch poten-
tiell öfters durchgeführt werden, maximal aber nur logarithmisch oft.
13.2
Beweis der logarithmischen Höhe
Alle erwähnten Funktionen können im schlimmsten Fall mit
führt werden.
O(Höhe)
durchge-
Die Datenstruktur (2-4)-Bäume garantiert eine logarithmische
Höhe (d.h. die Höhe hängt logarithmisch von der Datenmenge
n
ab).
Beweis:
2h ≤
≤ 4h = 22h
n
h ≤ log2 n
Also kann man die Höhe
h
≤ 2h
abschätzen mit
log2 n
≤ h ≤ log2 n
2
was der Denition der
13.3
Θ-Notation
entspricht:
h = Θ (log n)
Anwendung: Mischbare Warteschlangen
Eine Anwendung der (2-4)-Bäume sind sogenannte mischbare Warteschlangen,
Halde ),
EINFÜGEN(S,x), MAXIMUM(S) und ENTFERNE_MAX(S)
auch noch die Funktion MISCHE(S,S') implementiert.
die im Gegensatz zu den Warteschlangen mit Prioritäten (siehe Kapitel
zu den drei Funktionen
Die Struktur des (2-4)-Baums für mischbare Warteschlangen ist die folgende:
Die Blätter speichern
S
in beliebiger Reihenfolge und jeder innere Knoten spe-
ichert das Maximum seines Teilbaums, plus einen Zeiger, der auf das entsprechende
Maximum-Blatt zeigt.
42
Alle 4 Funktionen EINFÜGEN, MAXIMUM, ENTFERNE_MAX und MISCHEN können damit in
13.4
O(log n)
16 .
implementiert werden
Sortieren mit (2-4)-Bäumen
Sortieren mit (2-4)-Bäumen besitzt den Vorteil sowohl
auch
adaptiv zu sein.
worst-case optimal
als
Die inneren Knoten des (2-4)-Baums besitzen dabei nur die
Information des Maximums des jeweiligen Teilbaums. Die Idee beruht darauf
in einen anfangs leeren (2-4)-Baum die Werte einzufügen.
Folgende Schritte
werden durchgeführt (bottom-up):
•
Man startet mit dem Blatt ganz links (das aktuelle Minimum)
•
Man läuft bis zur Wurzel
der
•
w0
des Teilbaums
T 0 (w0
x
und man macht
ist der erste Knoten,
> ai )
Man läuft von
w0
zu Blatt
ai
wählt immer den ersten Teilbaum von links mit
Bemerkung: Es sind potentiell logarithmisch viele
zum linken Bruder (man
w(T B) > ai )
SPALT -Operationen durchzuführen.
Wie aber gezeigt werden kann, amortisieren sich diese im Laufe des Sortiervorgangs.
13.4.1 Analyse des Sortierens mit (2,4)-Bäumen
Dazu benötigt man ein Maÿ für die Unsortierheit einer Folge ( 'Die Anzahl der
Fehlstände), welches folgendermaÿen deniert ist:
Sei
ai
a1 , a2 , ..., an
eine (unsortierte) Zahlenfolge.
, d.h. Anzahl der Zahlen die rechts von
16 Eine
O(n)
Implementierung mit der Datenstruktur
möglich machen.
43
ai
fi
= Anzahl der Fehlstände für
stehen und aber kleiner als
Halde
ai
sind.
würde das Mischen nur teuer mit
fi = |{aj | j > i , aj > ai }|
Die Summe aller Fehlstände
F
ist dann ein Maÿ der Unsortiertheit der Zahlen-
folge:
F =
n
X
fi
i=1
Die Laufzeit
T (n)
beim Einfügen des
beträgt mit
ai
si =
Anzahl der nötigen
SPALT -Operationen
Elements:
T (n) = O
n
X
!
(log fi + si )
i=1
Mit der obigen Denition, der Abschätzung aus der Amoritisierungsanalyse
Pn
n
3
i=1 si ≤ 2 n und der Abschätzung
· log Fn erhält man für die Laufzeit:
Pn
i=1
log fi = log Πni=1 fi ≤ log
F
T (n) = O n · log + n
n
44
F n
n
=
14
Amortisierte Kosten bei (2,4)-Bäumen
Mithilfe der Amortisierungsanalyse ermittelt man die nötige durchschnittliche
Zeit über alle Operationen im schlimmsten Fall.
Damit kann gezeigt wer-
den, dass man auch mit z.B. relativ teuren Umstrukturierungsoperationen (hier
Spalten, Stehlen und Verschmelzen) die Gesamtlaufzeit verbessern kann, wenn
die übrigen Funktionen (Einfügen, Entfernen, Suchen) dank dieser Umstrukturierungen weniger Zeit benötigen
14.1
17 .
Denitionen
Orientiert am Begri Amortisierung deniert man eine
und entsprechend ein Konto
Die Balance
b
b(T)
Balance b(k) für Knoten
für den gesamten Baum
18 .
k ist folgendermaÿen deniert:


−1
1








 0
 2
3
1
b(k) =
α(k) =




0
4






−1
5
für einen Knoten
Der Kontostand eines Baums
T
ist deniert als die Summe der Balancen aller
inneren Knoten:
b(T ) =
X
b(k)
Folgende Variablen werden im Lauf der Analyse verwendet:
i
Anzahl der Einfügeoperationen
j
Anzahl der Löschoperationen
m
Anzahl aller Operationen: m=i+j
S
Anzahl der Spaltungen
V
Anzahl der Verschmelzungen
D
Anzahl der Stehlvorgänge
Satz (1):
D≤j
Beweis: Stehloperationen können nur bei Löschvorgängen auftreten und dann
höchsten einmal.
Satz (2):
S+V ≤m+
i−j
2
17 In der Analyse wird SUCHEN nicht berücksichtigt, da es keine Strukturänderung des
Baums zur Folge hat (keine Kontostandsänderung).
18 Zur Dierenzierung zu den Binärbäumen wird hier T als Bezeichnung für den (2,4)-Baum
verwendet.
45
Der 2.Satz wird mithilfe des Kontostandes und den Beobachtungen 1-4 bewiesen
(siehe weiter unten).
Falls Satz(1) und Satz(2) richtig sind, dann kann man zeigen, das alle Spalt-,
Verschmelz- und Stehloperationen zusammen nur linear mit den Einfüge- und
Löschoperationen (m
S+V +D ≤m+
= j + i)
anwachsen.
i−j
2i + 2j + i − j + 2j
3i + 3j
3
+j =
=
= m = O(m)
2
2
2
2
14.1.1 minimaler, maximaler Kontostand b(T )
Beobachtung 1: Ein (2,4)-Baum
T
mit
n
Blätter ist stets begrenzt in seinem
Kontostand durch:
0 ≤ b(T ) ≤
n
2
14.1.2 Einfügen, Entfernen
Beobachtung 2: Sei
T 0 aus
einem (2,4)-Baum
T
durch EINFÜGEN oder ENT-
FERNEN eines Blattes enstanden, dann gilt:
b(T 0 ) ≥ b(T ) − 1
14.1.3 Spalten
Beobachtung 3: Sei
T 0 aus einem Baum T
durch SPALTEN enstanden, dann gilt:
b(T 0 ) ≥ b(T ) + 1
14.1.4 Stehlen und Verschmelzen
Beobachtung 4a: Sei
T 0 aus
T
einem Baum
durch STEHLEN enstanden, dann
gilt:
b(T 0 ) ≥ b(T )
Beobachtung 4b: Sei
T 0 aus einem Baum T
durch VERSCHMELZEN enstanden,
dann gilt:
b(T 0 ) ≥ b(T ) + 1
14.2
Abrechnung
Man nimmt an, dass ingesamt
m=i+j
Operationen auf einen anfangs leeren
19 . Der Anfangskontostand ist b(T )
0
(2,4)-Baum ausgeführt werden
der Endkontostand maximal
Abbuchungen
b(Tm ) =
m
2
=
= 0, während
i−j
(Beobachtung 1) sein kann.
2
(Kontostand erniedrigt sich) geschehen nur durch ENTFER-
NEN oder EINFÜGEN von Blättern - (Beobachtung 2
19 Es
→≤ m).
wird vorausgesetzt, dass mehr Elemente eingefügt als entfernt werden i > j .
46
Einzahlungen (Kontostand erhöht sich) geschehen nur durch SPALTEN (Beobachtung 3→≥
1)
, VERSCHMELZEN (Beobachtung 4b
(Beobachtung 4a
→≥ 1)
oder STEHLEN
→≥ 0).
Die Kontoabrechnung schaut dann folgendermaÿen aus:
Anfangszustand - Abbuchungen + Einzahlungen = Endzustand
und damit erhält man den Beweis zu Satz (2)
0 − m + (S + V ) ≤
47
i−j
2
15
Optimales Kodieren
Es soll ein optimaler Kodierer
(z.B. Text
T)
C(T ) entworfen werden, welcher eine Information
mit möglichst geringer Bitanzahl eindeutig überträgt.
Die Anforderungen an den optimalen Kodierer
•
sollte schnell kodieren
•
sollte eindeutig dekodieren (präx-frei)
•
minimale Kodelänge
15.1
C(T )
sind:
Nötige Denitionen
Als Beispiel soll der Text='Susanne' kodiert werden.
Folgende Denitionen
werden benötigt:
• T =
'SUSANNE' ist der zu kodierende Text (im allg. eine Information,
welche kodiert werden sollte)
• Z = {S, U, A, N, E}
• |T | = 5
das Alphabet
ist die Anzahl der Buchstaben des Textes
• fi
ist die Auftrittsfrequenz. z.B.:
• pi
ist die Auftrittswahrscheinlichkeit. z.B.:
•
Entropie
H(T )
fS = 2, fU = 1
pS =
ist die Information, die der Text
2
7 , oder
T
pU =
enthält
1
7
20 (Pseudoein-
heit 'bit'):
H(T ) = −
|Z|
X
pj · log2 pj
j=1
Wenn man den Text 'SUSANNE' naiv kodiert, dann benötigt man 3 Bits pro
Buchstabe. Für den ganzen Text benötigt man
3 · 7 = 21 Bits. Die Entropie
2.23 Bits pro Buchstabe,
(der eigentliche Informationsgehalt) beträgt aber nur
was auch die theoretische untere Grenze darstellt.
20 Hier wird angenommen, dass binär kodiert wird, daher log zur Basis 2. Prinzipiell ist es
mit jeder beliebiger Basis möglich (log10 , log8 , ..)
48
Idee: Zeichen die häuger vorkommen, werden mit weniger Bits belegt.
Ein Problem, dass dabei auftritt, ist das Präx-Problem.
Damit der Kode
eindeutig interpretierbar bleibt, darf kein Buchstabe kodiert ein Präx eines
anderen Buchstaben seins. Zum Beispiel wenn
100
C(x1 ) = 10
für 'S' und
für 'A' gegeben wäre, dann wäre das Kodestück ...100...
dekodierbar!
15.2
21
C(x2 ) =
nicht eindeutig
Darstellung des Kodierers
Mit der Darstellung des Kodieres (und gleichzeitig des Dekodierers) als Binärbaum kann man präx-Freiheit garantieren. Die Datenstruktur hat dabei folgende Eigenschaften:
•
Binärbaum (sog. Kodebaum)
•
Werte sind blattorientiert (daher präxfrei)
•
Die Wortlänge entspricht der Astlänge li
Eine Methode einen solchen Kodebaum zu generieren ist die Methode nach
Shannon-Fano.
15.3
Shannon-Fano
Die Vorgangsweise ist folgende:
•
Sortiere die Zeichen nach ihrere Häugkeit
•
Teile die Zeichen entlang dieser Reihenfolge in zwei Gruppen, sodass die
fi
Summe der Häugkeiten in beiden Gruppen möglichst gleich ist. Daraus
mache den linken und rechten Teilbaum des neuen Baums.
•
Bendet sich mehr als ein Zeichen in einer entstandenen Gruppe, wende
den Algrithmus rekursiv auf diese Gruppe an.
Der Nachteil ist, dass diese Methode nicht immer einen optimalen (in bezug auf
die Kodelänge) Kodebaum liefert.
21 In
der Literatur wird oft der Begri 'prex codes' verwendet. Damit sind aber genau
präx-freie Kodes gemeint sind.
49
15.4
Human
22 . Mit
Die Methode nach Human liefert garantiert einen optimalen Kodebaum
folgender Vorgangsweise wird der Kodebaum konstruiert:
•
Erstelle einen 'Wald' mit jeweils einen Baum pro Zeichen.
•
Suche die beiden Bäume mit den kleinsten Wahrscheinlichkeiten und verbinde
sie zu einen neuen Baum, welche nun die Summe der Wahrscheinlichkeiten
der Unterbäume besitzt.
•
Wiederhole den Vorgang, bis nur noch ein Baum übrig ist.
15.4.1 Implementierung
Der Human-Algorithmus kann sehr eektiv mit einer Warteschlange
Q mit Pri-
oritäten implementiert werden, und zwar mit der Datenstruktur Halde. Dabei
wird inverse geordnet - d.h. das Minimum liegt an der Wurzel der Halde.
Dem Algorithmus wird das Alphabet
fi 's
Z
mit den zugehörigen Auftrittsfrequenzen
übergeben .
HUFFMAN (Z,f)
1: n=|Z|
2: INIT_Q (Z)
3: FOR i=1 TO (n-1)
4:
z=NEUER_KNOTEN
5:
LINKS(z) = MINIMUM(Q), ENTFERNE_MIN
6:
RECHTS(z)= MINIMUM(Q), ENTFERNE_MIN
7:
f(z) = f(x)+f(y)
8:
EINFÜGEN(Q,z)
9: RETURN MINIMUM(Q)
Analyse: Alle Operationen der Warteschlange mit Prioritäten (mit einer Halde
implementiert) können in
nerhalb der Schleife
n-mal
O(log n)
durchgeführt werden. Dabei werden sie in-
aufgerufen, d.h.
T (n) = O(n · log n).
22 Der Baum liefert das praktisch (es gibt keinen halben Bits) erreichbare Minimum an
Kodelänge. Die Entropie, als theoretische Grenze, kann durchaus weiter unten liegen.
50
Herunterladen