Prof. Thomas Richter 3. Mai 2017 Institut für Analysis und Numerik Otto-von-Guericke-Universität Magdeburg thomas.richter@ovgu.de Material zur Vorlesung Algorithmische Mathematik II am 27.04.2017 Stabiles Sortieren 1 Radix-Sort Gegeben seien Werte S = {s 1,..., s n } mit maximal d N Ziffern. Wir schreiben hierzu a S als d a = a 1 a 2 a d = a i 10 d i, gegebenenfalls mit a 1 = = a z = 0 für einem gewissen Index z. Die Ordnung auf S sei eine lexikographische Ordnung auf den Ziffern: a = b a i = b i i = 1,..., d a < b k {1,..., d} mit a k < b k und a i = b i für i = 1,..., k 1. Dieses Prinzip lässt sich natürlich auf beliebiege andere Zahlensystem oder auch komplett anders gestaltete Mengen übertragen. i=1 Folie 1 Ein Beispiel: Wir betragen Einträge in einem Personalverzeichnis. Die bestehen aus Vorname, Nachname und Geburtsdatum. Die Ordnung geht nun der Reihe nach Nachname, Vorname und dann Geburtsdatum durch. Es gilt dann z.b: Abels Fritz 1999 Fischer Fritz 1976 Fischer Gerd 1989 Richter Jakob 1939 Richter Thomas 1939 Richter Thomas 1976 Im Folgenden betrachten wir aber einfach Zahlenziffern. Die Idee des Radix-Sort beruht genau auf dieser Aufteilung. 1
Folie 2 Idee von Radix-Sort Gegeben Werte S = {s 1,..., s n } mit maximal d N Ziffern und einer Ordnung, welche lexikographisch gegeben ist. 1 Für i von d b i s 1 2 S o r t i e r e L i s t e S bzgl. S t e l l e i s t a b i l Definition 1 (Stabiles Sortieren). Ein Sortierverfahren heißt stabil, wenn gleich Werte nach Durchlauf des Verfahrens in der gleichen Reihenfolge sind wie vorher. Beispiel 1. Wir betrachten die folgenden Zahlen und sortieren rückwärts nach den Ziffern. 138 471 413 138 471 413 713 167 368 713 927 274 413 453 628 368 628 274 138 368 798 646 646 413 713 167 453 453 646 927 167 471 274 138 368 628 368 368 368 646 167 628 471 713 927 798 274 798 453 368 798 927 Man beachte, dass stets stabil sortiert wurde. Wir müssen also zunächst untersuchen, ob die bekannten Sortierverfahren stabil sind. Weiter kann Radix-Sort nur dann Sinn machen, wenn wir für die Sortierung nach einzelnen Ziffern ein besonders schnelles Verfahren gefunden wird. Denn bei Radix-Sort müssen wir die Menge der Elemente insgesamt d mal sortieren. Folie 3 Satz 1. Merge-Sort und Insertion-Sort sind stabile Sortierverfahren. Quicksort ist im allgemeinen nicht stabil. Es existieren stabile Varianten von Quicksort. 2
Beweis. Gegeben sei eine Menge S = {s 1,..., s n } mit totaler Ordnung. Wir betrachten hier Insertion-Sort. Eine stabile Variante von Merge Sort wird in der Vorlesung besprochen. Algorithmus 1: Insertion-Sort 1 Für i von 2 bis n 2 Setze j = i 3 Solange j > 1 und s j 1 > s j 4 Tausche s j und s j 1 5 Setze j = j 1 Wir betrachten jeweils einen Durchlauf der inneren Schleife, also Zeilen 3-5 der Algorithmus. Angenommen, für zwei Elemente s k und s l mit k < l gelte s k = s l. Angenommen, eines der Elemente hat einen Index größer i, also k > i oder l > i. Dann wird mindestens eines der Elemente sicher nicht getauscht, denn potentiell getauscht werden nur Indizes j i. Die Reihenfolge bleibt somit nach Durchlauf der Schleife bestehen. Nun gelte 1 k < l i. Falls die Schleife in Zeile 4 mit j > l abbricht, so werden die Elemente mit Index k und l nicht getauscht und die Reihenfolge bleibt erhalten. Angenommen, der Index j = l wird erreicht. Nun folgen gegebenenfalls Tauschvorgänge, bei denen s l schrittweise mit Elementen s l = s j s j 1 s j 2 s j. Falls Abbuch bei j > k+1, so wird die Reihenfolge der beiden Elemnte nicht getauscht. Falls j = k + 1 erreicht wird so kommt es wegen s j = s l = s k = s j 1 zu einem Abbruch der Schleife in Schritt 3. Elemente mit gleichem Wert werden also nie getauscht. Schnelles Sortieren kleiner Mengen Bucket-Sort ist prinzipiell ein sehr schneles Sortierverfahren. Zum Sortieren einer Liste von n Elementen werden O(n + m) Operationen benötigt, wobei m die Anzahl von möglicherweise verschiedenen Elementen ist. Falls m groß ist, so ist dieses Verfahren nicht stabil. Sortieren wir jedoch nur nach einer Ziffer, so gibt es nur 10 unterschiedliche Werte {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}. Es sind also nur 10 Eimer notwendig. Wir könnten somit auf die folgende Art nach der l-ten Ziffer sortieren. 3
Folie 4 Algorithmus 2: Sortieren mit Bucket-Sort nach Ziffer l Gegeben Liste S = {s 1,..., s n } und Stelle l 1 E r s t e l l e 10 l e e r e Buckets B 0 = = B 9 = 2 Für i von 1 bis n 3 z = s l i 4 B z = B z {s i } 5 Setze S = 6 Für z von 0 bis 9 7 S = S B z. Der Nachteil von Bucket-Sort ist der zusätzliche Speicheraufwand zum Speichern der Buckets, sowie das zweimalige Kopieren aller Elemente. Es ist zu Beginn nicht klar, wie groß die einzelnen Buckets sein müssen. Ein Vorteil von diesem Algorithmus ist die Stabilität. Die Elemente werden in gleicher Reihenfolge in die Buckets gelegt, in der sie nachher auch entnommen werden. Eine gute Alternative zum Sortieren nach Ziffern ist Counting-Sort. In einem ersten Durchgang wird gezählt, wie oft jede Stelle vorkommt. Im zweiten Druchgang können die Elemente gleich an der richtigen Stelle eingefügt werden. Folie 5 Algorithmus 3: Sortieren mit Counting-Sort nach Ziffer l Gegeben Liste S = {s 1,..., s n } und Stelle l 1 c 0 = = c 9 = 0 2 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / S c h l u e s s e l Zaehlen 3 Für i von 1 bis n 4 z = s l i 5 c z = c z + 1 6 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / S t a r t Index 7 i 0 = = i 9 = 0 8 Für z von 1 bis 9 9 i z = i z 1 + c z 1 10 / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / Ergebnis L i s t e 11 E r s t e l l e l e e r e L i s t e E der Länge n 12 Für i von 1 bis n 13 z = s l i 14 Schreibe s i an S t e l l e i z 15 i z = i z + 1 Zum Abschluss vergleichen wir die verschiedenen Sortierverfahren für große Listen von Zahlen zwischen 0 und 10 8. Wir müssen bei Radix-Sort somit stets 8 Durchgänge von Counting-Sort ausführen. 4
N Radix Quick Merge 20 0.0000046 0.0000036 0.000017 40 0.0000089 0.0000081 0.000031 80 0.0000110 0.0000146 0.000069 160 0.0000172 0.0000342 0.000126 320 0.0000293 0.0000692 0.000241 640 0.0000540 0.0001485 0.000529 1 280 0.0001024 0.0003106 0.001021 2 560 0.0001962 0.0005980 0.000642 5 120 0.0001327 0.0004300 0.001300 10 240 0.0002474 0.0008956 0.003478 20 480 0.0004856 0.0018293 0.005226 40 960 0.0010229 0.0037729 0.010197 81 920 0.0016786 0.0061296 0.016180 163 840 0.0028981 0.0113089 0.030600 327 680 0.0055382 0.0235784 0.062167 655 360 0.0111745 0.0500609 0.128783 1 310 720 0.0227151 0.1042850 0.268230 2 621 440 0.0455057 0.2149980 0.550568 5 242 880 0.0919180 0.4507670 1.160730 10.0000000 1.0000000 0.1000000 Radix-Sort Quick-Sort Merge-Sort xlog(x) x 0.0100000 0.0010000 0.0001000 0.0000100 0.0000010 0.0000001 10 100 1000 10000 100000 1 10 6 1 10 7 Der Unterschied zwischen der Laufzeit O(n) und O(n log(n)) scheint nicht sonderlich groß und wird nur für sehr große Listen deutlich. 5