Organisatorisches VL-06: Sortieren I • Vorlesung: Gerhard Woeginger (Zimmer 4024 im E1) Sprechstunde: Mittwoch 11:15–12:00 (Datenstrukturen und Algorithmen, SS 2017) • Übungen: Tim Hartmann, David Korzeniewski, Björn Tauer Email: [email protected] Janosch Fuchs • Webseite: http://algo.rwth-aachen.de/Lehre/SS17/DSA.php SS 2017, RWTH DSAL/SS 2017 VL-06: Sortieren I 1/34 DSAL/SS 2017 VL-06: Sortieren I 2/34 Sortieren I • • • • DSAL/SS 2017 Einführung ins Sortieren Einführung Sortieren durch Einfügen (InsertionSort) Divide-and-Conquer und MergeSort Geht es noch effizienter? VL-06: Sortieren I 3/34 DSAL/SS 2017 VL-06: Sortieren I 4/34 Sortieren Sortieren: Anwendungen Beispiel (Suchen) Donald Knuth (The Art of Computer Programming, Volume 3) Schnellere Suche = häufigste Anwendung des Sortierens. Computer manufacturers of the 1960s estimated that more than 25 percent of the running time on their computers was spent on sorting, when all their customers were taken into account. In fact, there were many installations in which the task of sorting was responsible for more than half of the computing time. Binäre Suche findet ein Element in O(log n). Beispiel (Closest pair) Gegeben seien n Zahlen. Finde Zahlenpaar mit geringstem Abstand. Nach dem Sortieren liegen die beiden Zahlen nebeneinander. Der Aufwand ist dann noch O(n). Sortieren ist wichtig Beispiel (Eigenschaften von Datenobjekten) Sortieren hat viele Anwendungen. Geniale Algorithmen Sind alle n Elemente einzigartig oder gibt es Duplikate? Grundlegende Ideen Welches Element kommt am häufigsten vor? Was ist das k-t grösste Element einer Menge? DSAL/SS 2017 VL-06: Sortieren I 5/34 Sortieren ist nicht trivial! DSAL/SS 2017 VL-06: Sortieren I 6/34 Sortieren ist nicht trivial! TimSort in der Wikipedia OpenJDK’s java.utils.Collection.sort() is broken: The good, the bad and the worst case? Timsort is a hybrid stable sorting algorithm, derived from merge sort and insertion sort, designed to perform well on many kinds of real-world data. It uses techniques from Peter McIlroy’s “Optimistic Sorting and Information Theoretic Complexity” It was implemented by Tim Peters in 2002 for use in the Python programming language. It is used in Java (OpenJDK + Oracle) and Android. Stijn de Gouw1,2 , Jurriaan Rot3,1 , Frank S. de Boer1,3 , Richard Bubel4 , and Reiner Hähnle4 1 CWI, Amsterdam, The Netherlands SDL, Amsterdam, The Netherlands Leiden University, The Netherlands Technische Universität Darmstadt, Germany 2 3 4 Abstract. We investigate the correctness of TimSort, which is the main sorting algorithm provided by the Java standard library. The goal is functional verification with mechanical proofs. During our verification attempt we discovered a bug which causes the implementation to crash. We characterize the conditions under which the bug occurs, and from this we derive a bug-free version that does not compromise the performance. We formally specify the new version and mechanically verify the absence of this bug with KeY, a state-of-the-art verification tool for Java. 1 DSAL/SS 2017 VL-06: Sortieren I 7/34 DSAL/SS 2017 Introduction Some of the arguments often invoked against the usage of formal software veriSortieren fication include the following: it is VL-06: expensive, itI is not worthwhile (compared to its cost), it is less e↵ective than bug finding (e.g., by testing, static analysis, or 8/34 Sortieren: Notation Sortieren: Spezifikation Permutation Eine Permutation einer Menge A = {a1 , . . . , an } ist eine bijektive Abbildung π : A → A. Das Sortier-Problem Eingabe: 1 2 Totale Ordnung Sei A = {a1 , . . . , an } eine Menge. Die binäre Relation ≤ auf A ist eine totale Ordnung wenn für alle ai , aj , ak ∈ A gilt: 1 2 3 Ein Array E mit n Einträgen. Die Einträge gehören zu einer Menge A mit totaler Ordnung ≤. Ausgabe: Ein Array F mit n Einträgen, so dass 1 F[1], . . ., F[n] eine Permutation von E[1], . . ., E[n] ist 2 Für alle 0 < i < n gilt: F[i] ≤ F[i+1]. Antisymmetrie: ai ≤ aj und aj ≤ ai impliziert ai = aj . Transitivität: ai ≤ aj und aj ≤ ak impliziert ai ≤ ak . Totalität: ai ≤ aj oder aj ≤ ai . Annahme in dieser Vorlesung Elementaroperation = Vergleich von Schlüsseln Beispiel Die lexikographische Ordnung von Zeichenketten und die numerische Ordnung von Zahlen sind totale Ordnungen. DSAL/SS 2017 VL-06: Sortieren I 9/34 Sortieren: Algorithmen DSAL/SS 2017 VL-06: Sortieren I 10/34 Dutch National Flag Problem (1) Beispiel (Einige Sortieralgorithmen) Insertionsort, Bubblesort, Shellsort, Mergesort, Heapsort, Quicksort, Countingsort, Bucketsort, Radixsort, Cocktailsort, Bogosort, etc. Stabilität Ein Sortieralgorithmus ist stabil, wenn er die Reihenfolge der Elemente, deren Sortierschlüssel gleich sind, aufrecht erhält. Zum Beispiel: Eine Liste von Personendateien ist alphabetisch nach dem Familiennamen sortiert. Diese Liste wird nun nach dem Geburtsdatum neu sortiert wird. Dann bleiben unter einem stabilen Sortierverfahren alle Personen mit gleichem Geburtsdatum weiterhin alphabetisch nach Familiennamen sortiert. DSAL/SS 2017 VL-06: Sortieren I 11/34 DSAL/SS 2017 VL-06: Sortieren I 12/34 Dutch National Flag Problem (2) Dutch National Flag Problem (3) Edsger W. Dijkstra (1930–2002): • Berühmter Informatiker • Vater der Algorithmik Grundidee Wir zerlegen das Array E in vier Regionen: (1) 0 < i ≤ r, (2) r < i < u, (3) u ≤ i < b, und (4) b ≤ i ≤ n Beispiel (Das niederländische Flaggen-Problem [Dijkstra, 1972]) Eingabe: 1 2 mit Hilfsvariablen r, u und b, so dass die folgende Invariante gilt: Ein Array E mit n Einträgen, wobei für alle 0 < i ≤ n E[i] == rot, E[i] == blau oder E[i] == weiss Ordnung: rot < weiss < blau . . ., E[r] ist “rote” Region: E[i] == rot für 0 < i ≤ r 1 E[1], 2 E[r+1], 3 E[u], . . ., E[b-1] ist unbekannte E[i] == weiss oder E[i] == blau 4 E[b], Ausgabe: Ein sortiertes Array mit den Einträgen aus E. . . ., E[u-1] ist “weisse” Region: E[i] == weiss für r < i < u Region: E[i] == rot oder für u ≤ i < b . . ., E[n] ist “blaue” Region: E[i] == blau für b ≤ i ≤ n Erwünschte Worst-Case Zeitkomplexität: Θ(n). Erwünschte Worst-Case Speicherkomplexität: Θ(1). DSAL/SS 2017 VL-06: Sortieren I Arrayelemente können mit der swap-Operation vertauscht werden. 13/34 Dutch National Flag Problem (4) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 DSAL/SS 2017 14/34 Frage Ist DNF-Algorithmus ein stabiles Sortierverfahren? Antwort: Nein Speicherkomplexität Die Worst-Case Speicherkomplexität vom DNF-Algorithmus ist Θ(1), da r, u und b die einzigen extra Variablen sind. DNF ist in-place: Algorithmus arbeitet ohne zusätzlichen Speicherplatz. Zeitkomplexität (Elementaroperation = Vergleich der Form E[i] == ....) Die Worst-Case Zeitkomplexität ist Θ(n): b VL-06: Sortieren I VL-06: Sortieren I Dutch National Flag Problem (5) void Dut chN ati o na l F la g ( Color E [] , int n ) { int r = 0 , b = n + 1; // rote und blaue Regionen leer int u = 1; // weisse Region leer ; unbekannte == E while ( u < b ) { if ( E [ u ] == rot ) { swap ( E [ r + 1] , E [ u ]) ; r = r + 1; // vergroessere rote Region u = u + 1; // verkleinere unbekannte Region } if ( E [ u ] == weiss ) { u = u + 1; } if ( E [ u ] == blau ) { swap ( E [ b - 1] , E [ u ]) ; b = b - 1; // vergroessere blaue Region } } } r u DSAL/SS 2017 15/34 1 in jedem Durchlauf werden konstant viele Vergleiche durchgeführt 2 die Anzahl der Durchläufe ist Θ(n), da in jedem Durchlauf die Grösse b-u des unbekannten Gebiets um mindestens eins und um höchstens drei verkleinert wird. DSAL/SS 2017 VL-06: Sortieren I 16/34 InsertionSort: Idee Sortieren durch Einfügen (InsertionSort) 0 12 17 17 19 8 25 3 6 69 26 4 2 13 34 41 bereits sortiert noch unsortiert als Nächstes einzusortieren Durchlaufe das (unsortierte) Array von links nach rechts Gehe zum ersten noch nicht behandelten Element (gelb) Füge es im sortierten Teil (grün) durch elementweise Vergleiche ein DSAL/SS 2017 VL-06: Sortieren I 17/34 Pro Element ist dann nur ein Vergleich nötig: Ergo B(n) = n − 1 ∈ Θ(n) void insertionSort ( int E []) { int i , j ; for ( i = 1; i < E . length ; i ++) { int v = E [ i ]; // speichere E [ i ] for ( j = i ; j > 0 && E [j -1] > v ; j - -) { E [ j ] = E [j -1]; // schiebe Element j -1 nach rechts } E [ j ] = v ; // fuege E [ i ] an der richtigen Stelle ein } } Worst-Case Im Worst-Case wird das einzusortierende Element immer ganz vorne eingefügt Es muss mit allen vorhergehenden Elementen verglichen werden Das tritt etwa auf, wenn das Array umgekehrt vorsortiert war Zum Einsortieren des i-ten Elements sind dann i Vergleiche nötig. n−1 X 1 Ergo W (n) = i = n · (n − 1) ∈ Θ(n2 ) 2 i=1 Insertionsort ist in-place: kein zusätzlicher Speicherplatz Insertionsort ist stabil: die Reihenfolge der gleichwertigen Arrayelemente bleibt unverändert VL-06: Sortieren I 18/34 Best-Case Im Best-Case ist das Array bereits sortiert. 26 41 17 25 19 17 8 3 6 69 12 4 2 13 34 0 DSAL/SS 2017 VL-06: Sortieren I InsertionSort: Best- und Worst-Case-Analyse InsertionSort: Animation und Algorithmus 1 2 3 4 5 6 7 8 9 10 DSAL/SS 2017 19/34 DSAL/SS 2017 VL-06: Sortieren I 20/34 InsertionSort: Average-Case-Analyse (1) Insertionsort – Average-Case-Analyse (2) i X Unsere Annahmen für Average-Case-Analyse ( Pr j=0 i-tes Element wird an Position j eingefügt ) · Anzahl Vergleiche, um E[i] an Position j einzufügen ! Alle Permutationen der Elemente treten mit gleicher Häufigkeit auf. (Comment: E[i] wird an beliebiger Position j mit gleicher W’lichkeit eingefügt) Die zu sortierenden Elemente sind alle verschieden. Dann gilt: = A(n) = n−1 X i X j=0 (erwartete Anzahl von Vergleichen, um E[i] einzusortieren) 1 · i +1 Anzahl Vergleiche, um E[i] an Position j einzufügen ! (Comment: Anzahl Vergleiche, um an Position 0 einzufügen ist i, sonst i−j+1.) i=1 = i X 1 1 ·i + · (i − j + 1) i +1 i + 1 j=1 = i i i 1 X 1 i·(i+1) i 1 + · j = + · = +1− . i + 1 i + 1 j=1 i +1 i +1 2 2 i +1 Wir bestimmen zunächst die erwartete Anzahl von Vergleichen, um den richtigen Platz für E[i] zu finden DSAL/SS 2017 VL-06: Sortieren I 21/34 DSAL/SS 2017 VL-06: Sortieren I 22/34 Insertionsort – Average-Case-Analyse (3) Harmonische Reihe: A(n) = Pn i=1 (1/i) n−1 X i i=1 ≈ ln n 1 +1− 2 i +1 Divide-and-Conquer und MergeSort n = X1 n · (n − 1) + (n − 1) − 4 i i=2 = X1 n · (n − 1) +n− 4 i i=1 ≈ n · (n − 1) + n − ln n 4 n ∈ Θ(n2 ) Insertionsort ist im Worst-Case und im Average-Case quadratisch. DSAL/SS 2017 VL-06: Sortieren I 23/34 DSAL/SS 2017 VL-06: Sortieren I 24/34 Divide-and-Conquer MergeSort: Strategie Teile-und-Beherrsche 41 26 17 25 19 17 8 3 6 69 12 4 2 13 34 0 Teile-und-Beherrsche Algorithmen (divide-and-conquer) teilen das Problem in mehrere Teilprobleme auf, die dem Ausgangsproblem ähneln, jedoch von kleinerer Grösse sind. Sie lösen die Teilprobleme rekursiv und kombinieren diese Lösungen dann, um die Lösung des eigentlichen Problems zu erstellen. rekursiv Sortieren Verschmelzen 0 2 3 4 6 8 12 13 17 17 19 25 26 34 41 69 Teile das Array in zwei – möglichst gleich grosse – Hälften. Teile das Problem in eine Anzahl von Teilproblemen auf. Beherrsche: Sortiere die Teile durch rekursive MergeSort-Aufrufe. Beherrsche die Teilprobleme durch rekursives Lösen. Hinreichend kleine Teilprobleme werden direkt gelöst. Verbinde: Mische je 2 sortierte Teilsequenzen zu einem einzigen, sortierten Array. Verbinde die Lösungen der Teilprobleme zur Lösung des Ausgangsproblems. VL-06: Sortieren I 25/34 MergeSort: Algorithmus 1 2 3 4 5 6 7 8 9 DSAL/SS 2017 VL-06: Sortieren I 26/34 MergeSort: Animation void mergeSort(int E[], int left, int right) { if (left < right) { int mid = (left + right)/2; // finde Mitte mergeSort(E, left, mid); // sortiere linke Haelfte mergeSort(E, mid + 1, right); // sortiere rechte Haelfte merge(E, left, mid, right); // Verschmelzen der sortierten Haelften } } Aufruf: rekursiv Sortieren 3 8 17 17 19 25 26 41 0 2 4 6 12 13 34 69 Das Paradigma von Teile-und-Beherrsche umfasst 3 Schritte auf jeder Rekursionsebene: DSAL/SS 2017 Mitte 41 26 17 25 19 17 8 3 6 69 12 4 2 13 34 0 41 26 17 25 19 17 8 3 6 69 12 4 2 13 34 0 mergeSort(E, 0, E.length-1); Verschmelzen kann man in Linearzeit. – Wie? DSAL/SS 2017 VL-06: Sortieren I 27/34 DSAL/SS 2017 VL-06: Sortieren I 28/34 MergeSort: Verschmelzen in Linearzeit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 MergeSort: Analyse void merge(int E[], int left, int mid, int right) { int a = left, b = mid + 1; int Eold[] = E; for (; left <= right; left++) { if (a > mid) { // Wir wissen (Widerspruch): b <= right E[left] = Eold[b]; b++; } else if (b > right || Eold[a] <= Eold[b]) { // stabil: <= E[left] = Eold[a]; a++; } else { // Eold[a] > Eold[b] E[left] = Eold[b]; b++; } } } Worst-Case Wir erhalten: W (n) = W (bn/2c) + W (dn/2e) + n − 1 Mit Hilfe des Mastertheorems ergibt sich: W (n) ∈ Θ(n · log n). Best-Case, Average-Case Das Worst-Case-Ergebnis gilt genauso im Best-Case, und damit auch im Average-Case: W (n) = B(n) = A(n) ∈ Θ(n · log n). Speicherbedarf Θ(n) für die Kopie des Arrays beim Mergen. Θ(log n) für die Verwaltung der Rekursion. MergeSort ist stabil (vgl. Zeile 8), d. h. die Reihenfolge von Elementen mit gleichem Schlüssel bleibt erhalten. DSAL/SS 2017 VL-06: Sortieren I mit W (1) = 0. 29/34 DSAL/SS 2017 VL-06: Sortieren I 30/34 Wie effizient kann man sortieren? (1) Ja E [1] < E [2]? Nein E [2] < E [3]? Geht es noch effizienter? Ja E [1] < E [3]? Nein Nein Ja E [1] < E [3]? E [2] < E [3]? Ja Ja Nein Nein Betrachte vergleichsbasierte Sortieralgorithmen als Entscheidungsbaum: Dieser beschreibt die Abfolge der durchgeführten Vergleiche. Sortieren verschiedener Eingabepermutationen ergibt also verschiedene Pfade im Baum. DSAL/SS 2017 VL-06: Sortieren I 31/34 DSAL/SS 2017 VL-06: Sortieren I 32/34 Wie effizient kann man sortieren? (2) Organisatorisches Theorem Vergleichsbasiertes Sortieren benötigt im Worst-Case Ω(n log n) Vergleiche. Beweis. Es gilt: • Nächste Vorlesung: Dienstag, Mai 9, 16:15–17:45 Uhr, Aula 1 Anzahl Vergleiche im Worst-Case = Länge des längsten Pfades = Baumhöhe k. • Webseite: http://algo.rwth-aachen.de/Lehre/SS17/DSA.php Da wir binäre Vergleiche verwenden, ergibt sich ein Binärbaum mit n! Blättern. Mit n! ≤ 2k erhält man k ≥ dlog(n!)e Vergleiche im Worst-Case. Da dlog(n!)e ≈ n · log n − 1.4 · n gilt, kann man n · log n asymptotisch nicht unterbieten. DSAL/SS 2017 VL-06: Sortieren I 33/34 DSAL/SS 2017 VL-06: Sortieren I 34/34