Algorithmen & Datenstrukturen
Wintersemester 2001/2002

Martin Steffen

1   Einleitung

Literatur: Baut auf Kapitel 1 von [CLR90] auf.
Inhalt Algorithmus ·Analyse von Algorithmen ·Zeitkomplexität und Speicherkomplexität ·Algorithmenentwurf
Aufbau
Algorithmus
Berechnungsproblem: Sortieren


Beispiel: 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 von Algorithmen


Analyse des Sortierens durch Einfügen




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

Analyse (2)




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)
   
c5
n
å
j=2
tj + (c6+c7)
n
å
j=2
(tj-1)

Analyse (3)


    tj T(n)
bester Fall sortiert 1 a n + b
schlechtester Fall rückwärts sortiert j a n2 + b n + c
Algorithmenentwurf


D&C: Mergesort


Mergesort (code)




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);



Analyse von Merge-Sort

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
Heap-Datenstruktur


Implementierung: Heaps als Array


Heapsort: Aufbau
Einfügen in den Heap




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);





Aufbau des Heaps


Build-Heap(A)  
  heapsize[A]  := length[A];
  for i= floor(length(A)/2)) downto 1  // bottom-up
  do  heapify(A,i);





Heapsort






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



Quicksort


Quicksort (2)


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







Quicksort mit arrays (1)




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


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.




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
Analyse von Quicksort: best case




3   Lineares Sortieren

Literatur: Kapitel 9 [CLR90].
Inhalt O-Notation ·Einführung ·Vergleichssortieren ·Counting sort ·Bucket sort
Einführung


Sortieren: Überblick



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
Vergleichssortieren: worst-case


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).

Abschätzung der Laufzeit(2)
Beweis:[of Theorem 1]



Lineares Sortieren: Counting Sort
Counting Sort




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
  1. erste Schleife: O(k)
  2. zweite Schleife: O(n)
  3. dritte Schleife: O(n)
Bucketsort
Bucketsort (2)




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;



Bucketsort: Analyse

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
Lösen der Rekurrenzgleichung


Sei n = bk. Auflösen durch Iteration.
T(n) =
a T(
n
b
) + f(n)
  =
a2 T(
n
b2
) + a f(
n
b
) + f(n)
...
  =
ak T(
n
bk
) +
k-1
å
i=0
ai f(
n
bi
)
  =
a
logb n
 
T æ
ç
ç
ç
è
n
b
logb n
 
ö
÷
÷
÷
ø
+
(logb n)-1
å
i=0
ai f(
n
bi
)
  =
a
logb n
 
T(1) +
(logb n)-1
å
i=0
ai f(
n
bi
)
  =
d n
logb a
 
+
(logb n)-1
å
i=0
ai f(
n
bi
)
Spezialfall: lineare Zeitkomplexität


T(n) =
d n
logb a
 
+
(logb n)-1
å
i=0
ai f(
n
bi
)
  =
d n
logb a
 
+
(logb n)-1
å
i=0
c ai
n
bi
  =
d n
logb a
 
+ c n
(logb n)-1
å
i=0
æ
ç
ç
è
a
b
ö
÷
÷
ø
i



 
  £ O(n)

Spezialfall: O(nlog n)




T(n) =
d n
logb a
 
+
(logb n)-1
å
i=0
c ai
n
bi
  =
d n
logb a
 
+ c n
(logb n)-1
å
i=0
æ
ç
ç
è
a
b
ö
÷
÷
ø
i



 
  =
d n + c n
(logb n)-1
å
i=0
1
  £ O(nlog n)
Spezialfall: polynomiale Laufzeit
T(n) =
d n
logb a
 
+
(logb n)-1
å
i=0
c æ
ç
ç
è
a
b
ö
÷
÷
ø
i



 
  £
d n
logb a
 
+ c n
æ
ç
ç
è
a
b
ö
÷
÷
ø
logb n



 
-1
a
b
-1
  £
O æ
ç
ç
ç
ç
ç
è
n
logb a
 
+ n æ
ç
ç
è
a
b
ö
÷
÷
ø
logb n



 
ö
÷
÷
÷
÷
÷
ø
  £
O æ
ç
ç
ç
è
n
logb a
 
+ n
a
logb n
 
b
logb n
 
ö
÷
÷
÷
ø
  £
O æ
è
n
logb a
 
ö
ø


5   Elementare Datenstrukturen

Literatur: Kapitel 11 [CLR90].
Inhalt Stacks und Queues ·verzeigerte Listen ·Bäume ·Implementierungsmöglichkeiten
Einleitung


Stacks und Queues
Stack als Array




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];




Queues als Array


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;
  



Verzeigerte Listen
Verzeigerte Listen (2)


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
Einleitung


Adreßtabellen mit direktem Zugriff




Search(T,k)   = return T[k];

Insert(T,x)   = T[key(x)] := x;

Delete(T,x)   = T[key(x)] := nil;

Hashtabelle
Kollisionsauflösung: Verkettung




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)];




Analyse


Definition 1  [Belegungsfaktor]   Der Belegungsfaktor a (load factor) einer Hashtabelle T[0,..., m-1] ist definiert als
a =
n
m
,
wobei n gleich die Anzahl der gespeicherten Elemente ist.
Analyse (2)
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 erfolgreich Annahmen: 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)


Hashfunktionen


Offene Adressierung


