VL-07: Sortieren II: HeapSort

Werbung
Organisatorisches
• Vorlesung: Gerhard Woeginger (Zimmer 4024 im E1)
• Email: [email protected]
• Webseite: http://algo.rwth-aachen.de/Lehre/SS17/DSA.php
VL-07: Sortieren II: HeapSort
• Nächste Vorlesung:
Donnerstag, Mai 11, 10:15–11:45 Uhr, Aula 1
(Datenstrukturen und Algorithmen, SS 2017)
Gerhard Woeginger
SS 2017, RWTH
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
1/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
2/28
Sortieren II / HeapSort
Heaps
• Einführung
• Heapaufbau
• HeapSort
• Prioritätswarteschlangen
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
3/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
4/28
Heaps (1)
Heaps (2)
0
16
1
Heap (deutsch: Halde, Haufen)
2
14
Ein Heap ist ein Binärbaum, der Elemente mit Schlüsseln enthält. Die
Heap-Bedingung für Max-Heaps fordert:
10
3
4
8
7
Der Schlüssel eines Knotens ist stets mindestens so gross wie die
Schlüssel seiner Kinder.
6
9
3
8 9
2
Weiters gilt:
5
7
4
1
16 14 10 8 7 9 3 2 4 1
Alle Ebenen (mit Ausnahme der untersten) sind komplett gefüllt.
0
1
2
3
4
5
6
7
8
9
Die Blätter befinden sich damit auf einer (oder zwei) Ebene(n).
Die Blätter der untersten Ebene sind linksbündig angeordnet.
Wurzel = roter Knoten
Blätter = blaue Knoten plus die beiden rechten grünen Knoten
Innere Knoten = alle Nicht-Blätter
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
5/28
Arrayeinbettung eines Heaps
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
6/28
Heaps: Eigenschaften (1)
Heaps werden oft in Arrays eingebettet:
Lemma
Vergrössert man den Schlüssel der Wurzel, so bleibt Baum ein Heap.
Arrayeinbettung
Das Array a wird wie folgt als Binärbaum aufgefasst:
Die Wurzel liegt in a[0].
Das linke Kind von a[i] liegt in a[2*i+1].
Lemma
Das rechte Kind von a[i] liegt in a[2*i+2].
Ab Position b n2 c erfüllt jedes Array die Heapbedingung.
Durch die “möglichst vollständige” Füllung der Ebenen werden
„Löcher“ im Array vermieden.
Lemma
Ein Heap hat b n2 c innere Knoten.
Vergrössert man den Baum um ein Element, so wird das Array um
genau ein Element länger.
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
7/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
8/28
Heaps: Eigenschaften (2)
Definition
Die Höhe eines Heaps ist die Anzahl von Kanten auf dem Weg von
Wurzel zum äusserst linken Blatt.
Heapaufbau
Lemma
Die Höhe H eines Heaps mit n Elementen erfüllt
(a) 2H ≤ n und (b) n ≤ 2H+1 − 1.
Ergo: H = blog2 nc und H ∈ O(log n).
Lemma
Es sei ` die Anzahl aller Knoten im linken Teilbaum, und
es sei r die Anzahl aller Knoten im rechten Teilbaum,
Dann gilt r ≤ ` ≤ 2r + 1.
Beweis: ` ≤ 2H − 1 und r ≥ 2H−1 − 1
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
9/28
Naiver Heapaufbau (1)
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
10/28
Naiver Heapaufbau (2)
2
7
3
9
14
4
1
2
3
4
5
6
7
8
9
10
10
8
16
15
2 7 10 3 14 8 16 9 4 15
void bubble ( int E [] , int pos ) {
while ( pos > 0) {
int parent = floor ( ( pos -1) /2 ) ;
if ( E [ parent ] > E [ pos ]) {
break ;
}
swap ( E [ parent ] , E [ pos ]) ;
pos = parent ;
}
}
Heap mit n Elementen hat Höhe H ≤ log n.
Heapaufbau, naiv
Damit kostet jedes Einfügen H ≈ log n Vergleiche.
Der Heap wird Reihe für Reihe von oben nach unten aufgebaut, indem
Ergo: Zum naiven Aufbau eines Heaps mit n Elementen benötigt
man O(n log n) Vergleiche, und im Worst-Case Ω(n log n) Vergleiche.
ein neues Element möglichst weit links angefügt wird, und dann
rekursiv nach oben getauscht wird, solange es grösser als sein
Elternknoten ist.
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
11/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
12/28
Heapify (1): Bessere Strategie von [Floyd, 1964]
Heapify (2)
Betrachte Element E[i] unter der Annahme, dass der rechte und linke
Teilbaum darunter bereits Heaps sind.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Problem: E[i] kann kleiner als seine Kinder sein.
Ziel: Wir wollen die beiden Teilbäume – Heaps – zusammen mit
E[i] zu einem GesamtHeap verschmelzen.
Methode: Wir lassen E[i] langsam in den Heap hineinsinken,
wodurch der gesamte Teilbaum mit Wurzel E[i] zum Heap wird.
Heapify
Finde das Maximum der Werte E[i] und seiner beiden Kinder.
Ist E[i] das grösste Element, dann ist der gesamte Teilbaum auch
ein Heap. Fertig.
void heapify ( int E [] , int n , int pos ) {
int next = 2 * pos + 1;
while ( next < n ) {
if ( next + 1 < n &&
E [ next + 1] > E [ next ]) {
next = next + 1;
}
if ( E [ pos ] >= E [ next ]) {
break ;
}
swap ( E [ pos ] , E [ next ]) ;
pos = next ;
next = 2 * pos + 1;
}
}
Andernfalls vertausche E[i] mit dem grössten Element und führe
Heapify in diesem Unterbaum weiter aus.
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
13/28
Heapify (3): Beispiel
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
14/28
Heapify (4): Laufzeit
0
16
1
2
12
3
4
14
7
7
8
2
8
Heapify vergleicht den Knoten mit den beiden Kindern.
⇒ zwei Vergleiche
10
5
6
9
Die Worst-Case Komplexität von Heapify in einem Heap mit n
Knoten ist daher maximal 2 blog2 nc.
3
9
Falls Heapify auf einen Teilbaum der Höhe h angewandt wird, ist die
Worst-Case Komplexität ≈ 2h.
1
16 12 10 14 7 9 3 2 8 1
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
15/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
16/28
BuildHeap (1)
BuildHeap (2): Korrektheit
Strategie: Wandle Array von unten nach oben (bottom-up) in Heap um.
Lemma
Der Algorithmus BuildHeap ist korrekt und terminiert.
2
7
10
3
9
1
2
3
4
5
14
4
8
Initialisierung: Jeder Knoten i ≥ bn/2c ist ein Blatt und damit
Wurzel eines trivialen Heaps.
Schleifen-Invariante: Zu Beginn der for-Schleife ist jeder Knoten
i+1, . . . , E .length die Wurzel eines Heaps.
16
15
In jedem Schritt sind alle Kinder des betrachteten Knotens i bereits
Wurzeln von Heaps (Schleifeninvariante).
void BuildHeap ( int E []) {
for ( int i = E . length / 2 - 1; i >= 0; i - -) {
heapify (E , E . length , i ) ;
}
}
⇒ Die Bedingung für den Aufruf von heapify ist erfüllt.
Dekrementierung von i stellt Schleifeninvariante wieder her.
Terminierung: Bei i = 0 ist gemäss Schleifeninvariante jeder Knoten
1, 2, . . . , n die Wurzel eines Heaps.
Nützliche Invariante: Nach jedem Aufruf von heapify(E,E.length,i) sind
die Knoten i, . . . , E.length - 1 schon Wurzeln von Heaps.
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
17/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
18/28
BuildHeap (3): Laufzeit
Fakten
h+1
• Ein Heap hat maximal
P∞dn/2 h e Knoten mit2 der Höhe h.
• Formel für q < 1:
h=0 h · q = q/(1 − q) .
HeapSort
Lemma
Die Worst-Case Komplexität von BuildHeap ist Θ(n)
Beweis:
Die Laufzeit von Heapify für einen Knoten der Höhe h ist 2h.
Die Zeitkomplexität von BuildHeap ist daher
!
blog2 (n)c l
∞
X
X
n m
h
· 2h ≤ 2n ·
= 4n ∈ O(n).
2h+1
2h
h=0
DSAL/SS 2017
h=0
VL-07: Sortieren II: HeapSort
19/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
20/28
HeapSort (1): Algorithmus und Beispiel
HeapSort (2): Analyse
41
26
17
25
3
19
6
69
Die Worst-Case Komplexität von BuildHeap ist Θ(n).
17
12
4
8
2
13
Die Worst-Case Komplexität von Heapify in einem Heap mit n
Knoten ist maximal 2 log2 n.
34
Für HeapSort erhalten wir somit:
!
n−1
X
W (n) ≤
2 log2 n + n
0
41 26 17 25 19 17 8 3 6 69 12 4 2 13 34 0
1
2
3
4
5
6
7
i=1
void heapSort ( int E []) {
BuildHeap ( E ) ;
for ( int i = E . length - 1; i > 0; i - -) {
swap ( E [0] , E [ i ]) ;
heapify (E , i , 0) ;
}
}
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
=
2 · (n − 1) · log2 (n) + n
∈
O(n log n)
Zusätzlicher Speicherplatzbedarf ist konstant (lokale Variablen).
21/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
22/28
HeapSort (3): Zusammenfassung
Prioritätswarteschlangen
HeapSort sortiert in O(n log n) Zeit.
HeapSort verwendet O(1) zusätzlichen Speicherplatz.
HeapSort ist ein in-place Algorithmus.
HeapSort ist nicht stabil.
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
23/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
24/28
Die Prioritätswarteschlange (1)
Die Prioritätswarteschlange (2)
Prioritätswarteschlange (priority queue)
void insert(PriorityQueue pq, Element e, int k)
mit dem Schlüssel k in pq ein.
Betrachte Elemente (Datensätze), die mit einem Schlüssel (key)
versehen sind.
fügt das Element e
gibt das Element mit dem kleinsten
Schlüssel zurück; benötigt nicht-leere pq.
Element getMin(PriorityQueue pq)
Jeder Schlüssel sei höchstens an ein Element vergeben.
entfernt das Element mit dem
kleinsten Schlüssel; benötigt nicht-leere pq.
void delMin(PriorityQueue pq)
Schlüssel werden als Priorität betrachtet.
setzt den Schlüssel
von Element e auf k; e muss in pq enthalten sein.
k muss ausserdem kleiner als der bisherige Schlüssel von e sein.
void decrKey(PriorityQueue pq, Element e, int k)
Mit Heaps ist eine effiziente Implementierung möglich.
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
25/28
Drei Prioritätswarteschlangenimplementierungen
Operation
Array
sortiertes Array
isEmpty(pq)
insert(pq,e,k)
getMin(pq)
delMin(pq)
decrKey(pq,e,k)
Θ(1)
Θ(1)
Θ(n)
Θ(n)*
Θ(1)
Θ(1)
Θ(n)*
Θ(1)
Θ(1)
Θ(n)*
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
26/28
Organisatorisches
Heap
Θ(1)
Θ(log n)
Θ(1)
Θ(log n)
Θ(log n)
• Nächste Vorlesung:
Donnerstag, Mai 11, 10:15–11:45 Uhr, Aula 1
• Webseite: http://algo.rwth-aachen.de/Lehre/SS17/DSA.php
* Beinhaltet das Verschieben aller Elemente rechts von k.
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
27/28
DSAL/SS 2017
VL-07: Sortieren II: HeapSort
28/28
Herunterladen