Adnan Kadric 56821 Kevin Kandler 57274 Seminararbeit Sommersemester 2017 1 Contents 1 Einleitung 1.1 Sortieren in Linearer Zeit . . . . . . . . . . . . . . . 1.1.1 Untere Schranke für das Sortieren . . . . . . 1.1.2 Das Entscheidungsbaum-Modell . . . . . . . 1.1.3 Eine untere Grenze für den schlechtesten Fall 1.1.4 Beweis . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 4 5 5 2 Countingsort 2.1 Zeit von Countingsort . . . . . . . . . . . . . . . . . . . . . . 6 8 3 Radixsort 3.1 Lochkarten . . . . . 3.2 Radixsort . . . . . . 3.3 Code von Radixsort 3.4 Lemma 1.3 . . . . . 3.5 Lemma 1.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 9 10 11 11 12 4 Bucketsort 13 4.1 Laufzeit Bucketsort . . . . . . . . . . . . . . . . . . . . . . . . 14 5 Literatur 18 2 1 Einleitung Es existieren Sortier-Algorithmen, die n Zahlen in Zeit O(n log n) sortieren können. Darunter befinden sich Heapsort und Mergesort, die diese obere Grenze im schlechtesten Fall erreichen, während Quicksort dies im Mittel schafft. Für jeden der genannten Algorithmen kann eine Folge von n Eingabezahlen angegeben werden, für die der Algorithmus in der Zeit Ω(n log n)läuft. Eine interessante Eigenschaft weisen all diese Algorithmen auf: Die sortierte Reihenfolge, die sie bestimmen, basiert nur auf Vergleichen zwischen den Eingabeelementen. Daher werden diese Algorithmen auch vergleichende Sortierverfahren genannt. Die oben genannten Sortierverfahren gehören dieser Klasse an. Im nachfolgenden Abschnitt 1.1 wird bewiesen, dass im schlechtesten Fall jedes der vergleichenden Sortierverfahren Ω(n log n) Vergleichsoperationen durchführen muss, um n Elemente zu sortieren. Das bedeutet, dass Heapsort und Mergesort asymptotisch optimal sind. Dabei existieren keine anderen vergleichende Sortierverfahren, die um mehr als einen konstanten Faktor schneller sind. In Abschnitt 2, 3 und 4 werden 3 weitere Sortieralgorithmen - Countingsort, Radixsort und Bucketsort - vorgestellt, die alle in linearer Zeit sortieren. Diese Algorithmen benutzen noch andere Operationen als Vergleich, um die sortierte Reihenfolge zu bestimmen. Folglich gilt für sie die untere Grenze Ω(n log n) nicht. 1.1 1.1.1 Sortieren in Linearer Zeit Untere Schranke für das Sortieren Bei vergleichenden Sortierverfahren kommen ausschließlich Vergleiche zwischen den Elementen vor, um Information über die Reihenfolge der Eingabesequenz (a1 , a2 , . . . , an ) zu gewinnen. Für zwei gegebene Elemente ai und aj werden für die Bestimmung der relativen Reihenfolge einer der Tests ai < aj , ai ≤ aj , ai = aj ,ai ≥ aj oder ai > aj vorgenommen. Es ist nicht möglich, die Werte der Elemente zu inspizieren oder auf anderem Weg Information über sie zu erlangen. Für die folgende Diskussion werden Elemente angenommen, die paarweise verschieden sind. Unter dieser Voraussetzung sind Vergleiche der Form ai = aj nutzlos. Daraus folgt, dass Vergleiche dieser Art nicht ausgeführt werden. Außerdem wird festgestellt, dass die Vergleichsoperationen ai ≤ aj , ai ≥ aj ,ai > aj und ai < aj äquivalent sind, da sie zu identischen Informationen über die Reihenfolge von ai und aj führen. Das bedeutet schlussendlich, dass alle Vergleiche von der Art ai ≤ aj sind. 3 1.1.2 Das Entscheidungsbaum-Modell Sortierverfahren können durch abstrakte Entscheidungsbäume dargestellt werden. Ein Entscheidungsbaum ist ein vollständiger binärer Baum, der die Vergleiche zwischen den Elementen eines vorgegebenen Eingabefeldes darstellt. Alle anderen Aspekte wie Steuerung oder Datenbewegung werden vernachlässigt. Somit zeigt der Baum, welche Operationen auf welche Elemente des Eingabefeldes einer vorgegebenen Länge durchgeführt werden. Abbildung 1.1 zeigt den Entscheidungsbaum für Insertion Sort bei der Eingabe von drei Elementen. Abbildung 1.1 Entscheidungsbaum für Insertionsort von drei Elementen. Ein mit i:j gekennzeichneter innerer Knoten stellt einen Vergleich zwischen ai und aj dar. Ein Endknoten (Blattknoten) stellt die Permutation (π(1), . . . ., π(n)) der Eingabeelemente dar und gibt die Reihenfolge aπ (1) ≤ aπ (2) ≤ ... ≤ aπ (n) an. Der schattierte Pfad zeigt die Entscheidungen die bei der Eingabefolge (a1 = 6, a2 = 8, a3 = 5) getroffen werden. Die Permutation(3,1,2) am Blattknoten dieses Pfades sagt, dass die sortierte Reihenfolge a3 = 5 ≤ a1 = 6 ≤ a2 = 8 ist. Es gibt somit 3!=6 mögliche Permutationen der Eingabeelemente und so hat der Entscheidungsbaum mindestens 6 Blätter. In einem Entscheidungsbaum wird jeder interne Knoten durch i:j dargestellt, für ein i und j aus dem Bereich 1 ≤ i, j ≤ n, wobei n die Anzahl der Elemente in der Eingabefolge angibt. Jeder Blattknoten steht für eine Permutation (π(1), ..., π(n)) der Eingabeelemente. Die Ausführung des Sortieralgorithmus entspricht der Verfolgung eines einfachen Pfades von der Wurzel bis hinunter zum Blattknoten. Jeder interne Knoten symbolisiert einen Ver4 gleich der Art ai ≤ aj . Führt der Vergleich zu einem positivem Ergebnis, so wird der linke Teilbaum beschritten, andernfalls der rechte. Dieses Szenario gilt für alle Knoten eines Entscheidungsbaumes. Bei Erreichen eines Blattknotens hat der Sortieralgorithmus die Reihenfolge aπ (1) ≤ ... ≤ aπ (n) und damit sein Ziel festgelegt. Da jeder korrekt arbeitende Sortieralgorithmus in der Lage sein muss jede Permutation der Eingabe zu erzeugen, muss jede der n! Permutationen als einer der Blattknoten im Entscheidungsbaum vorkommen. Darüber hinaus muss jeder Blattknoten von einem von der Wurzel startenden Pfad erreichbar sein, der einer tatsächlichen Ausführung des vergleichenden Sortierverfahrens entspricht. Folglich werden nur solche Entscheidungsbäume betrachtet, in denen jede Permutation als erreichbarer Blattknoten erscheint. 1.1.3 Eine untere Grenze für den schlechtesten Fall Die Länge des längsten Pfades von der Wurzel eines Entscheidungsbaumes zu den erreichbaren Blattknoten stellt die Anzahl von Vergleichen dar, die im schlechtesten Fall vom Sortieralgorithmus ausgeführt werden. Folglich entspricht die Anzahl von Vergleichen eines vergleichenden Sortieralgorithmus im schlechtesten Fall der Höhe des Entscheidungsbaumes. Eine untere Grenze für die Höhe aller Entscheidungsbäume, in der jede Permutation als ein erreichbarer Blattknoten erscheint, ist folglich eine untere Grenze für die Laufzeit irgendeines vergleichenden Sortieralgorithmus 1.1.4 Beweis Theorem 1.1. Jeder vergleichende Sortieralgorithmus erfordert im schlechtesten Fall Ω(n log n) Vergleichsoperationen. Nach der vorangegangenen Diskussion genügt es, die Höhe eines Entscheidungsbaumes zu bestimmen, in dem jede Permutation als erreichbarer Blattknoten vorkommt. Betrachte einen Entscheidungsbaum der Höhe h mit l erreichbaren Blattknoten, der für ein vergleichendes Sortierverfahren angewandt auf n Elemente steht. Da jede der n! Permutationen der Eingabefolge als Blattknoten vorkommt, gilt n! ≤ l. Da ein binärer Baum der Höhe h nicht mehr als 2h Blattknoten hat, gilt: n! ≤ l ≤ 2h sodass durch Anwenden des Logarithmus die Aussage folgt: h ≥ lg(n!) = Ω(n lg n) 5 Korollar 1.1. Heapsort und Mergesort sind asymptotisch optimale vergleichende Sortierverfahren. beweis 1.1. Die obere Grenze O(n lg n) der Laufzeiten von Heapsort und Mergesort entspricht der unteren Grenze im schlechtesten Fall von Theorem 1.1. 2 Countingsort Der Countingsort setzt für jedes Eingabeelement n voraus, dass n ∈ N0 ist. Die Variable k nimmt den größten Wert des Eingabefeldes an. Der Algorithmus bestimmt für jede eingegebene Zahl n die Anzahl der Elemente, die kleiner sind als n. Somit wird die Position ermittelt in dem n später stehen muss. Bsp 2.1: Auf das Eingabefeld A = [2, 1, 3, 5, 7] soll ein neuer Eingabewert n = 18 eingefügt werden. n gehört im Eingabefeld an die Stelle A[5]. Hier ist zu beachten, dass man in Feldern mit der Zahl 0 beginnt zu zählen. Somit wird k = 18 dadurch das der neue Wert betragsmäßig größer ist als alle anderen Werte im Feld. Zur Optimierung des Algorithmus wird im Eingabefeld mit der Zahl 1 begonnen zu zählen und nicht wie im bsp 2.1 mit der Zahl 0. Dies hat den Vorteil, dass der Algorithmus merkt das 5 Elemente kleiner gleich n sind und somit n an stelle A[6] kommt. Dadurch wird der Vorteil gewonnen das nun das Eingabefeld A die Größe A.länge besitzt. Das erlaubt es dem Algorithmus mit den Indexwerten der Felder zu arbeiten und nicht, wie man naiv vermuten würde mit Vergleichen. Somit wird der Code für den Sortieralgorithmus so programmiert, dass die Eingabe aus einem Feld A = [1 . . . n] und die Länge von A gleich der Anzahl der Eingabeelemente gleich n ist. Zum Code werden außerdem noch zwei weitere Felder benötigt. Ein Feld B das die sortierte Ausgabe speichert. Mit der Eigenschaft, dass dieses Feld die gleiche grösse hat wie A. Ebenso wird auch ein Hilfsfeld C benötigt was einen temporären Arbeitsspeicher darstellen soll. Dieses Feld zählt wie oft ein Wert in A vorkommt und wie viele Werte kleiner sind als A[i], i ∈ (1, ..., A.Länge). Anders als bei den Ein- und Ausgabefeld beginnt das Hilfsfeld mit dem Wert 0 zu zählen und hat die Länge k. Im Bsp 2.1 wäre k = 18 und somit wäre C[0...18] und C.Länge = 19. 6 Abbildung 2.1 Abbildung 2.1 zeigt die Arbeitsweise von Countingsort auf das Eingabefeld A[1..8], alle Elemente des Feldes sind positive Zahlen die ≤ 5 sind. (a) Das Feld A und das Hilfsfeld C nach Ausführung von Zeile 5. Das bedeutet, dass in C steht wie oft jeder Wert in A vorkommt.(b) Das Feld C nach Zeile 8. Jetzt kennt C durch Aussumieren über An−n+1 + An−n+2 + ... + An alle Werte die kleiner sind als i. (c)-(e) Das Ausgabefeld B und das Hilfsfeld C nach drei Iterationen der Schleife aus den Zeilen 10-12. Nur die schattierten Elemente des Feldes B wurden eingeführt. (f) Das abschließende sortierte Ausgabefeld B. Abbildung 2.2 Abbildung 2.2 gibt die Arbeitsweise des Countingssorts wieder. Schleife in Zeile 2 und 3 setzt jedes Element von C auf 0. Danach Schleife in 4 und 5. Hier arbeitet der Code mit der Laufvariable j geht bis zur Größe von A. Mit dieser Schleife inkrementiert dieser 7 Die forfolgt die = 1 und dann im Feld C den Wert von A[j] an der Position C[A[j]]. Somit erhalten wir wie oft der Wert in unserem Eingabef eld vorkommt. In den Zeilen 7-8 bekommt man durch aufsummieren wie viele Elemente kleiner als i sind. Hier ist zu beachten, dass das Feld C bei der 0 anfängt zu zählen. In der letzten Schleife platziert der Algorithmus jedes Element von A[j] an der richtigen Stelle im Ausgabefeld. Sollten alle Eingabeelemente paarweise verschieden sein, werden diese beim erstmaligen ausführen von Zeile 10 für jedes A[j] der Wert C[A[j]] in dessen richtige Endposition im Ausgabefeld bestimmt. Wieso das so ist, ist leicht nachzuvollziehen. Dadurch, dass es C[A[j]] Elemente gibt die kleiner oder gleich A[j] sind, kann es nie anders auftreten. Der Algorithmus schließt nicht aus, dass die Elemente paarweise verschieden sein müssen deswegen wird im Code C[A[j]] jedes mal dekrementiert, wenn er einen Wert A[j] in das Ausgabefeld B schreiben muss. Das bewirkt, falls ein Wert A[j] geschrieben werden soll und der Gleiche Wert im Ausgabefeld schon vorhanden ist, dieser an die unmittelbar vorherige Position von A[j] geschrieben wird. 2.1 Zeit von Countingsort Welche Zeit benötigt der Algorithmus Die Schleife in Zeile 2-3 benötigt die Zeit Θ(k), die Schleife der Zeilen 45 läuft in Zeit Θ(n) und die Schleife in Zeile 7-8 braucht genauso wie die Schleife aus Zeile 2-3 Θ(k) Zeit. Die letzte wiederum braucht auch Θ(n). Daraus folgt, dass die Gesamtlaufzeit von Countingsort nur Θ(n + k) Zeit braucht. In der Praxis ist es erstrebenswert das k = O(n) ist. Dadurch das dann das Verfahren auch in Θ(n) Zeit läuft. Die bewiesene Untere Schranke aus Abschnitt 1.1 gilt hier nicht, da der Countingsort diese tatsächlich unterbietet, da es kein vergleichbares Sortierverfahren ist. Im Code kommen auch keine Vergleiche zwischen den Elementenvor. Stattdessen arbeitet dieses Sortierverfahren mit den reellen Werten der Elemnte als Index in einem Feld. Die untere Schranke von Ω(n lg n) gilt nicht, wenn Sortierverfahren betrachtet werden die nicht vergleichbar sind. Eine wichtige Eigenschaft des Countingsort ist, dass er stabil ist. Dadurch, dass die Zahlen mit gleichen Werten im Ausgabefeld in der gleichen Reihenfolge erscheinen, wie sie eingelesen wurden. Das beduetet sind zwei Zahlen gleich entscheidet der Algorithmus, dass die zuerst eingelesene Zahl aus dem Eingabefeld als erstes in das Ausgefeld geschrieben wird. Eigentlich ist die Stabilität nur von Bedeutung, wenn auch sogenannte Satellitendaten mit den zu sortierenden Elementen mitgeführt werden. Die Stabilität ist jedoch aus noch einem anderen Grund wichtig. Der Countingsort wird meist als Unteroutine von Radixsort verwendet. 8 3 Radixsort Dieser Algorithmus hatte seinen Höhepunkt zur Zeit der Lochkarten, wie diese die man heute nur noch in Museen findet. 3.1 Lochkarten Die Lochkarte war zur seiner damaligen Zeit essentiell was die Datenverarbeitung betraf. Sie diente als Eingabe und Speichermedium. Die Lochkarten haben insgesamt 80 Spalten und 12 Zeilen. Die Zeilen stellen die Dezimalzahlen von 0 bis einschließlich 9 dar. Die restlichen Zeilen dienen als Vorzeichen, sprich dem plus und dem minus. Die Spalten wiederum müssen nicht als eine Zahl gewertet werden sondern lassen sich aufteilen. So könnte ein Unternehmen, dass die Ware mit der Produktnummer Y an den Kunden X , zum Preis i ,mit der Stückzahl Z verkauft, auswerten. Teilt man diese 80 Spalten durch vier, hätte jeder dieser Variablen 20 Spalten für sich. Da es jedoch unnötig ist dem Preis i 20 Spalten zuzuweisen, da der Preis in der Regel aus einigen vorkomma, und aus genau zwei nachkomma stellen, besteht. So teilt dieses Unternehmen für X = 30 Spalten, Y = 20, Z = 20 und i = 10, zu. Das Unternehmen erstellt jetzt einen Datensatz auf einer Lochkarte, auf einer der die Werte festgehalten werden. Bsp 3.1 Kunde Müller KundenI D = 213 kauft das Produkt mit der Produktnummer=1457 zum Preis von 14 Euro und einer Stückzahl von 1000 Stück. Wie im Bsp 3.1 gezeigt wird hat der Kunde Müller die Kundenid von 213 das setzt voraus das es weitere Kunden gibt und zwar mindestens 212 weitere. Beim Radixsort bietet es sich an, wenn jeder Wert der ausgewertet werden soll die gleiche Größe hat. So würde der Kunde 3 und 212 schwer zu sortieren sein. Deswegen werden die freien Plätze der Lochkarte mit 0 Initalisiert. Dies bedeutet wenn von 30 stellen tatsächlich nur 3 stellen verwendet werden, werden die restlichen Spalten mit 0 Initialisiert. Angenommen das Unternehmen hat einem Tag nicht nur einen Kunden, sondern 1000, so kann jetzt der Tagesumsatz nach der Größe sortiert werden. Es wird jedoch verausgesetzt, dass die Daten von einer geeigneten Kartenlesemaschine auf einen Computer überspielt werden und dieser die Daten dann Verarbeiten kann, Voraussetzung ist hier eine entsprechenden Programmierung. Nicht nur Zahlen sondern auch Buchstaben und Sonderzeichen lassen sich auf einer Lochkarte kodieren. Ermöglicht wird das durch Merhfachlochungen innerhalb einer Spalte. 4096 verschiedene Zeichen sind mit 12 Löchern darstellbar. 9 3.2 Radixsort Für den weiteren Verlauf des Algorithmuses wird das Bsp 3.1 betrachtet. Jedoch werden die Kunden nach Kundennummer sortiert. Hier bietet sich der Radixsort gut an. Der Radixsort arbeitet in dem es jeweils eine Zahl aus allen Datensätzen vergleicht und diese dann sortiert.Rein Inutitiv sollte man meinen, dass es sich anbietet Zahlen des Datensatzes mit dem Höchstwertigen Bit anzufangen zu vergleichen und dann zu Sortieren. Jedoch darf man hier nicht vergessen, dass der Algrithmus aus der Lochkarten Zeit stammt und diese Prozedur zu viele temporäre Stapel erzeugen würde. Entgegen der Intuition löst Radixsort das Problem indem es zuerst nach der Stelle mit der geringsten Wertigkeit sortiert. Daraufhin fügt dieser dann die Karten zu einem Kartenstapel zusammen, wobei die Karten aus dem Kasten 0 vor denen des Kasten 1 kommen, dieser wiederum vor denen aus Kasten 2 und so weiter. Ist dies für die geringste Wertigkeit abgeschlossen beginnt der Algorithmus mit der zweitgeringsten Wertigkeit und fügt sie auf die gleiche Art und weise wieder zusammen. Dies wird fortgeführt bis die Karten nach allen 30 Stellen sortiert wurden. Bemerkenswerter weise sind die Karten zu diesem Zeitpunkt vollständig nach der 30-stelligen Zahl sortiert. Somit werden zum Sortieren nur 30 durchläufe durch den Kartenstapel gebraucht. Bsp radix sort. Abbildung 3.1 Damit der Radixsort korrekt sortieren kann, muss das Verfahren zum Sortieren nach einer Stelle stabil sein. Das Sortierverfahren ist auch Stabil, jedoch muss der Operator darauf achten, dass die Reihenfolge der Lochkarten in der sie aus dem Kasten kommen nicht verändert werden. Auch nicht dann 10 wenn alle Lochkarten eines Kastens in der gewählten Spalte die gleiche Ziffer stehen haben. Bsp 3.2 Ein Beispiel aus heutiger Zeit ohne Lochkarten. Radixsort wendet man an, um Datensätze zu sortieren, die aus mehreren Komponenten-Schlüssel bestehen wie ein Datum. Bei einem Datum gibt es drei Schlüssel: der Tag, der Monat und das Jahr. Als erstes wird nach dem Jahr Sortiert sollten diese übereinstimmen, dann nach Monat und dann gegebenenfalls nach dem Tag. 3.3 Code von Radixsort Der Code für Radixsort ist einfach. Der Algorithmus setzt voraus das jedes Element des Feldes aus einer d stelligen Zahl besteht wobei die Stelle 1 die niederwertigste Stelle ist und die Stelle d die höchstwertigste. Im Zusammenspiel mit Countingsort wird auch vorausgesetzt, dass die Zahlen ganze Positive Zahlen sind. Abbildung 3.2: Code von Radixsort 3.4 Lemma 1.3 Sind n d-stellige Zahlen gegeben, bei denen jede Stelle k mögliche Werte annehmen kann, sortiert Radixsort diese Zahlen in der Zeit Θ(d(n + k)), wenn das in den Zwischenschritten verwendete stabile Sortierverfahren jeweils Zeit Θ(n + k) benötigt. Beweis: Die Richtigkeit von Radixsort erfolgt durch Induktion. Die Analyse der Laufzeit hängt von dem im Zwischenschritt verwendeten stabilen Sortierverfahren ab. Wenn jede Stelle im Bereich von 0 bis k − 1 liegt, sodass sie k mögliche Werte annehmen kann und k nicht zu groß ist, dann ist Countingsort die naheliegende Wahl. Jeder Durchlauf über die n d-stellige Zahlen benötigt dabei die Zeit θ(n+k). Es gibt somit d Durchläufe und die gesamte 11 Laufzeit für Radixsort θ(d(n + k)). Für konstantes d und k = O(n) läuft Radixsort in Linearer Zeit. 3.5 Lemma 1.4 Bei gegebenen n b-Bit Zahlen und einer positiven Zahl r ≤ b sortiert Radixsort diese Zahlen in der Zeit von Θ((b/r)(n + 2r ), wenn das verwendete stabile Sortierverfahren für die Zahlen im Bereich von 0 bis k die Zeit Θ(n + k) benötigt. Beweis: Für einen Wert r ≤ b wird jeder Schlüssel in d = db/re Stellen von r Bits unterteilt. Jede Stelle kann als eine Zahl im Bereich von 0 bis 2r − 1 betrachtet werden, mit dem Effekt, dass der Countingsort mit k = 2r zur Anwendung kommt. Unterteilt man z.B. ein 32-Bit Wort in 4 Stellen zu jeweils 8 Bits, dann ist b = 32, r = 8, k = 2r = 256 und d = b/r = 4. Jeder Durchlauf des Countingsort benötigt also Θ(n + k) = θ(n + 2r ) Zeit. Es sind d Durchläufe notwendig, mit einer Gesamtlaufzeit von θ(d(n + 2r )) = θ((b/r)/(n + 2r )). Sind Werte für n und b gegeben, dann kann der Wert von r, unter Berücksichtigung von r ≤ b, dahingehend gewählt werden, dass der Ausdruck (b/r)(n + 2r ) minimiert wird. Ist b < bblg nc, dann gilt für jeden Wert r ≤ b die Gleichung (n + 2r ) = θ(n). Wird r = b gewählt, dann ergibt sich eine Laufzeit von (b/b)(n/ + 2b ) = θ(n), was asymptotisch optimal ist. Ist b ≥ blg nc, dann wird mit der Wahl r = blg nc die, bis auf eine Konstante, beste Laufzeit erreicht. Mit der Wahl von r = blg nc beträgt die Laufzeit θ(bn/ lg n). Ist der Wert von r, wie oben angegeben, größer als blg nc, dann wächst der 2r Term im Zähler schneller als der r Term im Nenner; das führt zu einer Laufzeit von Ω(bn/ lg n). Ist der Wert von r dagegen kleiner als blg nc, dann wächst der Term b/r und der Term n + 2r bleibt bei Θ(n). Die Frage lautet also: Ist Radixsort einem vergleichenden Sortieralgorithmus, wie z.B. Quicksort, vorzuziehen? Angenommen b = O(lg n), was oft der Fall ist, und r ≈ lg n, dann beträgt die Laufzeit von Radixsort Θ(n). Im ersten Augenblick erscheint dies besser zu sein als die Laufzeit von Quicksort mit Θ(n lg n). Jedoch unterscheiden sich die konstanten, verborgenen Faktoren in der Θ Notation. Obwohl Radixsort eventuell weniger Durchläufe als Quicksort für die n Schlüssel benötigt, ist es vorstellbar, dass ein Durchlauf bei Radixsort eine signifikant längere Zeit braucht. Welcher Sortieralgorithmus nun vorzuziehen ist, hängt in erster Linie von den Eigenschaften der Implementierung ab, von der zugrunde liegenden Maschine (Quicksort ist bei der Nutzung von Hardware Caches oft effektiver als Radixsort) und von der Eingabe ab. Darüber hinaus sortiert die Version von Radixsort , die zwischendurch Countingsort verwendet, nicht in-place, was aber die Mehrheit 12 der vergleichenden Sortieralgorithmen mit einer Laufzeit von Θ(n lg n) tun. Folglich ist unter der Prämisse, dass primärer Speicher in ”ausreichender“ Zahl zur Verfügung steht, ein in-place Sortieralgorithmus wie Quicksort die erste Wahl. 4 Bucketsort Der Bucketsort unterliegt der Eigenschaft das die Eingabe unter der Gleichverteilung auftritt und hat eine mittlere Laufzeit von O(n). Bucketsort und der Countingsort sind schnell da sie gewisse Annahmen über die Eingabe treffen. Während der Countingsort vorgibt, dass die Eingabe positive ganze Zahlen aus einem kleinen Bereich sein müssen, geht Bucketsort davon aus, dass die Eingabe durch zufällige Prozesse erzeugt wird. Dieser Prozess verteilt die Eingabe gleichmäßig und unabhängig voneinander über den Zahlenbereich [0, 1) d.H die Eingabe darf nur von einschließlich 0 bis zur 1 gehen ohne die 1 selbst. Der Algrithmus teilt das Interval [0, 1) in Buckets auf, diese Intervalle sind gleichgroß, daraufhin verteilt er die n Eingabezahlen in diesem Bereich. Sind die Eingabezahlen gleichmäßig und unabhängig von einander verteilt ist nicht zu erwarten das viele Zahlen in die gleichen Buckets einsortiert werden. Um eine Ausgabe zu erzeugen werden die Zahlen innerhalb des Buckets sortiert und der Reihe nach ausgegeben. Der Code für den Bucketsort setzt voraus das die Eingabe ein Feld A mit der Größe n ist und jedes Element A[i] die Bedingung 0 ≤ a[i] < 1 erfüllt. Wie auch beim Countingsort wird hier ein Hilfsfeld benötigt mit der Größe b = [0 . . . n − 1]. Da dieser Algorithmus mit verketteten Listen (die Buckets) arbeitet, brauchen wir n solcher Hilfsfelder. Im Pseudocode wird davon ausgegangen das eine Verwaltung solcher Felder schon vorhanden ist (siehe code 8-9) 13 Abbildung 4.1: Code Bucketsort Abbildung 4.2 Um zu verstehen wie dieser Algorithmus arbeitet betrachtet man zwei Elemente A[i] und A[j]. unter der Annahme, ohne Beschränkung auf die Allgemeinheit, das A[i] ≤ A[j] ist. Wegen bnA[i] ≤ nA[j]c wird A[i] entweder in die gleiche Liste (Bucket) wie A[j] sortiert oder in eines mit einem kleinerem Index. Sollten A[i] und A[j] in das gleiche Bucket einsortiert werden, sorgt die for Schleife in zeile 7-8 dafür, dass diese in die richtige Reihenfolge kommen. Sollten diese Elemente in unterschiedliche Buckets kommen, dann sorgt die Schleife in Zeile 9 dafür, dass diese richtig sortiert werden. Somit arbeitet Bucketsort korrekt. 4.1 Laufzeit Bucketsort Um die Laufzeit auszuwerten, stellt man fest, dass alle Zeilen außer Zeile 8 im schlechtesten Fall die Zeit O(n) benötigt. Dabei legt man die Zeit fest, die durch die n Aufrufe von Sortieren durch Einfügen in Zeile 8 verbraucht wird. Um die Kosten für die Aufrufe von Sortieren durch Einfügen zu untersuchen, 14 führt man die Zufallsvariable ni ein, die die Anzahl der Elemente in Bucket B[i] beschreibt. Das Sortieren durch Einfügen in quadratischer Zeit läuft, ist die Laufzeit von Bucketsort T (n) = Θ(n) + n−1 X O(n2i ). i=0 Nun bestimmt man die Laufzeit im Mittel von Bucketsort, indem die erwartete Höhe der Laufzeit berechnet wird und die Eingangsverteilung zu Grunde gelegt wird. Auf beiden Seiten wird ein Erwartungswert gebildet und dabei dessen Linearität ausgenutzt, so erhält man E[T (n)] = E[θ(n) + = θ(n) + = θ(n) + n−1 X O(n2i )] i=0 n−1 X E[O(n2i )] wegen der Liniarität des Erwartungswertes i=0 n−1 X O(E[n2i ]) i=0 Wir behaupten, dass E[n2i ] = 2 − 1/n für i = 0, 1, ...., n − 1 gilt. Es überrascht nicht, dass jedes Bucket i für (E[n2i ]) den gleichen Wert hat, denn jeder Wert des Eingabefeldes A hat für jedes Bucket die gleiche Wahrscheinlichkeit, um in dieses eingefügt zu werden. Um die Gleichung E[n2i ] = 2 − 1/n zu beweisen, definieren wir die Indikatorfunktionen Xij = I{A[j] wird in Bucket i eingefügt} für i = 0, 1, ..., n − 1 und j = 1, 2, ..., n. somit gilt ni = Pn j=1 Xij . Um (E[n2i ]) zu berechnen, multiplizieren wir das Quadrat aus und ordnen die Terme um: 15 2 n X (E[n2i ]) = E Xij j=1 n X n X = E Xij Xik j=1 k=1 n X X 2 = E Xi j + = n X X j=1 1≤j≤n E[Xi2 j ] + X X 1≤j≤n 1≤i≤m k6=j j=1 Xij Xik 1≤i≤m k6=j E[Xij Xik ] Die letzte Zeile folgt aus der Linearität des Erwartungswertes. Wir werten die beiden Summen seperat aus. Die Indikatorfunktion Xij ist mit einer Wahrscheinlichkeit 1/n gleich 1 und mit einer Wahrscheinlichkeit 1 − 1/n gleich 0; also gilt: (E[Xi2 j ]) 1 1 2 = 1 ∗ +0 ∗ 1− n n 1 = n 2 für k 6= j sind die Variablen Xij und Xik unabhängig, sodass (E[Xij Xij ]) = E[Xij ] ∗ E[Xik ] 1 1 = ∗ n n 1 = n2 16 gilt. Substituieren wir diese beiden Erwartungswerte in die Gleichung, so erhalten wir (E[n2i ] = X1 X + n j=1 1≤j≤n X 1≤k≤n k6=j 1 n2 1 1 + n(n − 1) ∗ 2 n n n−1 = 1+ n 1 = 2− n = n∗ womit die Gleichung bewiesen ist. Setzen wir diese Erwartungswerte in die Gleichung ein, so sehen wir, dass die Laufzeit von Bucketsort im Mittel in Θ(n) + O(2 − 1/n) = θ(n) ist. Selbst wenn die Eingabefolge nicht einer gleichmäßigen Verteilung genügt, kann Bucketsort in linearer laufzeit laufen. Solange die Eigenschaft erfüllt ist, dass die Summe der Quadrate der Bucketgrößsen linear in der Gesamtzahl der Elemente ist, sagt uns die erste Gleichung, dass Bucketsort in linearer Zeit laufen wird. 17 5 Literatur Thomas H.Cormen. Introduction to Algorithms. Third Edition. July2009 1 1 Alle Bilder und Beweise in dieser Arbeit wurden aus diesem Buch übernommen 18