Sortieren Algorithmen und Datenstrukturen 10. Vorlesung a1 a2 a3 an 10 1 4 5 1 9 4 1 8 US SO RA OR RT HM LG IE IT Martin Dietzfelbinger Item: Schlüssel 16. Juni 2008 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 AuD – 16.06.2008 FG KTuEA, TU Ilmenau AuD – 16.06.2008 Maßzahlen für Sortieralgorithmen: In Abhängigkeit von n fragen wir nach: Sortieren Sortierproblem, abstrakt: Siehe Vorlesung 1. 1. Laufzeit (in O-Notation); Sortiermethoden, schon gesehen: 2. Anzahl der Vergleiche Straight Insertion Sort (Vorlesung), Bubble Sort (Übung), Mergesort (AuP, 1. Semester) 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 AuD – 16.06.2008 1 2 A[i] < A[j] bzw. A[i] ≤ A[j] ( wesentliche Vergleiche“); ” 3. Anzahl der Datenverschiebungen oder Kopiervorgänge (wichtig bei sehr umfangreichen Objekten); 4. der neben A benötigte Speicherplatz; wenn dieser nur O(1), also von n unabhängig, ist: Algorithmus arbeitet in situ“. ” FG KTuEA, TU Ilmenau AuD – 16.06.2008 3 Straight Insertion Sort: Θ(n2) Vergleiche, Zeit Θ(n2). (Schlechtester/durchschnittlicher Fall). Quicksort - Schnelles Sortieren im Durchschnitt (Bester Fall: Θ(n).) Divide-and-Conquer-Paradigma Stabil, in situ. Zerlege – Löse Teilprobleme rekursiv – Kombiniere Mergesort: Θ(n log n) Vergleiche, Zeit Θ(n log n). Zentrale Teilaufgabe: Stabil, nicht in situ (benötigt zusätzlichen Platz Θ(n)). Sortiere (Teil-)Array A[a..b], wo 1 ≤ a ≤ b ≤ n. Mergesort mit linearen Listen benötigt keinen Zusatzplatz (die Datenstruktur beinhaltet jedoch Zeiger). Zu erledigen von einer (rekursiven) Prozedur r qsort(a, b). Mergesort ist Basis für externes Sortieren (Daten auf Hintergrundspeicher). FG KTuEA, TU Ilmenau AuD – 16.06.2008 4 AuD – 16.06.2008 Quicksort−Schema: 0) Trivialitätstest: Wenn a ≥ b, ist nichts zu tun. Teilaufgabe: Sortiere A[a...b] (Oder: Wenn b ≤ a + Δ: StraightInsertionSort(a, b).) 1) Teile: (Hier partitioniere“) ” Wähle x ∈ {A[a],...,A[b]}. Arrangiere die Einträge von A[a..b] so um, dass x 4 a 23 5 8 17 9 1 8 b 16 Partitioniere! 4 1 9 9 p Sortiere rekursiv 1 5 4 AuD – 16.06.2008 5 23 A[a], . . . ,A[p − 1] ≤ A[p] = x < A[p + 1], . . . ,A[b] 16 17 für ein p mit a ≤ p ≤ b. >9 2) Löse Teilprobleme rekursiv: r qsort(a, p − 1) (links), r qsort(p + 1, b) (rechts). Sortiere rekursiv 8 9 16 17 5 Ansatz für r qsort(a, b), Korrektheit: Kernstück: Partitionieren und rekursiv sortieren. FG KTuEA, TU Ilmenau FG KTuEA, TU Ilmenau Nach I.V. gilt nun: A[a..p − 1], A[p + 1..b] sortiert. 23 3) Kombiniere“: Es ist nichts zu tun: A[a..b] ist sortiert. ” 6 FG KTuEA, TU Ilmenau AuD – 16.06.2008 7 Kernstück: Partitionieren. Kernstück: Partitionieren. x = A[a] Woher nehmen wir das Pivotelement“ x? ” Beispiele: x := A[a] (erstes Element des Teilarrays) Klar: b − a Vergleiche genügen, um Elemente ≤ x nach ” links“ und Elemente > x nach rechts“ zu schaffen, und x ” an die Stelle zwischen den Bereichen zu schreiben. x := A[b] (letztes Element des Teilarrays) x := A[(a + b)/2] (Mitte des Teilarrays) Clever Quicksort“ ” x := der Median der drei Einträge A[a], A[b], A[(a+b)/2] Eine sehr geschickte Methode (vom Erfinder von Quicksort, Hoare), kein zusätzlicher Platz, keine unnötige Datenbewegung. Randomisiertes Quicksort“ ” Wähle einen der b − a + 1 Einträge in A[a..b] zufällig. Technisch: Wähle ein s mit a ≤ s ≤ b, vertausche A[a] mit A[s]. Dann stets: Pivotelement x steht in A[a]. FG KTuEA, TU Ilmenau AuD – 16.06.2008 a x x 8 i >x ? j x b FG KTuEA, TU Ilmenau AuD – 16.06.2008 9 . . . bis sich i und j treffen“ und überkreuzen“. ” ” >x Ignoriere A[a]. a x x j i z >x a z x j i x >x b Lasse ab a + 1 einen Zeiger“ (Index) i nach rechts wandern, ” bis ein Eintrag > x erreicht wird. Lasse ab b einen Zeiger“ j (Index) nach links wandern, bis ” ein Eintrag ≤ x erreicht wird. Falls i < j: Vertausche A[i] und A[j]. a x x i x ? j >x b p=j b Vertausche A[a] mit A[j]. >x Position p des Pivotelements jetzt: A[j]. REST Erhöhe i um 1, erniedrige j um 1. Wiederhole. FG KTuEA, TU Ilmenau AuD – 16.06.2008 Sorgfalt bei der Implementierung der Idee nötig. 10 FG KTuEA, TU Ilmenau AuD – 16.06.2008 11 Prozedur partition(a, b, p) (∗ Partitionierungsprozedur in r qsort(a, b) ∗) (∗ Voraussetzung: a < b; Pivotelement: A[a] ∗) (1) x ← A[a]; (2) i ← a + 1; j ← b; (3) while (i ≤ j) do (∗ noch nicht fertig; Hauptschleife ∗) (4) while (i ≤ j) and (A[i] ≤ x) do i++; (5) while (j > i) and (A[j] > x) do j--; (6) if (i = j) then j--; (∗ Wissen: A[i] < x ∗) (7) if (i < j) then (8) vertausche A[i] und A[j]; (9) i++; j--; (10) A[a] ← A[j]; (11) A[j] ← x; (12) p ← j; (∗ Rückgabewert ∗) FG KTuEA, TU Ilmenau AuD – 16.06.2008 x =20 // 7 x =20 // 7 x =20 // 7 12 7 2 5 20 7 a x =20 // 7 i x =20 // 7 16 18 30 12 25 8 9 35 31 b Vorbereitung 16 18 30 12 25 8 9 j 16 18 30 12 25 i 8 9 j x =20 // 7 16 18 9 12 25 i 8 j 30 x =20 // 7 16 18 9 12 25 i 8 j 30 FG KTuEA, TU Ilmenau Vertauschen AuD – 16.06.2008 13 Lemma (Korrektheit von partition) 16 18 9 16 18 9 16 18 9 12 25 i 8 j 30 12 8 j 25 30 i 12 8 25 30 Ein Aufruf von partition(a, b, p) bewirkt folgendes, wenn 1 ≤ a < b ≤ n gilt: Vertauschen Sei x = A[a] (vor Aufruf), p der Inhalt von p (nach Aufruf). Das Teilarray A[a . . b] wird durch partition(a, b, p) so umarrangiert, dass x in A[p] steht und dass fertig! A[a],. . . ,A[p − 1] ≤ x < A[p + 1],. . . ,A[b] Aufräumen 8 Beispiel für partition: 16 18 9 gilt. 12 20 25 30 p FG KTuEA, TU Ilmenau AuD – 16.06.2008 14 FG KTuEA, TU Ilmenau AuD – 16.06.2008 15 Beweis: Wir beobachten die folgende Schleifeninvariante für die Hauptschleife (3)–(9) für a, b und die Inhalte i, j, x von i, j, x: Nach dem Test in Zeile (3), vor Beginn von Zeile (4) gilt: a+1≤i≤j ≤b A[a + 1], . . . ,A[i − 1] ≤ x A[j + 1], . . . ,A[b] > x Für den Beweis stellt man fest: Nach der i-Schleife in Zeile (4) gilt (!): a ≤ i − 1 ≤ j, A[a + 1], . . . ,A[i − 1] ≤ x, i ≤ j ⇒ A[i] > x. Nach Zeile (6) gilt: a ≤ i − 1 ≤ j, A[j + 1], . . . ,A[b] > x, i ≤ j ⇒ (i < j und A[j] ≤ x). Beweis durch Induktion über Schleifendurchläufe. 1. Fall: i > j. Dann gilt i = j + 1 und A[a + 1], . . . ,A[j] ≤ x und A[i], . . . ,A[b] > x. Hauptschleife endet, Vertauschung (Zeilen (10), (11)) stellt verlangte Ausgabe her. FG KTuEA, TU Ilmenau AuD – 16.06.2008 16 2. Fall: i ≤ j. Dann gilt wegen Zeile (6) sogar i < j. Durch die Vertauschung und Änderung der Indizes (Zeilen (8), (9)) entsteht wieder die Schleifeninvariante (falls i ≤ j) oder das korrekte Ende (i > j) der äußeren Schleife. FG KTuEA, TU Ilmenau AuD – 16.06.2008 17 Lemma (Aufwand von partition(a, b, p)) (a) Die Anzahl der Schlüsselvergleiche ist exakt b − a; (b) der (Zeit-)Aufwand ist Θ(b − a). Beweis: (a) Nach jedem Schlüsselvergleich wird i erhöht (Zeile (4) oder (9)) oder j erniedrigt (Zeile (5) oder (9)). In der Situation i = j wird der Vergleich A[i] ≤ x durchgeführt, und dies ist der letzte Vergleich. FG KTuEA, TU Ilmenau AuD – 16.06.2008 18 FG KTuEA, TU Ilmenau AuD – 16.06.2008 19 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) 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). Lemma 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]) if n > 1 then r qsort(1, n).. (1) Alternative zu den Tests in Zeilen (4), (5): Teilarrays der Länge ≤ Δ direkt mit StraightInsertionSort sortieren, für eine Konstante Δ. FG KTuEA, TU Ilmenau AuD – 16.06.2008 20 FG KTuEA, TU Ilmenau AuD – 16.06.2008 21 Die Laufzeit von r qsort(a, b), wobei n = b − a + 1. Schlechtester Fall: (worst case) Der Aufwand für einen Aufruf ist O(1) Wenn zwei Schlüssel verglichen werden, ist einer davon (x) das Pivotelement, der andere wandert in eines der beiden Teilarrays. 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. Konsequenz: Es werden niemals dieselben Schlüssel später nochmals verglichen. Also: V ≤ n2 = 12 n(n − 1). In jedem Aufruf ist ein anderer Arrayeintrag Pivotelement. 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. Gesamtaufwand für r qsort(a, b), wobei n = b − a + 1: O(n) plus Gesamtanzahl allerSchlüsselvergleiche. =:V V ist eine Größe, die von der Anordnung der Eingabe abhängt. FG KTuEA, TU Ilmenau AuD – 16.06.2008 22 (Dann ist das linke Teilarray leer, das rechte enthält b − a Elemente. Rekursionstiefe n, und V = (n − 1) + (n − 2) + · · · + 3 + 2 + 1 = n2 .) Actung: Ähnlicher Effekt, wenn Array fast“ sortiert. ” FG KTuEA, TU Ilmenau AuD – 16.06.2008 23 Cn := die erwartete Anzahl der Vergleiche beim Sortieren eines Teilarrays mit n Einträgen mit r qsort Durchschnittlicher/erwarteter Fall? Wahrscheinlichkeitsannahme: Die Eingabeelemente sind verschieden. O.B.d.A.: Eingabeelemente sind n verschiedene Zahlen x1 < . . . < xn. Jede Anordnung der Wahrscheinlichkeit. Eingabeelemente hat dieselbe (Es gibt n! solche Anordnungen.) Unter dieser Wahrscheinlichkeitsannahme wird V zu einer Zufallsgröße. Wir untersuchen den Erwartungswert/Mittelwert von V . FG KTuEA, TU Ilmenau AuD – 16.06.2008 24 Cn: Wenn man beim ersten r qsort(a, b)-Aufruf das i-t kleinste Element xi als Pivotelement hat, 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 = 13 · 2 + 23 · 3 = 83 . FG KTuEA, TU Ilmenau AuD – 16.06.2008 25 Falls also xi Pivotelement ist: Kosten (n − 1) + Ci−1 + Cn−i. dann erzeugt dieser Aufruf selbst zunächst genau n − 1 Vergleiche Die Wahrscheinlichkeit, dass xi Pivotelement ist, ist gleich 1/n, weil es ergibt sich als partitionierende Position p (in p) genau a + i − 1, nach der W-Annahme jede Anordnung der Einträge x1, . . . , xn in A[a . . b] gleichwahrscheinlich ist. und es entstehen die rekursiven Aufrufe Also: r qsort(a, a + i − 2) (wenn i ≥ 3) r qsort(a + i, b) (wenn i ≤ n − 2) n 1 ((n − 1) + Ci−1 + Cn−i). Cn = · n i=1 Diese kosten im Durchschnitt Ci−1 und Cn−i Vergleiche. FG KTuEA, TU Ilmenau AuD – 16.06.2008 26 FG KTuEA, TU Ilmenau AuD – 16.06.2008 27 Vorlesung am 19.05.2008, Folie 34: Satz Rekursionsformel für die mittlere totale Weglänge A(n) in zufällig erzeugten binären Suchbäumen. Die erwartete Vergleichsanzahl bei der Anwendung von Quicksort auf ein Array mit n verschiedenen Einträgen, die zufällig angeordnet sind, ist A(0) = A(1) = 0, A(2) = 1, n A(n) = (n − 1) + n1 · i=1(A(i − 1) + A(n − i)). 2(n + 1)Hn − 4(n + 1) + 4 ≈ (2 ln 2)n log n − 2,846 . . . n. Das ist genau die gleiche Rekursion wie die für Cn! Bemerkung: Kein Zufall, sondern enge Verbindung der beiden Verfahren Quicksort und Erzeugen von Suchbäumen. Dabei: 2 ln 2 ≈ 1,386 (die Quicksort-Konstante“) ” Also sind auch die Lösungen identisch. FG KTuEA, TU Ilmenau AuD – 16.06.2008 28 FG KTuEA, TU Ilmenau AuD – 16.06.2008 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] Bemerkungen: Randomisierung Quicksort. • Vermeide rekursive Aufrufe für allzu kleine Teilarrays. Direkt mit StraightInsertionSort sortieren. 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 AuD – 16.06.2008 30 29 • Quicksort ist in der Praxis sehr schnell. (Partition-Prozedur ist Cache-freundlich) • Noch besser: Rekursion bei Teilarrays der Länge Δ (z.B. Δ = 10) abbrechen, unsortiert lassen. Am Ende: ein Aufruf StraightInsertionSort(1, n). • 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: Median-of-3 oder Randomisiertes Quicksort. FG KTuEA, TU Ilmenau AuD – 16.06.2008 31 Bemerkungen: • Warnung 2: Wenn A[1 . . n] viele identische Schlüssel enthält, ist das beschriebene Verfahren nicht günstig. (Besondere Vorkehrungen im Programm nötig: Übung.) • 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 AuD – 16.06.2008 32 Beispiel: 4 5 12 12 10 B 3 E 5 12 17 3 AuD – 16.06.2008 F G 4 Hier nur interne Knoten relevant. Blätter: Knoten ohne (interne) Kinder. FG KTuEA, TU Ilmenau 33 12 12 3 12 17 max: 10 AuD – 16.06.2008 Ein Binärbaum T mit Knotenbeschriftungen m(v) aus (U, <) heißt min-heapgeordnet (oft: heapgeordnet), wenn für jeden Knoten v gilt: w Kind von v ⇒ m(v) ≤ m(w). Heap-Ordnung in Binärbaumen: 3 FG KTuEA, TU Ilmenau Definition Heapsort min: Prozedur hr qsort(a, b) (∗ Halb-rekursive Variante ∗) (1) a ← a; b ← b; (2) while a < b do (3) s ← ein 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 ∗) N 34 FG KTuEA, TU Ilmenau E P H F N AuD – 16.06.2008 35 Ein linksvollständiger Binärbaum: Alle Levels j = 0, 1, . . . voll (jeweils 2j Knoten) bis auf die tiefste. Das tiefste Level l hat von links her gesehen eine ununterbrochene Folge von Knoten. B E F G H E N P F N NB: 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 AuD – 16.06.2008 Können gut als Arrays dargestellt werden! 36 Nummerierung von Knoten in unendlichem, vollständigen Binärbaum gemäß Breitendurchlauf-Anordnung: FG KTuEA, TU Ilmenau AuD – 16.06.2008 37 Nummerierung von Knoten in unendlichem, vollständigen Binärbaum gemäß Breitendurchlauf-Anordnung: 1 1 0 5 8 0 0 1 0 0 11 10 9 1 7 6 1 0 1 12 0 1 14 13 1 0 1 0 1 0 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 AuD – 16.06.2008 5 0 1 0 7 6 1 0 11 10 9 1 1 0 0 1 8 15 0 3 1 4 0 1 0 1 2 0 1 0 0 1 1 0 3 1 4 0 1 2 0 Beispiel: (101110)2 = 46 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 38 FG KTuEA, TU Ilmenau 1 1 46 47 63 0 AuD – 16.06.2008 38 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 Definition 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] beschriftet) (min-)heapgeordnet ist. 1 ist die Wurzel; 2i ist linkes Kind von i; 2i + 1 ist rechtes Kind von i; i ≥ 2 hat Vater i/2. Beobachte: Ein linksvollständiger Binärbaum besteht aus einem Anfangsabschnitt {1, 2, . . . , n} der Knoten des unendlichen vollständigen Binärbaums (mit denselben Vater-SohnBeziehungen). 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 Kombiniere Heapbedingung und Array-Darstellung: AuD – 16.06.2008 D. h.: A[1 . . k] ist ein Heap, falls 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. (Analog: Max-Heap (benutze Maxheap-Ordnung)) 39 U = {A, B, C, . . . , Z} mit der Standardordnung. FG KTuEA, TU Ilmenau AuD – 16.06.2008 40 Bleibt Aufgabe: Heaps reparieren Im Beispiel: Daten weggelassen. 1 2 3 4 5 6 7 8 9 10 Ein Min-Heap und der zugeordnete Baum (k = 10): G E F G E H F H I * * * 1 2 3 4 5 6 7 8 9 10 B E F G E H F H I G 11 11 12 1 12 G * * 2 3 E F 1 2 E E G 8 8 F 5 FG KTuEA, TU Ilmenau I 7 H F I 7 H F Abwärts-Fast-Heap“ bis auf Position 1 (Wurzel), d. h.: ” Wenn man den Eintrag in A[1] verkleinert (hier z. B. auf C), entsteht ein Heap. G AuD – 16.06.2008 6 9 H 6 9 10 H E G 3 4 5 4 B 41 FG KTuEA, TU Ilmenau AuD – 16.06.2008 42 Heaps reparieren Heaps reparieren 11 12 1 2 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 1 G G 3 E 5 6 E G 8 2 F ? 4 F 5 43 9 FG KTuEA, TU Ilmenau AuD – 16.06.2008 43 Heaps reparieren 11 12 1 2 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 5 E 2 F 6 F AuD – 16.06.2008 5 8 I F < E G 6 7 H F 9 H Vergleich des kleineren“ Kinds mit dem Fehler“-Knoten. ” ” FG KTuEA, TU Ilmenau 3 E 4 7 H 9 H G < < 4 8 1 3 E 12 2 G ? 11 1 1 G F Vergleich der beiden Kinder des Fehler“-Knotens. ” Heaps reparieren 2 7 H I H AuD – 16.06.2008 6 E 8 Vergleich der beiden Kinder des Fehler“-Knotens. ” F < G 9 FG KTuEA, TU Ilmenau 3 E 4 7 H I H 12 2 1 2 11 1 I Vergleich des kleineren“ Kinds mit dem Fehler“-Knoten. ” ” 43 FG KTuEA, TU Ilmenau AuD – 16.06.2008 43 Heaps reparieren Heaps reparieren 11 12 1 2 3 4 5 6 7 8 9 10 E G F G E H F H I * * * 3 4 5 6 7 8 9 10 E G F G E H F H I * * * 1 E E 3 5 6 E G 8 2 F G 4 3 G F > 5 4 7 H F G 6 E > 8 9 I H 12 2 1 2 11 1 7 H F 9 I H Vertauschen des kleineren“ Kinds mit dem Fehler“-Knoten. ” ” Abwärts-Fast-Heap, Fehler in Knoten ein Level tiefer. Vergleich des kleineren“ Kinds mit dem Fehler“-Knoten. ” ” FG KTuEA, TU Ilmenau FG KTuEA, TU Ilmenau AuD – 16.06.2008 43 Heaps reparieren AuD – 16.06.2008 43 Heaps reparieren 11 12 1 2 3 4 5 6 7 8 9 10 E E F G G H F H I * * * 3 4 5 6 7 8 9 10 E E F G G H F H I * * * 1 E 2 F E 5 G G 8 E 3 4 6 3 F E 5 4 7 H F G G 8 9 H 12 2 1 2 11 1 I 7 H F 9 H Vertauschen des kleineren“ Kinds mit dem Fehler“-Knoten. ” ” Abwärts-Fast-Heap, Fehler in Knoten ein Level tiefer. 6 I Kein Fehler mehr: Reparatur beendet. Fehler“-Knoten hat keine Kinder. ” FG KTuEA, TU Ilmenau AuD – 16.06.2008 43 FG KTuEA, TU Ilmenau AuD – 16.06.2008 43 Prozedur bubbleDown(1, k) (∗ Heapreparatur in A[1 . . k] ∗) (∗ Ausgangspunkt: A[1 . . k] ist Abwärts-Fast-Heap, möglicher Fehler in A[1]∗) (1) j ← 1; m ← 2; done ← false; (2) while not done and m + 1 ≤ k do (∗ Abwärts-Fast-Heap, möglicher Fehler in A[j], A[j] hat 2 Kinder ∗) (3) if A[m+1].key < A[m].key (4) then (∗ A[m]: kleineres“ Kind ∗) ” (5) m ← m + 1; (6) if A[m].key < A[j].key (7) then (∗ Abwärts-Fast-Heap, möglicher Fehler in A[j] ∗) (8) vertausche A[m] mit A[j]; j ← m; m ← 2 ∗ j; (9) else (∗ fertig, kein Fehler mehr ∗) (10) done ← true; (11) if not done then (∗ Abwärts-Fast-Heap, möglicher Fehler in A[j], maximal ein Kind ∗) (12) m ← 2 ∗ j; (13) if m ≤ k then (14) if A[m].key < A[j].key (15) then vertausche A[m] mit A[j]; FG KTuEA, TU Ilmenau AuD – 16.06.2008 44 Korrektheit: Wenn vor dem Aufruf von bubbleDown(1, k) das Teilarray A[1 . . k] ein Abwärts-Fast-Heap war, mit möglichem Fehler in A[1], d. h.: es gibt einen Schlüssel x ≤ A[1].key derart, dass durch Ersetzen von A[1].key durch x ein Heap entsteht, dann ist nachher A[1 . . k] ein Heap. Beweis: Man zeigt folgende Behauptung durch Induktion über die Schleifendurchläufe: (IBj ) Zu Beginn des Durchlaufs, in dem j die Zahl j enthält, ist A[1 . . k] ein AbwärtsFast-Heap mit möglichem Fehler in A[j]. Der I.A. ist klar. FG KTuEA, TU Ilmenau AuD – 16.06.2008 45 Betrachte Schleifendurchlauf, in dem j die Zahl j enthält. Nur A[2j].key kann im Unterbaum unter A[2j] zu groß sein. 1. Fall: In Zeile (8) bzw. (15) wird vertauscht. Also: Abwärts-Fast-Heap mit möglichem Fehler in A[2j], das ist (IB2j ). Wir betrachten den Fall 2j + 1 ≤ k und A[m+1].key ≥ A[m].key, also m = 2j. (Andere Fälle analog.) 2. Fall: In Zeile (8) bzw. (15) wird nicht vertauscht. Nach der Vertauschung, vor der Änderung von j, gilt: A[j].key ≤ A[2j].key ≤ A[2j + 1].key (wegen der Bestimmung von m). Weiter: Falls j > 1, gilt A[j].key ≥ A[j/2].key, wegen der Definition von Abwärts-Fast-Heap“ ” (vor der Vertauschung war A[m].key ≥ A[j/2].key). Weiter: Im Unterbaum unter dem größeren“ Kind A[2j + 1] ” ist alles unverändert, also ist dort die Heapbedingung erfüllt. FG KTuEA, TU Ilmenau AuD – 16.06.2008 46 Dann gilt: Die Schleife endet. A[j].key ≤ A[2j].key, A[2j + 1].key (falls 2j + 1 ≤ k). Es muss auch A[j].key ≥ A[j/2].key gelten, wegen der Definition von Abwärts-Fast-Heap“ (A[j].key kann nicht ” zu klein“ sein). ” Also ist A[1 . . k] ein Heap. In jedem Schleifendurchlauf wird der Inhalt von j immer mindestens verdoppelt; daher terminiert die Schleife. FG KTuEA, TU Ilmenau AuD – 16.06.2008 47 Kosten: Angenommen, Array A[1 . . n] ist heapgeordnet. . . . von Aufruf bubbleDown(1, k): Dann können wir A[1 . . n] wie folgt fallend sortieren: O(1) + Θ(Anzahl der Schlüsselvergleiche A[.].key < A[.].key“). ” Vk : Anzahl der Schlüsselvergleiche im schlechtesten Fall. Für k = n, n − 1, n − 2, . . . , 2: In jedem Schleifendurchlauf 2 Vergleiche. (∗ Das minimale Element von A[1 . . k] ist A[1]! ∗) Vertausche A[1] mit A[k]. (∗ Repariere in A[1 . . k − 1] die Heapeigenschaft: ∗) jr : Inhalt von j in Runde r = 1, 2, 3, . . .. Verdopplung! Falls k ≥ 3: bubbleDown(1, k − 1); j1 = 1 = 20, jr ≥ 2r−1. Vergleiche finden nur statt, falls k ≥ 2·jr ≥ 2r , d. h. r ≤ log k. ⇒ Vk ≤ 2 · log k. Klar: Die Elemente im Array werden in steigender Reihenfolge nach A[n], A[n − 1], . . . , A[2] geschrieben; das maximale Element landet schließlich in A[1]. FG KTuEA, TU Ilmenau AuD – 16.06.2008 48 FG KTuEA, TU Ilmenau AuD – 16.06.2008 49 . . . und wie verwandelt man A[1 . . n] in einen Heap? Prozedur HeapSelect(1, n) (1) for k from n downto 3 do (2) vertausche A[1] mit A[k]; (3) bubbleDown(1, k − 1); (4) vertausche A[1] mit A[2]. Anzahl Vergleiche: ≤ 2≤k≤n Vk < 2n log n. Kosten: 2≤k≤n(O(1) + O(log k) 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 = O(n) + O(n log n) = O(n log n). A P R B K FG KTuEA, TU Ilmenau AuD – 16.06.2008 50 FG KTuEA, TU Ilmenau L T AuD – 16.06.2008 N M E F G 51 Definition 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] AuD – 16.06.2008 Kann mit einer Variante von bubbleDown Heapbedingung herstellen! Prozedur bubbleDown(, k) (∗ Heapreparatur in T,k ∗) j ← ; m ← 2*; done ← false; (2) while not done and m + 1 ≤ k do (3) . . . (1) Klar: Ein Teilheap ist die disjunkte Vereinigung von heapgeordneten Bäumen. FG KTuEA, TU Ilmenau 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 j verletzt. 52 Korrektheit: Wie bei bubbleDown(1, k). FG KTuEA, TU Ilmenau AuD – 16.06.2008 53 Prozedur makeHeap(1, n) (∗ Transformiert Array A[1 . . n] in Heap ∗) (1) for l from n/2 downto 1 do (2) (∗ stelle in A[l..n] Heapbedingung her! ∗) (3) bubbleDown(l, n); Laufzeit: O(1) plus Θ(Anzahl Vergleiche). V,k : Anzahl der Schlüsselvergleiche im schlechtesten Fall. In jedem Schleifendurchlauf 2 Vergleiche. jr : Inhalt von j in Runde r = 1, 2, 3, . . .. j1 = . Wegen Verdopplung: jr ≥ 2r−1 · . Korrektheit: Mit Induktion über = n/2, n/2 − 1, . . . , 2, 1 zeige: Nach Durchlauf mit in l ist A[ . . n] Teilheap. r Vergleiche nur, falls k ≥ 2 · jr ≥ 2 , d. h. r ≤ log(k/). ⇒ V,k ≤ 2 · log(k/). Ind.-Anfang: Vor der ersten Runde. In A[n/2 + 1 . . n] gibt es keine Vater-Kind-Paare; dieses Teilarray ist also auf jeden Fall Teilheap. (In jedem Schleifendurchlauf maximal 2 Vergleiche.) Ind.-Schritt: Folgt aus Korrektheit von bubbleDown(l, n). FG KTuEA, TU Ilmenau AuD – 16.06.2008 54 FG KTuEA, TU Ilmenau AuD – 16.06.2008 55 Kosten: Anzahl der Vergleiche: V,n ≤ 2 · 1≤≤n/2 = 2· Algorithmus heapSort(A[1 . . n]) (1) makeHeap(1, n); (2) HeapSelect(1, n); log(n/) 1≤≤n/2 1 Satz 1≤≤n/2 j : ·2j ≤n = 2· Algorithmus heapSort sortiert das Array A[1 . . n] in fallende Reihenfolge. 1 1≤j≤log n 1≤≤n/2j Die gesamte Laufzeit ist O(n log n). n 1 ≤ 2· < 2n · j 2 2j 1≤j≤log n 1≤j≤log n (∗ Geometrische Reihe: j≥1 2−j = 1. ∗) Die Gesamtzahl der Vergleiche ist kleiner als 2n(log n + 1), für genügend große n. < 2n. FG KTuEA, TU Ilmenau AuD – 16.06.2008 56 Beweis: Korrektheit und Zeitbedarf folgt aus der Analyse der Teilprozeduren. Gesamtzahl der Vergleiche: 2n + 2≤k≤n Vk ≤ 2n + 2 · 2≤k≤n log k FG KTuEA, TU Ilmenau AuD – 16.06.2008 57 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. ≤ 2n + 2n log n. Bemerkung 2: 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. Bem.: Kann sogar zeigen: < 2n log n Vergleiche. Bemerkung 3: Variante Bottom-Up-Heapsort“ hat ” n log n + O(n) Vergleiche im mittleren Fall. – Idee: Bei bubbleDown laufe gesamten Weg der kleineren Söhne“ bis zum Blatt, füge das kritische ” Element aus der Wurzel von unten nach oben an die richtige Stelle ein. FG KTuEA, TU Ilmenau AuD – 16.06.2008 58 FG KTuEA, TU Ilmenau AuD – 16.06.2008 59 Bemerkung 4: Bei Variante 4-Way-Heapsort“ hat Knoten 1 ” die Kinder 2, 3, . . . , 7 und jeder Knoten i ≥ 2 die 4 Kinder: 4i, 4i + 1, 4i + 2, 4i + 3. Die Heapbedingung wird verallgemeinert: Datenstruktur: Priority Queues oder Vorrangswarteschlangen A[1] ≤ A[2], . . . , A[7], A[i] ≤ A[4i], A[4i + 1], A[4i + 2], A[4i + 3], i ≥ 8. Idee: (Daten mit) Schlüssel aus sortiertem Universum (U, <) werden eingefügt und entnommen. Bei bubbleDown: Vertauschen mit dem kleinsten KindEintrag (Auswahl aus 4). Beim Entnehmen wird immer der Eintrag mit dem kleinsten Schlüssel gewählt. 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, AuD – 16.06.2008 60 Schlüssel = Prioritäten“ ” – je kleiner der Schlüssel, desto höher die Priorität – Wähle stets den Eintrag mit höchster Priorität. FG KTuEA, TU Ilmenau AuD – 16.06.2008 61 Anwendungen: Anwendungen: 1) Prozessverwaltung in Multitask-System 2) Discrete Event Simulation“ ” System von Aktionen soll auf dem Rechner simuliert werden. Jeder ausführbaren Aktion A ist ein Zeitpunkt tA ∈ [0, ∞) zugeordnet. 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. Die Ausführung einer Aktion A kann neue Aktionen A erzeugen oder ausführbar machen, mit neuen Zeitpunkten tA > tA. Ein Schritt: Wähle diejenige noch nicht ausgeführte Aktion mit dem frühesten Ausführungszeitpunkt und führe sie aus. 3) Innerhalb von Algorithmen (Dijkstra, Jarnı́k/Prim: später). FG KTuEA, TU Ilmenau AuD – 16.06.2008 62 FG KTuEA, TU Ilmenau AuD – 16.06.2008 63 Beispiel: Datensatz: (n, L), n ∈ N, L ∈ {A, . . . , Z}, Schlüssel ist der Buchstabe L. Bei Priority Queues zu berücksichtigen: Mehrere Objekte mit derselben Priorität sind möglich, die man nicht identifizieren darf. Mehrere identische Objekte sind möglich, die man nicht identifizieren darf: Einträge sind nicht blanke“ Schlüssel, sondern ” mit Prioritäten versehene Datensätze. Beim Entnehmen: Alle Exemplare des kleinsten Schlüssels gleichberechtigt, einer gewinnt. FG KTuEA, TU Ilmenau AuD – 16.06.2008 64 Operation empty insert(1, D) insert(2, B) insert(3, D) insert(4, E) insert(5, G) extract min extract min insert(4, E) insert(2, D) extract min isempty 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)} {(4, E), (1, D), (2, B), (3, D), (4, E)} {(4, E), (2, D), (1, D), (4, E), (5, G)} {(4, E), (1, D), (4, E), (5, G)} {(4, E), (1, D), (4, E), (5, G)} FG KTuEA, TU Ilmenau AuD – 16.06.2008 Datentyp: SimplePriorityQueue (Einfache Vorrangswarteschlange) Mathematisches Modell: Signatur: Sorten: Keys : Data : Boolean : PrioQ : Operationen: Sorten: Keys Data PrioQ (∗ Priority Queues“ ∗) ” Boolean Operatoren: empty : → PrioQ isempty : PrioQ → Boolean insert : Keys × Data × PrioQ → PrioQ extract min: PrioQ → Keys × Data × PrioQ FG KTuEA, TU Ilmenau AuD – 16.06.2008 empty ( ) = ∅ isempty (P ) = 66 FG KTuEA, TU Ilmenau Ausgabe – – – – – – (2, B) (3, D) – – (2, D) false 65 (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 = ∅ AuD – 16.06.2008 67 insert(x, d, P ) = P ∪ {(x, d)} (als Multimenge), extract min(P ) = undefiniert, Implementierung: Neben Array A[1 . . m]: Pegel k: Aktuelle Zahl k von Einträgen. für P = ∅ Überlaufprobleme werden ausgeklammert. (Verdoppelungsstrategie, falls nötig.) (x0, d0, P ), für P = ∅, wobei im zweiten Fall (x0, d0) ein Element in P ist mit x0 = min{x | ∃d : (x, d) ∈ P }, und P := P − {(p0, x0)} (als Multimenge). Standardimplementierung: (Binäre) Heaps FG KTuEA, TU Ilmenau AuD – 16.06.2008 68 FG KTuEA, TU Ilmenau AuD – 16.06.2008 69 empty(m): Lege Array A[1 . . m] an; Jeder Eintrag ist ein Paar (key, data). extractMin: Implementierung von extract min(P ): k ← 0. Entnehme A[1] (hier B“) und gib es aus. ” Ein Eintrag mit minimalem Schlüssel steht in der Wurzel, d. h. in Arrayposition 1. (∗ Zeitaufwand: O(1) oder O(m) ∗) 1 2 3 4 5 6 7 8 9 10 B E F G E H F H I G isempty(): return(k = 0); 11 12 * * 1 B 2 (∗ Zeitaufwand: O(1) ∗) 3 E F 4 5 E G 8 6 7 H F 9 10 H I G Loch“ im Binärbaum. ” FG KTuEA, TU Ilmenau AuD – 16.06.2008 70 FG KTuEA, TU Ilmenau AuD – 16.06.2008 71 extractMin: Implementierung von extract min(P ): extractMin: Implementierung von extract min(P ): Ein Eintrag mit minimalem Schlüssel steht in der Wurzel, d. h. in Arrayposition 1. Ein Eintrag mit minimalem Schlüssel steht in der Wurzel, d. h. in Arrayposition 1. Entnehme A[1] (hier B“) und gib es aus. ” Entnehme A[1] (und gib es aus). 1 2 3 4 5 6 7 8 9 10 ? E F G E H F H I G 11 12 * * 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 1 G ? 3 E 5 6 E G 8 2 F 4 71 11 3 4 5 6 7 8 9 10 G E F G E H F H I * * * 1 G 3 E F 5 8 6 7 H F 9 H AuD – 16.06.2008 I * FG KTuEA, TU Ilmenau AuD – 16.06.2008 72 Korrektheit: klar wegen Korrektheit von bubbleDown(1, k). I Zeitaufwand: O(log(k)). Abwärts-Fast-Heap“ bis auf Position 1 (Wurzel), d. h.: ” bubbleDown hilft hier! FG KTuEA, TU Ilmenau F Prozedur extractMin (∗ Entnehmen eines minimalen Eintrags aus Priority-Queue ∗) (∗ Ausgangspunkt: Pegel k, A[1 . . k] ist Heap, k ≥ 1 ∗) (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); 12 2 E 7 H Loch“ im Binärbaum – stopfen“ mit dem letzten Eintrag. ” ” 1 4 6 9 10 H Bleibt Aufgabe: Heap reparieren G 5 E 8 G AuD – 16.06.2008 2 F G F Loch“ im Binärbaum. ” FG KTuEA, TU Ilmenau 3 E 4 7 H 9 10 I H 12 2 1 2 11 1 73 FG KTuEA, TU Ilmenau AuD – 16.06.2008 74 Implementierung von insert(x, d): Heap: Voraussetzung: A[1..k] ist Heap; 1 ≤ i ≤ k < m. k++; A[k] ← (x, d). 1 2 3 4 5 6 7 8 9 C E F G J K F H L Wir nennen A[1..k] einen Aufwärts-Fast-Heap“ mit ” möglicher Fehlerstelle k. Heißt: Es gibt einen Schlüssel y ≥ A[k].key, so dass ein Heap entsteht, wenn man A[k].key durch y ersetzt. AuD – 16.06.2008 75 Einfügen von D“ an Stelle k = 11. ” 2 3 4 5 6 7 8 9 C E F G J K F H L 2 3 E F 4 5 G 6 J 8 9 10 H L FG KTuEA, TU Ilmenau Q AuD – 16.06.2008 10 Q 11 D 12 76 * 1 2 3 4 5 6 7 8 9 C E F G J K F H L 3 5 9 10 FG KTuEA, TU Ilmenau 2 6 J L AuD – 16.06.2008 Q 11 D 12 * 3 E F 4 7 K 5 G F 9 10 H D 76 FG KTuEA, TU Ilmenau 6 J 8 11 Q 10 C F G F 1 E 4 7 K C H * * C 1 8 12 Heapreparatur bei Aufwärts-Fast-Heaps: bubbleUp. 1 2 Q 11 1 An der Stelle k ist nun eventuell die Heapeigenschaft gestört (x zu klein). FG KTuEA, TU Ilmenau 10 L 7 K F 11 Q AuD – 16.06.2008 D 76 Heapreparatur bei Aufwärts-Fast-Heaps: bubbleUp. 1 2 3 4 5 6 7 8 9 C E F G D K F H L 10 11 Q J Heapreparatur bei Aufwärts-Fast-Heaps: bubbleUp. 12 * 1 2 3 4 5 6 7 8 9 C E F G D K F H L 1 E 5 6 9 10 H L FG KTuEA, TU Ilmenau 5 76 Heapreparatur bei Aufwärts-Fast-Heaps: bubbleUp. 1 2 3 4 5 6 7 8 9 C D F G E K F H L 10 11 Q J L FG KTuEA, TU Ilmenau J AuD – 16.06.2008 76 Heapreparatur bei Aufwärts-Fast-Heaps: bubbleUp. 12 * 1 2 3 4 5 6 7 8 9 C D F G E K F H L 3 6 FG KTuEA, TU Ilmenau AuD – 16.06.2008 J * F 5 G F 76 FG KTuEA, TU Ilmenau L 7 K 9 10 H J 6 E 8 11 Q Q 12 3 D 4 7 K 9 10 L 2 F 5 H 11 C D E 8 10 1 C G F 11 Q 1 4 7 K 9 10 H J 6 D 8 AuD – 16.06.2008 2 * F G F 11 Q J 3 E 4 7 K D 8 2 F G Q 12 C 3 4 11 1 C 2 10 F 11 Q AuD – 16.06.2008 J 76 Prozedur bubbleUp(i) (∗ Heapreparatur ab A[i] nach oben ∗) (1) j ← i; (2) h ← j ÷ 2; (3) x ← A[j].key; d ← A[j].data; (4) while h ≥ 1 and x < A[h].key do (5) A[j] ← A[h]; (6) j ← h; (7) h ← j/2; (8) A[j] ← (x, d); Heapreparatur bei Aufwärts-Fast-Heaps: bubbleUp. 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 8 9 10 H L 7 K E F 11 Q Auf dem Weg von A[i] zur Wurzel werden alle Elemente, deren Schlüssel größer als x (= der neue Eintrag in A[i]) ist, um eine Position (auf dem Weg) nach unten gezogen. J Eintrag A[i] landet in der freigewordenen Position. FG KTuEA, TU Ilmenau AuD – 16.06.2008 76 FG KTuEA, TU Ilmenau AuD – 16.06.2008 Behauptung 1 2 3 4 5 6 7 8 9 C D F G E K F H L Wenn A[1..k] ein Aufwärts-Fast-Heap ist mit möglichem Fehler an Stelle i, 10 11 Q J 12 * 1 C 2 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. log(i + 1). 77 3 D F 4 5 G 6 8 9 10 H L 7 K E F 11 Q J Beweis: Wenn sich ein Eintrag ändert, dann liegt er auf dem Weg von i zur Wurzel. Das Hochziehen“ von A[i] wirkt wie bei Sortieren durch ” Einfügen: nach bubbleUp(i) sind die Elemente auf diesem Weg (fallend) geordnet. FG KTuEA, TU Ilmenau AuD – 16.06.2008 78 FG KTuEA, TU Ilmenau AuD – 16.06.2008 79 1 2 3 4 5 6 7 8 9 C D F G E K F H L 10 11 Q J 12 Prozedur insert(x, d) (∗ Einfügen eines neuen Eintrags in Priority-Queue ∗) (∗ Ausgangspunkt: Pegel k, A[1 . . k] ist Heap, k < n ∗) (1) if k = m then Überlauf-Fehler“; ” (2) k++; (3) A[k] ← (x, d); (4) bubbleUp(k). * 1 C 2 3 D F 4 5 G 6 8 9 10 H L 7 K E F 11 Q J Falls A[j] durch A[j/2] ersetzt wird, ist nachher A[j] kleiner als seine beiden Kinder. Korrektheit: klar wegen Korrektheit von bubbleUp(k). Zeitaufwand: O(log(k)). Beide Kinder der Position , an die x gelangt, enthalten Schlüssel, die ≥ x sind: der Schlüssel auf dem Weg (vorher in A[]), weil er mit x verglichen wird; der Schlüssel im Kind von neben dem Weg, weil er ≥ dem alten Wert A[] ist. FG KTuEA, TU Ilmenau AuD – 16.06.2008 80 Wir können bubbleUp(i) sogar für eine etwas allgemeinere Operation verwenden: 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). FG KTuEA, TU Ilmenau AuD – 16.06.2008 81 SimplePriorityQueue plus decreaseKey“: Priority Queue. ” Satz Der Datentyp “Priority Queue” kann mit Hilfe eines Heaps implementiert werden. Dabei erfordern empty und isempty (und das Ermitteln des kleinsten Eintrags) konstante Zeit (bzw. O(m), wenn ein Array der Größe m initialisiert werden muss) 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 Priority Queue. (∗ Zeitaufwand: O(log(i)) ∗) FG KTuEA, TU Ilmenau AuD – 16.06.2008 82 FG KTuEA, TU Ilmenau AuD – 16.06.2008 83 Sei nun A ein Schlüsselvergleichsverfahren. Untere Schranke für Sortieren Wir wenden A auf n! verschiedene Eingaben Sortieralgorithmus A heißt ein Schlüsselvergleichsverfahren, wenn in A auf Schlüssel x, y aus U nur Operationen x≤y ? x<y ? (a1, 1), . . . , (an, n) an, wobei (a1, . . . , an) eine Permutation π von {1, . . . , n} ist. x=y ? sowie Verschieben und Kopieren angewendet werden. Sortierschlüssel: erste Komponente. Schlüssel sind atomare Objekte“. ” Mergesort, Quicksort, Heapsort, Shellsort, Insertion-Sort, Bubblesort sind Schlüsselvergleichsverfahren. 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, die die Eingabe (a1, . . . , an) sortiert. D.h.: n! verschiedene Eingabepermutation. FG KTuEA, TU Ilmenau AuD – 16.06.2008 84 Beispiel: Mergesort mit 3 Elementen a 1< a 2 ja Start 0 nein 231 213 0 a 2< a 3 312 ja nein AuD – 16.06.2008 a i3< a j3 1 0 1 132 a i6< a j6 a i5< a j5 1 0 ja 1 0 a i7< a j7 1 0 π’’ 1 π’’’ a i20< a j20 123 0 π In Blättern: die zweiten Komponenten FG KTuEA, TU Ilmenau 1 ja nein a 1< a 3 a i1< a j1 a i2< a j2 0 a i4< a j4 321 jede 85 a3 a2 a 1< a 3 nein für AuD – 16.06.2008 ja a 2< a 3 eine Abstraktion: Vergleichsbaum TA,n a1 nein FG KTuEA, TU Ilmenau Ausgaben, 86 FG KTuEA, TU Ilmenau 1 π’ AuD – 16.06.2008 87 Satz Bemerkung: Wenn A ein Schlüsselvergleichsverfahren ist, das das Sortierproblem löst, n! ≥ (n/e)n. dann gibt es eine Eingabe (a1, . . . , an), auf der A mindestens log(n!) Vergleiche ausführt. Beweis: Der Vergleichsbaum, den A erzeugt, hat n! (externe) Blätter, also hat er Tiefe ≥ log(n!). Die worst-case Vergleichsanzahl ist eine natürliche Zahl, also ist sie ≥ log(n!). Beweis: nn/n! < Also: n! ≥ (n/e)n. AuD – 16.06.2008 88 ni i≥0 i! = en. Also: log(n!) ≥ log((n/e)n) = n log n − n log e = n log n − 1,44269..n. Erinnerung (siehe Mergesort-Analyse): n! ≤ (en)(n/e)n. Mitteilung: Stirlingsche Formel Für n ≥ 1 gilt: √ FG KTuEA, TU Ilmenau FG KTuEA, TU Ilmenau 2πn n n e ≤ n! ≤ √ 2πn n n e 1 e 12n AuD – 16.06.2008 89 Satz Binärbaum-Fakten 3 Wenn A ein Schlüsselvergleichsverfahren ist, das das Sortierproblem löst, 0 dann gilt für die mittlere Vergleichsanzahl Vn (gemittelt über die n! verschiedenen Eingabeanordnungen): 0 0 C Vn ≥ log(n!). Beweis: Der Vergleichsbaum, den A erzeugt, hat n! (externe) Blätter. Aus den Betrachtungen zu Binärbäumen: 1 1 0 H 1 1 F 0 G 0 E 1 0 1 0 B 1 I 0 A 1 D 1 K Proposition T hat N externe Knoten ⇒ TEPL(T ) ≥ N log N . Mittlere äußere Weglänge ist ≥ log(N ). Dies wenden wir auf den Vergleichsbaum (mit N = n!) an: Mittlere Anzahl von Vergleichen ist ≥ log(n!). FG KTuEA, TU Ilmenau AuD – 16.06.2008 90 FG KTuEA, TU Ilmenau AuD – 16.06.2008 91 Satz 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. Beweis: Fortgeschritten, hier weggelassen. FG KTuEA, TU Ilmenau AuD – 16.06.2008 92