Algorithms & Data Structures 2 Sorting WS2017 B. Anzengruber-Tanase (Institute for Pervasive Computing, JKU Linz) (Institute of Pervasive Computing, JKU Linz)
SORTIEREN Sortierproblem Gegeben: Folge von Datensätzen (items) a 1, a 2,..., a N Jedes ai besitzt Schlu ssel (key) k i (meist vom Typ integer) Gesucht: Permutation π, so dass k π(1) k π(2) k π(n) z.b.: aufsteigend sortieren, d.h. für alle i, 1 < i < N muss gelten: a[i].key a[i+1].key Interne Sortierverfahren: alle Datensätze im Hauptspeicher Externe Sortierverfahren: Nutzung eines Externspeichers In-place Sortierverfahren: brauchen wenig zusätzlichen Speicherplatz Maße für die Laufzeit: Anzahl der Schlüsselvergleiche C (Comparisons) Anzahl der Zuweisungen von Datensätzen M (Moves) C min, C max, C avg jeweils minimale, maximale, M min, M max, M avg und durchschnittliche Anzahl Algorithms & Datastructures 2 // 2017W // 2
BEISPIEL :: ARRAY Vor dem Sortieren k 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 a [k] A S O R T I N G E X A M P L E [k] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Algorithms & Datastructures 2 // 2017W // 3
BEISPIEL :: ARRAY Vor dem Sortieren k a [k] [k] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A S O R T I N G E X A M P L E 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Nach dem Sortieren k a [k] [k] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A S O R T I N G E X A M P L E 1 13 10 12 14 6 9 5 3 15 2 8 11 7 4 Algorithms & Datastructures 2 // 2017W // 4
BEISPIEL :: ARRAY Vor dem Sortieren k a [k] [k] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A S O R T I N G E X A M P L E 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Nach dem Sortieren k a [k] [k] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A S O R T I N G E X A M P L E 1 13 10 12 14 6 9 5 3 15 2 8 11 7 4 Nach dem Permutieren k a [k] [k] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A A E E G I L M N O P R S T X 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Algorithms & Datastructures 2 // 2017W // 5
ALGORITHMENANIMATION :: http://www.sorting-algorithms.com/ Algorithms & Datastructures 2 // 2017W // 6
BUBBLE SORT Methode: Jeweils 2 benachbarte Schlüssel werden verglichen. Ist a[i].key > a[i+1].key, so werden items vertauscht. Kleinstes Element wandert wie eine bubble nach vorne. Terminierung, wenn keine Vertauschung mehr erfolgt ist, oder spätestens nach N-1 Durchläufen. Algorithms & Datastructures 2 // 2017W // 8
BUBBLE SORT Methode: Jeweils 2 benachbarte Schlüssel werden verglichen. Ist a[i].key > a[i+1].key, so werden items vertauscht. Kleinstes Element wandert wie eine bubble nach vorne. Terminierung, wenn keine Vertauschung mehr erfolgt ist, oder spätestens nach N-1 Durchläufen. Analyse: C min (N) = N-1 C max (N) = N*(N-1) = Q(N 2 ) M min (N) = 0 M max (N) = = Q(N 2 ) Algorithms & Datastructures 2 // 2017W // 9
BUBBLE SORT Methode: Jeweils 2 benachbarte Schlüssel werden verglichen. Ist a[i].key > a[i+1].key, so werden items vertauscht. Kleinstes Element wandert wie eine bubble nach vorne. Terminierung, wenn keine Vertauschung mehr erfolgt ist, oder spätestens nach N-1 Durchläufen. Analyse: C min (N) = N-1 C max (N) = N*(N-1) = Q(N 2 ) M min (N) = 0 M max (N) = = Q(N 2 ) Abschätzungen: C avg C max / 2 M avg M max / 2 => (d.h. Q(N 2 ) ) Algorithms & Datastructures 2 // 2017W // 10
BUBBLE SORT public <T extends Comparable<T>> void sort(t[] list) { // repeat list.length - 1 times: // move smallest element like a bubble to front for (int i = 0; i < list.length - 1; i++) for (int j = list.length - 1; j > i; j--) if (list[j].compareto(list[j - 1]) < 0) swap(list, j, j - 1); } Bei vorsortierten Listen sind weniger als N-1 Wiederholungen nötig. Besser: Nur so lange wiederholen, bis keine Vertauschung mehr auftritt Visualisierung: at.jku.pervasive.ad2.vo03.bubblesort.java Algorithms & Datastructures 2 // 2017W // 11
SELECTION SORT (SORTIEREN DURCH AUSWAHL) Methode: Finde zuerst das kleinste Element und tausche es gegen das an erster Stelle befindliche Element aus, finde danach das zweitkleinste Element und tausche es gegen das an zweiter Stelle befindliche Element aus, und fahre in dieser Weise fort bis das gesamte Feld sortiert ist. Für jedes i = 1,..., N-1 tausche a[i] gegen das kleinste Element in a[i],..., a[n] aus Algorithms & Datastructures 2 // 2017W // 12
SELECTION SORT (SORTIEREN DURCH AUSWAHL) Methode: Finde zuerst das kleinste Element und tausche es gegen das an erster Stelle befindliche Element aus, finde danach das zweitkleinste Element und tausche es gegen das an zweiter Stelle befindliche Element aus, und fahre in dieser Weise fort bis das gesamte Feld sortiert ist. Für jedes i = 1,..., N-1 tausche a[i] gegen das kleinste Element in a[i],..., a[n] aus Analyse: C avg = C min = C max : = Q (N 2 ) M avg = M min = M max : 3(N-1) Algorithms & Datastructures 2 // 2017W // 13
SELECTION SORT (SORTIEREN DURCH AUSWAHL) public <T extends Comparable<T>> void sort(t[] list) { // repeat for each element except the last one for (int i = 0; i < list.length-1; i++) { // find smallest element // in remaining list int min = i; for (int j = i+1; j < list.length; j++) if (list[j].compareto(list[min]) < 0) min = j; // swap smallest element // to first position swap(list, i, min); } } Visualisierung: at.jku.pervasive.ad2.vo03.selectionsort.java Algorithms & Datastructures 2 // 2017W // 14
INSERTION SORT (SORTIEREN DURCH EINFÜGEN) Methode: Betrachte die Elemente eines nach dem anderen und füge jedes an seinen richtigen Platz zwischen den bereits betrachteten ein. Das gerade betrachtete Element wird eingefügt, indem die größeren Elemente einfach um eine Position nach rechts bewegt werden und das Element dann auf dem frei gewordenen Platz eingefügt wird. Für jedes i von 2 bis N werden die Elemente a[1],..., a[i] sortiert, indem a[i] an die entsprechende Stelle in der sortierten Liste von Elementen in a[1],..., a[i-1] gesetzt wird. Algorithms & Datastructures 2 // 2017W // 15
INSERTION SORT (SORTIEREN DURCH EINFÜGEN) Methode: Betrachte die Elemente eines nach dem anderen und füge jedes an seinen richtigen Platz zwischen den bereits betrachteten ein. Das gerade betrachtete Element wird eingefügt, indem die größeren Elemente einfach um eine Position nach rechts bewegt werden und das Element dann auf dem frei gewordenen Platz eingefügt wird. Für jedes i von 2 bis N werden die Elemente a[1],..., a[i] sortiert, indem a[i] an die entsprechende Stelle in der sortierten Liste von Elementen in a[1],..., a[i-1] gesetzt wird. Analyse: C min (N) = N-1 C max (N) = = Q(N 2 ) M min (N) = 2(N-1) M max (N) = = Q(N 2 ) Algorithms & Datastructures 2 // 2017W // 16
INSERTION SORT (SORTIEREN DURCH EINFÜGEN) public <T extends Comparable<T>> void sort(t[] list) { // extend sorted range step by step: // [0..1], [0..2], [0..3],... for (int i = 1; i < list.length; i++) { int j = i; T elem = list[i]; // move elements to the right until elem // is at correct position while (j >= 1 && elem.compareto(list[j - 1]) < 0) { list[j] = list[j - 1]; j--; } list[j] = elem; } } Visualisierung: at.jku.pervasive.ad2.vo03.insertionsort.java Algorithms & Datastructures 2 // 2017W // 17
VERGLEICH ELEMENTARER SORTIERVERFAHREN Algorithmus Vergleiche Bewegungen Bubble Sort N 2 / 2 N 2 / 2 Selection Sort N 2 / 2 N Insertion Sort N 2 / 4 N 2 / 8 Daten fast vorsortiert: Bubble Sort und Insertion Sort beinahe linear Sortierverfahren über mehrere Schlüssel (keys): Sortiere nach 1., dann nach 2. key wenn bei Gleichheit im 2. die Sortierreihenfolge im 1. erhalten bleibt so spricht man von eine stabilen Verfahren ( stable sort ) Sortieren großer Records: Referenzen (oder Indizes) anstatt Records swappen Algorithms & Datastructures 2 // 2017W // 18
DONALD A. SHELL (1959)
DONALD A. SHELL (1959) Algorithms & Datastructures 2 // 2017W // 20
SHELL SORT Voraussetzung: n-sortierung teile Folge in n Teilfelder beginnend beim 1., 2., 3., usw. sortiere Teilfelder nach Insertion Sort Beispiel: 4-Sortierung Algorithms & Datastructures 2 // 2017W // 21
SHELL SORT Methode: Ermögliche Vertauschungen über größere Abstände. (=> Schrittweite ) Dazu abnehmende Folge von Inkrementen h t,..., h 1 definiert (mit h 1 = 1), z.b.: 7, 5, 3, 1 Eine Folge k 1,..., k N heißt h-sortiert, wenn für alle i, 1 i N-h: k i k i+h Array a wird nun mit Insertion Sort h t sortiert, dann h t-x sortiert usw. bis a 1-sortiert und damit sortiert ist. Algorithms & Datastructures 2 // 2017W // 22
SHELL SORT Methode: Ermögliche Vertauschungen über größere Abstände. (=> Schrittweite ) Dazu abnehmende Folge von Inkrementen h t,..., h 1 definiert (mit h 1 = 1), z.b.: 7, 5, 3, 1 Eine Folge k 1,..., k N heißt h-sortiert, wenn für alle i, 1 i N-h: k i k i+h Array a wird nun mit Insertion Sort h t sortiert, dann h t-x sortiert usw. bis a 1-sortiert und damit sortiert ist. Beispiel: Inkremente 4,2,1 16 3 22 11 9 7 4: 4-Sortieren 3 Zuweisungen 9 3 22 11 16 7 4 3 Zuweisungen 9 3 4 11 16 7 22: 2-Sortieren 3 Zuweisungen 4 3 9 11 16 7 22 3 Zuweisungen 4 3 9 7 16 11 22: 1-Sortieren 3 Zuweisungen 3 4 9 7 16 11 22 3 Zuweisungen 3 4 7 9 16 11 22 3 Zuweisungen 3 4 7 9 11 16 22 Algorithms & Datastructures 2 // 2017W // 23
SHELL SORT Vgl. Insertion-Sort: 16 3 22 11 9 7 4 3 Zuweisungen 3 16 22 11 9 7 4 4 Zuweisungen 3 11 16 22 9 7 4 5 Zuweisungen 3 9 11 16 22 7 4 6 Zuweisungen 3 7 9 11 16 22 4 7 Zuweisungen 3 4 7 9 11 16 22 gezählt jeweils 1 Zuweisung an Hilfsspeicher, 1 Zuweisung pro Stelle mit neuem Wert. Problem: Wie wählt man Inkremente h i richtig? (viele Resultate) Worst Case (subquadratisch!!): O(N log 2 N) falls h i {2 p 3 q < N} Sequenz Beispiel (Shell): 1 = h 1 < h 2 <... h t mit h t = N/2, h k = h k+1 /2 Q(N 2 ) Sequenz Beispiel (Hibbard): 1, 3, 7, 15, 2 i -1 Q(N 3/2 ) Sequenz Beispiel (Sedgewick): wenige moves: 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 oder: 9*4 i -9*2 i +1 oder 4 i 3*2 i + 1, z.b. 1, 5, 19, 41, 109,...beste bekannte Sequenz mit O(N 7/6 ) Algorithms & Datastructures 2 // 2017W // 24
Insertion Sort SHELL SORT public <T extends Comparable<T>> void sort(t[] list) { // sequence proposed by Sedgewick: 1 4 13 40 121 364 1093 3280... int h = 1; while (h <= (list.length - 1) / 9) h = 3 * h + 1; // repeat for each distance, from high to low for (; h > 0; h /= 3) { // the following loop is identical to insertion sort with h = 1 for (int i = h; i < list.length; i++) { int j = i; T elem = list[i]; while (j >= h && elem.compareto(list[j - h]) < 0) { list[j] = list[j - h]; j -= h; } list[j] = elem; } } } Visualisierung: at.jku.pervasive.ad2.vo03.shellsort.java Algorithms & Datastructures 2 // 2017W // 25
HEAP SORT Methode: 1. Ordne die N zu sortierenden Elemente in einem binären Heap an 2. Führe N removemin() Operationen aus 3. Füge die jeweils entfernten Elemente in neues Array ein Algorithm HeapSort Baue einen Heap auf mit Schlüsseln a 1,..., a N do { entferne Wurzel; // = Minimum reorganisiere Heap; } while (Heap ist nicht leer); Beachte: verwendet zusätzliches Array, daher doppelter Speicheraufwand Idee für reorganisieren nach Wurzelentfernen entferne letzten Knoten im Heap und schreibe seinen Schlu ssel in Wurzel vertausche solange Knoten mit kleinerem Sohn, bis Heapbedingung erfu llt: ( versickern lassen, downheap) Algorithms & Datastructures 2 // 2017W // 26
HEAP SORT Heap als Array implementiert: Nummeriere Knoten wie folgt Wurzel: Nummer 0 left Child von Knoten i: Nummer (2i+1) right Child von Knoten i: Nummer (2i+2) Im array double[] a = new double[n]; (in a[i] der Schlüssel von Knoten i) Vorteil: Parent/Child-Beziehung ist aus Knotenindex berechenbar public static int parent(int i) { // liefert Index des Parents zu i return (i - 1) / 2; // (Spezialfall: parent(0)==0) } Algorithms & Datastructures 2 // 2017W // 27
HEAP SORT Strategie zur Vermeidung eines zweiten Arrays: verwende Lu cken die durch removemin() entstehen zum Speichern der sortierten Elemente Ergebnis ist dann absteigend sortiert (im ersten Schritt wird Minimum auf Position N gespeichert, im zweiten das Minimum des verbleibenden Heaps auf Position N-1,...) kann vermieden werden, in dem Heap als Max-Heap (anstatt als Min-Heap wie bisher) implementiert wird Algorithms & Datastructures 2 // 2017W // 28
HEAP SORT Analyse: Aufbau des Heaps: O(N) => siehe Bottom-up Heap Konstruktion Begründung: h=höhe und N-1 = 2 h -1 Anzahl der Elemente Ebene Sickertiefe Anzahl h-1 1 N/2 h-2 2 N/4 h-3 3 N/8... 0 h N/2 h = 1 Aufwand:, da 1/2+2/4+3/8+... < 2 Aufwand für einmaliges removemin(): O(log N) (Begründung siehe Kapitel Heaps) wird N mal ausgeführt, daher: O(N log N) Aufwand Gesamt (Worst Case): O(N) + O(N log N) = O(N log N) Algorithms & Datastructures 2 // 2017W // 29
RÜCKBLICK :: SORTIERVERFAHREN AUF BASIS PQ Selection Sort verwendet Priority Queue P, implementiert als unsortierte Sequenz Phase 1: Einfügen von N Elementen in P: O(N) Phase 2: Entnahme von N Elementen (jeweils min) aus P: O(N 2 ) Gesamtkomplexität: O(N 2 ) Insertion Sort verwendet Priority Queue P, implementiert als sortierte Sequenz Phase 1: Einfügen von N Elementen in P: O(N 2 ) Phase 2: Entnahme von N Elementen (jeweils min) aus P: O(N) Gesamtkomplexität: O(N 2 ) Heap Sort verwendet Priority Queue P, implementiert als unsortierter Heap Phase 1: Einfügen von N Elementen in P: O(N log N) Phase 2: Entnahme von N Elementen (jeweils min) aus P: O(N log N) Gesamtkomplexität: O(N log N) Algorithms & Datastructures 2 // 2017W // 32
DIVIDE & CONQUER Grundprinzip für viele effiziente Algorithmen Besteht aus drei Phasen: Divide: wenn die Problemgröße zu groß ist, zerlege das Problem in zwei oder mehr Teilprobleme Rekursion: Wende Divide-and-Conquer auf Teilprobleme an (rekursives Lösen der Teilprobleme) Conquer: Füge die Lösungen der Teilprobleme zu einer Lösung des Gesamtproblems zusammen Algorithms & Datastructures 2 // 2017W // 33
MERGE SORT Rekursive Methode: Betrachte die Elemente a[1],..., a[n] als Sequenz S Falls S leer oder 1-elementig => fertig Sonst: Divide: Teile S in 2 möglichst gleichgroße Hälften S1 und S2 Conquer: Sortiere S1 und S2 mittels Mergesort (rekursive Anwendung). Merge: Verschmelze die sortierten Teillisten zu sortierter Liste. Iterative Methode: Verschmelzen kann durch 2 Zeiger erfolgen, die die sortierten Teillisten durchwandern. Zeigen zunächst auf erstes Element, vergleichen Schlüssel, tragen kleineres Element in neu konstruierte Sequenz ein und bewegen den Zeiger auf dieses Element um eine Position weiter. John von Neumann, 1945 Algorithms & Datastructures 2 // 2017W // 34
MERGE SORT Visualisierung als Binärbaum T (Merge-Sort Tree): 7 6 2 10 4 5 9 8 jeder interne Knoten entspricht einem Rekursionsaufruf 7 6 2 10 4 5 9 8 zu jedem Knoten ist zu bearbeitende Sequenz assoziiert 7 6 2 10 4 5 9 8 Externe Knoten sind mit einzelnen Elementen von S assoziiert 7 6 2 10 4 5 9 8 Algorithms & Datastructures 2 // 2017W // 35
MERGE SORT Visualisierung als Binärbaum T (Merge-Sort Tree): 2 4 5 6 7 8 9 10 Mischen der sortierten Teilsequenzen 2 6 7 10 4 5 8 9 6 7 2 10 4 5 8 9 7 6 2 10 4 5 9 8 Algorithms & Datastructures 2 // 2017W // 36
MERGE SORT Pseudo-Code für Merge-Phase Algorithm merge (S1, S2, S): Input: Sequenzen S1 und S2, sortiert in nicht-absteigender Reihenfolge, leere Sequenz S Output: Sequenz S, enthält Vereinigung der Elemente von S1 und S2 in nicht-absteigender Reihenfolge while S1 is not empty and S2 is not empty do if S1.first().element() S2.first().element() then { move first element of S1 at end of S } S.insertLast(S1.remove(S1.first())) else { move first element of S2 at end of S } S.insertLast(S2.remove(S2.first())) while S1 is not empty do S.insertLast(S1.remove(S1.first())) while S2 is not empty do S.insertLast(S2.remove(S2.first())) at.jku.pervasive.ad2.vo03.mergesortvector.java at.jku.pervasive.ad2.vo03.mergesort.java Algorithms & Datastructures 2 // 2017W // 37
LAUFZEIT VON MERGE SORT Merge-Sort-Baum für eine Sequenz von N Elementen hat Höhe log N Behauptung: Mergesort sortiert N Elemente in Laufzeit O(N log N) Begründung: Annahme: Zugriff auf erstes und letztes Element (sowie insert und delete) jeder Sequenz ist in O(1) möglich die Zeit, die im Knoten v eines Merge-Sort-Baumes T verbracht wird, ohne die Dauer der rekursiven Aufrufe der Kinder, sei mit Laufzeit des rekursiven Aufrufes bezeichnet Sei i die Tiefe des Knoten v in T, so ist die Laufzeit des rekursiven Aufrufes O(N/2 i ), da die Länge der zugehörigen Sequenz N/2 i ist T hat exakt 2 i Knoten auf Tiefe i, daher ist die Zeit, die auf Tiefe i insgesamt verbracht wird O(2 i N/2 i ) = O(N) Da Baum Höhe von log N hat, ist Gesamtkomplexität O( N log N ) Beim Mischen Q(N) Schlüsselvergleiche Rekursionstiefe logarithmisch beschränkt, Q(N log N) Schlüsselvergleiche Anzahl Bewegungen (Moves) ist Q(N log N). O(N log N) (immer) Algorithms & Datastructures 2 // 2017W // 38
LAUFZEIT VON MERGE SORT Höhe N Laufzeit pro Level O(N) N/2 N/2 O(N) log (N) N/4 N/4 N/4 N/4 O(N) N/8 N/8 N/8 N/8 N/8 N/8 N/8 N/8 O(N) O(N) Gesamtkomplexität: O(N log N) Algorithms & Datastructures 2 // 2017W // 41
MERGE SORT (ARRAYS, REKURSIV) private Object[] buffer; public synchronized <T extends Comparable<T>> void sort(t[] list) { // create buffer with equal length to list, used by the merge method. // merge is not recursive, so a single buffer is sufficient and reduces // memory requirements. drawback: the method needs to be synchronized buffer = java.util.arrays.copyof(list, list.length); // start mergesort mergesort(list, 0, list.length - 1); } /** Sort list from l to r. */ private <T extends Comparable<T>> void mergesort(t[] list, int l, int r) { if (r > l) { // center element int m = (r + l) / 2; // divide: recursive sorting of left and right half mergesort(list, l, m); mergesort(list, m + 1, r); // conquer: merge both halfs merge(list, l, m, r); } } at.jku.pervasive.ad2.vo03.mergesortvector.java at.jku.pervasive.ad2.vo03.mergesort.java Algorithms & Datastructures 2 // 2017W // 42
MERGE SORT (ARRAYS, REKURSIV) /** Merge two lists, contained in array list from [l.. m] and [m+1.. r]. * This method requires the private buffer to be initialized to an array * of equal length to list. */ <T extends Comparable<T>> void merge(t[] list, int l, int m, int r) { @SuppressWarnings("unchecked") T[] aux = (T[]) buffer; // use buffer int i, j; // move both lists to aux for (i = m + 1; i > l; i--) aux[i - 1] = list[i - 1]; for (j = m; j < r; j++) aux[r + m - j] = list[j + 1]; // sort both lists back to list for (int k = l; k <= r; k++) if (aux[j].compareto(aux[i]) < 0) list[k] = aux[j--]; else list[k] = aux[i++]; } Algorithms & Datastructures 2 // 2017W // 43
QUICK SORT Divide and Conquer Sicht: 1. Divide: Falls zu sortierende Sequenz S mehr als 1 Element hat, so wähle beliebiges Element x aus S als Pivotelement Entferne alle Elemente aus S und teile sie in drei Teile: S L : enthält alle Elemente aus S, die kleiner als x sind S E : enthält alle Elemente aus S, die gleich x sind S R : enthält alle Elemente aus S, die größer als x sind 2. Rekursion: sortiere S L und S R rekursiv 3. Conquer: Füge Elemente wieder in S ein, wobei zuerst die Elemente aus S L genommen werden, dann aus S E, dann aus S R C.A.R. Hoare, 1962 Algorithms & Datastructures 2 // 2017W // 44
QUICK SORT Visualisierung in Baumstruktur (Quick-Sort Tree) Divide: 7 6 2 10 4 5 9 8 7 6 2 4 5 10 9 2 4 7 6 10 2 7 Algorithms & Datastructures 2 // 2017W // 45
QUICK SORT Visualisierung in Baumstruktur (Quick-Sort Tree) Conquer: 2 4 5 6 7 8 9 10 2 4 5 6 7 9 10 2 4 6 7 Algorithms & Datastructures 2 // 2017W // 46
QUICK SORT Methode: Betrachte (wieder) die Elemente a[1],..., a[n] als Sequenz S Zerlege die Sequenz S = a[1],..., a[n] in S1 und S2, so dass gilt: Für jeden Schlüsselwert k i1 von S 1 und jeden Schlüsselwert k i2 von S 2 gilt die Beziehung k i1 < k i2, d.h. jedes Element der ersten Teilfolge ist kleiner als jedes Element der zweiten Teilfolge. Führe diese Zerlegung wiederum für S 1 und S 2 durch, usw. Das Verfahren bricht für eine einelementige Teilsequenz ab Nach dem Abbruch des Verfahrens ist S sortiert Algorithms & Datastructures 2 // 2017W // 47
QUICK SORT Arrays: Vorgang des Zerlegens und Zusammensetzens: (i) (ii) (iii) (iv) (v) Wähle Pivotelement x aus der Folge a[1],..., a[n], etwa x=a[1]; Durchsuche die Folge von links, bis ein Element a[i] mit x < a[i] gefunden wurde. Durchsuche die Folge von rechts, bis ein Element a[j] mit a[j] < x gefunden wurde. Vertausche beide Elemente. Wiederhole (ii), (iii) und (iv) so lange, bis i >= j gilt. Anschließend wird das Element x = a[1] mit a[j] vertauscht und es gilt für die neue Folge a[1],...,a[j-1], x, a[j+1],...,a[n]: a[i 1 ] < x < a[i 2 ], für alle i 1 {1,...,j-1}, i 2 {j+1,...,n} Daraufhin wird der gesamte Prozess für die Teilfolgen a[1],...,a[j-1] und a[j+1],..., a[n] durchgeführt, und es ist kein Zusammensetzen der Ergebnisse mehr erforderlich. Algorithms & Datastructures 2 // 2017W // 48
QUICK SORT BEISPIEL Betrachten die Folge i j 44 55 12 42 94 6 18 67 und sortieren sie bezüglich der Ordnung <=. Erstes Vergleichselement gewählt: x = a[1] = 44 Mit der Variablen i von links so weit laufen, bis wir auf ein Element stoßen das größer ist als 44. Mit der Variablen j von rechts so weit laufen, bis ein Element gefunden wird, das kleiner ist als x, a[i] und a[j] werden nun vertauscht und wir erhalten: 44 18 12 42 94 6 55 67 i j Algorithms & Datastructures 2 // 2017W // 49
QUICK SORT BEISPIEL Mit i anschließend auf das Element a[5] = 94 und mit j auf a[6] = 6 gestoßen. Wiederum werden beide vertauscht: j i 44 18 12 42 6 94 55 67 Mit i >= j ist das Abbruchkriterium der Zerlegung erreicht. Jetzt werden a[1] und a[j] vertauscht, und wir erhalten: 6 18 12 42 44 94 55 67 Jetzt gilt: Alle Elemente der linken Teilfolge sind kleiner oder gleich x, und jedes Element der rechten Teilfolge ist größer oder gleich x. Das Verfahren wird nun auf beide Teilfolgen angewendet: 6 18 12 42 und 94 55 67 Algorithms & Datastructures 2 // 2017W // 50
QUICK SORT <T extends Comparable<T>> void quicksort(t[] list, int left, int right) { if (left >= right) return; } // partitioning T pivot = list[(left + right) / 2]; int i = left, j = right; do { while (list[i].compareto(pivot) < 0) i++; while (list[j].compareto(pivot) > 0) j--; if (i <= j) swap(list, i++, j--); } while (i <= j); quicksort(list, left, j); quicksort(list, i, right); Visualisierung: at.jku.pervasive.ad2.vo03.quicksort.java Algorithms & Datastructures 2 // 2017W // 51
IN-PLACE QUICK SORT Divide-Schritt: l durchsucht Sequenz von links nach rechts, r umgekehrt l r 85 24 63 45 17 31 96 50 falls l auf größeres Element zeigt, als r, dann SWAP l r 85 24 63 45 17 31 96 50 Pivot Pivot 31 24 63 45 17 85 96 50... r l 31 24 17 45 63 85 96 50 im letzten Schritt: SWAP mit Pivot-Element r l 31 24 17 45 50 85 96 63 Algorithms & Datastructures 2 // 2017W // 52
ANALYSE QUICK SORT Betrachte Quick-Sort Baum T Sei s i (N) die Anzahl der Elemente der Knoten der Tiefe i in T Es gilt: s 0 (N) = N, da Wurzel mit gesamter zu sortierender Sequenz assoziiert ist s 1 (N) N-1, da Pivot-Element nicht mehr betrachtet werden muss s 2 (N) N-2 (oder sogar N-3 (falls einer der Knoten leer ist)) s i (N) N-i Daraus folgt Komplexität im schlechtesten Fall: Laufzeit im besten Fall: Bester Fall bedeutet, dass bei jeder Teilung S L und S R gleich groß sind s i (N) = N - (1+2+2 2 +...+2 i -1) = N - 2 i + 1 somit hat T Höhe O(log N) Daher Komplexität im besten Fall O(N log N) Algorithms & Datastructures 2 // 2017W // 53
ANALYSE QUICK SORT Best Case Pivot-Element teilt Liste in gleich große Teile N ld(n) ld(n) Ebenen in jeder Ebene N Vergleiche mit Pivot-Element O(N log N) Algorithms & Datastructures 2 // 2017W // 54
ANALYSE QUICK SORT Best Case Pivot-Element teilt Liste in gleich große Teile N Worst Case Pivot-Element trennt ein Element von der Liste N ld(n) N ld(n) Ebenen in jeder Ebene N Vergleiche mit Pivot-Element N Ebenen in jeder Ebene N Vergleiche mit Pivot-Element O(N log N) O(N 2 ) Algorithms & Datastructures 2 // 2017W // 55
ANALYSE QUICK SORT Average Case (in etwa gleich der Laufzeit im best case) Folgende Annahmen/Überlegungen werden vorausgesetzt: N Schlüssel paarweise verschieden (keys sind also die Zahlen 1 N) jeder der N! möglichen Anordnungen gleich wahrscheinlich für eine Folge k 1,,k N folgt, dass jede Zahl k, (1 k N) mit gleicher Wahrscheinlichkeit 1/N an Position N auftritt und damit als Pivotelement gewählt wird Wird k als Pivot gewählt, entstehen zwei Folgen mit den Längen (k-1) und (N-k) jeweils rek. Aufruf von Quicksort Teilfolgen zufällig wenn ursprüngliche Folge zufällig war Durch Aufteilung sämtlicher Folgen k 1,..., k N mit k N = k erhält man wieder sämtliche Folgen von k-1 und (N-k) Elementen Dadurch ergibt sich eine Rekurrenzformel für die mittlere Laufzeit T(N): Algorithms & Datastructures 2 // 2017W // 56
ANALYSE QUICK SORT Average Case (in etwa gleich der Laufzeit im best case) für N 2 folgt (da T(0) = 0): per Induktion zeigen, dass hieraus folgt (für N 2, mit einer hinreichend großen Konstanten c): T(N) c * N log N Sei nun N 3, und setzen wir für alle i < N voraus, dass bereits T(i) c*i*log i gilt. Dann folgt: für c 4b folgt: T(N) c * N log N Algorithms & Datastructures 2 // 2017W // 57
MEDIAN-OF-THREE QUICK SORT Wähle Pivot als Median von drei Elementen im aufzuteilenden Bereich Auswahl des Pivotelementes (Median-of-3 Strategie) Variante (a): m = (l+r)/2 oder Variante (b): m = l+1 Pivot: mittleres Element von {a[l],a[m],a[r]} Bemerkung: Median von 3 Objekten kann in durchschnittlich 8/3 Vergleichen gefunden werden => Divide im Mittel N - 3 + 8/3 = N - 1/3 Vergleiche Tausche a[r] mit Pivot Rest wie bisher Worst Case: verschwindet in Variante (a) für auf- bzw. absteigende Sortierung existiert in Variante (b) immer noch Praktischer Hinweis: Insertion Sort bei kleinem N besser als Quicksort Übergang bei N < 20 auf Insertion Sort Algorithms & Datastructures 2 // 2017W // 58
MEDIAN-OF-THREE QUICK SORT <T extends Comparable<T>> void quicksort(t[] list, int left, int right) { if (left >= right) return; if (right - left < 20) insertionsort(list, left, right); else { // put median element at middle position int middle = (left + right) / 2; if (list[middle].compareto(list[left]) < 0) swap(list, left, middle); if (list[right].compareto(list[left]) < 0) swap(list, left, right); if (list[right].compareto(list[middle]) < 0) swap(list, middle, right); // partitioning T pivot = list[middle]; int i = left + 1, j = right - 1; do { while (list[i].compareto(pivot) < 0) i++; while (list[j].compareto(pivot) > 0) j--; if (i <= j) swap(list, i++, j--); } while (i <= j); unter 20 Elemente Insertion Sort Median von left, middle und right als Pivot-Element } } quicksort(list, left, j); // sort smaller elements quicksort(list, i, right); // sort larger elements at.jku.pervasive.ad2.vo03.motquicksort.java Algorithms & Datastructures 2 // 2017W // 59
MEDIAN-OF-THREE QUICK SORT Wahrscheinlichkeit, dass x an Position k steht ist da k-1 Positionen für das kleinere und n-k Positionen für das größere Objekt möglich sind Schlüsselvergleiche im Mittel: C ave (N) 1.188 N log N 2.55N + O(log N) = O(N log N) Algorithms & Datastructures 2 // 2017W // 60
VERGLEICH VON SORTIERALGORITHMEN Algorithmus Worst Case Best Case Avg. Case BubbleSort O(N 2 ) O(N) O(N 2 ) InsertionSort O(N 2 ) O(N) O(N 2 ) SelectionSort O(N 2 ) O(N 2 ) O(N 2 ) HeapSort O(N log N) O(N log N) O(N log N) MergeSort O(N log N) O(N log N) O(N log N) QuickSort O(N 2 ) O(N log N) O(N log N) Algorithms & Datastructures 2 // 2017W // 61
UNTERE SCHRANKE FÜR SORTIEREN DURCH VERGLEICHEN Entscheidungsbaum ist Binärbaum, in jedem internen Knoten wird ein Vergleich durchgeführt, in den externen Knoten stehen die möglichen Anordnungen der Elemente entsprechend dem Ausgang der Vergleiche Entscheidungsbaum zur Sortierung von 3 Elementen A, B, C: A < B ja nein B < C C < B A B C A < C C B A C < A A C B C A B B C A B A C Algorithms & Datastructures 2 // 2017W // 62
UNTERE SCHRANKE FÜR SORTIEREN DURCH VERGLEICHEN Erinnere: Ein Binärbaum der Tiefe d hat höchstens 2 d Blätter. Beweis durch Induktion: Fall d=0, Anzahl Blätter = 1 = 2 0 Falls d>0, so existiert Wurzel mit linkem und rechtem Subbaum als Kinder, deren Tiefe d-1 ist, daher haben sie höchstens 2 d-1 Blätter. Daher Gesamtzahl der Blätter 2 d. Analog gilt: Ein Binärbaum mit L Blättern hat zumindest eine Höhe von log(l) Anwendung auf Entscheidungsbaum: Entscheidungsbaum zur Sortierung von N Elementen hat N! Blätter (Anzahl der unterschiedlichen Permutationen der N Elemente) und somit eine Höhe von zumindest log (N!) Daher gilt: Ein vergleichsbasierter Sortieralgorithmus benötigt zumindest log (N!) Vergleichsoperationen (worst case) Algorithms & Datastructures 2 // 2017W // 63
UNTERE SCHRANKE FÜR SORTIEREN DURCH VERGLEICHEN Jeder Sortieralgorithmus, der auf Vergleichen beruht, hat als Laufzeit mindestens O (N log N). Dies ist eine (informations-theoretische) untere Schranke! Verallgemeinerung: Wenn P mögliche Fälle unterschieden werden und als Ja/Nein Entscheidungen auftreten, dann sind von einem beliebigen Algorithmus zur Lösung des Problems zumindest log P solcher Entscheidungsfragen zu beantworten Algorithms & Datastructures 2 // 2017W // 64
ALGORITHMENANIMATION :: http://www.sorting-algorithms.com/ Algorithms & Datastructures 2 // 2017W // 66
Algorithms & Data Structures 2 Sorting WS2017 B. Anzengruber-Tanase (Institute for Pervasive Computing, JKU Linz) (Institute of Pervasive Computing, JKU Linz)