Sortieren in linearer Zeit

Werbung
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
Herunterladen