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
|
February 5, 2002