Algorithmen und Datenstrukturen
– Wintersemester 2019 –
Kapitel 03:
Sortieren
Fachbereich Informatik TU Kaiserslautern
Dozent: Dr. Patrick Michel
Folien urspr¨unglich von Prof. Dr. Adrian Ulges (Hochschule RheinMain)
12. November 2019
1
Sortieren: Motivation
Warum ist Sortieren wichtig?
1. Praxisrelevanz
▸ Sortieren ist praxisrelevant: 25% aller Rechenzeitentf¨allt auf Sortiervorg¨ange1.
▸ Beispiele: Datenbanken, Suchmaschinen, ...
2. Didaktik
▸ Sortieren ist dasEinf¨uhrungsproblemder Algorithmik.
▸ Es gibt eine Vielfalt an Ans¨atzen.
▸ Viele Standardkonzepte werden behandelt(O-Notation,
Rekursion, Divide-and-Conquer, untere Schranken, ...)
1Sch¨atzung aus den 1960er Jahren... Trotzdem auch noch heute wichtig!
2
Sortieren: Formalisierung
Praxis
▸ In der Praxis sortieren wir
beliebige Objekte (z.B. Kunden).
▸ Diese Objekte enthalten jeweils einenSchl¨ussel(z.B. Umsatz) und Nutzinformation(Kundennummer, Name, Adresse, ...).
▸ DerSchl¨usseldefiniert eineOrdnungsrelation auf Objekten.
Vorlesungsfolien: Reduktion aufs Wesentliche
Waswir sortieren ist eigentlich irrelevant: Wir ben¨otigen nur Objekte mit einer gegebenenOrdnungsrelation. Wir vereinfachen:
▸ Die Objekte sind int-Zahlen.
▸ die Ordnungsrelation ist≤ / ≥.
▸ die Nutzdaten entfallen.
3
Nutz- information Schlüssel
Sicht in der Praxis Sicht in ADS
int
Sortieren: Formalisierung
Gegeben
▸ Ein n-elementiges Array von ganzen Zahlena[0], ...,a[n−1] (oder a[1], ...,a[n]).
Gesucht
▸ Eine Permutation π∶ {0, ...,n−1} → {0, ...,n−1} so dass a[π(0)] ≤a[π(1)] ≤...≤a[π(n−1)]
▸ Beispiel (rechts): π= (3,6,8,4,0,7,1,9,2,5) Anmerkungen
▸ Die obige Problemstellung nennen wir aufsteigendes Sortieren. Bei absteigendemSortieren fordern wir
a[π(0)] ≥a[π(1)] ≥...≥a[π(n−1)]
▸ Wir messen dieLaufzeitvon Sortieralgorithmen als die Anzahl der Vergleicheund/oderLese-/Schreibzugriffe auf das Array.
4
11 17 23 2 7 29 3 13 5 19
0 1 2 3 4 5 6 7 8 9
a (unsortiert)
2 3 5 7 11 13 17 19 23 29
a (sortiert)
0 1 2 3 4 5 6 7 8 9
Sortieren: Grundannahmen
1. Internes Sortieren
▸ Wir nehmen an: Alle Daten befinden sich imHauptspeicher
→Lesezugriffe aufa[i] sind g¨unstig (O(1)).
▸ Gegenteil:Externes Sortieren: Die Daten befinden sich auf einem externen, langsamen Speicher2 (z.B. Festplatte).
2. G¨unstige Vergleiche
▸ Eine Auswertung der Ordnungsrelation ist g¨unstig (O(1)).
▸ Gegenbeispiel: Stringvergleich (O(length(string))).
3. G¨unstige Schreibzugriffe
▸ Die ¨Anderung von Werten (a[i]=c) sind g¨unstig (O(1)).
▸ Gegenbeispiel: Datenbanken ( ¨Anderungen k¨onnen Reorganisation ausl¨osen, O(n)).
2siehe auchLatency Numbers Every Programmer Should Know[4].
5
Outline
1. Einfache Verfahren: Insertionsort
2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
6
Insertionsort
Wir behandeln zun¨achst einige einfache, weniger effiziente Sortierverfahren:Insertionsort,Selectionsort,Bubblesort.
Insertionsort
▸ Annahme: Die linke Seite des Arrays (a[0],...,a[pos-1]) ist bereitssortiert.
▸ In jedem Schleifendurchlauf f¨ugen wir a[pos] an der richtigen Stellein den sortierten Bereich ein. Der sortierte Bereich ist jetzt um ein Feld gr¨oßer
(a[1],...,a[pos]).
▸ Am Ende ist das komplette Array sortiert.
7
1117 23 2 7 29 3 13 5 19 111723 2 7 29 3 13 5 19 11 1723 2 7 29 3 13 5 19 11 17 23 2 7 29 3 13 5 19 2 11 17 23 7 29 3 13 5 19 2 7 11 17 2329 3 13 5 19 2 7 11 17 23 29 3 13 5 19 2 3 7 11 17 23 2913 5 19 2 3 7 11 13 17 23 29 5 19 2 3 5 7 11 13 17 23 2919 2 3 5 7 11 13 17 19 23 29
0 1 2 3 4 5 6 7 8 9 pos
0 1 2 3 4 5 6 7 8 9 10
Insertionsort: Pseuco-Code
Ablauf
▸ In der inneren Schleife (i) werden – beginnend beipos – alle Elemente gr¨oßer a[pos] um eins nach rechtsverschoben.
▸ a[pos] wird an der richtigen Stellewieder eingef¨ugt.
▸ Die innere Schleife wird verlassen.
8
# Gegeben: Array a = a[0],a[1],...,a[n-1]
for pos = 1,.., n-1:
value = a[pos]
# Verschiebe alle Werte > value # um 1 nach rechts
for i = pos, pos-1, ..., 0:
if i>0 and a[i-1]>value:
a[i] = a[i-1]
else:
# Füge value an der # richtigen Stelle ein ...
a[i] = value
# und beende innere Schleife break
2 3 7 11 17 23 2913 5 19 2 3 7 11 17 23 29 19
0 1 2 3 4 5 6 7 8 9
i 7 6 5 4
value=13
5 2 3 7 11 17 23 29 5 19 2 3 7 11 17 23 29 5 19 2 3 7 11 17 23 29 5 19
value=13
13
Insertionsort: Aufwands-Analyse
Wir berechnen die Worst-Case-Laufzeit (= Anzahl derVergleiche) f(n). Es seiginner(pos) die Anzahl
der Vergleiche ineinem Durchlauf der inneren Schleife
(in Abh¨angigkeit von pos).
9
# Gegeben: Array a = a[0],a[1],...,a[n-1]
for pos = 1,.., n-1:
value = a[pos]
# Verschiebe alle Werte > value # um 1 nach rechts
for i = pos, pos-1, ..., 0:
if i>0 and a[i-1]>value:
a[i] = a[i-1]
else:
# Füge value an der # richtigen Stelle ein ...
a[i] = value
# und beende innere Schleife break
Insertionsort: Diskussion
▸ Insertionsort besitztquadratische Komplexit¨at./
▸ Beispiel: Benchmark einer Java-Implementierung (Insertionsort auf Zufallszahlen).
▸ Wir sehen: Verzehnfacht sich n, verhundertfachen sich (grob) Vergleiche und ¨Anderungen.
10 62 Vergleiche 27 Änderungen 0.00 secs 100 5124 Vergleiche 2516 Änderungen 0.00 secs 1000 505613 Vergleiche 252311 Änderungen 0.02 secs 10000 50393577 Vergleiche 25191792 Änderungen 0.09 secs 20000 200660443 Vergleiche 100320226 Änderungen 0.17 secs 40000 797198244 Vergleiche 398579124 Änderungen 1.15 secs 60000 1806771971 Vergleiche 903355989 Änderungen 1.31 secs 100000 5012503054 Vergleiche 2506201529 Änderungen 3.72 secs
x 10 x 100 x 100
Feldlänge n #Vergleiche #Änderungen Laufzeit (Sek.)
Was macht Insertionsort aufw¨andig?
▸ Insertionsort ben¨otigt im Vergleich zu anderen Sortierverfahren besonders viele Schreibzugriffe!
▸ Jedes Mal, wenn das Elementa[pos]ganz vorne eingef¨ugt wird, erfolgen posSchreibzugriffe!
10
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort
3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
11
Selectionsort
Ansatz
▸ Bestimme daskleinste Element und tausche es an Position 0.
▸ Tausche das n¨achstkleinste Element an Position 2.
▸ Tausche das n¨achstkleinste Element an Position 3.
▸ ...
Pseudo-Code
# Gegeben: Array a = a[0],a[1],...,a[n-1]
for pos = 0,.., n-1:
min := die Position des Minimums von a[pos],a[pos+1],...,a[n-1]
Vertausche a[pos] und a[min]
12
11 17 23 2 7 29 3 13 5 19 2 3 23 11 7 29 17 13 5 19 2 3 5 11 7 29 17 13 23 19 2 3 5 7 11 29 171323 19 2 3 5 7 1129 17 13 23 19 2 17 23 11 7 29 3 13 5 19 11 17 23 2 7 29 3 13 5 19
2 3 5 7 11 131729 23 19 2 3 5 7 11 13 17 192329 2 3 5 7 11 13 17 19 5 29 2 3 5 7 11 13 17 29 2319
2 3 5 7 11 13 17 19 23 29
0 1 2 3 4 5 6 7 8 9
pos 0 1 2 3 4 5 6 7 8 9 10
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort
4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
13
Bubblesort: Ansatz
▸ Es seipos=n-1 eine “Endposition”.
▸ Wir durchlaufen das Array von vorne bispos. Immer wenn zwei benachbarte Elementein falscher Reihenfolge sind,vertauschenwir sie.
▸ Hierbei “steigt” das gr¨oßte Element des Arrayswie eine Blasean die letzte Stelle.
▸ Wir wiederholen den Durchlauf, diesmal bispos = n-1. Danach befindet sich das zweitgr¨oßte Element an vorletzter Stelle.
▸ Wir wiederholen den Durchlauf mit pos = n-2.
▸ ...
14
11 17 23 2 7 29 3 13 5 19
11 17 2 23 7 29 3 13 5 19
11 17 2 7 2329 3 13 5 19 11 17 2 7 23 3 29 13 5 19 11 17 2 7 23 3 1329 5 19 11 17 2 7 23 3 13 5 2919 11 17 2 7 23 3 13 5 1929 17 1123 2 7 29 3 13 5 19
pos=9
0 1 2 3 4 5 6 7 8 9
(Ende)
1117 23 2 7 29 3 13 5 19 11 1723 2 7 29 3 13 5 19
i = 1 2 3 4
7 8 9 11 17 2 23 7 29 3 13 5 19 11 17 2 7 23 29 3 13 5 19 11 17 2 7 23 29 3 13 5 19 5
6 11 17 2 7 23 3 29 13 5 19 11 17 2 7 23 3 13 29 5 19
11 17 2 7 23 3 13 5 2919
Bubblesort: Pseudo-Code
15
# Gegeben: Array a = a[0],a[1],...,a[n-1]
Für alle Endpositionen pos = n-1, n-2,... 1:
Durchlaufe das Array von i = 1 bis pos An jeder Stelle i:
Falls a[i] < a[i-1]:
Vertausche beide.
pos=8
0 1 2 3 4 5 6 7 8 9
(Ende)
pos=7
0 1 2 3 4 5 6 7 8 9
(Ende)
11 2 7 17 3 13 5 192329
...
2 11 7 17 3 13 5 192329 2 7 1117 3 13 5 192329 2 7 11 3 17 13 5 192329 2 7 11 3 1317 5 192329 2 7 11 3 13 5 17192329 1117 2 7 23 3 13 5 1929 11 2 17 7 23 3 13 5 1929 11 2 7 1723 3 13 5 1929 11 2 7 17 3 23 13 5 1929 11 2 7 17 3 1323 5 1929 11 2 7 17 3 13 5 231929 11 2 7 17 3 13 5 192329
Bubblesort: Optimierung
▸ Wenn wir Gl¨uck haben, ist das Array bereits ab einer Position i < possortiert.
▸ Woran erkennen wir dies?
Ab einer bestimmten Position pos′ wurdekein Tausch mehr durchgef¨uhrt!
▸ Was bringt das?Wir k¨onnen possofort auf pos′ setzen und uns einige Iterationen sparen!
16
pos=9
0 1 2 3 4 5 6 7 8 9
(Ende)
pos=3
0 1 2 3 4 5 6 7 8 9
(Ende)
3 5 2 7 11 13 17192329 ...
3 5 11 2 7 13 17 19 23 29 3 5 2 11 7 13 17 19 23 29 3 5 2 7 1113 17 19 23 29 11 3 5 2 7 13 17 19 23 29 3 11 5 2 7 13 17 19 23 29
Keine Tauschs (rot) nötig
→ Bereich ist schon sortiert.
Wir können ihn überspringen.
→ Setze pos = 3
Bubblesort: Optimierung
17
# Gegeben: Array a = a[0],a[1],...,a[n-1]
pos = n-1 Wiederhole:
Durchlaufe das Array von i=1 bis pos pos' = 0
An jeder Stelle i:
falls a[i] < a[i-1]:
Vertausche beide.
pos' = i-1 pos = pos' bis pos < 1
pos=9
0 1 2 3 4 5 6 7 8 9
(Ende)
pos=3
0 1 2 3 4 5 6 7 8 9
(Ende)
3 5 2 7 11 13 17192329 3 5 11 2 7 13 17 19 23 29 3 5 2 11 7 13 17 19 23 29 3 5 2 7 1113 17 19 23 29 11 3 5 2 7 13 17 19 23 29 3 11 5 2 7 13 17 19 23 29
3 2 5 7 11 13 17192329 pos=1
0 1 2 3 4 5 6 7 8 9
(Ende)
3 2 5 7 11 13 17192329 2 3 5 7 11 13 17192329 pos=0 → Ende.
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort
5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
18
Mergesort
19
Mergesort
▸ Die bisher vorgestellten Suchverfahren besitzen eine
Worst-Case-Komplexit¨atvon O(n2). Sie sind in der Praxis nicht zu empfehlen.
▸ Wie erreichen wir einebessere Komplexit¨at?
▸ Idee(siehe bin¨are Suche):Divide-and-Conquer. Zerlege das Problem rekursivin zwei kleinere Teilprobleme(sortiere die H¨alften des Arrays).
▸ Die resultierenden sortierten H¨alften mischenwir anschließend ineinander.
20
11 17 23 2 7 29 3 13 5 19 11 17 23 2 7 29 3 13 5 19
2 7 11 17 23 3 5 13 19 29
2 3 5 7 11 13 17 19 23 29 Mergesort Mergesort
Mischen
Mergesort: Pseudo-Code
▸ Bestimmung der Mitte m.
▸ Rekursiver Aufruf von Mergesort f¨ur beide H¨alften, Mischen der beiden H¨alften.
▸ Sortiere das komplette Array mit mergesort(a, 0, n-1).
21
# Subroutine: sortiert Teilbereich
# a[left...right]
function mergesort(a, left, right):
if left >= right: return
m = (left + right) / 2 mergesort(a, left, m) mergesort(a, m+1, right)
Mische die Teilbereiche [left...m]
und [m+1...right] in [left...right]
# sortiere das ganze Feld mergesort(a, 0, n-1)
17 11 23 7 2 29 3 13 5 19 17 11 23 7 2 29 3 13 5 19
2 3 5 7 11 13 17 19 23 29
mergesort
17 11 23 7 2 17 11 23 17 11
11 17 23 11 17 23
mergesort mergesort
2 7 11 17 23 2 7
3 5 13 19 29
mergesort
mergesort
mergesort
Mischen
Mischen
Mischen
Mischen
Mergesort: Fazit
Zusammenfassung
▸ Die Misch-Operation eines Teilbereichs kostetO(n).
▸ Rekurrenzgleichung: T(n) =2⋅T(n/2) +n.
▸ Komplexit¨at: O(n⋅log n) → Viel, viel besser alsO(n2) (O(n⋅logn)gilt ¨ubrigens auch im Best Case + Average Case).
Fazit
▸ Gutes Allround-Sortierverfahren f¨ur alle F¨alle.
▸ Ben¨otigtExtra Speicher (O(n)) als Puffer bei der Misch-Operation.
22
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort
6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
23
Quicksort
24
Quicksort
Quicksort(C.A.R. Hoare, 1962)ist in der Praxis h¨aufig das schnellsteunter den popul¨aren Sortierverfahren.
Grundidee
▸ Ebenfalls Divide-and-Conquer(siehe Mergesort).
▸ W¨ahle ein sogenanntesPivot-Element. Bewege alle Werte in die “richtige H¨alfte” (links: <Pivot. Rechts: ≥Pivot).
▸ Sortiere die beiden Teile rekursiv.
25
1117 23 2 7 29 3 13 5 19
5 3 7 2 29 23 17 13 11
2 3 5 7 11 13 17 19 23 29 Quicksort Quicksort
teile Pivot
19
< Pivot Pivot
Quicksort: Pseudo-Code (hier: Variante nach Hoare)
▸ Das Pivot ist der Wertganz links.
▸ Subroutine teile(): teilt den Bereich in kleine Werte (links) und große Werte (rechts). Nutzt zwei Zeiger iund j:
▸ isucht von links kommend nach Werten≥pivot
▸ jsucht von rechts kommend nach Werten<pivot
▸ Ende sobald beide Zeiger sich treffen.
26
# sortiert Teilbereich a[left...right]
function quicksort(a, left, right):
if left >= right: return
pivot = a[left]
# Teile den Bereich # a[left...right], so dass:
# - a[left...m] < pivot # - a[m+1...right] >= pivot m := teile(a, left, right, pivot) quicksort(a, left, m)
quicksort(a, m+1, right)
# sortiere das ganze Feld quicksort(a, 0, n-1)
# teilt Teilbereich in kleine und große Werte function teile(a, left, right, pivot):
i = left – 1 j = right + 1 while true:
# Suche nächste Positionen:
# - i sucht große Werte in linker Hälfte # - j sucht kleine Werte in rechter Hälfte do i += 1 while i<=right && a[i] < pivot do j -= 1 while j>=left && a[j] > pivot if i>=j:
return j swap a[i] and a[j]
Quicksort: teile() – Beispiel
27
# teilt Teilbereich in kleine und große Werte function teile(a, left, right, pivot):
i = left – 1 j = right + 1 while true:
# Suche nächste Positionen:
# - i sucht große Werte in linker Hälfte # - j sucht kleine Werte in rechter Hälfte do i += 1 while i<=right && a[i] < pivot do j -= 1 while j>=left && a[j] > pivot if i>=j:
return j swap a[i] and a[j]
11 17 23 2 7 29 3 13 5 19 Pivot-Element = 11
Quicksort: Komplexit¨ at
Komplexit¨at
▸ Worst Case: O(n2) (Pivot immer gr¨oßtes Element).
▸ Best Case: O(n⋅log(n))(immer Halbierung des Feldes).
▸ Average Case: O(n⋅log(n)) →Im Mittel wirklich “quick”3. Schl¨ussel: Wahl eines “guten” Pivots
▸ zuf¨allig(Monte Carlo – Quicksort)
▸ Median aus erstem, letztem und mittleren Wert3.
Vorteile in der Praxis
▸ Quicksort ist in der Praxis h¨aufig dasschnellste der hier behandelten Verfahren.
▸ Die innere Schleife bestehe aus zwei koh¨arenten Felddurchl¨aufen(gute Lokalit¨atseigenschaften, schnell)
▸ Vergleich gegen einen festen Wert, das Pivot(schnell!)
3R. Sedgewick: Algorithmen in{C, C++, Java}
28
Quicksort: Fazit (cont’d)
Vorteile in der Praxis
▸ Im Gegensatz zu Mergesort ben¨otigt Quicksort keinen Extra-Speicher (außer dem Call Stack f¨ur die rekursiven Aufrufe, O(n) im Worst Case).
29
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
30
Heapsort
4Heapsort, unser letzter vergleichsbasierter Sortieralgorithmus, ...
▸ besitzt eineWorst-Case-Komplexit¨atvon O(n⋅log(n)),
▸ ben¨otigtkeinen zus¨atzlichen Speicher,
▸ basiert auf einer speziellen Datenstruktur, dem Heap.
B¨aume
▸ Heaps sind eine spezielle Art von B¨aumen(sp¨ater mehr).
▸ B¨aume bestehen aus Knoten (= Datenelementen) und Kanten.
▸ Die Knoten sind in Ebenen angeordnet. Auf Ebene 1 befindet sich die Wurzel.
▸ Die H¨ohe(bzw.Tiefe) des Baums entspricht der maximalen Ebene.
4Cormen et al.: Algorithmen – eine Einf¨uhrung. Oldenbourg-Verlag, 2004.
31 Wurzel
Ebene 1 Ebene 2
Ebene 3 Ebene 4
Ebene 5
Höhe = 5
Exkurs: B¨ aume
x Vater(x)
Kinder(x)
Blätter Kante verboten (Zyklus!)
Binärbaum (kein Knoten Hat mehr als Zwei Kinder)
▸ Alle Knoten (außer der Wurzel) besitzen einenElternknoten (oder Vaterknoten) und sind ihrerseitsKinderdieses Knotens.
▸ Wir unterscheiden zwischen Bl¨attern undinneren Knoten.
Innere Knoten besitzen Kinder, Bl¨atter nicht.
▸ B¨aume enthaltenkeine Zyklen.
▸ In einem bin¨aren Baumhaben Knoten maximal zwei Kinder.
32
Exkurs: B¨ aume
Vollst¨andigkeit Vollst¨andige B¨aume besitzen nur auf der letzten Ebene Bl¨atter,
Fast vollst¨andige B¨aumeauf den letzten zwei Ebenen.
Die H¨ohe eines vollst¨andigen Baums ist logarithmisch!
▸ Was ist die H¨ohe einesvollst¨andigen bin¨aren Baums mitn Elementen?
▸ Ein Baum der H¨oheh kann n=2h−1 Elemente speichern(Beweis: Induktion).
▸ Gleichung umstellen→h=log2(n+1).
▸ Zentrale Eigenschaft von B¨aumen:
Vollst¨andige B¨aume mit sehr sehr vielen Elementen sind ziemlich flach!
(Bsp. 1,000,000 Elemente→H¨ohe 20).
33 vollständig fast vollständig nicht vollständig,
nicht fast vollständig
Höhe h #Elemente n
1 1
2 3
3 7
4 15
5 31
... ...
10 1023
... ...
h 2h-1
Heaps
Heapssind eine besondere Form von B¨aumen:
▸ Sie sind bin¨ar(jeder Knoten besitzt ≤2 Kinder).
▸ Sie sind fast vollst¨andig
(die unterste Ebene wird von links aufgef¨ullt!).
▸ Sie erf¨ullen dieHeap-Eigenschaft (gleich mehr).
kein Heap, denn
nicht binär kein Heap, denn
nicht fast vollständig Könnte ein Heap sein
34
Heaps (cont’d)
Wir stellen uns das zu sortierendeArray a[1], ...,a[n] alsHeap vor!
▸ Element Nr. 1 wird zur Wurzel
▸ Elemente Nr. 2-3 bilden die 1. Ebene
▸ Elemente Nr. 4-7 bilden die 2. Ebene
▸ ...
11 17 23 2 7 29 3 13 5 19
1 2 3 4 5 6 7 8 9
11
17 23
2 7 29 3
5
13 19
1
2 3
7 6 5
4
8 9 10
10
▸ Mit folgenden einfachen Funktionen greifen wir auf den Vater/ die Kinder eines Elementes i zu:
parent(i) = ⌊i/2⌋ left child(i) =2⋅i right child(i) =2⋅i+1
35
Heaps (cont’d)
Heaps sind also bin¨are, fast vollst¨andige B¨aume. Zus¨atzlich erf¨ullen sie noch die folgendeHeap-Eigenschaft (sie sindteilsortiert):
Definition (Heap-Eigenschaft)
Wir interpretieren ein Array a[1], ...,a[n] als bin¨aren Baum. Wir bezeichnen das Array als Min-Heap(bzw.Max-Heap), wenn f¨ur alle i mit1<i ≤n gilt:
a[parent(i)] ≥a[i]
(Max-Heap)
a[parent(i)] ≤a[i]
(Min-Heap)
29
19 23
12 17 18 3
5
3 15
1
2 3
7 6 5 4
8 9 10
3
3 17
5 15 23 19
5
12 18
1
2 3
7 6 5 4
8 9 10
29 19 23 12 17 18 3 3 5 15
1 2 3 4 5 6 7 8 9 10
Max-Heap
Min-Heap
36
Heapsort: Grundidee
▸ Das zu sortierende Array sei einMax-Heap.
▸ Entnehme in jeder Iteration dieSpitze des Heaps (= das Maximum!) und f¨uge es am Ende des Arrays an.
▸ Stelle f¨ur den Rest des Heaps die Heap-Eigenschaft wieder her.
1 3 22 24 28 46 48 54 73 74 20 10 18 9 7 15 8 6 4 5
Sortierter Teilbereich Heap
Maximum des Heaps
Heap wird kleiner Tausche!
Sortierter Bereich wächst 1 20 22 24 28 46 48 54 73 74 3 10 18 9 7 15 8 6 4 5
Heap kaputt!
Repariere den Heap mit max_heapify()
1 20 22 24 28 46 48 54 73 74 18 10 15 9 7 3 8 6 4 5
37 method heapsort(a):
# turn a into a heap build_max_heap(a) for i = n, n-1, ..., 2:
swap(a[1], a[i])
# decrease the heap by 1 heapsize -= 1
# repair the heap max_heapify(a, 1)
Heapsort: max heapify(a)
▸ Ausgangssituation: Das Array aist fast ein Heap: Nur an Stelleiist die Heap-Eigenschaft verletzt.
▸ Ansatz: Lasse das “falsche” Element rekursiv an die korrekte Position sinken.
Das Array ist wieder Ein Heap (OK)!
29
4 23
17 15 18 3
5
3 12
1
2 3
7 6 5 4
8 9 10
1 Tausche das Maximum der 3 Werte hoch!
29
17 23
4 15 18 3
5
3 12
1
2 3
7 6 5 4
8 9 10
1
29
17 23
5 15 18 3
4
3 12
1
2 3
7 6 5 4
8 9 10
38
Heap Heap
Heap Heap-Eigenschaft an Stelle i verletzt!
max heapify(): Komplexit¨ at
Wie teuer ist ein Aufruf vonmax heapify()?
▸ Worst Case: Ein Element sinkt bis nach ganz unten.
▸ Je Ebene: O(1) (3 Werte vergleichen, 2 Werte tauschen).
▸ # Ebenen: H¨ohe des Baums →O(log(n)).
▸ Gesamtaufwand: O(log(n)).
39
Heap
build max heap()
ZuBeginn verwandeln wir das zu sortierende Array mit build max heap()in einenHeap.
▸ Wir durchlaufen das Array von hinten nach vorne.
▸ An jeder Positioni stellen wir f¨ur den Bereicha[i], ...,a[n] die Heap-Eigenschaft sicher, indem wirmax heapify(a, i) aufrufen.
4 1 3 2 7 9 10 14 8 16
1 2 3 4 5 6 7 8 9 10
i=5 4
1 3
2 7 9 10
8
14 16
1
2 3
7 6 5 4
8 9 10
i=4 4
1 3
2 16 9 10
8
14 7
1
2 3
7 6 5 4
8 9 10
i=3 4
1 3
14 16 9 10
8
2 7
1
2 3
7 6 5 4
8 9 10
Array a
40
method build_max_heap(a):
for i in n/2, ..., 1:
max_heapify(a, i)
build max heap() (cont’d)
i=2 4
1 10
14 16 9 3
8
2 7
1
2 3
7 6 5 4
8 9 10
i=1 4
16 10
14 7 9 3
8
2 1
1
2 3
7 6 5 4
8 9 10
16
14 10
8 7 9 3
4
2 1
1
2 3
7 6 5 4
8 9 10
Array ist ein Heap (OK)
41
build max heap()
Was ist die Komplexit¨at vonbuild max heap()?
▸ Ein Aufruf vonmax heapify() kostet O(log(n)).
▸ Wir rufen n/2 (also O(n)) malmax heapify()auf.
▸ Gesamtaufwand: O(n⋅log(n)).
▸ Anmerkung: Man kann sogar zeigen dass der Aufwand O(n)– d.h. noch g¨unstiger – ist5.
5Cormen: Algorithmen -– eine Einf¨uhrung. Oldenbourg-Verlag, 2004.
42
method build_max_heap(a):
for i in n/2, ..., 1:
max_heapify(a, i)
Heapsort: Diskussion
Aufwandsanalyse
▸ Aufwand f¨urbuild max heap():O(n).
▸ Aufwand pro Schleifendurchlauf:
O(1) +O(1) +O(log(n)) =O(log(n)).
▸ Aufwand der Schleife (n Durchl¨aufe):O(n⋅log(n)).
▸ Gesamtaufwand: O(n) +O(n⋅log(n)) =O(n⋅log(n)). Vergleich mit anderen
effizienten Sortierverfahren
▸ Besserer Worst-Case-Aufwand als Quicksort (O(n2)).
▸ Besserer Speicheraufwand als Mergesort, denn Heapsort ist in-place!
▸ In der Praxis ist Quicksort oft schneller, weil cache-effizienter.
43 method heapsort(a):
# turn a into a heap build_max_heap(a) for i = n, ..., 2:
swap(a[1], a[i]) # decrease the heap heapsize -= 1 # repair the heap max_heapify(a, 1) O(n)
n x O(1) O(1)
O(log n)
Sortieren: Video
image: [1]44
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort
8. Stabilit¨at von Sortierverfahren 9. Untere Schranke
10. Externes Sortieren 11. Abschlussbemerkungen
45
Digitales Sortieren
▸ Die bisherige Sortierverfahren vergleichen den kompletten Schl¨ussel.
▸ Im folgenden wollen wir die Strukturinformation des Schl¨ussels (d.h. die einzelnen Ziffern)nutzen(“digitales Sortieren”).
Digitales Sortieren: Grundannahmen
▸ Schl¨ussel sind Zeichenketten ¨uber einem Alphabet ausm Elementen (den einzelnen Ziffern).
▸ Wir nennenm auch die Wurzel(Latein: radix).
▸ Wir nehmen an dass die L¨ange der Schl¨ussel (und somit ihr Wertebereich) beschr¨ankt ist.
Radix m Bezeichnung Beispiel
10 Dezimalzahlen 72945
16 Hexadezimalzahlen 48fc
2 Bin¨arzahlen 0011101
256 Strings (extended ASCII) hallo.
46
Radix Exchange Sort
Voraussetzungen
▸ m-adische Zahlen (hier: Bin¨arzahlen) fester L¨angeK.
▸ Beispiel: 32-Bit-Bin¨arzahlen→232 verschiedene Schl¨ussel.
Ansatz
▸ Wir durchlaufen die einzelnen Ziffern/Bits k =0, ...,K−1 der Schl¨ussel.
▸ F¨ur jede Zifferk parti- tionieren wir rekursiv die Schl¨ussel nach der entspre- chenden Ziffer in 0 (links) und 1 (rechts).
47 function radixsort(a, left, right, k):
for i = left, ..., right:
b[i] = k-tes Bit von Element a[i]
# Teile die Elemente gemäß des k-ten Bits:
# Nullen nach links und Einsen nach rechts.
# -> Schema von Hoare (siehe QuickSort) m = teile (a, b, left, right)
if k >= L:
# letztes Bit erreicht return
else:
# sortiere nach dem nächsten Bit radixsort(a, left, m, k+1) radixsort(a, m+1, right, k+1)
# ganzen Bereich sortieren radixsort(a, 0, n-1, 0)
Radix Exchange Sort
11 17 23 2 7 29 3 13 5 19
0 1 2 3 4 5 6 7 8
0101110001 10111 00010 00111 1110100011 011010010110011 0101110001 10111 00010 00111 1110100011 011010010110011
00011 00101 00111 00010
00010 00011
01011 00101 01101 00010 00111 00011
00101 00111 01011 01101 2 3 5 7 11 13 17 19 23 29
9
10011 10111 11101 10001
0101100101 10111 00010 00111 1110100011 011011000110011 01011001010110100010 00111 1110100011 10111 1000110011 01011001010110100010 00111 0001111101 10111 1000110011 Bit k=0
00011 00101 00111 00010 01101 01011...
Bit k=1
...
00011 00010 00111 00101 Bit k=2
Bit k=3 00011 00010 00011 00010
Bit k=4 ... ... ... ...
48
Radix Exchange Sort: Diskussion
Aufwandsbetrachtung
▸ Es sei n die Anzahl der zu sortierenden Schl¨ussel und K die L¨ange der Schl¨ussel.
▸ Jedes Element des Arrays wird genauK-mal besucht (das macht insgesamt K ×n Besuche!).
▸ Jeder Besuch eines Elements kostet O(1).
→ Gesamtkomplexit¨at: O(n⋅K).
Anmerkungen
▸ Achtung:K ist keine Konstante, d.h. Der Aufwand ist abh¨angig vom darstellbaremZahlenbereich!
▸ Sortieren wir z.B.n unterschiedliche Schl¨ussel, muss gelten K ≥logm(n) (also mindestens O(n⋅log(n)).
▸ Radix Exchange Sort ist ung¨unstigbei sehr langen Schl¨ussel / großen Zahlenbereichen. Beispiel: Sortiere 10 64-Bit-Zahlen.
49
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
50
Stabilit¨ at von Sortierverfahren
Definition (Stabilit¨at)
Wir bezeichnen ein Sortierverfahren alsstabilwenn das Verfahren die Reihenfolgeidentischer Schl¨usselnicht ver¨andert.
Anmerkungen
▸ Relevanz: Objekte nicht unerwartet tauschen.
▸ Beispiel: Sortiert man nach der Note, sollte sich bei gleichen Noten die Reihenfolge der Matrikelnummern nicht ¨andern.
51 Note MatNr
1 1001 2 1002 1 1003 1 1004 1 1005 2 1006
Note MatNr 1 1003 1 1004 1 1001 1 1005 2 1006 2 1002
Note MatNr 1 1001 1 1003 1 1004 1 1005 2 1002 2 1006
OO-Calc, früher nicht
stabil Excel,
stabil
Beispiel Quicksort
▸ QuickSort istnicht stabil!
▸ Aufruf von teile(): Keine Kontrolle wie Werte links und rechts des Pivots landen.
17 23 2
A7 29 3 13 2
B19
Pivot = 11
11 17 23 2
A7 29 3 13
2
B19
teile()
11
52
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke
10. Externes Sortieren 11. Abschlussbemerkungen
53
Komplexit¨ at Algorithmus ≠ Problem
Wiederholung
▸ Komplexit¨at eines Problems :=
Komplexit¨at desbesten Algorithmus.
▸ Beispiel: Lineare SucheO(n), Bin¨are SucheO(log n).
Worst-Case-Komplexit¨at des Problems “Sortieren?
▸ Sortieren ist maximal O(n⋅log n) (siehe z.B. Mergesort).
Fragestellung: Untere Schranke des Sortierens
▸ Untere Schranke= Worst-Case-Komplexit¨at, die von keinem Verfahren unterschrittenwerden kann.
▸ Eine offensichtliche untere Schranke des Sortierens ist O(n) (wir m¨ussen alle Werte des Arrays besuchen).
▸ Wir zeigen: Auch O(n⋅log(n))ist eine untere Schranke.
54
Hier: Vergleichsbasierte Verfahren
Wir betrachten hier nurvergleichsbasierte Verfahren, die auf Schl¨usselvergleichen (a[i] <a[j]?) beruhen.
Vergleichsbasiert Selectionsort
Insertionsort Quicksort
Bubblesort Mergesort Heapsort
Nicht Vergleichsbasiert RadixExchangeSort
Countingsort Bucketsort
55
Untere Schranke O ( n ⋅ log ( n )) : Beweis
Ansatz
▸ Wir k¨onnen nicht alle Sortierverfahren analysieren./.
▸ Stattdessen f¨uhren wir einen Beweis anhand des Entscheidungsbaums von Sortierverfahren.
Beweisidee
▸ Wir erinnern uns:
Ziel von Sortieren ist es, eine passendePermutation π der Eingabedaten zu finden.
▸ Es gibt sehr viele Permutationen.
▸ Ein Sortierverfahren muss also aus sehr vielen m¨oglichen Permutationen diepassendefinden.
▸ Hieraus ergibt sich ein gewisserMindestaufwand.
56
11 17 23 2 7 29 3 13 5 19
0 1 2 3 4 5 6 7 8 9
a (unsortiert)
2 3 5 7 11 13 17 19 23 29
a (sortiert)
0 1 2 3 4 5 6 7 8 9
Wieviele Permutationen gibt es?
Definition (Permutationen)
Gegeben n Zahlen, lautet die Anzahl m¨oglicher Permutationen n! ∶= 1⋅2⋅3⋅...⋅n
Beweis per vollständiger Induktion (Illustration):
n=1 1 = 1! Permutationen Induktionsanfang
n=2 n=3
2 = 2! Permutationen
3 x 2 = 6 = 3! Permutationen
n → n+1 (n+1) x n! = (n+1)!
Permutationen Induktionsschritt
... ...
Bisher: n! Möglichkeiten
Je Möglichkeit: n+1 neue
...
57
Entscheidungsb¨ aume
▸ Um die richtige Permutation zu finden, f¨uhrt das Sortierverfahren Vergleiche durch (z.B.a3<a5?).
▸ Wir z¨ahlen zur Berechnung derLaufzeit dieseVergleiche.
▸ Es entsteht ein Entscheidungsbaum.
Beispiel: Sortiere 3 Zahlen
a1<a2 a2<a3
a1<a3
a1<a3
a2<a3
ja
ja
ja ja
ja nein
nein
nein nein nein
a1 a2 a3 a1 a3 a2 a3 a1 a2 a2 a1 a3 a2 a3 a1 a3 a2 a1
Wichtige Beobachtung: Die Anzahl der Bl¨atter entspricht der Anzahl m¨oglicherPermutationen(3!=6).
58
Beweis untere Schranke
▸ Unterschiedliche Sortierverfahren f¨uhren die Vergleiche in unterschiedlicher Reihenfolge durch (und erzeugen so unterschiedliche Entscheidungsb¨aume).
▸ K¨onnen wir denWorst-Case-Aufwandam Entscheidungsbaumablesen? →Ja: Der
Worst-Case-Aufwand entspricht der Tiefe des Baums!
▸ Wie tief muss der Entscheidungsbaum mindestens sein?
59
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
60
Motivation: Sehr große Daten sortieren
▸ Bisher: internes Sortieren →das komplette Array befindet sich imHauptspeicher.
▸ Bequemer Zugriffauf alle Elemente in O(1). Externes Sortieren
▸ Die Daten seien nun gr¨oßer als der Hauptspeicher.
▸ Sie befinden sich z.B. in Dateien auf der Festplatte.
▸ Hier erfolgt der Zugriffsequentiell (¨uber I/O-Streams).
Hauptspeicher
O(1)
Datei
... ...
61
Externes Sortieren
Ansatz
1. Verteile die Daten auf Bl¨ocke
(jeder so groß wie der Hauptspeicher).
2. Sortiere die einzelnen Bl¨ocke intern.
3. Verteile die Bl¨ocke auf mehrere Dateien.
4. Mische die Dateien(vgl. Mergesort).
Mischen vonN Dateien Ahnlich Mergesort:¨
▸ Lese vorderstes Element jeder Eingabedatei →N Werte.
▸ W¨ahle den kleinsten Wert, schreibe ihn in die Ausgabedatei.
▸ Gehe zum n¨achsten Wert.
62 Zerlegen
Sortieren
Mischen
Externes Sortieren: Beispiel
▸ Wir arbeiten mit 4 Dateien d1,d2,d3,d4.
▸ Hauptspeicher: Gr¨oße 3.
▸ Eingabedaten: in Datei d1. Schritt 1: In Bl¨ocke zerlegen, Bl¨ocke sortieren
▸ Lese Daten blockweise und sortiere Bl¨ocke
▸ Schreibe (sortierte) Bl¨ocke in d3 und d4.
63
17 11 23 2 29 7 3 13 5 19 d1
d2 d3 d4
3 13 5 19 d1
d2 d3 d4
11 17 23 2 7 29
19 d1
d2 d3 d4
11 17 23 2 7 29
3 5 13
1. einlesen, sortieren, schreiben in d3
2. einlesen, sortieren, schreiben in d4
3. einlesen, sortieren, schreiben in d3
4. einlesen, sortieren, schreiben in d4
In d3 und d4 stehen sortierte Blöcke.
Externes Sortieren: Beispiel
Schritt 2: Mischen
▸ Mische jeDreier-Bl¨ocke ausd3 und d4.
▸ Wir erhalten sortierte Bl¨ocke der Gr¨oße 6.
▸ Schreibe diese abwechselnd in d1 und d2.
▸ Wenn wir dieses Mischen wiederholen, erhalten wir sortierte 12er-Bl¨ocke, 24er-Bl¨ocke, ...
▸ Wir mischen bis die kompletten Daten sortiert sind.
64
19 3 5 13 d1
d2 d3 d4
11 17 23
2 7 29
19 d1
d2 d3 d4
3 5 13 11 17 23
2 7 29
Sechser- Blöcke 19
d1 d2 d3 d4
11 17 23 2 7 29
3 5 13
Dreier- Blöcke
d3 2 3 5 7 111317192329
4. d3 noch nach d1 kopieren!
1. Mische diese Blöcke in d1
1. Mische diese Blöcke in d2
3. Mische diese Blöcke in d3
N-Wege-Mischen
Im Beispiel wurde ein sogenanntesZwei-Wege-Mischen durchgef¨uhrt:
▸ Jeder Mischvorgang liest aus zweiDateien (z.B. d1,d2).
▸ Jeder Mischvorgang schreibt in zweiDateien (z.B. d3,d4). Verallgemeinerung:N-Wege-Mischen
▸ Mische ausN Dateien inN Dateien (N Quellen, N Senken).
▸ Je gr¨oßer N, destoschneller wachsen die Bl¨ocke und desto weniger Mischvorg¨ange werden ben¨otigt.
▸ N ist in der Praxisbeschr¨ankt:
Es gibt eine Obergrenze parallel lesbarer Dateien(z.B. Linux:
“ulimit -n”).
65
d1 d2 dN ...
dN+1 dN+2 d2N ...
N-Wege-Mischen: Analyse
Gegebene Parameter
Parameter Definition
Anzahl zu sortierender Werte n Kapazit¨at des Hauptspeichers H
Anzahl Dateien 2N
Anzahl Bl¨ocke B := n / H
Wir berechnen die Laufzeit
▸ Anzahl der Mischvorg¨ange:logN(B) =logN(n/H).
▸ Beispiel: 1 Mrd. Objekte der Gr¨oße 1KB (= 1TB), 1 GB Hauptspeicher
→ Objekte im Hauptspeicher: H = 1 Mio.
→ Anzahl Bl¨ocke: B = 1 Mrd. / 1 Mio. = 1000.
→ Anzahl Mischvorg¨ange: N=10→log10(1000) =3.
66
Outline
1. Einfache Verfahren: Insertionsort 2. Einfache Verfahren: Selectionsort 3. Einfache Verfahren: Bubblesort 4. Effiziente Verfahren 1: Mergesort 5. Effiziente Verfahren 2: Quicksort 6. Effiziente Verfahren 3: Heapsort
7. Effiziente Verfahren 4: Radix Exchange Sort 8. Stabilit¨at von Sortierverfahren
9. Untere Schranke 10. Externes Sortieren 11. Abschlussbemerkungen
67
Benchmark Sortierverfahren
Einfache vs. schnelle Sortierverfahren in der Praxis:
68
Sortierverfahren in Java
In der Bibliothekjava.util bietet die KlasseArrays Sortierverfahren als statische Methoden, z.B.sort(int[] a).
Implementierung?
▸ F¨urshort, int, long:
“Dual-Pivot” QuickSort.
▸ F¨urObjekte:
MergeSort (stabil).
Hybride Verfahren
In der Praxis werden h¨aufig Kombinationen mehrerer Verfahren verwendet.
▸ Merge-Insertion-Sort [3]
▸ Introsort [2] (Quick-/Merge)
▸ ...
69
References I
[1] 15 Sorting Algorithms in 6 Minutes.
https://www.youtube.com/watch?v=kPRA0W1kECg(retrieved: May 2019).
[2] Introsort (Wikipedia).
https://en.wikipedia.org/wiki/Introsort(retrieved: May 2019).
[3] Merge-Insertionsort (Wikipedia).
https://en.wikipedia.org/wiki/Merge-insertion_sort(retrieved: May 2019).
[4] Colin Scott.
Latency Numbers Every Programmer Should Know.
https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html(retrieved: Mar 2018).
70