Algorithmen & Datenstrukturen
Wintersemester 2001/2002
1 Einleitung
Literatur:
Baut auf Kapitel 1 von [CLR90] auf.
Inhalt
Algorithmus ·Analyse von Algorithmen ·Zeitkomplexität und
Speicherkomplexität ·Algorithmenentwurf
-
Einleitung und Überblick
- Sortieren
- Suchen
- dynamische Datenstrukturen
- Bäume, Graphen ...
- Problemlösungs stragegien:
-
Divide and Conquer
- Rekursion und Iteration
- Greedy-Algorithmen
- Relaxation
- ...
-
Namensgeber:
Abu Ja'far
Mohammed ibn Mûsâ al-Kwoarizmî: Kitab al jabr
w'al-muqabala (Regeln zur Wiederherstellung und Reduktion)
- heutige (informelle) Bedeutung ([Knu73a])
-
(endliche Beschreibung)
- Definiertheit
- Terminierung
- besitzt Input1 und
Output
- Effektivität/ effectivness, jeder
Einzelschritt ist effektiv ausführbar
- zur Lösung eines wohlspezifizieren Berechnungsproblems
Berechnungsproblem: Sortieren
|
-
Eingabe: endliche Sequenz von s = (a1, a2, ..., an)
- Ausgabe: Permutation (a1', ... an') von s mit
ai' £ ai+i' für alle i Î {1,... n}
- Algorithmus korrekt/ löst das Problem,
falls für alle Eingaben
-
Algorithmus hält nach endlicher Zeit
- liefert das korrekte Ergebnis
-
Sortieren durch Einfügen
- Imperative Lösung:
-
Eingabe: Array A fester Länge n von Zahlen
- Ausgabe: monoton-steigend sortierter Array, Permutation
von A.
- direktes Einfügen (``in situ'' sortieren)
- inkrementelle Lösung
- Bild: Verhalten von Insertion sort
Insertion_sort(A)
for j = 2 to length[A]
do key := A[j];
i := j-1;
while i > 0 and A[i] > key
do
A[i+1] := A[i]
i := i-1;
A[i+1] := key;
-
Analyse: mathematische Abschätzung des
(vorraussichtlichen) Resourcenverbrauches abhängig von der
Größe der Eingabe
- Fragestellungen:
-
(Korrektheit, Terminierung)
- Zeitkomplexität: Anzahl elementarer Schritte
- Speicherkomplexität:
- Sonstiges (Kommunikationsbandbreite, ...)
- Idealisierungen:
-
abstraktes Maschinenmodell (Turingmaschine, RAM-Maschine)
- asymptotisches Verhalten (O-Notation).
-
Abschätzung nach oben: worst-case
- Abschätzung nach unten: best-case
- mittlere Komplexität: average-case
- Untersuchung/Vergleich/Klassifizierung der Komplexität von
Algorithmen: Komplexitätstheorie (s.
[Pap94], auch [AHU89])
Analyse des Sortierens durch Einfügen
|
-
Abhängig vom Input
- sei tj die Anzahl
der while-tests für den Wert von j
Insertion_sort(A)
for j = 2 to length[A] // c1 n
do key := A[j]; // c2 n-1
i := j-1; // c4 n-1
while i > 0 and A[i] > key // c5 sum(j=2 to n) t_j
do
A[i+1] := A[i] // c6 sum(j=2 to n) (t_j-1)
i := i-1; // c7 -"-
od
A[i+1] := key; // c8 n-1
Insertion_sort(A)
for j = 2 to length[A] // c1 n
do key := A[j]; // c2 n-1
i := j-1; // c4 n-1
while i > 0 and A[i] > key // c5 sum(j=2 to n) t_j
do
A[i+1] := A[i] // c6 sum(j=2 to n) (t_j-1)
i := i-1; // c7 -"-
od
A[i+1] := key; // c8 n-1
|
T(n) |
= |
c1 n + (c2+c4+c8)(n-1) |
|
|
|
|
-
Zussammenfassen aller Konstanten ci:
|
|
tj |
T(n) |
bester Fall |
sortiert |
1 |
a n + b |
schlechtester Fall |
rückwärts sortiert |
j |
a n2 + b n + c |
-
für große n: nur der höchste Exponent des Polynoms
zählt:
-
Bester Fall: lineare Zeitkomplexität
( O(n))
- schlechtester Fall quadratische Zeitkomplexität
( O(n2))
- Komplexität darstellbar durch ein Polynom über der Problemgröße:
polynomielle Komplexität
( O(nc))
-
Inkrementell: vgl. Sortieren durch Einfügen.
- Divide-and-Conquer: rekursive Zerlegung des Problems:
-
Divide: Zerlegen des Problem in
(gleichartige, aber kleinere) Unterprobleme
- Löse die Teilprobleme rekursiv
- Conquer: Zusammenfügen zur Gesamtlösung.
-
klassisches Beispiel für D&C
-
Divide: Zerlege die Sequenz in zwei Hälften
- Sortiere sie rekursiv
- Conquer: Verschmelzen (``merge'') der
sortierten Sequenzen
- Aufruf: Merge_Sort(A, 1, length(A))
Merge_Sort(A,p,r)
if p < r
then q := floor(p+r/2); // the ``middle''
Merge_Sort(A,p,q);
Merge_Sort(A,q+1,r);
Merge(A,p,q,r);
-
rekursiver Algorithmus Þ Komplexität durch
Rekurrenzgleichung
- im schlimmsten Fall
T(n) = |
ì í î |
|
O(1) |
falls n=1 |
2T(n/2) + O(n) |
falls n >1 |
|
|
- Vereinfachung: Länge = 2n
- Ansatz:
T(n) = |
ì í î |
|
0 |
falls n=1 |
2T(n/2) + n |
falls n = 2k, k³ 1 |
|
|
- Þ T(n) = n log2 n
2 Sortieren
Literatur:
Kapitel 7 und 8 aus [CLR90]. Knuth
[Knu73b] enthält mehr über Sortieren als man wissen will.
Heapsort stammt von Williams
[Wil64], Quicksort von Tony Hoare [Hoa62]
Inhalt
Heaps ·Heapsort ·Quicksort
-
(gerichteter) Graph G = (V,E):
-
V: Menge von Knoten (vertices),
- E: Kanten, binäre Relation auf V (edges)
- ungerichteter Graph: E: Menge ungeordneter
Paare {u,v} von Knoten mit u¹v (keine self-loops)
- Wurzelbaum: (rooted tree): verbundener, azyklischer,
ungerichteter Graph mit einem ausgezeichnetem Knoten = Wurzel
- (binärer) Heap: vollständiger, geordneter, binärer
Wurzelbaum
-
Binär: Anzahl der Kinder £ 2.
- vollständiger (``complete'') Binärbaum: alle Blätter
haben die selbe Tiefe + alle internen Knoten mit
exakt 2 Kindern2
- geordnet: Reihenfolge der Kinder spielt eine Rolle,
d.h. man kann linkes und rechtes Kind
unterscheiden.3
- + Heap-Eigenschaft: Für alle Teilbäume t gilt: t =
Node(l, a, r) Þ l = Leaf oder a ³
Key(l) (und genauso für den rechten: r = Leaf oder
a ³ Key(r))
- Bild: Heap als Baum
Implementierung: Heaps als Array
|
-
Implementierung von Heaps als Array:
Knoten als Arrayelemente
- Þ weitere Zusatzbedingung: alle Ebenen des
Baums gefüllt bis auf evtl. die letzte, diese kann von
links nach rechts teilgefüllt sein.4
- Füllung des Arrays von 1 bis heapsize (nochmal Bild)
- Repräsentierung des Binärbaumes:
|
Parent(i) |
= |
|
Left(i) |
= |
2 i |
Right(i) |
= |
2 i + 1 |
|
- Ein weiteres Mal das Bild: Heap + Implementierung
-
Heapify: Einfügen, Erhalt der
Heapbedingung
- Annahme: der rechte und der linke Teilbaum erfüllen die
Heapbedingung!
- rekursive Prozedur: `` Einsickern'' des
neuen Elementes
Heapify (A, i)
l := Left(i); r := Right(i); // Left(i), Right(i) are heaps
if l <= heapsize(A) and A[l] > A[i] // finde das maximum
then largest := l else largest := i;
if r <= heapsize(A) and A[r] > A[largest]
then largest := r;
if largest != i // Verletzung der Heapbedingung?
then exchange (A[i] , A[largest]);
Heapify(A, largest);
-
repariere alle Verletzungen der Heapbedingung mit Heapify
- Bottom-up mittels Heapify
- Blätter erfüllen Heap-Eigenschaft Þ Start bei
den untersten nicht-Blatt Knoten
(ë length(A)/2 û).
Build-Heap(A)
heapsize[A] := length[A];
for i= floor(length(A)/2)) downto 1 // bottom-up
do heapify(A,i);
-
Heap als unsortiertes, aber teilsortiertes
Reservoir, der Rest des Arrays ist bereits sortiert
- Wurzel des Heaps: Maximum
- Þ Aufbau der sortierten Elemente von hinten
- Entfernen aus dem Heap ist billig
(Heapify log2(n))
Heapsort(A)
build_heap(A); // build initial heap
for i = length(A) downto 2
do exchange A[1] A[i]; // extract maximum
heapsize[A] := heapsize[A] - 1; // decrease size of heap
Heapify(A,1); // repair heap condition
-
Divide & Conquer
- im Gegensatz zu Mergesort: Verschmelzen trivial,
investiere dafür mehr in das Divide
- D&C-Schritte:
-
Divide: Teile die Sequenz s in zwei
Teilsequenzen s1 und s2, sodaß s1£ s2
(punktweise)
- Sortiere Teilsequenzen s1 und s2
- Conquer: Zusammenfügen trivial
language=Ocaml,escapeinside=(**)
(* $Id: quicksort.ml,v 1.8 2001/11/11 12:40:19 softtech Exp $ *)
(* A functional implementation of quicksort. The divide-and-conquer
approach is visible in the recursive inner $\ocwlowerid{sort}$-function:
It chooses as pivot the first element. Using the auxiliary function
$\ocwlowerid{filter}$,\footnote{The function is directly available from
the standard-library. It's included here for sake of reference.} it
splits the remainder of the list into two halves, the elements less or
equal the pivot and those larger than the pivot. The sublists are
sorted and, together with the pivot in the middle, put together to the
sorted list.
*)
(*i*)
open Basic;;
open Sort;;
(*i*)
module QuicksortFun (E: ORDERED) : (SORT with type elem = E.t) =
struct
type elem = E.t
let rec filter p l = match l with (* auxiliary function *)
[] -> []
| x :: tl ->
if (p x)
then x::(filter p tl)
else filter p tl
let rec sort (l : elem list) =
match l with
[] -> []
| x :: tl -> (* x = pivot *)
sort (filter (function y -> E.lt(y,x)) tl)
@ [x] @
sort (filter (function y -> E.geq(y,x)) tl)
end
-
Zwei rekursive Aufrufe
- das Divide: Partition liefert den Index für die
Trennung in die kleinere und größere Hälfte
Quicksort(A,p,r)
if p < r
then q := Partition(A,p,r)
Quicksort(A,p,q);
Quicksort(A,q+1,r);
Quicksort mit arrays: Partitionieren
|
-
Wähle ein Element des Arrays: das Pivot x
- Partitionieren: trenne/ordne A in 2 Hälften,
links alle £ x, rechts alle ³ x.
- Invariante:
|
I1 |
A[i'] £ x £ A[j'] |
für alle i'< i, j' > j. |
I2 |
A[i'] £ x £ A[j'] |
für alle i'£ i, j' ³ j. |
|
-
Abbruch: (i ³ j) & I1, d.h., A[p...
i-1] £ x £ A[j+1... r], daraus folgt
A[p... j] £ x £ A[j+1... r]
Partition(A,p,r) /* p <= r */
x := A[p];
i := p-1;
j := r+1;
while true /* Invariante I_2 */
do repeat j := j-1
until A[j] <= x;
repeat i := i+1
until A[i] >= x;
if i < j /* Invariante I_1 */
then exchange A[i] <-> A[j] /* Invariante I_2 */
else return j; /* j <= i */
od;
Analyse von Quicksort: worst case
|
-
Schlechtester Fall: Partitionierung trennt den Ausschnitt
[p... r] der Länge n = r-p+1 in Teile der Größe
1 und n-1.
- Schlimmster Fall: Falls A bereits sortiert
- Kosten der Partitionierung: O(n)
- Rekurrenz: T(n) = T(n-1) +O(n) Þ
T(n) = åk=1n O(k) = O(n2)
Analyse von Quicksort: best case
|
3 Lineares Sortieren
Literatur:
Kapitel 9 [CLR90].
Inhalt
O-Notation ·Einführung ·Vergleichssortieren ·Counting sort ·Bucket sort
-
Bisher: die Algorithmen O(nlog n) oder O(n2)
- Gemeinsamkeit:
Die Ordnung die die Algorithen bestimmen, beruht ausschließlich
auf dem Vergleich zwischen Elementen
- Þ
Verfahren |
Tmin |
Tmax |
Tav |
Auswahl |
O(n2) |
O(n2) |
O(n2) |
Einfügen |
O(n) |
O(n2) |
O(n2) |
Bubblesort |
O(n) |
O(n2) |
O(n2) |
Mischen |
O(n log n) |
O(n log n) |
O(nlog n) |
direktes Mischen |
O(n log n) |
O(n log n) |
O(nlog n) |
natürliches Mischen |
O(n) |
O(n log n) |
O(nlog n) |
Quicksort |
O(nlog n) |
O(n2) |
O(nlog n) |
Heapsort |
O(n log n) |
O(n log n) |
O(n log n) |
Table 1: Vergleich
Abschätzung für Vergleichssortieren
|
-
Vorraussetzung: Sortieren einer Sequenz (a1,..., an) (oBdA:
alle Elemente verschieden, wichtig ist nur < oder das Gegenteil.)
- mögliche Eingaben der Länge n: alle Permutationen
- Entscheidungsbaum: Darstellung der
Entscheidungen eines Sortieralgorithmusses
-
interner Knoten: ai:aj wobei 1£ i<j£ n,
repräsentiert den Vergleich von ai mit aj, seine Unterbäume die
Vergleiche, je nachdem wie sein Vergleich ausgefallen ist.
- Blatt: Permutation
- Lauf der Sortierung: Pfad durch den Baum
- Þ Höhe des Baumes @ worst-case der
Vergleiche @ worst-case der Laufzeit
- Folie: insertion sort
Vergleichssortieren: worst-case
|
-
gegeben: Entscheidungsbaum für einen Algorithmus
-
worst-case = Tiefe des Baumes
|
- Þ Abschätzung für die Laufzeit:
Theorem 1 [Untere Schranke für worst-case]
Die Höhe ein Entscheidungsbaums der n Elemente sortiert
besitzt eine asymptotische untere
Schranke der Größe
O(n lg n).
-
Mischsortierung und Heapsort sind asymptotisch
optimal.
Abschätzung der Laufzeit(2)
|
Beweis:[of Theorem 1]
-
Es gibt ³ n! Blätter
- Eigenschaft eines binären Baumes: n! £ 2h, d.h.,
h³ lg(n!) (h ist die Höhe)
- mit Stirling-Approximation n! > ( n/e)n
- Þ h³ n lg n - nlg e
- Þ untere Schranke O(nlg n).
Lineares Sortieren: Counting Sort
|
-
Beispiel für einen Algorithmus, der kein
Vergleichssortieren ist
- man braucht zusätzliche Information
- Counting Sort: Information, daß der Wertebereich der
Elemente endlich ist: 1,..., k mit k
=O(n).
- Þ Zählen der Elemente =i möglich (im
Hilfsarray C[1... k])
- kein Vergleich im Algorithmus
Counting-Sort(A,B,k)
for i := 1 to k do C[i] := 0;
for j := 1 to length[A]
do C[A[j]] := C[A[j]] + 1 /* C[i]: Anzahl der Elemente = i */
for i := 2 to k /* Erlaubter Input von 1...k */
do C[i] := C[i] + C[i-1]; /* C[i]: Anzahl Elemente <= i */
for j := length[A] downto 1
do
B[C[A[j]]] := A[j]; /* C[A[j]] gibt Pos. in B */
C[A[j]] := C[A[j]] - 1;
od;
Analyse von Counting Sort
|
-
erste Schleife: O(k)
- zweite Schleife: O(n)
- dritte Schleife: O(n)
-
Insgesamt: O(k+n), und falls k =O(n) Þ
lineare Laufzeit: O(n)
-
Annahme von Bucketsort: uniforme
Verteilung der Eingabe im Intervall I = [0, 1[
- Teilung von I in n Unterintervalle ( Buckets)
-
Verteilen der Elemente in die Buckets
- Sortieren der Buckets
- Aneinanderhängen der sortierten Teillisten
BucketSort(A)
n := length(A);
for i=1 to n
do insert A[i] into list B[floor (n * A[i])];
for i=0 to n-1
do sort list B[i] with insertion sort;
concatenate lists B[0], B[1], B[n-1] together;
-
Alles linear bis auf Insertionsort
= quadratisch
- Zufallsvariable ni: Anzahl der Elemente in B[i].
- Insertionsort quadratisch Þ erwarteter Gesamtaufwand:
|
|
O ( E(ni2))
=
O |
æ ç ç è |
|
( E(ni2)) |
ö ÷ ÷ ø |
- Verteilung von ni: Binomialverteilung B(k,n,p=1/n)
P(ni = k) = n kpk qn-k
- E(ni) = 1 und Var(ni) = 1- 1/n.
- E(ni2) = Var(ni)+ E(ni)2 = 2 - 1/n = O(1)
- Þ
T(n) = O(n)
4 Analyse
Literatur:
Verschiedene Kapitel aus [CLR90].
Daneben Abschnitt 2.5 aus [Heu97].
Inhalt
Analyse von Divide and Conquer ·lineare, O(nlog n),
polynomielle Komplexität
Laufzeitanalyse für Divide & Conquer
|
-
Aufstellen einer Rekurrenzgleichung:
T(n) |
= |
O(1)
für n£ n0 |
T(n) |
= |
|
T(ni) + D(n) + C(n)
für n > n0 |
|
- T(n): Laufzeit für Problem der Größe n, D und C Kosten
für das Divide resp. Conquer
- Vereinfachung: Teilprobleme gleichgroß, n0 = 1.
ferner f(n) = D(n) + C(n).
- Þ a³ 1, b > 1 konstant
- Beispiel: Merge-Sort:
-
Zwei rekursive Aufrufe a=2
- Halbierung der Problemgröße: b=2
- Kosten des Mergens: O(n).
Lösen der Rekurrenzgleichung
|
Sei n = bk. Auflösen durch Iteration.
T(n) |
= |
|
|
= |
|
... |
|
= |
|
|
= |
a |
|
T |
æ ç ç ç è |
|
ö ÷ ÷ ÷ ø |
+ |
|
ai f( |
|
) |
|
|
= |
|
|
= |
|
-
Sei f(n) = D(n) + C(n) linear, f(n) = c n.
Spezialfall: lineare Zeitkomplexität
|
-
Summe der Größen der Teilprobleme a n/b
echt kleiner als n
- Þ a < b
-
Summe der Größen der Teilpobleme = Größe des
Ausgangsproblems:Þ a = b
Spezialfall: polynomiale Laufzeit
|
-
Summe der Größen der Teilpobleme größer als das
Ausgangsproblem Þ a > b
T(n) |
= |
|
|
£ |
|
|
£ |
O |
æ ç ç ç ç ç è |
n |
|
+
n |
æ ç ç è |
|
ö ÷ ÷ ø |
|
ö ÷ ÷ ÷ ÷ ÷ ø |
|
|
£ |
|
|
£ |
|
5 Elementare Datenstrukturen
Literatur:
Kapitel 11 [CLR90].
Inhalt
Stacks und Queues ·verzeigerte Listen ·Bäume ·Implementierungsmöglichkeiten
-
"`dynamische Mengen"': veränderbar
- viele verschiedene Varianten
- Unterschieden nach
-
Zugriffsmöglichkeiten (Schnittstelle)
- Repräsentierung
- zwei Arten von Zugriffen
-
modifizend
- lesend (queries)
- Beispiele für Zugriffe:
-
Suchen
- Einfügen, Löschen
- Minumium, Maximum bestimmen
- Vorläufer, Nachfolger bestimmen
- ...
- Beispiele für Repräsentierungen:
-
(einfach o. doppelt) verzeigerte Listen
- Bäume unterschiedlichster Art (balanciert, Heaps ...)
- Hashstrukturen
-
Stack/Queue: dynamische (Multi-)Mengen mit spezifischer
Strategie zum Einfügen und Löschen:
-
Stack: Lifo: last-in, first-out
-
Push: Einfügen
- Pop: Entfernen
- Queue: Fifo: first-in, first-out
-
Enqueue: Einfügen
- Dequeue: Entfernen
-
Implementiert als Array 1... n
- dynamischer Inhalt: Elemente S[1], ..., S[top]
- Komplexität: O(1)
Stack-empty(S)
if top == 0 then return true else false;
Push(S,x)
top := top + 1;
S[top] := x;
Pop(S)
if Stack-empty(S)
then error "underflow"
else top := top -1;
return S[top+1];
-
Array als Ringpuffer (circular buffer)
- zwei Pointer: head and
tail: erste benutzte und erste
freie
- Komplexität: O(1)
Enqueue(Q,x) =
Q[tail] := x;
if tail == length(Q)
then tail := 1 // start again with 1
else tail := tail + 1;
Dequeue(Q) =
x := Q[head]
if head == length(Q)
then head := 1 // start again with 1
else head := head + 1
return x;
-
lineare Datenstuktur
- keine wahlfreier Zugriff wie beim Array, sonder über
Zeiger
- Verschiedene Varianten:
-
einfach verzeigert: nur prev
- doppelt verzeigert: prev und
next
- zirkulär
Operation |
Komplexität |
|
(vorne) Einfügen |
O(1) |
Löschen (bei geg. Element) |
O(1) |
Suchen |
O(n) |
dopelt verzeigerte Listen (3)
|
List-search(L,k) // k: key
x := head;
while x != nil and x.key != k
do x := next(x);
return x;
List-insert(L,x) // vorne Einfuegen
next(x) := head;
if head != nil then prev(head) := x
head := x;
prev(x) := nil;
List-delete(L,x)
if prev(x) != nil
then next(prev(x)) := next(x)
else head := next(x)
if next(x) != nil
then prev(next(x)) := prev(x);
6 Hashstrukturen
Literatur:
Kapitel 12 aus [CLR90]. Hashfunktionen
werden auch in [Knu73b] diskutiert.
Inhalt
Hashstrukturen ·Hashing mit externer Verkettung ·Hashing mit
offener Adressierung ·Hashfunktionen
-
drei Operationen eines Wörterbuches
(dictionaries)
-
Suchen
- Einfügen
- Entfernen
- Hashtabellen: effizient zur Implementierung von
Dictionaries
Adreßtabellen mit direktem Zugriff
|
-
implementiert mit Arrays, Zugriff direkt
über Index = Schlüssel
(key, eindeutig)
- Suchen, Einfügen, Entfernen: in konstanter Zeit
(O(1)).
Search(T,k) = return T[k];
Insert(T,x) = T[key(x)] := x;
Delete(T,x) = T[key(x)] := nil;
-
Problem:
-
machbar nur für kleine Bereiche der Schlüssel,
Platzverschwendung
- nur destruktives Einfügen möglich
- Þ Hashtabellen
-
Verallgemeinerung von Arrays mit direkten
Zugriff
- gegeben: Bereich U der Schlüssel (``Universum'')
- hash-Funktion: h : U ® {0,... ,
m - 1}
-
|U|> m
- Þ h nicht injektiv Þ Kollision
- h(k): Hashwert von k
- ``zufällige'' Funktion
Kollisionsauflösung: Verkettung
|
-
Verkettung (chaining, auch external
chaining)
- Bild
- Varianten: Eindeutiger Schlüssel, implementierbar durch
-
Überschreibendes Einfügen, oder
- Löschen nur mittels key (Delete(k)) und
Löschen von allen Elementen mit passendem key, search gibt nur den
ersten Treffer zurück.
Insert(T,x) = T[h(key(x))] := insert x at the head of T[h(key)];
Search(T,k) = Search for an element with key k in T[h(k)];
Delete(T,x) = Delete x from list T[h(k)];
Definition 1 [Belegungsfaktor]
Der Belegungsfaktor a (load factor)
einer Hashtabelle T[0,..., m-1] ist definiert als
wobei n gleich die Anzahl der gespeicherten Elemente ist.
-
Worst-case: alle Elemente mit dem selben
Schlüssel Þ O(n)
- im Mittel:
-
Annahme: Gleichverteilung = einfaches,
uniformes Hashing
- Hashfunktion h(k) mit konstantem Aufwand O(1)
- Þ Suchen nach Element mit Schlüssel k: linear
in der Länge von T[h(k)]
Satz 1
Das erfolgreiche sowie erfolglose Suchen in einer
Hashtabelle mit Verkettung und unter der Annahme
einfachen, uniformen Hashings benötigt im Mittel
O(1+a) Zeitaufwand.
Beweis:
Unterscheidung in erfolglose und erfolgreiche Suche.
erfolglos
-
eine Liste wird bis zum Ende durchsucht
- durchschnittliche Länge der Listen: a =
n/m
- ÞO(1+ a)
erfolgreich
Annahmen:
-
alle Schlüssel gleichwahrscheinlich, kein Schlüssel doppelt,
Anfügen ans Ende
Ansatz:
|Zugriffe beim Finden von e|
=
|Zugriffe beim Einfügen von e| +1
Durschnittliche Länge beim Einfügen des i-ten
Elements: i-1/m
im Durchschnitt: 1/n
åi=1n(1+i-1/m) = ... 1 +
a/2 - 1/2m
Þ O(1+a)
-
Ziel: Gleichverteilung der Ergebnisse j
von h(k) (aus dem Bereich 0 bis m-1):
- falls keys gleichverteilt aus [0,1[
Þ h(k) = ë k m û Î {0, ..., m-1}
- ansonsten: Heuristiken
-
Divisionsmethode |
h(k) = k mod m |
Multiplikationsmethode |
h(k) = ë m(k A - ë k A û) û
(mit 0<A<1) |
Universal hashing |
``zufällige'' Auswahl von h |
- wichtig: Auswahl der Parameter, abhängig auch von den
keys
-
Divisionsmethode: m prim, möglichst nicht nahe 2er-Potenz
- Multiplikation: m egal
-
alle Elemente in der Hashtabelle gespeichert (keine
Pointer, keine externen Listen) Þ Verkettung wird
errechnet
- Belegungsfaktor £ 1
- Sondierung (probe): Suche nach freiem Slot
- Þ Hashfunktion
h: U * {0,... m-1} ® {0,...,m-1}
- Sondierungs-Sequenz (probe sequence) für
Schlüssel k:
(h(k,0), h(k,1), ..., h(k, m-1))
- gute Hashfunktion: Vermeidung von Häufungen
(clusters)
Offene Adressierung: Hashfunktionen
|
-
lineares Probing
- Quadratisches Probing
- Doppeltes Hashing
7 Binäre Bäume
Literatur:
Kapitel 13 aus [CLR90].
Balancierte Bäume stammen aus [AVL62].
Inhalt
Binäre Bäume ·Baumoperationen
-
Unterstützung vieler Wörterbuchoperationen
- Baumstrukturen effizient
- Effizienz der Operationen proportional Höhe
des Baumes = O(log n) (falls der Baum
``ausgeglichen'')
- Viele Varianten, abhängig von der Anwendung
-
Rot/Schwarz-Bäume [Bay72]
- B-Bäume
- AVL-Bäume [AVL62]
- Splay-Trees
- (a,b)-Bäume, z.B. (2,3)-Bäume (2-3-Bäume)
- selbst-balancierende Bäume
- ...
Definition 2 [Binärer Suchbaum]
Ein binärer Suchbaum ist ein binärer Baum
(left, right, (evtl. parent)) bei dem
für alle Knoten n gilt:
key(l) £ key(n) £ key(r).
wobei l und r beliebige Knoten aus dem rechten bzw. linken Unterbaum von n sind
-
Die Elemente des Baumes sind `` sortiert''
- Þ Ausgeben der sortierten Elemente = rekursives Durchlaufen
des Baumes (in-order tree walk)5
InOrder-Tree-Walk(t) =
if t != nil
then InOrder-Tree-Walk(left(t));
print (key(t));
InOrder-Tree-Walk(right(t));
-
wichtige Operation auf Suchbäumen
- Komplexität: O(h), wobei h die Höhe
- Minimum/Maximum bestimmen: genauso einfach (der linkeste/rechteste
Knoten im Baum)
Search(t,k) = // t : tree, k : key
if t == nil then raise error("Not found")
else if k == key(t) then return t
else if k < key(t)
then Search( left(t),k)
else Search(right(t),k)
Baumoperationen: Suchen (2)
|
Hier das Ganze nochmals iterativ:6
Search(t,k) = // t : tree, k : key
while (t != nil) and (k != key(t))
do
if k < key(t)
then t := left(t)
else t := right(t)
od;
if (t == nil)
then raise error("Not Found")
else return t
Baumoperationen: Einfügen
|
-
Einfügen immer an den Blättern
- Komplexität: O(h)
Insert(T,z) = // T : tree, z : node to insert
y := nil; x := root(T); // zwei Hilfsvariablen
while x != nil // finde das passende Blatt
do
y := x; // y zum merken des Vaters
if key(z) < key(x)
then x := left(x)
else x := right(x)
od;
p(z) := y; // => x ist nil, y sein Vorgaenger
if y = nil // Sonderfall: y = Wurzel;
then root(T) := z
else if key(z) < key(y)// f"uge an der passenden Stelle
then left(y) := z // z als neues Blatt ein.
else right(y) := z
Minimum(z) = // input: binary search tree, output: node with min. key
while left(z) $\not=$ nil
do z := left(z)
od
return z;
Sucessor(z)
if right(z) $\not=$ nil
then return Minimum(right(z))
y:=parent(z);
while y $\not=$ nil and z=right(y)
do
z := y;
y := parent(y);
od
return y;
Delete(T,z) // T = tree, z = zu loeschender Knoten
if left(z) = nil or right(z) = nil
then y := z // y tritt an die Stelle von x
else y := Successor(z); // wobei: y hat <= 1 Kind!
if left(y) != nil // Bestimmung des Kindes x
then x := left(y) // von y.
else x := right(y);
// Ver"andern der Verzeigerung
if x != nil then p(x) = p(y); // y herausgenommen => x mit
// neuem Vater
if p(y) = nil // Spezialfall: wurzel
then root(t) = x
else if y = left(p(y))
then left(p(y)) := x
else right(p(y)) := x // y ist nun vom
// urspr. Platz entfernt
if y != z then key(z) := key(y);// z durch y ersetzen
return y;
8 Rot-schwarz-Bäume
Literatur:
Kapitel 13 aus [CLR90]. Die
Rot/Schwarz-Bäume wurden von [Bay72] eingeführt.
Inhalt
Rot/schwarz-Bäume ·Rotation ·Erhalt der
Rot-Schwarzeigenschaft bei Einfügen und Löschen
-
Hauptproblem der Suchbäume: Effizienz hängt von der
Höhe ab
- Þ je balanzierter desto "`besser"' der Baum
- für die Suche: ja; andererseits
-
dauerndes Rebalanzieren kostet auch Zeit Þ
Balanciertheitsanforderungen am besten nicht zu strikt (vor
allem bei vielen Lösch- und Einfügeoperationen
- Wie stellt man fest ob ein Baum balanciert ist?
(Laufzeit!)
- speichert man Zusatzinformationen darüber ab?
(Speicherplatz!)
-
zwei Arten von Knoten: Schwarze werden
strikt balanciert, rote dienen als
Schlupf.
- Anteil des Schlupfes muß beschränkt sein
Definition 3
Rot/schwarz-Bäume sind binäre Suchbäume mit
einem Bit Zusatzinformation: der Farbe und folgenden
Bedingungen:
-
jeder Knoten ist entweder rot oder schwarz
- Blätter (nil) sind schwarz
- die Kinder eines roten Knotens sind schwarz
- jeder einfache Pfad von einem gegebenen Knoten zu einem Blatt
enthält die selbe Anzahl an schwarzen
Knoten8
-
Bild
- Schwarzhöhe bh(x) eines Knotens: die Anzahl der
schwarzen Knoten auf den Pfaden (ausschließlich x) zu den Blättern
Lemma 2
Ein rot-schwarz Baum mit n internen Knoten hat eine Höhe
von höchstens 2lg(n+1).
-
Þ die Operationen Suchen, Minimum, Maximum, Successor,
Predecessor: O(lg n).
- Einfügen und Löschen?
-
Rotation
- Erhalt der In-Ordnung der Schlüssel
- Komplexität O(1)
- Bild
Rotate-l(t,x) = // t = Baum, x = Knoten
if (x = nil) or (right(x) = nil)
then raise error "illegal rotation"
else y := right(x); // rette y
p(y) := p(x); // ver"andere y's Verwandschaft
if y != nil
then right(x) := left(y); // beta
if (left(y)) != nil
then p(left(y)) := x;
if root(t) = x // Spezialfall
then root(t) := y
else if x = left(p(x)) // p(x) definiert
then left(p(x)) := y
else right(p(x)) := y
left(y) := x; // zum Schluss: x als Kind von y
p(x) := y;
-
Zunächst Einfügen wie im gewöhnlichen binären
Suchbaum
- Þ neues rotes Blatt.
-
keine Schwarz-Verletzung, aber u. U.
- Rot-Verletzung
- Bei rot-Verletzung:
-
Reparieren durch Umfärben und
Rotation
- keine Neueinführung von Schwarzverletzungen
Einfügen: drei Fälle (+ 3 symmetrische)
|
Die Graphik sind leider noch nicht nach HTML übersetzbar
insert(T,x)
tree_insert(T,x); // normal einfuegen
color(x) := red // am Anfang rot
while x != root(t) and color(p(x)) = red // Rot-verletzung
do
if p(x) = left(p(p(x)); // Vater ist linker Sohn
then y := right(p(p(x)) // y = Onkel, d.h.
// rechter Sohn des Opas
if color(y) = red // Onkel rot
then color(p(x)) := black; // => umfaerben
color(y) := black;
color(p(p(x)) := red;
x := p(p(x))
else if x = right(p(x)) // Onkel schwarz
then x := p(x); // x ist rechtes Kind
left_rotate(T,x)
color(p(x)) := black; // Vater <- schwarz
color(p(p(x)) := red; // Opa <- rot
right_rotate(T,p(p(x))); // Ausgleich
else ....... // analog
done;
color(root(T)) := black;
Siehe Übung
9 Graphen
Literatur:
Kapitel 23 aus [CLR90]. Breitensuche
stammt von Moore [Moo59]. Der Erfinder der Tiefensuche
scheint unbekannt. Der angegebene Algorithmus für die starken
Zusammenhangskomponenten ist von Tarjan [Tar72].
Inhalt
Graphen ·Repräsentierungen ·Erreichbarkeit ·Breitensuche ·Tiefensuche ·backtracking ·topologisches
Sortieren ·Berechnung starker Zusammenhangskomponenten
-
Wichtige Klasse von Algorithmen, viele Anwendungen
- Problem: Durchlaufen eines gegebenen Graphen,
mit Besuch der Knoten + Berechnung von Strukturinformation über den
Graphen
- Unterscheidung nach
-
Durchlaufordnung und (davon abhängig)
- Art der Zusatzinformation über den Graphen und
seine Struktur: z. B.
-
gegenseitige Erreichbarkeit von Knoten
- Zyklen
- Bäume als Teilstrukturen im Graphen
- (stark) zusammenhängende Teilgraphen
- Zwei Klassiker:
-
Tiefensuche
- Breitensuche
Graphen und ihre Repräsentierung
|
Definition 4 [Graph]
Ein (gerichteter) Graph G = (V,E): V Menge von
Knoten, EÍ V × V Kanten.
-
Zwei Standardmöglichkeiten:
-
Adjazenzlisten
- Adjazenzmatrix
Adjazenzlistendarstellung
|
-
Graph G = (V,E) dargestellt durch Array
Adj von |V| Listen von Knoten
- für alle v Î V: Adj[v] = Liste der zu v
adjazierenden Knoten
- Speicherbedarf: O(V+E)
- robuste Darstellung, anwendbar auf viele Arten von Graphen
- Nachteil: List suche zum Finden der Nachbarn.
Adjazenzmatrixdarstellung
|
-
Gegeben Graph G = (V,E), die Knoten V seien beliebig numeriert
von 1,... |V|.
- Darstellung als Matrix A = (a)ij mit
|
aij |
= |
ì í î |
|
1 |
|
falls (ui,uj) Î E |
0 |
|
sonst |
|
|
|
|
- Speicherbedarf O(V2)
- Nachteil: für dünnbesetzte Matrizen verschwenderisch
- Vorteile
-
einfache Darstellung, für kleine Graphen gut
- Transponieren der Matrix = Umkehren der Pfeilrichtung
-
klassischer Graphsuchalgorithmus, Reihe von
Verallgemeinerungen
- ursprüngliches Problem [Moo59]: kürzeste Wege in
Labyrinthen
- Problem:
-
gegeben: G = (V,E) + einen ausgezeichneten Knoten
(Quelle) sÎ V.
- gesucht: Alle von s aus erreichbaren Knoten
- Zusatzinfo:
-
Abstand d(s,u) jeden Knotens u zu
s9
- Breitensuchbaum: Baum mit kürzesten Pfaden von
s zu jedem Knoten.
-
Grundgedanke der Breitensuche:
Suche ``in die Breite'': Bevor Knoten mit Abstand k+1 von der
Quelle entdeckt (bzw. abgehandelt) werden, sind alle Knoten
mit Abstand k" entdeckt (bzw. abgehandelt)
|
- Sicherstellung der Breitendurchlaufordnung:
Behandlung der entdeckten Knoten in Fifo-Manier (Queue)
|
-
Zur Erkennen von bereits gesehenen Knoten: 3
Farben:10
-
weiß: unentdeckt
- grau: entdeckt, aber noch nicht fertigbehandelt
- schwarz: fertig (und damit auch entdeckt)
- Reihenfolge: weiß Þ grau Þ
schwarz11
- Zusatzinfo:
-
Distanz: Distanz des Knotens, von dem man entdeckt wird + 1
- Breitensuchbaum: Merken des Knotens, von dem man
entdeckt wird.
bfs (G,s) = // Graph und Quellknoten
for each vertex u in V[G] - {s}
do color[u] := white
d[u] := $\infty$;
p[u] := nil
done;
color[s] := gray;
d[s] := 0;
p[s] := nil;
Q.enqueue(s); // beginne mit der Quelle
while not Q.isempty()
do
u := Q.head(); // noch kein dequeue!
for each v in Adj[u] // alle Nachbarn
do
if color[v] = white
then color[v] := gray;
d[v] := d[u] + 1;
p[v] := u;
Q.enqueue(v) // speichere den Nachbarn
fi;
done;
Q.dequeue() // u ist abgearbeitet
color(u) := black; // und als fertig markiert
done
Breitensuchalgorithmus: Analyse
|
-
Zeitkomplexität:
-
jeder Knoten wird höchstens einmal in Q eingereiht, und damit
auch höchstens einmal aus ausgereiht:
O(V)12
- für jeden Knoten wird die Adjazenzliste durchlaufen, insgesamt
höchstens O(E)
- Þ O(E+V)
Siehe Vorlesung
-
Suche zunächst in die Tiefe13
- Wie bei Breitensuche: Farben zur Suchsteuerung
-
Weiß: ungesehen
- Grau: entdeckt
- Schwarz: fertig
- anstelle von Queue: Stack
- ``Backtracking''
- Teilgraph der Vorgänger Gp= (V,
Ep):
E |
|
={(p(v),v) | vÎ V, p(v) ¹ ^}
|
- Þ Menge von Tiefensuchbäumen (=
Tiefensuchwald, depth-first forest)
- Zusatzinformation: Zeitstempel d[v] (discovered) und
f[v] (finished) wobei d[v] < f[v].
DFS(G) // Gerichteter Graph
for all vertices $v \in V[G]$ // Initialisierung
do
color[u] := white;
pred[u] := nil;
od
time := 0; // Ende der Initialisierung
for all vertices $u \in V[G]$
do
if color[u] = white then DFS-Visit(u); // eigentl. Tiefensuche
done;
DFS-visit(u) // Eigentliche Tiefensuche
color[u] := gray; // Knoten entdeckt
time := time + 1; d[u] := time; // merke Entdeckungszeit
for each v in Adj[u] // erkunde Kante (u,v)
do if color[v] = white
then pred[v] := u // pred = Entdeckerknoten
DFS-visit(v) // rek. Abstieg
done;
color[u] := black; // u ist fertig, merke
time := time + 1; f[u] := time; // die Fertigstellzeit
-
Bild 23.5(1)
- Komplexität:
-
O(V) für DSF
- O(E) für DFS-Visit
- Þ Laufzeit für Tiefensuche: O(E+V)
[Klammerung]
Gegeben zwei Knoten u, v aus G = (V, E). Es gilt genau eine der
folgenden Bedingungen:
-
[d[u],f[u]] ist echtes Teilintervall von [d[v],
f[v]], u ist Nachfahre von v in Tiefensuchbaum
- Symmetrisch.
- [d[u],f[u]] und [d[v], f[v]] disjunkt
-
Þ Verschachtelung der Intervalle:
Knoten v ist echter Nachfolger von u im Tiefensuchwald
gdw. d[u] < d[v] < f[v] < f[u]
-
Klassifikation von Kanten:
-
Baumkante: Kanten des Tiefensuchwaldes
- Rückwärtskante: Kanten von Nachfahren zu Vorfahren
in einem Tiefensuchbaum (inkl. Selbst-Schleifen)
- Vorwärtskante: Kante von Vorfahren zu Nachfahren
gemäß einen Tiefensuchbaumes, die nicht im Baum sind
- Querkante: alle anderen
-
Anwendung der Tiefensuche
- algorithmische Umsetzung der bekannten Tatsache: jede
Halbordnung läßt sich zu einer totalen/linearen Ordnung erweitern.
- Graphdarstellung einer Halbordnung: DAG =
"`Gerüst"' der Ordnung
Zunächst ein paar Definitionen:
Definition 5
Gegeben eine binäre Relation R. Die reflexive, transitive
Hülle R of R ist gegeben als die kleinste binäre Relation
sodaß x R x und x R y R z impliziert x R
z. Mit R+ ist die Relation R R gemeint.
Definition 6
Eine binäre Relation R Í S × S auf einer Menge S ist
eine Halbordnung (Notation dann oft £ für R)
wenn sie reflexiv, transitiv, und
antisymmentrisch ist. Eine totale oder
lineare Ordnung ist eine Halbordnung mit der
zusätzlichen Eigenschaft der Vergleichbarkeit.
(" x,y. x£ y oder y£ x)
Definition 7 [DAG]
Ein gerichteter, azyklischer Graph (DAG) ist ein
gerichteter Graph ohne Zyklen, d.h., es für alle Knoten u, v Î V
gilt: Wenn u ®+ v, dann u ¹ v.
-
Eine topologische Sortierung eines DAGs G ist eine
lineare Ordnung der Knoten von G, Kompatibilität: falls (u,v) eine
Kante auf G, dann u < v in der linearen Ordnung.
- d.h., kein Sortieren wie bei den Sortieralgorithmen
- Bild
- Lineare Ordnung = Zeitstempel der Schwarzfärbung =
f-Zeit.
Topologisches Sortieren (2)
|
-
Eingabe: DAG ("`="' Halbordnung), Ausgabe: verzeigerte Liste ("`="'
lineare Ordnung)
Topological-Sort(G) // G ist ein DAG
call DFS(G) to compute finishing-times f[v] for all v;
as each vertex is finished, insert it onto the front of a list;
return the linked list
-
Komplexität: Laufzeit O(V+E).
Topologisches Sortieren (3)
|
Satz 2 [Weiße Pfade]
Gegeben Graph G. Im Tiefensuchwald für G gilt: v ist
Nachfahre von u gdw. zur Zeit d[u] der Knoten v durch
u auf einem weißen Pfad erreichbar ist.14
Lemma 3
Ein gerichteter Graph ist azyklisch gdw. die Tiefensuche
keine Rückwärtskanten produziert.
Beweis:
Sei G zyklisch Þ es gibt einen Zyklus c: w ®+ w.
Sei v der erste entdeckte Knoten aus c und u sein
Vorgänger im Zyklus. Zur Zeit d[v]: weißer
Pfad v ®* u Þ u wird Nachfolger im
Tiefensuchwald Þ Behauptung ((u,v) ist Rückwärtskante).
Þ
DFS findet Rückwärtskante (v,u), andererseits u ®* v
mittels Baumkanten (weil u Vorgänger von v im Tiefensuchbaum)
Lemma 4 [Korrektheit]
Der Algorithmus angewandt auf einen DAG, liefert eine
topologische Sortierung.
Beweis:
Prüfen der Kompatibilität: falls u ®+ v in G, dann f[u] <
f[v]. Sei (u,v) eine Kante. Wenn sie mit DFS erkundet wird,
ist v nicht Grau (sonst wäre v ein Vorfahre Þ
Rückwärtskante) Þ v ist weiß oder
schwarz.
v weiß
v wird Nachfahre von u Þ f[v] < f[u]
v schwarz
Dann sofort f[v] < f[u]
Starke Zusammenhangskomponenten
|
-
klassische Anwendung der DFS: Zerlegen eines Graphen in
stark-zusammenhängende Komponenten
Definition 8 [Starker Zusammenhang]
Gegeben gerichteter Graph G = (V, E). Eine starke
Zusammenhangskomponente von G ist eine maximale
Knotenmenge UÍ V mit: für alle u1, u2 Î U gilt
u1®* u2 und u2®*u1.
-
Idee: G und Gt haben die selben starken
Zusammenhangskomponenten Þ zweimaliges Anwenden von
DFS, auf G und auf den transponierten
Graphen15 Gt
- Komplexität: lineare Zeitkomplexität
O(E+V)
Strongly-connected-components(G) // G: gerichteter Graph
call DFS(G) to obtain finishing times f[u] for the vertices;
compute $G^t$ = transpose(G) // transponiere G
call DFS($G^t$), but in the main loop of DFS,
consider the vertices in order of
decreasing f[u]
output the vertices of each tree in the depth-first
forest from the previous step as a separated strongly
connected component.
-
Vorvater f(u) eines Knoten u ist derjenige Knoten
w mit u®* w und maximalen f[w].
Lemma 5
Der Vorvater f(u) bzgl. einer Tiefensuche ist ein
Vorgänger von u,
Für alle Knoten u gilt: u und f(u) liegen in der
selben starken Zusammenhangskomponente.
Beweis:
Folgt direkt aus dem vorangegangenen und der Definition von Vorvater.
-
es gilt also: für jede SCC ist der Vorvater
-
der erste Knoten der entdeckt wird und
- der letzte Knoten der beendet wird!
- Þ
die starke Zusammenhangskomponente von r sind
diejenigen Knoten die von r in Gt erreichbar sind.
|
10 Spannbäume
Literatur:
Kapitel 24 aus [CLR90].
Inhalt
Kantengewichte ·minimale Spannbäume·nimmersatte Strategien
(``greedy'') ·Kruskals Algorithmus ·Prims Algorithmus
-
Motivation: Verknüpfung16 einer geg. Anzahl von Knoten mit
minimalen Kosten (Verkabelung, Routing, ...)
- Verknüpfung von n Knoten: mit n-1 Kanten
- Kosten: modelliert als "` Kantengewicht"'
- Gegeben:
-
G = (V, E) ungerichtet, zusammenhängend, und mit E
Í V × V Menge potentieller Verbindungen
- Kantengewicht: w: V2 ® R.
- Gesucht: TÍ E sodaß
-
azyklisch (Baum)
- Verbindung aller Knoten (aufspannend,
spanning)
- minimale Kosten
minimal.
- Þ Problem des minimalen Spannbaums
- Beispiel für greedy-Strategie
- Bild
-
greedy-Strategie:
-
Heuristik für Optimierungsprobleme
- falls eine Entscheidung zu treffen ist: entscheide dich für die
augenblicklich beste Alternative (ohne auf mögliche
Nachfolge-Nachteile zu achten)
- iterativer Aufbau eines minimalen Spannbaumes
- Þ schrittweises Hinzufügen von Kanten.
Definition 9
Sei A Teilmenge eines minimalen Spannbaumes für G. Eine Kante
(u,v) ist sicher für A, falls A È {(u,v)}
Teilmenge eines minimalen Spannbaumes ist.
Generic-MST(G,w) =
A := 0;
while A does not form a spanning tree
do
find an edge (u,v) that is safe wrt. A
A := A $\cup$ {(u,v)}
done;
return A
-
Problem: wie findet man sichere Kanten?
Definition 10
Ein Schnitt (S, V-S) (cut) eines ungerichteten
Graphen ist eine Partition von V. Eine Kante
kreuzt den Schnitt, falls ein Endpunkt in S ist und der
andere in V-S. Ein Schnitt respektiert eine Menge von
Kanten, wenn keine Kante den Schnitt kreuzt. Eine Kante, die einen
Schnitt kreuzt, ist leicht falls ihr Gewicht von allen
kreuzenden Kanten minimal ist.17
Theorem 6
-
G = (V,E) zusammenhängend, ungerichtet, mit reellwertiger
Gewichtsfunktion w : E ® R.
- A Í E, A Teil eines min. Spannbaumes
- (S, V-S): Schnitt der A respektiert
- (u,v): leichte Kante, die den Schnitt (S, V-S)
kreuzt
- Þ (u,v) sicher für A.
minimaler-Spannbaum-Algorithmus
|
-
während der Algo läuft: A immer azyklisch
(Invariante)
- Þ GA = (V, A) Wald
- zu Beginn: Wald mit |V| (einknotigen) Bäumen
- jede hinzugefügte sichere Kante verknüpft zwei Bäume
- Þ Schleife |V| -1-mal durchlaufen
-
G = (V,E) zusammenhängend, ungerichtet, Gewichtungsfunktion
W: E ® R.
- A Í E, A Teil eines min. Spannbaumes
- C eine Zusammenhangskomponente (hier Baum) im Wald
GA = (V, A).
Es gilt: Wenn (u,v) eine leichte Kante ist die C mit
einer anderen Komponente verbindet, dann ist (u,v)
sicher für A.
-
Spezialisierung des generischen Algorithmus'
- greedy-Strategie
- direkt Umsetzung des Korollars 10
- Þ sichere Kante = die mit dem
geringsten Gewicht, die zwei Bäume verbindet.
- Þ nimm immer die Kante mit geringstem Gewicht: wenn sie zwei
Komponenten verbindet: hinzufügen, wenn nicht, ignorieren
- Hilfsfunktionen + Hilfsdatenstrukturen:
-
Wald = disjunkte Mengen/Partition (von
Knoten)
- find-set: finde Repräsentanten
- Union: Vereinigung
- Makeset: ein-elementige Menge
mst-kruskal(G,w)
A := 0; // A: Kantenmenge
// Invariante: A Teil eines min. S-B
for all $v \in V[G]$
do Make-Set(v) done; // Wald aus lauter Knoten
sort the edges of $E$ by non-decreasing weight $w$;
for all edges $(u,v) \in E$ (in order)
do
if Find-Set(u) $\not=$ Find-Set(v) // nicht im selben Baum?
then
A := A $\cup$ {(u,v)};// fuege Kante hinzu
Union(u,v); // vergroebere Partition
fi;
done;
return A;
-
Laufzeit von Kruskal:
- hängt von der Implementierung der Hilfsstukturen ab
Initialisierung |
O(V) |
Sortieren |
O(Elog(E)) |
innere Schleife |
O(E) |
(Datenrepräsentierung |
a(E,V)). |
- Þ O(Elog(E))18
-
Spezialisierung des generischen Algorithmus'
- A: kein Wald, sondern ein einzelner Baum19
- Wachsen des Baumes startend von beliebiger Wurzel r
- Baum bestimmt den Schnitt
- Þ Iterationsschitt: füge leichte Kante vom Baum
nach außerhalb des Baumes hinzu
- greedy: mit jedem Schritt wird der Baum
minimal schwerer
- Datenstruktur
-
Ordnen der Knoten außerhalb des Baumes
- Þ Priority Queue (z.B. wieder mittels Heap)
mst-prim(G,w,r) // G = Graph, w = Gewichtung, r: Wurzel
Q := V(G); // priority queue von Knoten
// Q : Knoten noch nicht im Baum
for all $u \in Q$ do key[u] := $\infty$ done;
key[r] := 0;
p(r) := nil; // r ohne parent
while Q $\not= \emptyset$
do
u := extract_min(Q); // u dem Baum hinzugefuegt
for all $v \in Adj(u)$ // bewerte alle Nachbarn
do
if $v \in$ Q and $w(u,v) < key(v)$
then p(v) := u; // parent
key(v) := w(u,v)
fi
done
done
11 Kürzeste Pfade
Literatur:
Teile von Kapitel 25/26 aus [CLR90].
[Ski97]
Inhalt
Einleitung ·Varianten ·Relaxation ·Dijkstras
Algorithmus ·Bellman-Ford ·lineare Programmierung
-
Motivation: Routing, Streckenplanung
- klass. Beispiel für ein (gutartiges)
Optimierungsproblem
- gegeben: gerichteter Graph mit Gewichtungsfunktion w:
E ® R
- Problem: finde einen Verbindung/Pfad mit
minimalen Kosten
Kürzeste Pfade: Definition
|
Definition 11 [Kürzester Pfad]
Gegeben G = (V, E) gerichteter Pfad, w : E ® R. Das
Gewicht eines Pfades p = (v0, v1, ..., vk) ist
definiert:
Das Gewicht des kürzesten Pfades von u nach v ist das
Minimum:
d(u,v) =
|
ì ï í ï î |
|
|
falls $ Pfad von u nach v |
¥ |
sonst |
|
|
Ein kürzester Pfad von u nach v ist ein Pfad von
u nach v mit w(p) = d(u,v).
-
spezifizierte Quelle (single-source): finde
einen kürzesten Pfad von einem gegeben Knoten für jeden Zielknoten
- spezifiziertes Ziel: (single-destination):
duales Problem
- spezifizierte Quelle und Ziel (single-pair)
- kürzeste Pfade für alle Knotenpaare.
-
Hier: single-source
- Eingabe: Gerichteter Graph G = (V,E) + Quelle s Î V.
- Ausgabe: nicht nur minimales Gewicht, sondern
auch Pfad
- Þ Baum kürzester Pfade bei gegebenem
Startknoten s:20 G' = (V',
E') mit:
-
V' Í V: Menge der von s aus erreichbaren
Knoten
- G': bewurzelter Baum mit Wurzel s
- der (eindeutige, einfache) Pfad von s nach v in V' ist ein
kürzester Pfad von s nach v in G.
- Repräsentierung: Im Lauf des Graphtraversierung: merke
den Vorgänger p für jeden Knoten Þ Gp =
(Vp, Ep).21
- Problem: Kanten mit negativen Kosten, insbesondere
Zyklen mit negativen Kosten.
Hauptidee:
Der kürzeste Pfad zwischen zwei Knoten
enthält kürzeste Pfade als Teile
-
Þ gutartiges Problem, viele Techniken anwendbar
-
greedy
- Relaxation
- dynamische Programmierung....
Lemma 7
geg G = (V, E), gerichtet, gewichtet mit w: E ® R.
(v1,..., vn) ist ein kürzester Pfad von v1 nach vn und w =
(vi,..., vj) mit 1£ i £ j £ n ein Teilpfad.
Dann ist w ein kürzester Pfad von vi nach vj.
-
Þ Zerlegung: Sei s v kürzester Pfad mit s
u v, dann d(s,v) = d(s,u) + w(u,v).
- "`Dreiecksungleichung"'
-
d(s,v) £ d(s,u) + w(u,v) (mit (u,v) Î E)
- d(s,v) £ d(s,u) + d(u,v)
-
Methode zur Optimierung bei gutartigen Probleme
- pessimistische (worst-case) Abschätzungen
- im Laufe des Algos immer weiter verbessert
- gutartig: ``monoton'', neue Information verbessern die
Schätzung nur
- für kürzeste Pfade: für alle v Î V: d(v)
als obere Schranke für den Pfad von s nach v.
-
generischer Relaxationsalgorithmus
-
Initialisierung: für all v Î V:
-
d(v) = ¥ (außer s)
- d(s) = 0
- p(V) = ^
- Relaxierung einer Kante: Anwendung der
Dreiecksungleichung:
[mathescape=true,tabsize=2]
Relax(v1, v2) =
if d(v2) > d(v1) + w(v1,v2)
then p(v2) := v1;
d(v2) := d(v1) + w(v1,v2)
fi
<\code>
- Unterschiedliche Algorithmen, je nachdem wann und wieoft Kanten
relaxiert werden.
-
Voraussetzung: nicht-negative Gewichtung
- S: Menge von Knoten, deren min. Gewicht bereits
bestimmt ist: d.h. v Î S Þ d(v) = d(s,v)
- Hauptschritt:
Nimm den Knoten u mit der momentan geringsten
Pfadabschätzung aus V-S, relaxiere alle an u
adjazierenden Knoten und stecke u nach S.
- Þ Hilfsstruktur: Priority Queue
- Greedy-strategie: relaxiere immer eine Kante zu dem
unbehandelten Knoten mit dem geringsten Abschätzung des
Abstandes.
- Graphsuche, gesteuert nach den besten Abschätzungen, Ähnlich
Breitensuche
- Laufzeitkomplexität:
-
Jeder Knoten einmal aus der Queue genommen:
O(V2)22
- jede Kante genau einmal relaxiert: O(E)
- Þ O(V2 + E) = O(V2).
Dijkstra(G,w,s)
Intitialise(G,s); // siehe generischer Algo.
S := $\emptyset$; // Knoten mit bekannter Distanz
Q := V; // priority queue
while $Q \not= \emptyset$
do
u := extract-min(Q);
S := $S \cup \{u\}$;
for all vertices $v \in Adj(u)$
do
relax(u,v)
done
done
-
beliebige Kantengewichtung, entdeckt
negativ-gewichtete Zyklen
- Relaxation der Abschätzung des Abstandes.
- Laufzeitkomplexität: O(V E).
- Relaxation aller Kanten, ohne Berücksichtigung ihres
Gewichtes, dafür genügend oft (d.h. keine
greedy Strategie
Bellmann-Ford(G,w,s) // G = (V,E), Gewichtung w
initialize(G,s); // s. generischer Algo.
for i := 1 to size(V)-1
do
for all edges $(u,v) \in E$
do
relax(u,v)
done
done;
for all edges $(u,v) \in E$ // Test auf negative Zyklen
do
if d(v) > d(u) + w(u,v)
then return false
else return true
done;
12 Dynamische Programmierung
Literatur:
Siehe Kapitel 43 aus [Sed90] und Kapitel 3 aus aus
[Ski97]. Zu dynamischer Programmierung findet sich
auch in [CLR90] etwas, vor allem in
Kapitel 16, aber auch sonst "uber das Buch verteilt.
Inhalt
Probleml"osung durch Zergliederung ·Optimierungsprobleme ·Ansatz der dynamischen Programmierung ·Rekursiver Probleme mit
gemeinsamen Teilstrukturen ·Beispiel: lineares Partitionieren
·Beispiel: k"urzeste Pfade ·Anwendungsbereich von DB
Dynamische Programmierung
|
Speicher-- vs. Laufzeit: das Karnickelproblem
|
-
die n-te Fibonacci-Zahl Fn:24:
F0 = 0, F1 = 1, Fn+2 = Fn + Fn+1.
fib(n) = // fibonacci; naive, recursive solution
if n=0 then return 0
else if n=1 then return 1
else return fib(n-1) + fib(n-2)
fi fi
- Problem: exponentielle Laufzeit
- Bild: Aufrufbaum
- alternativ: Speichere alle Werte
Fi:25
fib(n) = // Fibonacci: speichere alle $F_i$
$F_0 = 0$; $F_1 = 1$;
for i = 1 to n do
$F_i = F_{i-1} + F_{i-2}$
done;
- Þ lineare Laufzeitkomplexit"at.
Dynamische Programmierung
|
-
Probleml"osung durch Zerteilen (wie D& C)
- D&C: getrennte Teilprobleme, hier "uberlappenden
Unterprobleme
- Þ Vermeidung von wiederholter Berechnung durch
Speichern von Zwischenl"osungen.
- meist bei Optimierungproblemen
-
Charakterisiere die Struktur der optimalen L"osung
- Definiere rekursiv die optimale L"osung
- Berechne den Wert der opt. L"osung
bottom-up
- ggf: berechne/rekonstruiere die opt. L"osung selbst.
|
-
Problem: ``gerechte'' Aufteilung der B"ucher eines Buchregals,
Loadbalancing wechsel
- gegeben: Sequenz S = (s1, s2,..., sn), si Î
, und nat"urliche Zahl k ³ 1
- gesucht: Partition der Sequenz in k
Teilsequenzen S1, S2, ..., Sk, wobei die maximale
Teilsequenzen-Summe minimal sei Þ Problem der
linearen Partitionierung
- verschiedene Heuristiken denkbar, aber: um sicher das Optimum
zu finden: exhaustive Suche
- Rekursiver Ansatz:26: M(n,k) sei das gesuchte Optimum. Bild
|
M(n,k) |
= |
|
M(1,k) |
= |
s1 f"ur k>0 |
M(n,1) |
= |
|
|
Linear Partitionieren (2)
|
-
Komplexit"at: direkt "uber Rekurrenzgleichungen:
exponentiell
- Speichern u. Wiederverwendung von Zwischenergebnisse:
-
Darstellung der Funktionswerte von M(n,k) als Matrix
M[n,k].
- Komplexit"at:
-
Anzahl der Eintr"age: k n
- Zeit pro Berechnung eines Eintrags:27 O(n2)
- Þ Zeitkomplexit"at:28O(k n3)
- Berechnung ``bottom-up'': Durchlaufen der Matrix, da"s
Matrixeintr"age immer bereits da sind, wenn gebraucht Þ
Schleifen von kleineren Indizes zu gr"o"seren.
Partition(S,k) = // linear Partitionieren
p[0] = 0; // P = Teilsummen
for i = 1 to n do
p[i] := p[i-1] + $S_i$
done; // Initialisierung der ``Raender''
for i = 1 to n do M[i,1] := p[i] done;
for j = 1 to k do M[1,j] := $s_1$ done;
for i = 2 to n
do
for j = 2 to k do
M[i,j] := $\infty$;
for x = 1 to i-1
do
s = max(M[x,j-1],p[i]-p[x]);
if M[i,j] > s
then M[i,j] = s; // glob. Minimum
D[i,j] = x // Zur Rekonstruktion
fi
done
done
done
13 Kombinatorische Suche und Heuristiken
Literatur:
Meist aus Kapitel 5 von [Ski97].
Inhalt
Backtracking ·Einschr"ankung der Suche ·Heuristiken
-
Backtracking: systematisches Ablaufen/Generieren
aller m"oglichen L"osungen/Konfigurationen (``exhaustiv'') (vgl. Graphsuche)
- m"oglichst keine L"osung doppelt generieren
- Darstellung
-
Konfiguration A = (a1, a2,..., an), mit ai
Î Si f"ur jede Position i Î {1,..., n}
- Þ Suchraum, Konfigurationsraum: Õi=1n
Si.
- Suchen mittels Backtracking:
-
erweitere eine Teill"osung (a1,... ak) f"ur ein k £ n
mit jeweils allen Elementen f"ur Position k+1 (allen
Elementen aus Sk+1
- falls keine Erweiterung m"oglich:
Backtracking
- Vergleiche: Tiefensuche bei Graphsuche
-
Backtracking: konstruiert einen Baum von Teill"osungen.
-
Knoten: Teill"osungen
- Kanten: zwischen Teill"osungen a und a', falls a' aus
a durch einen Erweiterungsschritt des Algorithmusses erzeugt
wurde
backtrack(A) = // A = Vektor/Array $(a_1,\ldots ,a_n)$
compute $S_1$; // m"ogliche Loesungen fuer 1. Position
k := 1;
while k > 0
do
while $S_k \not= \emptyset$
do
$a_k$ = naechstes Element aus $S_k$;
$S_k := S_k\without a_k$;
if $A = (a_1,a_2,\ldots, a_k)$ Loesung, speichere sie fi;
k := k+1;
berechne S_k
done;
k := k-1 // ``Backtrack''
done
-
Naheliegend: rekursive Tiefensuche zur Durchlaufen
- Vergleiche: Tiefensuche auf Graphen: die Mengen Sk
sind "uber die Nachbarschaften/Adjazenzen gegeben
- andere Durchlaufordnungen f"ur komb. Suche m"oglich,
meist Tiefensuche am besten ( Speicherplatz)29
backtrack-dfs(A,k) =
if $A = (a_1,a_2,\ldots, a_k)$ Loesung,
then speichere sie
else k := k+1;
compute $S_{k}$;
while $S_{k} \not= \emptyset$
do
Waehle ein Element $a$ aus $S_k$.
$a_{k} := a$;
$S_k := S_k\without a$;
backtrack-dfs(A, k)
done
fi
Beispiele f"ur kombinatorische Suche
|
-
Generierung aller Permutationen von {1,... n}:
-
``anwendbar'' bei Problemem wie
Handlungsreisender30
- Suchraum der Gr"o"se n!
- Darstellung: Array A, Si: Menge der Elemente die
nicht in A[1] bis A[n-1] auftauchen
- volle L"osung (d.h. eine Permutation): k = n +1
- Bild: Lauf des Backtracking-Algorithmusses
- Generierung aller Teilmengen einer Menge {1,..., n}
-
``anwendbar'' u.a. bei Problemen der Logik/boolesche
Algebra/Schaltkreisentwurf
- Suchraum der Gr"o"se 2n
- Darstellung: boolescher Array,31 Sk = {,}.
- Bild: Lauf durch den Suchraum
- Konstruktion aller Pfade eines Graphen
- etc.
Beschr"ankung des Suchraumes
|
-
Pruning
-
exhaustive/komb. Suche: findet L"osung, falls existent
- aber: leidet unter kombinatorischer Explosion
- Problemabh"angig: oft Einschr"ankung des Suchraums m"oglich
- Þ Pruning = ``Stutzen'' des
Suchbaumes: Abbruch der Exploration des Suchraumes, sobald
man erkennt, da"s eine Teill"osung nicht mehr zu einer vollst"andigen
L"osung erweitert werden kann.
- wichtige (und selbstverst"andliche) algorithmische
Optimierungsmethode
- problemabh"angig
- Beispiel: Problem des Handlungsreisenden:
-
schlecht: alle n!-Permutationen der St"adte
ausrechnen, danach die Kosten der entsprechende Rundreisen
- besser: bei Rundreise startend von v1: falls keine
Kante von v1 nach v2: generiere die (n-2)! Permutationen mit
Pr"afix v1, v2 ... nicht.
- besser: die bislang beste Rundreise mit Kosten
k Þ keine Exploration von Teil-Rundreisen mit gleichgro"sen oder
h"oheren Kosten32
- Ausnutzen von Symmetrie
-
wichtiger Speziallfall von Pruning
- Beispiel: Handlungsreisender: es reicht, einen
Startknoten auszuw"ahlen (Rundreisen sind ``rotationssymmetrisch'')
Beispiel: Bandbreitenminimisierung
|
-
Bandwidth minimization
- Problem
-
gegeben: Graph G = (V, E), bzw. seine
Adjazenzmatrix
- gesucht welche Permutation p der Knoten
aus V minimiert die L"ange der l"angsten Kante, wenn die Kanten
linear angeordnet werden.
- Anwendungen:
-
L"osungen linearer Gleichungen (Gausselimination);
Bandbreite: max. Abstand eines nicht-Null Eintrags von der
Hauptdiagonalen33
- Schaltkreisentwurf
- ``Linearisierung'' von verzeigerten Strukturen (z.B. optimale (d.h,
minimale Suchzeit) Speicherung von Hypertextdokumenten)
- NP-vollst"andig34
-
oft f"ur Optimierungsprobleme35
- oft wichtig in der Praxis
- allerdings: allgemeine Heuristiken meist geschlagen von
problemspezifischen Heuristiken
- Simulated Annealing
- genetische Algorithmen
- Neuronale Netze
- ...
14 ``Harte'' Probleme
Literatur:
Verschiedenes aus [CLR90] oder
"ahnlichem. Vor allem Kapitel 36. Dazu Kapitel 11 aus
[HU79]. Ein (schwieriges) Standardwerk zur
Komplexit"atstheorie ist [Pap94].
Inhalt
Komplexit"atsklassen P und NP ·Algorithmisch harte Probleme
-
Bisher nur: leichte Probleme36: Laufzeit O(nk) (polynomiell,
oft quadratisch)
- allgemeine Techniken f"ur leichte Probleme
-
Greedy: entscheide dich immer f"ur den
augenblicklich gr"o"sten Gewinn
- Divide-and-Conquer: zerlege das Problem in unabh"angige
Teilprobleme und l"ose diese rekursiv
- Relaxation
- dynamische Programmierung als ``Verallgemeinerung'' von
divide-an-conquer
- viele andere:
- inh"arent harte Probleme
-
einfache Strategien scheitern; schlimmer: effiziente, komplexere
Strategien ebenfalls nicht bekannt
- oft Optimierungsprobleme
- oft praktisch relevant: Plazierungen, Scheduling,
Kostenminimierung, Probleme aus der Logik, Netzwerkdesign,
Zahlentheorie, ...
Problem des Handlungsreisenden
|
-
TSP: ber"uhmtes NP-vollst"andiges Problem
- gegeben:
-
ungerichteter Graph (Knoten = ``St"adte'')
- d: E ® R+ (``Distanz'')
- Gesucht: Teilgraph G' = (V',E') als Kreis mit V' = V
und minimalen Kosten (``beste
Rundreise'')37, d.h.
wobei n+1 sei 1 und das Minimum werde "uber alle
Permutationen p gebildet.
-
Formales Modell: Turingmaschinen o. "a.
- Klassifizierung von Problemen nach ihrer
Komplexit"at (meist Laufzeit)
- Unterscheidung
-
deterministisch (DTIME) oder
- nicht-deterministische (NTIME) Komplexit"at
- Beispiel: DTIME(n): linear
Definition 12 [NP]
Die Komplexit"atsklasse NP ist die Klasse von
Problemen, die nichtdeterministisch-polynomiell gel"ost
werden kann.
-
Alternativ: NP enth"alt die Probleme, die polynomiell
verifiziert werden k"onnen
- P = È DTIME(ni)
- NP = È NTIME(ni)
- "ahnliche Klasse auch f"ur Speicherkomplexit"at
(DSPACE, NSPACE)
-
ber"uhmte Klasse schwerer Probleme
- Klasse = ``gleichschwere'' Probleme
- Status unbekannt, wichtiges offenes Problem
-
gibt es eine polynomielle L"osung f"ur derartige Probleme oder
- gibt es eine nicht-polynomielle untere Absch"atzung
-
Reduzierbarkeit und Komplexit"atsklassen
|
-
Reduktion: Vergleich der ``Schwere'' von
Algorithmen
- analog der Klassenbildung der Berechenbarkeitstheorie
-
Beispiel: Klasse der rekursiv-aufz"ahlbaren Probleme
(r.e.)
- Reduktion als Hilfsmittel zu Aussagen "uber
Entscheidbarkeit/Rekursive Aufz"ahlbarkeit etc.
- Reduktion: Algorithmus der eine Instanz eines
Problems A in eine Instanz eines Problems B "ubersetzt.
- Þ falls B entscheidbar, re. etc., dann A
entscheidbar, oder umgekehrt, falls A unentscheidbar, dann B
ebenfalls.
- Halteproblem: falls eine
- Komplexit"atstheorie: nicht mehr nur Ja/Nein:
Komplexit"at der "Ubersetzung mu"s ber"ucksichtigt werden.
Definition 13 [Polynomielle Reduktion]
Ein Problem L1 (Sprache) ist reduzierbar in polynomieller
Zeit auf L2 (L1£p L2, wenn es einen in polynomieller Zeit
berechenbaren Algorithmus (Turingmaschine, ...) f gibt soda"s: x
Î L1 gdw. f(x) Î L2.
-
Reduktion: zweiseitig anwendbar
-
Zum Beweis der Existenz eines effizienten Algorithmus
- Beweis der Nicht-Existenz eines effizienten Algorithmus
- Þ Begriff der Vollst"andigkeit
-
Þ Definition von NP-vollst"andigen Probleme: die
``maximalen'' in NP.
Definition 14 [NP-vollst"andig]
Ein Problem L aus der Klasse NP ist NP-vollst"andig,
falls sich alle Probleme aus der Klasse NP sich polynomiell
auf L reduzieren lassen.
-
Beispiel: Hamiltonsche Kreise £p TSP
-
ber"uhmtes Graphentheoretisches Problem
- "ahnlich TSP, aber kein Optimierungsproblem
- gegeben: ungerichteter Graph
- gesucht: gibt es einen einfachen Pfad der alle Knoten enth"alt
- Reduktion auf das TSP-Problem: bilde den
vollst"andigen Graphen aus den gegebenen Knoten, gewichte die
Kanten mit 1 f"ur alle Kanten bereits in G, die anderen gewichte mit
2.
- die umgekehrte Reduktion ist viel schwerer.
-
berechtigte Frage: gibt es NP-vollst"andige Probleme?
-
Erf"ullbarkeit von Boolescher Logik/Schaltkreisen
- Problem des Handlungsreisenden
- Hamiltonsche Graphen, Max-Clique, ......
- z.B. Graphenprobleme: MINIMUM VERTEX COVER, MINIMUM
DOMINATING SET, MINIMUM EDGE DOMINATING SET, MINIMUM INDEPENDENT
DOMINATING SET, MINIMUM GRAPH COLORING, MAXIMUM ACHROMATIC NUMBER,
MINIMUM EDGE COLORING, MINIMUM FEEDBACK VERTEX SET, MINIMUM FEEDBACK
ARC SET, MINIMUM MAXIMAL MATCHING, MAXIMUM TRIANGLE PACKING MAXIMUM
H-MATCHING, MINIMUM BOTTLENECK PATH MATCHING, MINIMUM CLIQUE
PARTITION, MINIMUM K-CAPACITATED TREE PARTITION, MAXIMUM BALANCED
CONNECTED PARTITION, MINIMUM CLIQUE COVER, MINIMUM COMPLETE BIPARTITE
SUBGRAPH COVER, MINIMUM VERTEX DISJOINT CYCLE COVER, MINIMUM EDGE
DISJOINT CYCLE COVER, MINIMUM CUT COVER, ...
15 Schluß und Ausblick
Inhalt
R"uckblick ·Was wurde nicht behandelt ·Programmentwicklung
Literatur:
Keine spezielle Literatur zum abschlie"senden Kapitel, ein wenig aus
[Ski97].
-
"Uberblick "uber Datenstrukturen und ihre
Algorithmen
-
grundlegende Datenstrukturen
- Sortieren
- Graphen und Graphsuche, ...
- Grundlegende Probleml"osungsstrategien
-
Divide & Conquer,
- dyn. Programmierung
- greedy-Strategien
- Analyse (bzw. zumindest Absch"atzung) der Laufzeit
Was wurde nicht behandelt?
|
-
Von den ``klassischen'' algorithmischen Fragestellungen: von jeder
Problemklasse ein paar Instanzen behandelt, aber bei weitem nat"urlich
nicht alles
- weitere wichtige (im Kurs unbehandelte) Gebiete der Algorithmik
(u. a.)
-
numerische, arithmetische Algorithmen, Kryptographie
- Matrixberechnungen, numerische Mathematik
- Geometrische Probleme (computational geometry)
(f"ur Computergraphik, VLSI-Design, und vieles mehr)
- lineare Programmierung
- Algorithmen in bestimmten Programmiersprachen und bestimmten
Maschinemodellen
- parallele Algorithmen: L"osung von algorithmischen Problemen mittels
paralleler Rechner
- verteilte Programmierung: keine Algorithmen im engeren
Sinne (mit Berechnung von Output aus Input)
- Programmentwicklung/Softwareentwurf
Programmentwicklung im weiteren Rahmen
|
-
abstrakte Datentypen: Trennung von
Implementierung und Schnittstellen. Beispiele
-
Priority queue: stellt Einf"ugen, Lesenden und
Extrahierenden Zugriff auf das maximale Element zur Verf"ugung,
implementierbar z.B. mittels (verschiedener Arten von) Heaps, Arrays
- W"orterbuch: Wahlfreier Zugriff "uber Schl"ussel,
implementierbar mittels Hashtabelle, verschiedene B"aumen, etc.
- Þ Erh"ohte Wartbarkeit der Software
- allgemein: ``Modularisierung'' der Programmentwicklung, Teamarbeit,
Wiederverwendbarkeit, Versionenverwaltung
- Vom Problem zum korrekten Programm:
-
Spezifikation,
- Testen
- Verifizieren
- ...
References
- [AHU89]
-
Alfred Aho, John E. Hopcroft, and Jeffrey Ullman.
The Design and Analysis of Computer Algorithms.
Addison-Wesley, 1989.
- [AVL62]
-
G. M. Adel'son-Vel'skivi and E. M. Landis.
An algorithm for the organization of information.
Soviet Mathematics Doklady, 3:1259--1263, 1962.
- [Bay72]
-
R. Bayer.
Symmetric binary B-trees: Data structure and maintenance
algorithms.
Acta Informatica, 1:290--306, 1972.
- [CLR90]
-
Thomas H. Cormen, Charles E. Leierson, and Ronald L. Rivest.
An Introduction to Algorithms.
MIT Press, 1990.
- [dPF02]
-
Leonardo di Pisa (``Fibonacci'').
Liber Abaci.
1202.
- [Heu97]
-
Volker Heun.
Grundlegende Algorithmen.
Vorlesungsskript TU München, October 1997.
- [HJ89]
-
C. A. R. Hoare and Cliff B. Jones, editors.
Essays in Computing Science.
International Series in Computer Science. Prentice Hall, 1989.
- [Hoa62]
-
C. A. R. Hoare.
Quicksort.
BCS Computer Journal, 5(1):10--15, 1962.
Reprinted in [HJ89].
- [HU79]
-
J.E. Hopcroft and J.D. Ullman.
Introduction to Automata Theory, Languages, and Computation.
Addison-Wesley, 1979.
- [Knu73a]
-
Donald Knuth.
Fundamental Algorithms, volume 1 of The Art of Computer
Programming.
Addison-Wesley, Reading, Massachusetts, 1973.
- [Knu73b]
-
Donald Knuth.
Sorting and Searching, volume 3 of The Art of Computer
Programming.
Addison-Wesley, Reading, Massachusetts, 1973.
- [Moo59]
-
Edward F. Moore.
The shortest path through a maze.
In Proceedings of the International Symposium on the Theory of
Switching, pages 285--292. Harvard University Press, 1959.
- [Pap94]
-
Christos H. Papadimitriou.
Computational Complexity.
Addison-Wesley, 1994.
- [Sed90]
-
Robert Sedgewick.
Algorithms in C.
Addison-Wesley, 1990.
- [Ski97]
-
Steven S. Skiena.
The Algorithm Design Manual.
Springer, Telos, 1997.
- [Tar72]
-
Robert Tarjan.
Depth-first search and linear graph algorithms.
SIAM Journal on Computing, 1(2):146--160, June 1972.
- [Wil64]
-
J. W. J. Williams.
Heapsort (Algorithm 232).
Communications of the ACM, 7(6):347--348, 1964.
- 1
- eventuell leer
- 2
- nicht zu verwechseln mit
voll: entweder Blatt oder genau zwei geordnete Kinder.
Vollständig heißt, es gibt keine teilgefüllten ``Ebenen'' des
Baumes.
- 3
- 'a Btree = Leaf | Node of ('a Btree *
'a * 'a Btree) oder, genauer auch 'a Btree = Leaf of 'a |
Node of ('a Btree * 'a * 'a Btree), was mehr
[CLR90] entspricht
- 4
- Länge des Arrays
l = 2n -1 mit 2n - 1 ³ heapsize für minimales n. Da
die letzte Ebene teilgefüllt sein kann ist streng genommen der Baum
nicht mehr vollständig.
- 5
- es gibt daneben
Durchlaufen in Präordnung und Postordnung.
- 6
- Beachte: die rekursive L"osung
ist endrekursiv.
- 7
- Wenn die Schlüssel alle
unterschiedlich sind, gilt für die beiden ersten Ungleichungen
schärfer < anstelle £. Die erste Ungleichung gilt nur
falls das linke Kind existiert.
- 8
- einfacher Pfad: alle Knoten (des Pfades)
unterschiedlich. Falls die Pfade nur von der Eltern zu den Kindern
erlaubt sind, sind alle Pfade einfach!
- 9
- = Länge des kürzesten Pfades von s zum Knoten,
falls unerreichbar, Abstand = ¥.
- 10
- Daß man 2 Farben braucht, sollte klar sein, die
dritte kennzeichnet Knoten die man bereits entdeckt hat, deren
adjazierene Knoten man aber noch nicht kennt. Wg. Breitenstrategie
arbeitet man die Nachbarn eines neuentdeckten Knotens nicht sofort ab.
Das sind die grauen Knoten.
- 11
- graue Knoten: auf der ``Grenze'' der Breitensuche:
ein schwarzer Knoten kann nie einem weißen benachbart sein.
- 12
- Queue-Operationen enqueue und
dequeue kosten O(1), siehe
Kapitel 5.
- 13
- der vorgestellte
Algorithmus wendet dies auf alle Knoten der Reihe nach an, das ist aber
nicht zentral. Die eigentliche Tiefensuche ist DFS-Visit.
- 14
- Ein
weißer Pfad ist ein Pfad, der nur weiße Knoten enthält.
- 15
- Sei G = (V, E) gegeben, dann ist Gt = (V, Et)
wobei (v, u) Î Et, gdw. (u,v) Î E. Die starken
Zusammenhangskomponenten von G und Gt stimmen
überein.
- 16
- jeder von jedem
erreichbar. Zum Vergleich mit den starken Zusammenhangskomponenten: der
Graph ist hier ungerichtet!
- 17
- Es kann natürlich
mehrere leichte/minimale Kanten geben.
- 18
- unter der
Voraussetzung, daß a(E,V) = O(log E), z.B.,
heap-Implementierung.
- 19
- genauer: alle
anderen Bäume des Waldes sind nur Einzelknoten.
- 20
- es kann mehrere Bäume mit
kürzesten Pfaden geben. In der Regel will man einen davon.
- 21
- Vergleiche Breitensuche und
Vorgängergraph. Der Vorgängergraph des
Breitensuchalgorithmus entspricht für den Spezialfall, daß alle
Kanten das Gewicht 1 haben, den Baum der
kürzesten Pfade.
- 22
- extrahieren kostet O(V) (z.B.
Arrayimplementierung), wenn man nicht noch Zusatzannahmen macht.
- 23
- konkrete Beispiele aus der Vorlesung: Problem der
k"urzesten Pfade. Speziell f"ur D&C: Quicksort, Mergesort.
- 24
- nach
Leonardo von Pisa, ``Sohn des Bonacci'' [dPF02].
- 25
- Das Beispiel wird manchmal mi"sinterpretiert als:
``Fibonacci zeigt, wieviel schlechter rekursive L"osungen gegen"uber
iterativen sind''. Das ist hier nicht der Punkt. Man erh"alt eine
effiziente rekursive L"osung mittels
Parameterakkumulation.
- 26
- Punkt 1 und 2 des Vorgehens
bei DP
- 27
- zweifache Schleife,
eine f"ur min, eine f"ur die Summe.
- 28
- der Algorithmus l"auft
tats"achlich mit nur mit O(k n2), denn bei der Berechnung
der Summen åj=i+1n si kann man ebenfalls sparen, in dem
man Teilsummen wiederverwendet.
- 29
- Die Wahl
der Strategie h"angt nat"urlich auch von der Struktur des Problems ab;
wenn man keine speziellen Eigenschaften des Suchraums kennt/ausnutzen
will, ist Tiefensuche meist die erste Wahl, Breitensuche eher selten.
- 30
- Anwendbar meint: im Prinzip
anwendbar, aber das faktorielle Wachstum macht das Problem
``unbehandelbar'' (intractable).
- 31
- die
charakteristische Funktion
- 32
- die Abst"ande/Kosten seien als immer ³ 0
angenommen
- 33
- Es gibt Algorithmen, die Gausselimination in
O(b n2) machen, wobei b die Bandbreite ist.
- 34
- man kennt nichtmal
Approximationen mit garantierter G"ute. Allerdings gibt es ad-hoc
Heuristiken
- 35
- von denen sehr
viele ``hart'' sind
- 36
- zumindest
komplexit"atsm"a"sig
- 37
- alternativ: gibt es eine Rundreise mit Kosten £
k
February 5, 2002
This document was translated from LATEX by
HEVEA.