Offene Adressierung: Hashfunktionen


7   Binäre Bäume

Literatur: Kapitel 13 aus [CLR90]. Balancierte Bäume stammen aus [AVL62].
Inhalt Binäre Bäume ·Baumoperationen
Einleitung
Binäre Suchbä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

Durchlaufen


InOrder-Tree-Walk(t) =
  if   t != nil
  then InOrder-Tree-Walk(left(t));
       print (key(t));
       InOrder-Tree-Walk(right(t));



Baumoperationen: Suchen


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: code


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 



Baumoperationen: Löschen


Baumnachfolger & Minimum




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
Einführung


Definition


Definition 3   Rot/schwarz-Bäume sind binäre Suchbäume mit einem Bit Zusatzinformation: der Farbe und folgenden Bedingungen:
Lemma 2   Ein rot-schwarz Baum mit n internen Knoten hat eine Höhe von höchstens 2lg(n+1).
Rotation




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;               



Einfügen
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;



Löschen


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
Suchen in Graphen
Graphen und ihre Repräsentierung
Definition 4  [Graph]   Ein (gerichteter) Graph G = (V,E): V Menge von Knoten, EÍ V × V Kanten.
Adjazenzlistendarstellung
Adjazenzmatrixdarstellung
Breitensuche
Prinzip
Breitensuche (2)




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
Korrektheitsargument
Siehe Vorlesung

Tiefensuche
Algorithmus (1)


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;
 

Algorithmus (2)


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



Tiefensuche: Analyse


Weitere Eigenschaften
[Klammerung] Gegeben zwei Knoten u, v aus G = (V, E). Es gilt genau eine der folgenden Bedingungen:
  1. [d[u],f[u]] ist echtes Teilintervall von [d[v], f[v]], u ist Nachfahre von v in Tiefensuchbaum
  2. Symmetrisch.
  3. [d[u],f[u]] und [d[v], f[v]] disjunkt
Knoten v ist echter Nachfolger von u im Tiefensuchwald gdw. d[u] < d[v] < f[v] < f[u]

Topologisches Sortieren


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.

Topologisches Sortieren (2)




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



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


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.
Algorithmus




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.





Analyse
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.


Analyse (2)


10   Spannbäume

Literatur: Kapitel 24 aus [CLR90].
Inhalt Kantengewichte ·minimale Spannbäume·nimmersatte Strategien (``greedy'') ·Kruskals Algorithmus ·Prims Algorithmus
Minimaler Spannbaum


Spannbaumalgorithmus
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





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  

minimaler-Spannbaum-Algorithmus
Es gilt: Wenn (u,v) eine leichte Kante ist die C mit einer anderen Komponente verbindet, dann ist (u,v) sicher für A.



Kruskals Algorithmus
Kruskal (2)






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;

Kruskal (3)


Prims Algorithmus


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



Prim (3)


11   Kürzeste Pfade

Literatur: Teile von Kapitel 25/26 aus [CLR90]. [Ski97]
Inhalt Einleitung ·Varianten ·Relaxation ·Dijkstras Algorithmus ·Bellman-Ford ·lineare Programmierung
Einleitung
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:
w(p) =
k
å
i=1
w(vi-1, vi).
Das Gewicht des kürzesten Pfades von u nach v ist das Minimum:
d(u,v) = ì
ï
í
ï
î
min{w(p) | u
p
®
 
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).

Problemvarianten
Kürzeste Pfade
Kürzeste Pfade (2)


Hauptidee: Der kürzeste Pfad zwischen zwei Knoten
enthält kürzeste Pfade als Teile
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.
Relaxation
generische Relaxation
Dijkstra


Dijkstra (2)




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
  



Bellmann-Ford
Bellmann-Ford (2)






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


Dynamische Programmierung


  1. Charakterisiere die Struktur der optimalen L"osung
  2. Definiere rekursiv die optimale L"osung
  3. Berechne den Wert der opt. L"osung bottom-up
  4. ggf: berechne/rekonstruiere die opt. L"osung selbst.


Linear Partitionieren
Linear Partitionieren (2)


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
Backtracking (2)




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



Backtracking (3)




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
Beschr"ankung des Suchraumes
Beispiel: Bandbreitenminimisierung


allgemeine Heuristiken

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
Harte Probleme


Problem des Handlungsreisenden
Komplexit"atsklasse NP


Definition 12  [NP]   Die Komplexit"atsklasse NP ist die Klasse von Problemen, die nichtdeterministisch-polynomiell gel"ost werden kann.
Die Klasse NP


Reduzierbarkeit und Komplexit"atsklassen
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.
NP-Vollst"andigkeit
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.

Beispiele

15   Schluß und Ausblick

Inhalt R"uckblick ·Was wurde nicht behandelt ·Programmentwicklung
Literatur: Keine spezielle Literatur zum abschlie"senden Kapitel, ein wenig aus [Ski97].
R"uckblick
  1. "Uberblick "uber Datenstrukturen und ihre Algorithmen
    • grundlegende Datenstrukturen
    • Sortieren
    • Graphen und Graphsuche, ...
  2. Grundlegende Probleml"osungsstrategien
    • Divide & Conquer,
    • dyn. Programmierung
    • greedy-Strategien
    • Analyse (bzw. zumindest Absch"atzung) der Laufzeit
Was wurde nicht behandelt?


Programmentwicklung im weiteren Rahmen


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.