SS14 Algorithmen und Datenstrukturen 6. Kapitel Sortieralgorithmen Martin Dietzfelbinger Mai 2014 FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 6.1 Erinnerung, Grundbegriffe a1 a2 a3 an 10 1 4 5 1 9 4 1 8 US SO RA OR RT HM LG IE IT Item: Schlüssel Daten: 1 1 1 4 4 5 8 9 10 SO RT IE RA LG OR IT HM US x "key" dat "data" "Stabilität" FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 1 Sortierproblem, abstrakt: Siehe Kapitel 1. Input: Array A[1..n], Einträge: Schlüssel, Daten. Achtung: In Bildern unterdrücken wir oft den Datenteil. Das Objekt wird also einfach als Schlüssel dargestellt. Schon gesehen: Sortieralgorithmus Straight Insertion Sort. Bei Vorkommen gleicher Sortierschlüssel: Sortierverfahren heißt stabil, wenn Objekte mit identischen Schlüsseln in der Ausgabe in derselben Reihenfolge stehen wie in der Eingabe. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 2 Maßzahlen für Sortieralgorithmen: In Abhängigkeit von n fragen wir nach: 1. Laufzeit (in O-Notation); 2. Anzahl der Vergleiche ( wesentliche Vergleiche“) ” A[i].key < A[j].key bzw. A[i].key ≤ A[j].key; 3. Anzahl der Datenverschiebungen oder Kopiervorgänge (wichtig bei sehr umfangreichen Objekten); 4. der neben dem Array A[1..n] (bzw. der zu sortierenden Liste) benötigte Speicherplatz; wenn dieser nur O(1), also von n unabhängig, ist: Algorithmus arbeitet in situ“ oder in-place“. ” ” FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 3 Straight Insertion Sort: 1 2 n(n n2 2 − 1) ≈ Vergleiche Schlechtester Fall: (kann auch vorkommen); Zeit Θ(n2). Bester Fall: n − 1 Vergleiche, Zeit Θ(n). Durchschnittlicher Fall, alle Eingabereihenfolgen gleich wahrscheinlich: Zeit Θ(n2). Stabil, in situ. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 4 6.2 Mergesort (engl. merge = vermengen, reißverschlussartig zusammenfügen) Algorithmenparadigma oder -schema: Divide-and-Conquer“ (D-a-C) ” ( teile und herrsche“) ” FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 5 MergeSort ist Sortierproblem. konkreter D-a-C-Algorithmus für das Lege eine Trivialitätsschranke n0 ≥ 1 fest. (Einfachster Fall: n0 = 1, für n = 1 ist nichts zu tun.) Falls 2 ≤ n ≤ n0: Sortiere mit einem naiven Algorithmus (z. B. Straight Insertion Sort). Sonst: Teilen: Setze m := dn/2e. Rekursion für Teilprobleme: Sortiere a1, . . . , am rekursiv, Ergebnis a01, . . . , a0m; sortiere am+1, . . . , an rekursiv, Ergebnis a0m+1, . . . , a0n. Kombinieren: Mische“ die Folgen a01, . . . , a0m und a0m+1, . . . , a0n zu einer ” sortierten Folge zusammen. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 6 Prozedur r MergeSort(a, b): A: a 5 b m 7 2 3 6 3 1 a 2 2 b m 3 5 6 3 Rekur− sion Rekur− sion A: 5 7 1 2 3 3 5 Aufteilen von A[a . . . b] in A[a . . . m] und A[m + 1 . . . b]. Rekursives Sortieren der beiden Segmente. Dann: Merge(a, m, b). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 7 Datenstruktur: 2 Arrays A[1..n] (Ein-/Ausgabe), B[1..n] (Hilfsarray) Prozedur r MergeSort(a, b) // für 1 ≤ a < b ≤ n, sortiert A[a..b] (1) if b − a ≤ n0 (2) then StraightInsertionSort(a, b) (3) else (4) m ← b(a + b)/2c; (5) if a < m then r MergeSort(a, m); (6) if m + 1 < b then r MergeSort(m + 1, b); (7) Merge(a, m, b) // benutzt Hilfsarray B[1..n] Prozedur MergeSort(A[1..n]) // sortiert A[1..n] (1) if n ≤ n0 (2) then StraightInsertionSort(1, n) (3) else r MergeSort(1, n). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 8 Benötigt: Prozedur Merge(a, m, b), 1 ≤ a ≤ m < b ≤ n die folgendes tut: Voraussetzung: A[a . . m] und A[m + 1 . . b] sind sortiert. Resultat: A[a . . b] ist sortiert. Ansatz: Quasiparalleler Durchlauf durch die Teilarrays: Reißverschlussverfahren“. ” Benutze Hilfsarray B[a . . b] für das Ergebnis. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 9 Prozedur Merge(a, m, b) // für 1 ≤ a ≤ m < b ≤ n, sortiert A[a..b], // nimmt an, dass A[a..m] und A[m + 1..b] sortiert sind. (1) i ← a; // Index in A[a..m] (2) j ← m + 1; // Index in A[m + 1..b] (3) k ← a; // Index in B[a..b] (4) while i ≤ m and j ≤ b do (5) if A[i] ≤ A[j] (6) then B[k] ← A[i]; i++; k++ (7) else B[k] ← A[j]; j++; k++ // verbleibendes Schlussstück kopieren: (8) while i ≤ m do B[k] ← A[i]; i++; k++; (9) while j ≤ b do B[k] ← A[j]; j++; k++; // B[a..b] nach A[a..b] kopieren: (10) for k from a to b do A[k] ← B[k]. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 10 a b m 2 3 5 6 7 1 2 3 3 5 1 2 2 3 3 3 5 5 6 7 a m b Mischen von A[a . . . m] und A[m + 1 . . . b]. Rote Pfeile: i–k-Paare in Zeile (6). Grüne Pfeile: j–k-Paare in Zeile (7). Blaue Pfeile: i–k-Paare in Zeile (8), Kopieren. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 11 Eigenschaften von Prozedur Merge(a, m, b): • korrekt; • höchstens b − a Schlüsselvergleiche, (bei jedem Vergleich ein Transport, am Ende mindestens ein Eintrag übrig); • Zeitbedarf Θ(b − a); • Merge ist stabil (Vergleichsmodus Zeile (5) bevorzugt linkes Teilarray). Satz 6.2.1 MergeSort(A[1..n]) sortiert korrekt und stabil. Beweis: Durch Induktion über rekursive Aufrufe von r MergeSort(a, b) zeigt man, dass diese Prozedur die behaupteten Eigenschaften hat. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 12 Zeitanalyse von Mergesort über Rekurrenzgleichungen“: ” Wir zählen nur Vergleiche. C(n) = Anzahl der Vergleiche beim Sortieren von n Eingaben, schlechtester Fall. C(1) = 0, C(2) = 1, C(3) = 3. C(n) = C(dn/2e) + C(bn/2c) + n − 1. Ausrechnen von Hand liefert: n C(n) 1 0 2 1 3 3 4 5 5 8 6 11 7 14 8 17 9 21 10 25 11 29 12 33 13 37 14 41 Sieht stückweise linear aus. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 13 ... ... Zunächst: Zweierpotenzen n = 2k , k ≥ 0. n C(n) 1 0 2 1 4 5 8 17 16 49 32 129 ... ... Geratene Behauptung: k ≥ 0 ⇒ C(2k ) = (k − 1) · 2k + 1. Beweis durch Induktion über k. Für k = 0: C(20) = (0 − 1) · 20 + 1. Sein nun k ≥ 1. Die I.V. sagt: C(2k−1) = (k − 2) · 2k−1 + 1. I.-Schr.: Nach Rekurrenz: C(2k ) = 2C(2k−1) + 2k − 1. Nach I.V.: C(2k ) = 2((k−2)·2k−1 +1)+2k −1 = (k−1)·2k +1. Behauptung (umgeschrieben): C(2k ) = 2k · k − 2k + 1 = 2k · (k + 1) − 2k+1 + 1. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 14 n C(n) 1 0 2 1 3 3 4 5 5 8 6 11 7 14 8 17 9 21 10 25 11 29 12 33 13 37 14 41 Zwischen 2k−1 und 2k jeweils Zuwachs um k. Damit Vermutung: Für 2k−1 < n ≤ 2k , d. h. k = dlog ne: C(n) = (k − 2)2k−1 + 1 + (n − 2k−1)k = nk − 2k + 1. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 15 ... ... Satz 6.2.2 C(n) = ndlog ne − 2dlog ne + 1, für n ≥ 1. Beweis: Induktion über n, unter Benutzung der Behauptung“ ” k für n = 2 . I.-Anf.: n = 1 = 20 und n = 2 = 21, durch Beh. erledigt. Für den Induktionsschritt sei nun n > 2. Sei k = dlog ne ≥ 2. Wenn n = 2k , benutze Beh. – Ab hier: 2k−1 < n < 2k . Es gilt bn/2c ≤ dn/2e < n und 2k−2 < dn/2e ≤ 2k−1. Also gilt log dn/2e = k − 1, und nach I.V. haben wir: (1) FG KTuEA, TU Ilmenau C(dn/2e) = dn/2e · (k − 1) − 2k−1 + 1. Algorithmen und Datenstrukturen – SS14 – Kapitel 6 16 1. Fall: 2k−2 < bn/2c. – Nach I.V.: C(bn/2c) = bn/2c · (k − 1) − 2k−1 + 1. (2) 2. Fall: 2k−2 = bn/2c. – Nach Beh. (umgeschrieben) ist C(bn/2c) = bn/2c · (k − 1) − 2k−1 + 1, also wieder (2)! Aus der Rekurrenzgleichung erhalten wir, mit (1) und (2): C(n) = C(dn/2e) + C(bn/2c) + n − 1 = dn/2e(k − 1) − 2k−1 + 1 + bn/2c(k − 1) − 2k−1 + 1 + n − 1 = n(k − 1) + n − 2k + 1 = nk − 2k + 1 = ndlog ne − 2dlog ne + 1, das ist die Induktionsbehauptung. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 17 Korollar 6.2.3 C(n) ≤ n log n, für n ≥ 1. Beweis: Wenn n = 2k ist, gilt nach Satz 6.2.2: C(n) = 2k · k − 2k + 1 ≤ nk = n log n. Wenn 2k−1 < n < 2k ist für ein k ≥ 1, dann gilt: nk − 2k + 1 = n(k − 1) + (n − 2k + 1) ≤ n log n. Korollar 6.2.4 Die Rechenzeit von Mergesort ist Θ(n log n). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 18 Variante 1: (Sollte man immer anwenden) Ziel: Überproportional hohen Zeitaufwand für die vielen rekursiven Aufrufe bei kleinen Teilarrays vermeiden. Strategie: In r MergeSort kleine Teilarrays (b − a ≤ n0) durch (z. B.) StraightInsertionSort sortieren. Das optimale n0 auf einer konkreten Maschine kann ein Experiment liefern. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 19 Variante 2: Mergesort für lineare Listen. Wenn die Liste L nur ein Element enthält, ist nichts zu tun. Andernfalls wird L in 2 Teillisten L1, L2 der halben Länge zerteilt. (Durchlaufe L, hänge die Einträge abwechselnd an L1 und L2 an. Kosten: Θ(Länge von L).) Auf L1 und L2 wird der Algorithmus rekursiv angewendet. Dann werden die beiden nun sortierten Listen gemischt“. ” Analyse der Vergleiche: Identisch zum Obigen. Laufzeit: Proportional zur Anzahl der Vergleiche, also auch Θ(n log n). Kein zusätzlicher Platz (aber Zeiger für die Liste). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 20 Variante 3: Iteratives Mergesort, bottom-up“ ” Idee: Arbeite in Runden i = 1, 2, 3, . . . , dlog ne. Vor Runde i: Teilarrays A[(l − 1) · 2i + 1 . . . l · 2i] sind aufsteigend sortiert. (Eventuell ist das am weitesten rechts stehende Teilarray unvollständig.) In Runde i führe merge((l − 2) · 2i + 1, (l − 1)2i, l · 2i) aus, für l = 2, 4, 6, . . . , 2bn/2i+1c. (Mögliche Sonderrolle für unvollständige Teilarrays ganz rechts.) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 21 Beispiel: Runde A: 7 2 9 6 3 1 6 10 2 17 12 6 2 7 4 C: 2 7 6 9 1 3 6 10 2 17 6 12 2 7 4 A: 2 6 7 9 1 3 6 10 2 6 12 17 2 4 7 C: 1 2 3 6 6 7 9 10 2 2 4 6 7 12 17 A: 1 2 2 2 3 4 6 6 7 7 9 10 12 17 1 2 3 4 FG KTuEA, TU Ilmenau 6 Algorithmen und Datenstrukturen – SS14 – Kapitel 6 22 Trick, spart Kopierarbeit: Benutze 2 Arrays, die in jeder Runde ihre Rolle wechseln. Zeitverhalten von iterativem Mergesort: wie bei rekursivem Mergesort. Alternative, einfache Zeitanalyse für diese Variante: Jede Runde kostet offenbar < n Vergleiche und Zeit Θ(n), und es gibt dlog ne Runden: also ist die die Zahl der Vergleiche < ndlog ne und die Gesamtzeit Θ(ndlog ne) = Θ(n log n). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 23 Variante 4: Mehrweg-Mergesort Besonders in Kombination mit dem iterativen Verfahren interessant ist die Möglichkeit, in einer merge-Runde nicht nur ein Array, sondern 3, 4, oder mehr Teilarrays zusammenzumischen. Beispiel: A: 1 7 9 13 2 5 7 9 3 6 6 10 2 4 11 12 4−way merge: wähle kleinstes Element aus 4 (Rest−)Arrays. C: 1 2 2 3 4 5 6 6 7 7 Die nächsten gewählten Elemente: "9" aus 1.Teilarray, "9" aus 2. Teilarray, 10, 11, etc. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 24 Beim Sortieren von Daten auf Hintergrundspeichern (Platten) bringt diese Variante gebenüber dem Mischen von zwei Teilfolgen Laufzeitvorteile, da die Anzahl der Seitenfehler (page faults) verringert wird. Für Sortieren von großen Datenmengen auf Hintergrundspeichern sollte man Mergesort-artige Verfahren benutzen. Ein ähnlicher Effekt tritt auch beim Sortieren im Hauptspeicher ein: Die Zahl der Cachefehler (cache misses) sinkt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 25 6.3 Quicksort - Schnelles Sortieren im Durchschnitt Folgt ebenfalls dem Divide-and-Conquer-Ansatz Zentrale Teilaufgabe: Sortiere (Teil-)Array A[a..b], wo 1 ≤ a ≤ b ≤ n. Zu erledigen von einer (rekursiven) Prozedur r qsort(a, b). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 26 Kernstück: Partitionieren und rekursiv sortieren. Quicksort−Schema: Teilaufgabe: Sortiere A[a...b] x 4 a 23 17 9 1 5 8 b 16 Partitioniere! 5 1 FG KTuEA, TU Ilmenau 4 8 1 9 p 23 16 17 9 >9 Sortiere rekursiv Sortiere rekursiv 4 5 8 9 16 17 Algorithmen und Datenstrukturen – SS14 – Kapitel 6 23 27 Ansatz für r qsort(a, b), Korrektheit: 0) Trivialitätstest: Wenn a ≥ b, ist nichts zu tun. (Oder: Wenn b − a < n0: StraightInsertionSort(a, b).) 1. Teile: (Hier partitioniere“) ” Wähle x ∈ {A[a].key,...,A[b].key}. Arrangiere die Einträge von A[a..b] so um, dass A[a].key, . . . ,A[p − 1].key ≤ A[p].key = x < A[p + 1].key, . . . ,A[b].key für ein p mit a ≤ p ≤ b. 2. Teilprobleme rekursiv lösen: r qsort(a, p − 1) (links), r qsort(p + 1, b) (rechts). Nach I.V. gilt nun: A[a..p − 1], A[p + 1..b] sortiert. 3. Kombiniere: Es ist nichts zu tun: A[a..b] ist sortiert. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 28 Kernstück: Partitionieren. Woher nehmen wir das Pivotelement“ x? ” Beispiele: x := A[a].key (erstes Element des Teilarrays) x := A[b].key (letztes Element des Teilarrays) x := A[b(a + b)/2c].key (Mitte des Teilarrays) Clever Quicksort“: x ist der Median von A[a].key, ” A[b].key, A[b(a + b)/2c].key Randomisiertes Quicksort“ ” Wähle einen der b − a + 1 Einträge in A[a..b] zufällig. Allgemein: Wähle ein s mit a ≤ s ≤ b, vertausche A[a] mit A[s]. Dann: Pivotelement x := A[a].key. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 29 Kernstück: Partitionieren. Pivotelelement: x = A[a].key Klar: b−a Vergleiche genügen, um Schlüssel ≤ x nach links“ ” und Schlüssel > x nach rechts“ zu schaffen, und den Eintrag ” mit dem Pivotelement an die Stelle zwischen den Bereichen zu schreiben. Eine sehr geschickte Methode wurde schon vom Erfinder von Quicksort (C. A. R. Hoare, ca. 1960) angegeben: kein zusätzlicher Platz, keine unnötige Datenbewegung. (Andere Verfahren sind möglich.) Hier: Variante des in der AuP-Vorlesung angegebenen Verfahrens. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 30 a+1 i 0000 1111 00 11 0000 00 11 x >x x 1111 0000 1111 00 11 j 1111 b 0000 000 111 0000 000 111 x 1111 >x 0000 1111 000 111 ? Ignoriere A[a]. Lasse ab a + 1 einen Zeiger“ (Index) i nach rechts wandern, ” bis ein Schlüssel > x erreicht wird. Lasse ab b einen Zeiger“ j (Index) nach links wandern, bis ” ein Schlüssel ≤ x erreicht wird. Falls i < j: Vertausche A[i] und A[j]. a 11111 00000 00 11 00000 11111 00 11 x x 00000 11111 00 11 i x j ? b 1111 0000 000 111 0000 000 111 >x 1111 >x 0000 1111 000 111 REST Erhöhe i um 1, erniedrige j um 1. Wiederhole. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 31 . . . bis sich i und j treffen“ und überkreuzen“. ” ” a x a z x j i z x j i x b >x b >x p=j Vertausche A[a] mit A[j]. Position p des Pivotelements jetzt: A[j]. Sorgfalt bei der Implementierung der Idee nötig. Randfälle und Abschluss korrekt behandeln! Nach einigem Tüfteln ergibt sich: FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 32 Prozedur partition(a, b, p) // Partitionierungsprozedur in r qsort(a, b) // Voraussetzung: a < b; Pivotelement: A[a] (1) x ← A[a].key; (2) i ← a + 1; j ← b; (3) while (i ≤ j) do // noch nicht fertig; Hauptschleife (4) while (i ≤ j) and (A[i].key ≤ x) do i++; (5) while (j > i) and (A[j].key > x) do j--; (6) if (i = j) then j--; // Wissen: A[i].key > x (7) if (i < j) then (8) vertausche A[i] und A[j]; (9) i++; j--; (10) if (a < j) then vertausche A[a] und A[j]; (11) p ← j; // Rückgabewert FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 33 Beispiel für partition: 2 5 20 7 16 18 30 a x =20 // 7 16 18 30 i x =20 // 7 16 18 30 i x =20 // 7 16 18 9 x =20 // 7 FG KTuEA, TU Ilmenau 16 18 9 12 25 8 9 35 31 b Vorbereitung 12 25 8 9 j 12 25 8 9 j 12 25 i 8 j 30 12 25 i 8 j 30 Algorithmen und Datenstrukturen – SS14 – Kapitel 6 Vertauschen 34 x =20 // 7 x =20 // 7 x =20 // 7 16 18 9 16 18 9 16 18 9 12 25 i 8 j 12 8 j 25 30 i 8 25 30 12 30 Vertauschen fertig! Aufräumen 8 7 16 18 9 12 20 25 30 p FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 35 Lemma 6.3.1 (Korrektheit von partition) Ein Aufruf von partition(a, b, p) bewirkt folgendes, wenn 1 ≤ a < b ≤ n gilt: Sei x = A[a].key (vor Aufruf), p der Inhalt von p (nach Aufruf). (a) Das Teilarray A[a . . b] wird durch partition(a, b, p) so umarrangiert, dass gilt: x = A[p].key und A[a].key,. . . ,A[p−1].key ≤ x < A[p+1].key,. . . ,A[b].key. (b) An welche Stelle A[j], a ≤ j ≤ b, gelangt, hängt nur von der Menge {i ∈ [a + 1, b] | A[i].key > x} ab, nicht von den konkreten Schlüsselwerten. (Beweis von (a): Weggelassen. Beweis von (b): Alle Entscheidungen werden nur aufgrund von Vergleichen mit x getroffen.) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 36 Lemma 6.3.2 (Aufwand von partition(a, b, p)) (a) Die Anzahl der Schlüsselvergleiche ist exakt b − a; (b) der (Zeit-)Aufwand ist Θ(b − a). Beweis: Sei i der Inhalt von i, j der Inhalt von j. (a) Schlüsselvergleiche finden nur statt, so lange i < j gilt. Nach jedem Vergleich A[i ].key≤ x“ wird i um 1 erhöht ” (Zeile (4) oder (9)), nach jedem Vergleich A[j ].key> x“ wird j um 1 erniedrigt ” (Zeile (5) oder (9)). In der Situation i ≥ j wird kein Vergleich mehr ausgeführt. Daher kann kein Eintrag zweimal mit x verglichen werden. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 37 Prozedur r qsort(a, b) // Rekursive Prozedur im Quicksort-Algorithmus, 1 ≤ a < b ≤ n (1) s ← ein Element von {a, . . . , b}; // Es gibt verschiedene Methoden/Arten, s zu wählen! (2) if (a < s) then vertausche A[a] und A[s]; (3) partition(a, b, p); // p: Ausgabeparameter (4) if a < p − 1 then r qsort(a, p − 1); (5) if p + 1 < b then r qsort(p + 1, b). Effiziente Variation zu den Abbruchkriterien in Zeilen (4), (5): Teilarrays der Länge ≤ n0 mit StraightInsertionSort(a, b) sortieren, für ein passendes n0. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 38 Lemma 6.3.3 Ein Aufruf von r qsort(a, b) sortiert A[a . . b]. (Beweis schon gesehen.) Um A[1 . . n] zu sortieren: Prozedur quicksort(A[1..n]) (1) if n > 1 then r qsort(1, n). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 39 Die Laufzeit von r qsort(a, b), wobei n = b − a + 1. Der Aufwand für einen Aufruf ist O(1) plus der Aufwand für partition(a, b, p) plus der Aufwand für die rekursiven Aufrufe. Der Aufwand für partition(a, b, p) ist proportional zu b − a, und das ist genau die Anzahl der Schlüsselvergleiche, die partition(a, b, p) durchführt. In jedem Aufruf ist ein anderer Arrayeintrag Pivotelement. Gesamtaufwand für r qsort(a, b), wobei n = b − a + 1: O(n) plus O(Gesamtanzahl aller{zSchlüsselvergleiche}). | =:V V ist eine Größe, die von der Anordnung der Eingabe abhängt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 40 Schlechtester Fall: ( worst case“) ” Wenn zwei Schlüssel verglichen werden, ist einer davon (x) das Pivotelement, der andere wandert in eines der beiden Teilarrays. Konsequenz: Es werden niemals dieselben Schlüssel später nochmals verglichen. 1 n Also: V ≤ 2 = 2 n(n − 1). Dieser Wert kommt vor, wenn die Eingabe schon sortiert ist und man in Prozedur r qsort(a, b) immer A[a] als Pivotelement wählt. (Dann ist das linke Teilarray leer, das rechte enthält b − a Elemente. n Rekursionstiefe n, und V = (n − 1) + (n − 2) + · · · + 3 + 2 + 1 = 2 .) Achtung: Ähnlicher Effekt, wenn Array fast“ sortiert. ” FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 41 Durchschnittlicher/erwarteter Fall? Wahrscheinlichkeitsannahme: Die Eingabeschlüssel sind verschieden. – O.B.d.A.: Eingabeschlüssel sind n verschiedene Zahlen x1 < . . . < xn. Jede Anordnung der Eingabeschlüssel hat dieselbe Wahrscheinlichkeit. (Es gibt n! solche Anordnungen.) Als Pivotelement wählen wir den Eintrag an einer festen Stelle, z. B. A[a]. Unter dieser Wahrscheinlichkeitsannahme wird V zu einer Zufallsgröße. Wir untersuchen den Erwartungswert/Mittelwert von V . (Vorsicht! Die folgende Überlegung gilt nicht für Clever-Quicksort“ und ” nicht, wenn die Schlüssel nicht alle verschieden sind.) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 42 Cn := die erwartete Anzahl der Vergleiche beim Sortieren eines Teilarrays mit n Einträgen mit r qsort C0 = 0, C1 = 0, C2 = 1, C3: Wenn man beim ersten r qsort(a, b)-Aufruf mit b = a + 2 das mittlere Element x2 als Pivot hat, gibt es 2 Vergleiche insgesamt. Wahrscheinlichkeit hierfür: 13 . Wenn man x1 oder x3 als Pivot hat, hat man zunächst 2 Vergleiche, und einer der rekursiven Aufrufe führt zu einem dritten. Insgesamt: 3 Vergleiche. Wahrscheinlichkeit hierfür: 23 . Mittlere Vergleichsanzahl: C3 = 31 · 2 + 23 · 3 = 83 . FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 43 Cn: Wenn man beim ersten r qsort(a, b)-Aufruf das i-t kleinste Element xi als Pivotelement hat, dann erzeugt dieser Aufruf selbst zunächst genau n − 1 Vergleiche, es ergibt sich als partitionierende Position p (in p) genau a + i − 1, und es entstehen die rekursiven Aufrufe r qsort(a, a + i − 2) (wenn i ≥ 3) r qsort(a + i, b) (wenn i ≤ n − 2) Diese kosten im Durchschnitt Ci−1 und Cn−i Vergleiche. (Beachte: Da anfangs die Schlüssel zufällig angeordnet waren, und bei partition die Schlüssel nur mit dem Pivotelement verglichen werden, ist nachher die Anordnung in der beiden Teilarrays ebenfalls zufällig.) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 44 Falls also xi Pivotelement ist: Durchschnittliche Kosten sind (n − 1) + Ci−1 + Cn−i. Die Wahrscheinlichkeit, dass xi Pivotelement ist, ist gleich 1/n, weil nach der W-Annahme jede Anordnung der Einträge x1, . . . , xn in A[a . . b] gleichwahrscheinlich ist (Lemma 6.3.1(b)). Also: Cn n X 1 · ((n − 1) + Ci−1 + Cn−i) = n i=1 n 1 X = (n − 1) + · (Ci−1 + Cn−i). n i=1 FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 45 Abschnitt 4.2 in Kap. 4, Folien 32–37 : Rekursionsformel für die mittlere totale Weglänge A(n) in zufällig erzeugten binären Suchbäumen. A(0) = A(1) = 0, A(2) = 1, Pn 1 A(n) = (n − 1) + n · i=1(A(i − 1) + A(n − i)). Das ist genau die gleiche Rekursion wie die für Cn! Bemerkung: Dies ist kein Zufall, sondern es besteht eine enge Verbindung der beiden Verfahren Quicksort und Erzeugen von Suchbäumen. Also sind auch die Lösungen identisch. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 46 Satz 6.3.4 Die erwartete Vergleichsanzahl bei der Anwendung von Quicksort auf ein Array mit n verschiedenen Einträgen, die zufällig angeordnet sind, ist 2(n + 1)Hn − 4n ≈ (2 ln 2)n log n − 2,846 . . . n. Dabei ist 2 ln 2 ≈ 1,386 (die Quicksort-Konstante“). ” FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 47 Bemerkung: Dieselbe Aussage gilt für die randomisierte Version, bei der das Pivotelement zufällig gewählt wird, und zwar für jedes beliebige feste Eingabearray A[1 . . n]. Randomisierung Quicksort. verhindert worst-case-Eingaben bei Bemerkung: Eine O(n log n)-Laufzeit stellt sich bei (randomisiertem) Quicksort sogar mit hoher Wahrscheinlichkeit ein. ( Zuverlässigkeitsaussage“.) ” FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 48 Bemerkungen: • Quicksort ist in der Praxis sehr schnell. (Partition-Prozedur ist auch Cache-freundlich.) • Vermeide rekursive Aufrufe für allzu kleine Teilarrays: besser direkt mit StraightInsertionSort sortieren. • Warnung 1: Wenn A[1 . . n] teilweise sortiert ist, und man das Pivotelement naiv wählt, erhält man schlechte Laufzeiten. (Bis zu Ω(n2).) Abhilfe: Clever Quicksort oder Randomisiertes Quicksort. • Warnung 2: Wenn A[1 . . n] (viele) identische Schlüssel enthält, ist das beschriebene Verfahren nicht günstig. Einfache Abhilfe: Ersetze Schlüssel A[i].key durch das Paar (A[i].key,i), und sortiere lexikographisch mit randomisiertem Quicksort. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 49 Bemerkungen: • Quicksort ist nicht stabil. • Platz (für den Rekursionsstack) kann im schlechtesten Fall bis zu O(n) groß werden. Ausweg: Formuliere r qsort um, so dass Rekursion nur für das kleinere der beiden Teilarray angewendet wird. Das größere wird in einer Iteration weiterbehandelt. Effekt: Deutlich weniger Prozeduraufrufe. Rekursionsstack hat höchstens Tiefe log n – fast in situ“. ” FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 50 Prozedur hr qsort(a, b) // Halb-rekursive Variante (1) a ← a; b ← b; (2) while a < b do (3) s ← ein (z.B. zufälliges) Element von {a, . . . , b}; (4) vertausche A[a] und A[s]; (5) partition(a, b, p); (6) if p − a < b − p then (7) if a < p − 1 then hr qsort(a, p − 1); (8) a ← p + 1; // while-Schleife bearbeitet rechtes Teilarray weiter (9) else // p − a ≥ b − p (10) if p + 1 < b then hr qsort(p + 1, b); (11) b ← p − 1; // while-Schleife bearbeitet linkes Teilarray weiter FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 51 6.4 Heapsort Heap-Ordnung in Binärbäumen: Beispiel: 3 min: 10 4 5 12 10 3 5 12 12 12 12 3 12 17 max: 17 3 4 Hier nur interne Knoten relevant. Blätter: Knoten ohne (interne) Kinder. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 52 Definition 6.4.1 Ein Binärbaum T mit Knotenbeschriftungen m(v) aus (U, <) heißt min-heapgeordnet (kurz oft: heapgeordnet), wenn für jeden Knoten v gilt: w Kind von v ⇒ m(v) ≤ m(w). B E F G N FG KTuEA, TU Ilmenau E P H F N Algorithmen und Datenstrukturen – SS14 – Kapitel 6 53 B E F G N E P H F N Beachte: T heapgeordnet ⇒ in Knoten v steht der minimale Eintrag des Teilbaums Tv mit Wurzel v. (T heißt max-heapgeordnet, wenn für jeden Knoten v gilt: w Kind von v ⇒ m(v) ≥ m(w).) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 54 Ein linksvollständiger“ Binärbaum: ” Alle Levels j = 0, 1, . . . voll (jeweils 2j Knoten) bis auf das tiefste. Das tiefste Level l hat von links her gesehen eine ununterbrochene Folge von Knoten. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 55 Nummerierung von Knoten in unendlichem, vollständigen Binärbaum gemäß Leveldurchlauf-Anordnung: 1 1 0 2 0 1 1 0 1 1 0 0 11 10 9 7 6 0 1 1 0 5 8 0 3 4 0 1 0 1 12 0 1 14 13 1 0 1 0 1 0 15 0 1 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 32 FG KTuEA, TU Ilmenau 1 1 46 47 63 0 Algorithmen und Datenstrukturen – SS14 – Kapitel 6 56 Nummerierung von Knoten in unendlichem, vollständigen Binärbaum gemäß Breitendurchlauf-Anordnung: Beispiel: (101110)2 = 46 1 1 0 2 0 1 1 0 1 1 0 0 11 10 9 7 6 0 1 1 0 5 8 0 3 4 0 1 0 1 12 0 1 14 13 1 0 1 0 1 0 15 0 1 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 32 FG KTuEA, TU Ilmenau 1 1 46 47 63 0 Algorithmen und Datenstrukturen – SS14 – Kapitel 6 56 Ist s1 · · · sl ∈ {0, 1}l die Markierung auf dem Weg von der Wurzel zu Knoten i auf Level l, so ist 1s1 · · · sl die Binärdarstellung von i. Knoten Knoten Knoten Knoten 1 ist die Wurzel; 2i ist linkes Kind von i; 2i + 1 ist rechtes Kind von i; i ≥ 2 hat Vater bi/2c. Damit: Array-Darstellung für linksvollständige Binärbaume: Speichere Einträge in Knoten 1, . . . , n in Array A[1 . . n]. Spart den Platz für die Zeiger! FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 57 Kombiniere Heapbedingung, Array-Darstellung, Daten: Definition 6.4.2 Sei (U, <) Totalordnung. Ein (Teil-)Array A[1 . . k] mit Einträgen aus U × D heißt ein Min-Heap (oder Heap), wenn der entsprechende linksvollständige Binärbaum mit k Knoten (Knoten i mit A[i].key ∈ U und A[i].data ∈ D beschriftet) bezüglich der key-Komponenten (min-)heapgeordnet ist, d. h., wenn für 1 ≤ i ≤ k gilt: 2i ≤ k ⇒ A[i].key ≤ A[2i].key und 2i + 1 ≤ k ⇒ A[i].key ≤ A[2i + 1].key. Die zweite Definition erwähnt den (gedachten) Binärbaum nicht mehr explizit (Analog: Max-Heap (benutze Maxheap-Ordnung)) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 58 Beispiel: U = {A, B, C, . . . , Z} mit der Standardordnung. Daten weggelassen. Ein Min-Heap und der zugeordnete Baum (k = 10): 1 2 3 4 5 6 7 8 9 10 B E F G E H F H I G 11 12 * * 1 B 2 3 E F 4 5 E G 8 6 7 H F 9 10 H FG KTuEA, TU Ilmenau I G Algorithmen und Datenstrukturen – SS14 – Kapitel 6 59 Aufgabe: Heaps reparieren 11 12 1 2 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 1 G 2 3 E F 5 4 E G 8 6 7 H F 9 H I Beobachte: Wenn man den Wurzelschlüssel G z. B. durch C ersetzt, entsteht ein Heap. Definition: Höchstens A[j] ist zu groß :⇔ es gibt einen Schlüssel x ≤ A[j].key, so dass ein Heap entsteht, wenn man A[j].key durch x ersetzt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 59 Aufgabe: Heaps reparieren 11 12 1 2 3 4 5 6 7 8 9 10 C E F G E H F H I * * * 1 C 2 3 F E 4 5 E G 8 6 7 H F 9 H I Beobachte: Wenn man den Wurzelschlüssel G z. B. durch C ersetzt, entsteht ein Heap. Definition: Höchstens A[j] ist zu groß :⇔ es gibt einen Schlüssel x ≤ A[j].key, so dass ein Heap entsteht, wenn man A[j].key durch x ersetzt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 59 Aufgabe: Heaps reparieren 11 12 1 2 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 1 G 2 3 E F 5 4 E G 8 6 7 H F 9 H I Beobachte: Wenn man den Wurzelschlüssel G z. B. durch C ersetzt, entsteht ein Heap. Definition: Höchstens A[j] ist zu groß :⇔ es gibt einen Schlüssel x ≤ A[j].key, so dass ein Heap entsteht, wenn man A[j].key durch x ersetzt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 60 Heaps reparieren 11 12 1 2 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 1 G < 2 3 E 5 4 E G 8 F < 6 7 H F 9 H I Vergleich der beiden Kinder des aktuellen Knotens. Höchstens A[1] ist zu groß: 1 ist aktueller Knoten“. ” Vergleich des kleineren“ Kinds mit dem aktuellen Knoten. ” Vertauschen des kleineren“ Kinds (E) mit dem aktuellen ” Knoten (G). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 61 Heaps reparieren Höchstens A[2] ist zu groß. 11 12 1 2 3 4 5 6 7 8 9 10 E G F G E H F H I * * * 1 E 2 3 G F > 4 5 G > 8 E 6 7 H F 9 H I Vergleich der beiden Kinder von Knoten 2. Vergleich des kleineren“ Kinds mit dem aktuellen Knoten. ” Vertauschen des kleineren“ Kinds (Knoten 5) mit dem aktuellen“ ” ” Knoten 2. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 62 Heaps reparieren Höchstens A[5] ist zu groß. 11 12 1 2 3 4 5 6 7 8 9 10 E E F G G H F H I * * * 1 E 2 3 F E 5 4 G G 8 6 7 H F 9 H I Aktueller Knoten 5 hat keine Kinder. Kein Fehler mehr: Reparatur beendet. Andere Möglichkeit für Ende: Eintrag im aktuellen Knoten ist nicht größer als der im kleineren Kind“. ” FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 63 bubbleDown(1, k) // Heap-Reparatur in A[1 . . k] // Vorbedingung: Höchstens A[1] ist zu groß (1) j ← 1; m ← 2; done ← false; (2) while not done and m + 1 ≤ k do // Höchstens A[j] ist zu groß, A[j] hat Kinder A[m] und A[m+1] (3) if A[m+1].key < A[m].key (4) then // A[m] soll kleineres“ Kind sein ” (5) m ← m + 1; (6) if A[m].key < A[j].key (7) then vertausche A[m] mit A[j]; j ← m; m ← 2 ∗ j; (8) // nun wieder: höchstens A[j] ist zu groß (9) else // fertig, kein Fehler mehr (10) done ← true; (11) if not done then (12) if m ≤ k then // Höchstens A[j] ist zu groß, ein Kind in A[m] (13) if A[m].key < A[j].key (14) then vertausche A[m] mit A[j];// fertig. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 64 Korrektheit: Wir behaupten: Wenn höchstens A[1] zu groß ist und bubbleDown(1, k) durchgeführt wird, dann entsteht ein Heap. Dazu zeigen wir folgende Behauptung durch Induktion über die Schleifendurchläufe: (IBj ) Zu Beginn des Durchlaufs, in dem j die Zahl j enthält, ist höchstens A[j] zu groß. Wir formulieren den Beweis in der Baum-Sprechweise. Der Ind.-Anfang (j = 1) gilt nach Voraussetzung. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 65 Betrachte Schleifendurchlauf für Knoten j. 1. Fall: In Zeile (7) wird A[j] mit A[2j] vertauscht. Es seien u, v, w die Schlüssel in A[j], A[2j] bzw. A[2j + 1] vor der Vertauschung. Nach (IBj ) können wir ein x ≤ u wählen, so dass (∗) ein Heap entsteht, wenn man u durch x ersetzt. Wissen nach Algorithmus: v ≤ w und v < u. Nach der Vertauschung steht u in A[2j] und v in A[j]. Wir behaupten, dass ein Heap entsteht, wenn wir A[2j].key durch v ersetzen. (Weil v < u, liefert das (IB2j ).) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 66 z z x u 2j v j j/2 v w 2j+1 2j u v j w 2j+1 Dazu müssen wir wegen (∗) nur die Beziehung der Schlüssel in Knoten j und 2j untereinander und zu Kindern und Vorgängern betrachten (rote Kanten). Knoten j und 2j haben den gleichen Eintrag v. (1) Falls j > 1, gilt für den Schlüssel z im Vaterknoten bj/2c von j: z ≤ x ≤ v (nach (∗)). (2) v ≤ Schlüssel in Kindknoten von 2j (nach (∗)); (3) v ≤ w (wurde verglichen). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 67 2. Fall: In Zeile (7) wird A[j] mit A[2j + 1] vertauscht: analog. 3. Fall: In Zeile (14) wird A[j] mit A[2j] vertauscht: analog. Nach einer Reihe von Schleifendurchläufen endet die Schleife mit j in j, weil Knoten j keine Kinder hat oder die Schlüssel in den Kindern ≥ u = A[j].key sind. Nach der Behauptung von eben gilt (IBj ). Sei x ≤ u so gewählt, dass ein Heap entsteht, wenn man u durch x ersetzt. Dann ist aber auch A[1 . . k] ohne diese Ersetzung ein Heap, weil die Schlüssel in den Kindern von Knoten j nicht kleiner als u sind und im Fall j > 1 der Schlüssel im Vaterknoten bj/2c nicht größer als x, also erst recht nicht größer als u ist. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 68 Kosten: Der Inhalt j der Variablen j ist anfangs 1 und verdoppelt sich in jeder Runde (mindestens); ein Schleifendurchlauf findet nur statt, wenn 2j ≤ k ist. Daher: Wenn es s Durchläufe durch die while-Schleife gibt, muss 2s ≤ n sein, d. h. s ≤ log k. Kosten: O(log k), bei nicht mehr als 2 log k Vergleichen. Wir haben damit gezeigt: Lemma 6.4.3 Wenn anfangs höchstens A[1] zu groß ist, stellt bubbleDown(1, k) die Heapeigenschaft her. Die Prozedur führt maximal 2 log k Vergleiche durch und hat Laufzeit O(log k). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 69 Die Idee von Heapsort für ein Array A[1..n] ist nun folgende: 1. (Heapaufbau) Arrangiere A[1..n] so um, dass ein Heap entsteht. 2. (Auswahlphase) Für k von n abwärts bis 2: Entnimm kleinsten Eintrag A[1] aus dem Heap A[1..k]. Stelle mit den in A[2..k] verbliebenen Einträgen in A[1..k −1] wieder einen Heap her. (In A[k] ist Platz für den entnommenen Eintrag.) Dadurch werden die Arrayeinträge in steigender Reihenfolge entnommen. Wenn wir den in Runde k entnommenen Eintrag in A[k] speichern, ist am Ende das Array A[1..n] fallend sortiert. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 70 Auswahlphase als Programm: Prozedur HeapSelect(1, n) (1) for k from n downto 3 do // in A[1] steht das Minimum von A[1..k] (2) vertausche A[1] mit A[k]; //in A[1..k − 1] ist höchstens A[1] zu groß (3) bubbleDown(1, k − 1); // Heapreparatur //in A[1..2] ist Heap, also A[1] ≤ A[2] (4) vertausche A[1] mit A[2]. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 C E F G E H F H I * * * 1 C 2 3 F E 4 5 E G 8 6 7 H F 9 H I Ein Heap: A[1..9]. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 I E F G E H F H C * * * 1 I 2 3 F E 5 4 G E 6 7 H F 8 H Minimum nach A[9], altes A[9] in die Wurzel. In A[1..8] höchstens A[1] zu groß. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 E E F G I H F H C * * * 1 I 2 3 F E 5 4 G E 6 7 H F 8 H Von bubbleDown verfolgter Weg. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 E E F G I H F H C * * * 1 E 2 3 F E 4 5 G I 6 7 H F 8 H A[1..8] ist Heap. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 H E F G I H F E C * * * 1 H 2 3 F E 5 4 G I 6 7 H F Minimum nach A[8], altes A[8] in die Wurzel. In A[1..7] höchstens A[1] zu groß. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 E G F H I H F E C * * * 1 E 2 3 F G 5 4 H I 6 7 H F A[1..7] ist Heap. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 F G F H I H E E C * * * 1 F 2 3 G F 4 5 H I 6 H Minimum nach A[7], altes A[7] in die Wurzel. In A[1..6] höchstens A[1] zu groß. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 F G F H I H E E C * * * 1 F 2 3 G F 4 5 H I 6 H A[1..6] ist Heap. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 H G F H I F E E C * * * 1 H 2 3 G F 4 5 H I Minimum nach A[6], altes A[6] in die Wurzel. In A[1..5] höchstens A[1] zu groß. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 F G H H I F E E C * * * 1 F 2 3 G H 4 5 H I A[1..5] ist Heap. usw. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 H I H G F F E E C * * * 1 H 2 I A[1..2] ist Heap. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Auswahlphase 11 12 1 2 3 4 5 6 7 8 9 10 I H H G F F E E C * * * Vertauschen von A[1] und A[2] vollendet fallende Sortierung. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 71 Satz 6.4.4 Die Prozedur HeapSelect(1, n) führt höchstens 2n log n Vergleiche durch und hat Rechenzeit O(n log n). Beweis: P Anzahl Vergleiche: höchstens 2≤k≤n 2 log k < 2n log n. P Kosten: 2≤k≤n (O(1) + O(log k)) = O(n) + O(n log n) = O(n log n). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 72 . . . und wie verwandelt man A[1 . . n] in einen Heap? Beispiel: Teil eines linksvollständigen Binärbaums, der einem Teilheap“ A[3 . . 10] entspricht: ” Teilheap D P A B R MN K L T E F G D A P R B K FG KTuEA, TU Ilmenau L T N M E F G Algorithmen und Datenstrukturen – SS14 – Kapitel 6 73 Definition 6.4.5 Das Teilarray A[` . . k] von A[1 . . n] (1 ≤ ` ≤ k ≤ n) heißt ein Teilheap, falls innerhalb“ des Teilarrays die ” Heapeigenschaft gilt, d. h. wenn für alle i mit ` ≤ i ≤ k gilt: HE(i, k) :⇔ ( 2i ≤ k 2i + 1 ≤ k ⇒ A[i] ≤ A[2i] und ⇒ A[i] ≤ A[2i + 1] Klar: Ein Teilheap ist die disjunkte Vereinigung von heapgeordneten Bäumen. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 74 Idee: Wenn A[` + 1 . . k] ein Teilheap ist, dann ist in dem Teilbaum T`,k mit Wurzel ` und Knoten in {`, . . . , k} die Heapbedingung höchstens in ` verletzt, d. h. höchstens der Schlüssel in Knoten ` ist zu groß. Wir können mit einer Variante von bubbleDown in diesem Teilbaum die Heapbedingung herstellen. Prozedur bubbleDown(`, k) // Heapreparatur in T`,k (1) j ← `; m ← 2 ∗ `; done ← false; (2) while not done and m + 1 ≤ k do (3) . . . FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 75 Lemma 6.4.6 Wenn A[` + 1..k] Teilheap ist, dann ist nach Ausführung von bubbleDown(`, k) das Teilarray A[`..k] Teilheap. Die Anzahl der Vergleiche ist nicht größer als 2 log(k/`), die Laufzeit O(log(k/`)). Beweis: Korrektheit wie bei Lemma 6.4.3. Anzahl Schlüsselvergleiche ähnlich wie bei Lemma 6.4.3: Wenn es in bubbleDown(`, k) s Schleifendurchläufe gibt, muss 2s · ` ≤ k sein, d. h. s ≤ log(k/`). Also gibt es maximal 2 log(k/`) Vergleiche bei einem Zeitaufwand von O(log(k/`)). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 76 Prozedur makeHeap(1, n) // Transformiert Array A[1 . . n] in einen Heap (1) for l from bn/2c downto 1 do (2) // stelle in A[l..n] Heapbedingung her! (3) bubbleDown(l, n); FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 77 Lemma 6.4.7 Ein Aufruf makeHeap(1, n) wandelt A[1..n] in einen Heap um. Die Anzahl der Vergleiche ist nicht größer als 2n; die Rechenzeit ist O(n). Beweis der Korrektheit: Mit Induktion über ` = bn/2c, bn/2c − 1, . . . , 2, 1 zeige: Nach Durchlauf mit ` in l ist A[` . . n] Teilheap. Ind.-Anfang: Vor der ersten Runde. In A[bn/2c + 1 . . n] gibt es keine Vater-Kind-Paare; dieses Teilarray ist also auf jeden Fall Teilheap. Ind.-Schritt: Folgt aus Korrektheit von bubbleDown(`, n). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 78 Kosten: Anzahl der Vergleiche: X 2· blog(n/`)c 1≤`≤n/2 X = 2· X 1 1≤`≤n/2 j : `·2j ≤n X = 2· X 1 1≤j≤log n 1≤`≤n/2j X ≤ 2· 1≤j≤log n n < 2n · j 2 (Geometrische Reihe: X 1≤j≤log n 1 2j −j 2 = 1.) j≥1 P < 2n. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 79 Algorithmus HeapSort(A[1 . . n]) (1) makeHeap(1, n); (2) HeapSelect(1, n); Satz 6.4.8 Algorithmus HeapSort sortiert das Array A[1 . . n] in fallende Reihenfolge. Die gesamte Laufzeit ist O(n log n), die Gesamtzahl der Vergleiche ist kleiner als 2n(log n + 1), für genügend große n. HeapSort arbeitet in situ (ist aber nicht stabil). Beweis: Korrektheit und Zeitbedarf folgt aus der Analyse der Teilprozeduren. Die Gesamtzahl der Vergleiche ist nach der Analyse von HeapSelect und Lemma 6.4.7 höchstens: P P 2n + 2≤k≤n 2 log k ≤ 2n + 2 · 2≤k≤n log k ≤ 2n + 2n log n. Bemerkung: Man kann sogar zeigen: < 2n log n Vergleiche. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 80 Bemerkung 1: Wenn Sortierung in aufsteigende Reihenfolge gewünscht wird, ersetze in allen Prozeduren und Programmen Schlüsselvergleiche A[i] ≤ A[j]“ durch A[i] ≥ A[j]“ und ” ” umgekehrt. Die in A[1 . . n] entstehende Struktur heißt ein Max-Heap – in der Wurzel des Binärbaumes steht immer das Maximum. Bemerkung 2: Die mittlere Anzahl von Vergleichen, unter der Annahme, dass jede Anordnung der Eingabeobjekte in A[1 . . n] dieselbe Wahrscheinlichkeit 1/n! hat, ist 2n log n − O(n). Bemerkung 3: Variante Bottom-Up-Heapsort“ hat n log n + O(n) ” Vergleiche im mittleren Fall. – Idee: Bei bubbleDown laufe gesamten Weg der kleineren Kinder“ bis zum Blatt, ziehe die kleineren Kinder“ ” ” um eine Ebene nach oben und füge dann das kritische Element aus der Wurzel von unten nach oben an die richtige Stelle ein. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 81 Bemerkung 4: Bei Variante 4-Way-Heapsort“ hat jeder Kno” ten i ≥ 1 vier Kinder, nämlich 4i − 2, 4i − 1, 4i, 4i + 1 (soweit ≤ n). Die Heapbedingung wird verallgemeinert: A[i] ≤ A[4i − 2], A[4i − 1], A[4i], A[4i + 1], für i ≥ 1, soweit die Indizes ≤ n sind. Bei bubbleDown: Vertauschen mit dem kleinsten Kind-Eintrag, wenn nötig. (Insgesamt 4 Vergleiche pro Ebene.). Vorteil: Baum hat nur Tiefe log4 n = 12 log n. Zugriff nur auf schneller. FG KTuEA, TU Ilmenau 1 2 log n Bereiche im Array: cache-freundlicher, Algorithmen und Datenstrukturen – SS14 – Kapitel 6 82 6.5 Datenstruktur: Prioritätswarteschlange oder Vorrangwarteschlangen Idee: (Daten mit) Schlüssel aus sortiertem Universum (U, <) werden eingefügt und entnommen. Beim Entnehmen wird immer ein Eintrag mit minimalem Schlüssel gewählt. Schlüssel = b Prioritäten“ ” – je kleiner der Schlüssel, desto höher die Priorität – Wähle stets einen Eintrag mit höchster Priorität. Mit Heaps effizient zu realisieren! FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 83 Anwendungen: 1) Prozessverwaltung in Multitask-System Jeder Prozess hat einen Namen (eine ID“) und eine Priorität ” (Zahl in N). (Üblich: kleinere Zahlen bedeuten höhere Prioritäten.) Aktuell rechenbereite Prozesse befinden sich in der Prozesswarteschlange. Bei Freiwerden eines Prozessors (z. B. durch Unterbrechen eines Prozesses) wird einer der Prozesse mit höchster Priorität aus der Warteschlange entnommen und weitergeführt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 84 Anwendungen: 2) Discrete Event Simulation“ ” System von Aktionen soll auf dem Rechner simuliert werden. Jeder ausführbaren Aktion A ist ein Zeitpunkt tA ∈ [0, ∞) zugeordnet. Die Ausführung einer Aktion A kann neue Aktionen A0 erzeugen oder ausführbar machen, mit neuen Zeitpunkten tA0 > tA. Ein Schritt: Wähle diejenige noch nicht ausgeführte Aktion mit dem frühesten Ausführungszeitpunkt und führe sie aus. 3) (Noch etwas erweitert:) Innerhalb von Algorithmen (Huffman-Codes, Dijkstra, Jarnı́k/Prim: später). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 85 Bei Prioritätswarteschlangen zu berücksichtigen: Einträge sind nicht blanke“ Schlüssel, sondern ” mit Prioritäten versehene Datensätze. Mehrere Objekte mit derselben Priorität sind möglich. Sogar: Mehrere identische Objekte sind möglich, die man nicht identifizieren darf. Beim Entnehmen: Alle Exemplare des kleinsten Schlüssels gleichberechtigt, einer gewinnt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 86 Beispiel: Datensatz: (n, L), n ∈ N, L ∈ {A, . . . , Z}, Schlüssel ist der Buchstabe L. Operation empty isempty insert(1, D) insert(2, B) insert(3, D) insert(4, E) insert(5, G) extractMin extractMin insert(4, E) insert(2, D) extractMin isempty FG KTuEA, TU Ilmenau innerer Zustand ∅ ∅ {(1, D)} {(1, D), (2, B)} {(1, D), (2, B), (3, D)} {(1, D), (2, B), (3, D), (4, E)} {(1, D), (2, B), (3, D), (4, E), (5, G)} {(1, D), (3, D), (4, E), (5, G)} {(1, D), (4, E), (5, G)} {(1, D), (4, E), (5, G), (4, E)} {(1, D), (4, E), (5, G), (4, E), (2, D)} {(1, D), (4, E), (5, G), (4, E)} {(1, D), (4, E), (5, G), (4, E)} Algorithmen und Datenstrukturen – SS14 – Kapitel 6 Ausgabe – true – – – – – (2, B) (3, D) – – (2, D) false 87 Datentyp: SimplePriorityQueue (Einfache Prioritätswarteschlange) Signatur: Sorten: Keys Data PrioQ // Priority Queues“ ” Boolean Operatoren: empty : → PrioQ isempty : PrioQ → Boolean insert : Keys × Data × PrioQ → PrioQ extractMin: PrioQ → Keys × Data × PrioQ FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 88 Mathematisches Modell: Sorten: Keys : Data : Boolean : PrioQ : Operationen: empty ( ) = ∅ isempty (P ) = (U, <) // totalgeordnetes Universum“ ” D // Menge von Datensätzen“ ” {false, true} die Menge aller endlichen Multimengen∗ P ⊆ U × D // die leere Multimenge ( true, für P = ∅ false, für P 6= ∅ ∗ Multimenge: Elemente dürfen mehrfach vorkommen, wie z. B. in {1, 1, 4, 5, 5, 5, 9}. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 89 Mathematisches Modell (Forts.): insert(x, d, P ) = P ∪ {(x, d)} (als Multimenge), extractMin(P ) = ( undefiniert, für P = ∅ (x0, d0, P 0), für P 6= ∅, für ein Element (x0, d0) in P mit minimalem Schlüssel x0, und P 0 = P − {(x0, d0)} (als Multimenge). Standardimplementierung: (Binäre) Heaps FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 90 Implementierung: Neben Array A[1 . . m]: Pegel k: Aktuelle Zahl k von Einträgen. Überlaufprobleme werden ausgeklammert. (Verdoppelungsstrategie, falls nötig.) empty(m): // Zeitaufwand: O(1) oder O(m) Lege Array A[1 . . m] an; Jeder Eintrag ist ein Paar (key, data). k ← 0. isempty(): return(k = 0); FG KTuEA, TU Ilmenau // Zeitaufwand: O(1) Algorithmen und Datenstrukturen – SS14 – Kapitel 6 91 extractMin: Implementierung von extractMin(P ): Ein Eintrag mit minimalem Schlüssel steht in der Wurzel, d. h. in Arrayposition 1. Entnehme A[1] (hier B“) und gib es aus. ” 1 2 3 4 5 6 7 8 9 G E F G E H F H I 10 11 12 * * * 1 G B 2 3 E F 4 5 E G 8 6 7 H F 9 10 H I * Loch“ im Binärbaum – Stopfen“ mit dem letzten Eintrag. ” ” FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 92 Bleibt Aufgabe: Heap reparieren 11 12 1 2 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 1 G 2 3 E F 5 4 E G 8 6 7 H F 9 H I Höchstens A[1] ist zu groß, also: bubbleDown hilft hier! FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 93 Prozedur extractMin // Entnehmen eines minimalen Eintrags aus Priority-Queue // Ausgangspunkt: Pegelstand k, A[1 . . k] ist Heap, k ≥ 1 (0) if k = 0 then Fehlerbehandlung“; ” (1) x ← A[1].key; d ← A[1].data; (2) A[1] ← A[k]; (3) k--; (4) if k > 1 then bubbleDown(1, k); (5) return (x, d); Korrektheit: klar wegen Korrektheit von bubbleDown(1, k). Zeitaufwand: O(log(k)), wobei k (in k) die aktuelle Größe des Heaps ist. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 94 Implementierung von insert(x, d): Voraussetzung: A[1..k] ist Heap; 1 ≤ k < m; k enthält k. k++; A[k] ← (x, d). An der Stelle k 0 = k + 1 ist nun eventuell die Heapeigenschaft gestört, weil x zu klein ist. Definition Höchstens A[j] ist zu klein :⇔ es entsteht ein Heap, wenn man u = A[j].key durch einen geeigneten Schlüssel x ≥ u ersetzt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 95 Einfügen von D“ an Stelle k = 11. ” 1 2 3 4 5 6 7 8 9 10 C E F G J K F H L Q 11 D 12 * 1 C 2 3 E F 4 5 G J 8 9 10 H FG KTuEA, TU Ilmenau 6 L 7 K F 11 Q D Algorithmen und Datenstrukturen – SS14 – Kapitel 6 96 Reparatur, wenn höchstens ein Schlüssel zu klein: bubbleUp. 1 2 3 4 5 6 7 8 9 10 C E F G J K F H L Q 11 D 12 * 1 C 2 3 E F 4 5 G J 8 9 10 H FG KTuEA, TU Ilmenau 6 L 7 K F 11 Q D Algorithmen und Datenstrukturen – SS14 – Kapitel 6 96 Reparatur, wenn höchstens ein Schlüssel zu klein: bubbleUp. 1 2 3 4 5 6 7 8 9 C E F G D K F H L 10 11 Q J 12 * 1 C 2 3 E F 4 5 G 6 K D 8 9 10 H L 7 F 11 Q J und so weiter . . . FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 97 Prozedur bubbleUp(i) // Heapreparatur ab A[i] nach oben (1) j ← i; (2) h ← j div 2; (4) while h ≥ 1 and A[h].key > A[j].key do (5) vertausche A[j] mit A[h]; (6) j ← h; (7) h ← j div 2. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 98 1 2 3 4 5 6 7 8 9 C D F G E K F H L 10 11 Q J 12 * 1 C 2 3 D F 4 5 G 6 K E 8 9 10 H L 7 F 11 Q J Anschaulich: Auf dem Weg von A[i] zur Wurzel werden alle Elemente, deren Schlüssel größer als der (alte) Eintrag in A[i]) ist, um eine Position (auf dem Weg) nach unten gezogen. Eintrag A[i] landet in der freigewordenen Position. Man kann dies auch effizienter (wie in StraightInsertionSort) programmieren. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 99 Behauptung 6.5.1 Wenn in A[1..k] höchstens A[i] zu klein ist dann ist nach Ausführung von bubbleUp(i) das Array A[1..k] ein Heap. Der Zeitaufwand ist O(log i), die Anzahl der Schlüsselvergleiche ist höchstens das Level von i im Baum, also die Anzahl der Bits in der Binärdarstellung bin(i), d. h. dlog(i + 1)e. Beweis: Ähnlich wie bei bubbleDown. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 100 (Nur Korrektheit.) Durch Induktion über die Runden zeigt man: (IBj ) Zu Beginn des Schleifendurchlaufs für j ist höchstens A[j] zu klein. Zu Beginn stimmt dies nach Annahme über die Eingabe. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 101 Nun betrachte einen Schleifendurchlauf der while-Schleife. 1. Fall: j ≥ 2 und h = bj/2c und u = A[j].key < v = A[h].key; es wird getauscht. Beh.: Wenn man den Schlüssel u in h auf v erhöht, entsteht ein Heap. Das heißt: (IBh) gilt. – Beweis: Im Unterbaum mit Wurzel j liegt ein Heap vor, da nach (IBj ) kein Eintrag in diesem Baum kleiner als der neue Wurzeleintrag v ist. Falls j einen Geschwisterknoten ` = j ± 1 hat, so hat sich der Baum mit Wurzel ` nicht geändert, also liegt auch dort ein Heap vor. Bleiben h mit Nachfolgern/Vorgänger: h und j enthalten denselben Schlüssel v. Zwischen h und ` gilt die Heapbedingung nach (IBj ). Zwischen h und seinem Vorgänger gilt die Heapbedingung nach (IBj ). 2. Fall: j = 1 oder u = A[j].key ≥ v = A[h].key, Schluss. Wegen (IBj ) können wir u = A[j].key durch ein x ≥ u ersetzen, so dass A[1..k] Heap wird. Dann liegt aber auch mit Schlüssel u ein Heap vor. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 102 Prozedur insert(x, d) // Einfügen eines neuen Eintrags in Priority-Queue // Ausgangspunkt: Pegel k, A[1 . . k] ist Heap, k < m (1) if k = m then Überlauf-Fehler“; ” (2) k++; (3) A[k] ← (x, d); (4) bubbleUp(k). Korrektheit: klar wegen Korrektheit von bubbleUp(k). Zeitaufwand: O(log(k)). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 103 Wir können bubbleUp(i) sogar für eine etwas allgemeinere Operation verwenden (Korrektheitsbeweis gilt weiter): Wir ersetzen einen beliebigen Schlüssel im Heap (Position i) durch einen kleineren. Mit bubbleUp(i) kann die Heapeigenschaft wieder hergestellt werden. Prozedur decreaseKey(x, i) // (Erniedrigen des Schlüssels an Arrayposition i auf x) (1) if A[i].key < x then Fehlerbehandlung; (2) A[i].key ← x; (3) bubbleUp(i). // Zeitaufwand: O(log(i)) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 104 SimplePriorityQueue plus decreaseKey“: ” Prioritätswarteschlange. Satz 6.5.2 Der Datentyp “Prioritätswarteschlange” kann mit Hilfe eines Heaps implementiert werden. Dabei erfordern empty und isempty (und das Ermitteln des kleinsten Eintrags) konstante Zeit und insert, extractMin und decreaseKey benötigen jeweils Zeit O(log n). Dabei ist n jeweils der aktuelle Pegelstand, also die Anzahl der Einträge in der Prioritätswarteschlange. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 105 6.6 Untere Schranke für Sortieren Sortieralgorithmus A heißt ein Schlüsselvergleichsverfahren, wenn in A auf Schlüssel x, y aus U nur Operationen x<y ? x≤y ? x=y ? sowie Verschieben und Kopieren angewendet werden. Schlüssel sind atomare Objekte“. ” Mergesort, Quicksort, Heapsort, StraightInsertionSort sind Schlüsselvergleichsverfahren. Jeweils: O(n log n) oder O(n2) Vergleiche. Ziel: Es geht nicht besser als Ω(n log n). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 106 Sei nun A ein Schlüsselvergleichsverfahren. Wir wenden A auf die n! verschiedenen Eingaben (a1, 1), . . . , (an, n) an, wobei σ = (a1, . . . , an) eine Permutation von {1, . . . , n} ist. Sortierschlüssel: erste Komponente. Beispiel: Aus (2, 1), (4, 2), (1, 3), (3, 4) wird (1, 3), (2, 1), (3, 4), (4, 2). Effekt: In den zweiten Komponenten steht die Permutation π = σ −1 (im Beispiel π = (3, 1, 4, 2)), die die Eingabe (a1, . . . , an) sortiert: aπ(1) < · · · < aπ(n). Also: n! verschiedene Ausgaben, eine für jede Anordnung der Eingabeobjekte. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 107 Beispiel: Mergesort mit 3 Elementen a1 a 1< a 2 nein ja a 1< a 3 a 2< a 3 nein ja nein 231 ja nein a 1< a 3 321 a3 a2 a 2< a 3 312 ja 213 nein 132 ja 123 In Blättern: die zweiten Komponenten FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 108 Allgemein: Vergleichsbaum TA,n mit n! Blättern. Start 0 a i1< a j1 1 a i2< a j2 0 1 a i4< a j4 0 a i3< a j3 0 a i6< a j6 a i5< a j5 1 0 1 1 0 a i7< a j7 1 0 π’’ 1 π’’’ a i20< a j20 0 π FG KTuEA, TU Ilmenau 1 π’ Algorithmen und Datenstrukturen – SS14 – Kapitel 6 109 Allgemein: Jeder vergleichsbasierte Sortieralgorithmus A erzeugt einen Vergleichsbaum TA,n mit genau n! Blättern. Die Wege im Baum entsprechen den Berechnungen von A auf den n! genannten Eingaben. Lasse (gedacht) A ablaufen, beginnend mit der Menge I aller genannten n! Eingaben. Wenn die verbleibende Menge von Inputs I 0 ist und A als nächstes einen Vergleich ai < aj durchführt, wird ein (Wurzel-)Knoten mit Beschriftung a < aj“ erzeugt. ” i Der linke Unterbaum unter diesem Knoten wird rekursiv gebildet, indem man A auf den Inputs aus I 0 weiterlaufen lässt, die ai > aj erfüllen, der rechte Unterbaum analog mit den Inputs aus I 0, die ai < aj erfüllen. Wenn die Rechnung endet, kann nur noch ein Input übrig sein (sonst arbeitet A nicht korrekt); die von dieser Rechnung erzeugte Permutation wird in ein Blatt eingetragen. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 110 Satz 6.6.1 Wenn A ein Schlüsselvergleichsverfahren ist, das das Sortierproblem löst, dann gibt es eine Eingabe (a1, . . . , an), auf der A mindestens dlog(n!)e Vergleiche ausführt. Beweis: Der Vergleichsbaum, den A erzeugt, hat n! (externe) Blätter, also hat er Tiefe ≥ dlog(n!)e. Dies wurde in Abschnitt 3.3, Proposition 3.3.2, gezeigt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 111 Bemerkung: n n! ≥ (n/e) . n Beweis: n /n! < ni i≥0 i! P = en . Also: log(n!) ≥ log((n/e)n) = n log n − n log e = n log n − 1,44269 . . . n. Rn P Andersherum: ln(n!) = 1≤i≤n ln i ≤ ln n + 1 ln x dx = ln n + [x(ln x − 1)]n1 = ln n + n ln n − n + 1, also n! ≤ (en)(n/e)n. Mitteilung: Für n ≥ 1 gilt (Stirlingsche Formel): √ FG KTuEA, TU Ilmenau 2πn n n e ≤ n! ≤ √ 2πn n n Algorithmen und Datenstrukturen – SS14 – Kapitel 6 e e 1 12n 112 Satz 6.6.2 Wenn A ein Schlüsselvergleichsverfahren ist, das das Sortierproblem löst, dann gilt für die mittlere Vergleichsanzahl V n (gemittelt über die n! verschiedenen Eingabeanordnungen): V n ≥ log(n!). Beweis: Der Vergleichsbaum TA,n, den A erzeugt, hat N = n! (externe) Blätter. – Proposition 3.3.5, zu totaler äußerer Weglänge besagt: TA,n hat N externe Knoten ⇒ TEPL(TA,n) ≥ N log N . Erwartete, mittlere äußere Weglänge E( N1 TEPL(TA,n)) ist ≥ log(N ). Daraus: Die mittlere Anzahl von Vergleichen ist ≥ log(n!). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 113 Satz 6.6.3 Wenn A ein Schlüsselvergleichsverfahren ist, das das Sortierproblem löst und dabei Randomisierung benutzt (d. h. Zufallsexperimente ausführt, z. B. Randomisiertes Quicksort) dann gibt es eine Eingabe a = (a1, . . . , an) aus verschiedenen Elementen von U , so dass die erwartete Vergleichsanzahl von A auf a (gemittelt über die Zufallsexperimente von A) ≥ log(n!) ist. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 114 Beweis: (Nicht prüfungsrelevant, für Interessierte.) Abstrakt stellt man sich vor, dass man alle Zufallsexperimente, die in A vorkommen können, vorab ausführt. Beispiel Quicksort: Für jedes Paar (a, b), 1 ≤ a < b ≤ n, wird vorab zufällig eine Position s ∈ [a..b] gewählt. Ergebnis: Ein (langes) Zufallswort α. Jedes α hat eine Wahrscheinlichkeit pα. Dabei P α pα = 1. Aα: Algorithmus A, der mit den Ergebnissen α der Experimente läuft. Aα ist ein gewöhnlicher deterministischer Algorithmus! Tα(σ) := #(Vergleiche bei Aα auf Eingabe σ). (σ = (σ(1), . . . , σ(n)): eine Permutation von {1, . . . , n}.) P 1 Nach Satz von oben: n! σ Tα(σ) ≥ log(n!). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 115 Haben: 1 n! P σ Tα(σ) ≥ log(n!). Summiere über α’s: X α 1 X Tα(σ) ≥ log(n!). pα · n! σ Umstellen der Summation: 1 X X pα · Tα(σ) ≥ log(n!). n! σ α P Daraus folgt: Es gibt ein σ mit α pα · Tα(σ) ≥ log(n!). P α pα · Tα (σ): erwartete Laufzeit von A auf Eingabe σ. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 116 6.7. Sortieren in Linearzeit Schlüssel nicht als atomares Objekt, sondern als Index benutzen → untere Schranke gilt nicht. U = {0, 1, . . . , m − 1} = [m]. Gegeben: n Objekte mit Schlüsseln a1, . . . , an aus U . Sortiere! Präziser: Gegeben sind in A[1..n] n Datenobjekte (a1, d1), . . . , (an, dn) mit Schlüsseln a1, . . . , an aus U . Sortiere (stabil)! FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 117 (A) Countingsort – Sortieren durch Zählen Idee, Phasen: 1. Zähle in Komponente C[i] eines Arrays C[0..m − 1], wie oft der Schlüssel i vorkommt. 2. Benutze dies (wieder in C[i]), um zu berechnen, wie viele Einträge in A einen Schlüssel ≤ i haben. 3. Übertrage die Einträge mit Schlüssel i aus A in die Positionen C[i − 1] + 1, . . . ,C[i] in Array B, stabil. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 118 1. Zähle in Komponente C[i] eines Arrays C[0..m − 1], wie oft der Schlüssel i vorkommt. for i from 0 to m − 1 do (2) C[i] ← 0; // Zeitaufwand: Θ(m) (3) for j from 1 to n do C[A[j].key]++; // Zeitaufwand: Θ(n) (1) 2. Benutze dies (wieder in C[i]), um zu berechnen, wie viele Einträge in A einen Schlüssel ≤ i haben. for i from 1 to m − 1 do (5) C[i] ← C[i−1] + C[i]; // Zeitaufwand: Θ(m) (4) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 119 3. Übertrage die Einträge mit Schlüssel i aus A in die Positionen C[i − 1] + 1, . . . ,C[i] in Array B, stabil. Geschickt: In A[1..n] einmal von hinten nach vorne laufen. Zum Verständnis beobachte: Wenn Eintrag mit Schlüssel i, der in A am weitesten rechts steht, an Stelle A[j] steht, muss dieser an die Stelle B[C[i]]. Der nächste Eintrag mit diesem Schlüssel muss in das Fach unmittelbar links daneben – daher: übertragen, dann C[i] herunterzählen. for j from n downto 1 do (7) i ← A[j].key; (8) B[C[i]] ← A[j]; // aktueller Platz für Schlüssel i (9) C[i]--; // auf linken Nachbarn umsetzen // Zeitaufwand: Θ(n) (6) FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 1 2 3 4 5 6 7 C: 8 9 B: Eingabe, Datenstruktur. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 0 1 3 0 2 2 1 2 3 4 5 6 7 8 9 B: Nach (1)–(3): Einträge gezählt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 2 5 5 7 9 1 2 3 4 5 6 7 8 9 B: Nach (4)–(5): Endpunkte der Segmente gleicher Schlüssel ermittelt. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 2 5 5 7 9 1 2 3 4 5 6 7 8 9 33 B: Zeile (8): Eintrag kopieren. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 2 4 5 7 9 1 2 3 4 5 6 7 8 9 33 B: Zeile (9): Index heruntersetzen. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 2 4 5 7 9 1 2 3 4 5 6 7 B: FG KTuEA, TU Ilmenau 8 9 33 Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 2 4 5 7 9 1 2 3 4 5 6 7 B: 21 8 9 33 Zeile (8): Eintrag kopieren. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 1 4 5 7 9 1 2 3 4 5 6 7 B: 21 8 9 33 Zeile (9): Index heruntersetzen. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 1 4 5 7 9 1 2 3 4 5 6 7 B: FG KTuEA, TU Ilmenau 21 8 9 33 Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 1 4 5 7 9 1 2 3 4 5 6 7 B: 21 8 9 3 2 33 Zeile (8): Eintrag kopieren. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 1 3 5 7 9 1 2 3 4 5 6 7 B: 21 8 9 3 2 33 Zeile (9): Index heruntersetzen. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 1 1 1 3 5 7 9 1 2 3 4 5 6 7 B: FG KTuEA, TU Ilmenau 21 8 9 3 2 33 Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 1 2 3 4 5 6 7 8 9 A: 5 1 0 1 6 1 3 1 5 2 6 2 3 2 2 1 3 3 0 1 2 3 4 5 6 C: 0 1 1 2 5 5 7 1 2 3 4 5 6 7 8 9 B: 0 1 2 1 3 1 3 2 3 3 5 1 5 2 6 1 6 2 Resultat nach Beendigung der Schleife (6)–(9). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 120 Satz 6.7.1 (Countingsort – Sortieren durch Zählen) n Objekte mit Schlüsseln aus [m] (gegeben in Array) können in Zeit Θ(n + m) und mit Zusatzplatz Θ(n + m) sortiert werden. Das Sortierverfahren ist stabil. Linearzeit und linearer Platz, falls m ≤ cn, c konstant. Countingsort ist auch in der praktischen Anwendung sehr schnell (wenn auch nicht Cache-freundlich). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 121 (B) Bucketsort – Fachsortieren Nun seien die zu sortierenden Objekte mit Schlüsseln aus [m] als (einfach verkettete) lineare Liste gegeben. A: 5 a 2 a 5 b 3 a 1 b 6 a 5 c 2 b 1 a Datenstruktur: Array B[0..m − 1] von Listen L0 . . . , Lm−1. Initialisierung mit NULL-Zeigern: Zeit Θ(m). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 122 Durchlaufe Eingabeliste, übertrage Einträge mit Schlüssel i an den Anfang der Liste Li (angehängt in B[i]): Zeit Θ(n). Nach Transport in das Listenarray: B: 0: 1: 1 b 1 a 2: 2 b 2 a 3: 3 a 4: 5: 5 c 6: 6 a 5 b 5 a Bemerke: Identische Schlüssel in umgekehrter Reihenfolge. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 123 Durchlaufe Array B[0..m − 1] von hinten nach vorn, baue neue sortierte Liste A (anfangs leer). Für i: lies Elemente der Liste Li aus, hänge sie vorne in die Liste A ein. Nach Auslesen: A: 1 a FG KTuEA, TU Ilmenau 1 b 2 a 2 b 5 b 5 c 6 a Algorithmen und Datenstrukturen – SS14 – Kapitel 6 3 a 5 a 124 Algorithmus Bucketsort (Fachsortieren) (1) Bearbeite Elemente der Liste A nacheinander. Füge Listenelement i data vorn in Liste B[i] ein. NB: Reihenfolge von Elementen mit demselben Schlüssel wird umgedreht. (2) Bearbeite die Listen B[m − 1], B[m − 2], ...,B[1], B[0] in dieser (absteigenden) Reihenfolge. Für i: lies B[i] von vorn nach hinten. Füge Element i data vorn in Ausgabeliste ein. NB: Reihenfolge wird erneut umgedreht. Zweimaliges Umdrehen → Stabilität. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 125 Korrektheit: klar. Laufzeit: (0) Anlegen von B, Initialisieren mit NULL-Zeigern: Θ(m). (1) Verteilen auf die Listen: Θ(n) (2) Zusammenfügen: Θ(m) + Θ(n) (Jeder Listenanfang B[i] muss inspiziert werden.) Gesamt: Θ(m + n). Linearzeit und linearer Platz, falls m ≤ cn, c konstant. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 126 Alternative: Bei B[i]-Listen Endezeiger benutzen, in Phase (1) hinten anfügen. In Phase (2): Teillisten konkatenieren. Vorteil: in Phase (2) nur O(m) Zeigerbewegungen, Zeit O(m). Nachteil: Zusätzlicher Speicherplatz O(m) für Endezeiger. Günstig insbesondere dann, wenn m n ist. Satz 6.7.2 (Bucketsort/Fachsortieren) n Objekte mit Schlüsseln aus [m] (gegeben als lineare Liste) können in Zeit Θ(n + m) und mit Zusatzplatz für m Zeiger stabil sortiert werden. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 127 Was tun, wenn die Anzahl m der möglichen Schlüssel begrenzt, aber deutlich größer als n ist? (Beispiel: m ≤ n2.) (C) Radixsort – Mehrphasen-Counting-/Bucketsort Anwendbar in verschiedenen Situationen: 1. Schlüssel sind Folgen x = (x1, . . . , xk ) ∈ U k , U = [m], m ≤ n. Anordnungskriterium: lexikographisch, d. h. (x1, . . . , xk ) < (y1, . . . , yk ) ⇔ ∃j ∈ {1, . . . , k} : (x1, . . . , xj−1) = (y1, . . . , yj−1) ∧ xj < yj . 2. Schlüssel sind Zahlen x in U ⊆ {0, 1, . . . , mk − 1}, m ≤ n. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 128 3. k (möglicherweise verschiedene) geordnete Mengen (U1, <1), . . . , (Uk , <k ) Schlüssel sind Folgen x = (x1, . . . , xk ) ∈ U1 × · · · × Uk , mit lexikographischer Ordnung. Beispiele: Daten: {1, . . . , 31} × {1, . . . , 12} × {1801, . . . , 2090}. Spielkarten: {♦, ♥, ♠, ♣}×{2, . . . , 10, Bube, Dame, König, Ass}. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 129 4. Σ<∞ mit lexikographischer Ordnung. Dabei: Σ ist Alpha” bet“, d. h. nichtleere endliche Menge (z. B. ASCII-Alphabet) mit Ordnung <. Lexikographische Ordnung auf Σ<∞ (wie im Lexikon): (x1, . . . , xk ) <lex (y1, . . . , y`) ⇔ k < ` ∧ (x1, . . . , xk ) = (y1, . . . , yk ) or ∃j ∈ {1, . . . , min{k, `}} : (x1, . . . , xj−1) = (y1, . . . , yj−1) ∧ xj < yj . Kanonische Ordnung auf Σ<∞ (kürzere Strings zuerst): (x1, . . . , xk ) <kan (y1, . . . , y`) ⇔ k < ` or k = ` ∧ ∃j ∈ {1, . . . , k} : (x1, . . . , xj−1) = (y1, . . . , yj−1) ∧ xj < yj . FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 130 Zunächst: Situation 1, Schlüssel x = (x1, . . . , xk ) ∈ U k , U = [m]. Idee: sortiere k-mal mittels Counting-/Bucketsort und zwar gemäß Komponente k, dann k − 1, . . . , dann 1 (die am wenigsten signifikante“ Komponente zuerst). ” Eingabe: Array/Liste A: Einträge mit Schlüsseln (x1, . . . , xk ) aus [m]k . Methode: für l = k, k − 1, . . . , 1 tue: sortiere A mittels Counting- bzw. Bucketsort für U = [m] mit Komponente xl aus (x1, . . . , xk ) als Sortierschlüssel. Zeit insgesamt: Θ(k · (n + m)) Zusatzplatz: Θ(m + n) bzw. Θ(m). FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 131 Beispiel: U = {A,B,C,D} Eingabe A: BCC ABC BAC CAD ABA Nach Runde l = 3: ABA BCC ABC BAC CAD Nach Runde l = 2: BAC CAD ABA ABC BCC Nach Runde l = 1: ABA ABC BAC BCC CAD FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 132 Satz 6.7.3 Der skizzierte Algorithmus Radixsort sortiert n Objekte mit Schlüsseln aus [m]k in Zeit Θ(k(n + m)) und Platz Θ(m) (bzw. Θ(n + m) für Arrays). Das Verfahren ist stabil. Beweis: Nur Korrektheit. Man zeigt durch Induktion über l = k, k − 1, . . . , 1 die folgende Schleifeninvariante: Nach Schleifendurchlauf für l ist das Array/die Liste A gemäß den Teilschlüsseln (xl, xl+1, . . . , xk ) ∈ [m]k−l+1 lexikographisch sortiert. Für Induktionsschritt l + 1 → l benutzt man, dass Counting/Bucketsort stabil ist, und die schon hergestellte Ordnung bezüglich der weniger signifikanten Komponenten nicht zerstört. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 133 Das Verfahren für U1 × · · · × Uk (Situation 3) ist im wesentlichen dasselbe, nur benötigt man verschiedene B-Arrays (oder Arrayabschnitte) in den einzelnen Durchgängen. Zeit: O(kn+|U1|+· · ·+|Uk |); Platz: O(n+max1≤i≤k |Ui|). Situation 2: Schlüssel sind Zahlen in {0, 1, . . . , mk − 1}: Repräsentiere Zahlen in m-ärer Darstellung (mit k Ziffern); wende Radixsort an. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 134 Beispiel: " m = 10 (Dezimaldarstellung) 531 729 617 312 425 436 ! " # ! # l =3 531 312 425 436 617 729 l =2 312 617 425 729 531 436 l =1 312 425 436 531 617 729 So kleine m sind nicht typisch. Sinnvoll: m ≈ n, m Zweierpotenz, z. B. m = 28 oder 216. Dann ist eine Ziffer“ xl ein Segment der Binärdarstellung von ” x, die leicht aus x zu extrahieren ist. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 135 Satz 6.7.4 Radixsort für Zahlen sortiert n Objekte mit Schlüsseln aus [mk ] in Zeit Θ(k(n + m)) und Platz Θ(m) (bzw. Θ(n + m) für Arrays). Das Verfahren ist stabil. FG KTuEA, TU Ilmenau Algorithmen und Datenstrukturen – SS14 – Kapitel 6 136