21.07.2013 Aufrufe

Bubblesort

Bubblesort

Bubblesort

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

Erstellt von Mario Mayr<br />

<strong>Bubblesort</strong><br />

<strong>Bubblesort</strong> bezeichnet einen einfachen, stabilen Sortieralgorithmus, der eine Reihe linear<br />

angeordneter Elemente (etwa Zahlen) der Größe nach anordnet.<br />

<strong>Bubblesort</strong> wird von Donald E. Knuth als vergleichsbasierter Sortieralgorithmus bezeichnet<br />

[1] . Das bedeutet, dass der Sortieralgorithmus sämtliche Entscheidungen alleine auf Basis des<br />

Größenvergleichs je zweier Elemente fällt, nicht etwa aufgrund der Inspektion der<br />

Binärdarstellung eines Elements.<br />

<strong>Bubblesort</strong> ist der einfachste solcher Algorithmen. Einzige Anforderung an den<br />

Vergleichsoperator ist, dass er eine Totalordnung der Liste ermöglicht.<br />

<strong>Bubblesort</strong> gehört zur Klasse der In-place-Verfahren, was bedeutet, dass der Algorithmus zum<br />

Sortieren keinen zusätzlichen Arbeitsspeicher außer den lokalen Laufvariablen der Prozedur<br />

benötigt.<br />

Wegen der einerseits mangelhaften Effizienz und anderseits breiten Verfügbarkeit deutlich<br />

besserer Alternativen wird <strong>Bubblesort</strong> meistens nur als Beispiel für einen schlechten oder<br />

naiven Sortieralgorithmus verwendet.<br />

Prinzip<br />

Der Algorithmus vergleicht der Reihe nach zwei benachbarte Elemente und vertauscht sie,<br />

falls sie in der falschen Reihenfolge vorliegen. Dieser Vorgang wird solange wiederholt, bis<br />

keine Vertauschungen mehr nötig sind. Hierzu sind in der Regel mehrere Durchläufe<br />

erforderlich.<br />

Je nachdem, ob auf- oder absteigend sortiert wird, steigen die größeren oder kleineren<br />

Elemente wie Blasen im Wasser (daher der Name) immer weiter nach oben, das heißt, an das<br />

Ende der Reihe. Auch werden immer zwei Zahlen miteinander in „Bubbles“ vertauscht.<br />

Schlimmster Fall:<br />

Umgekehrt sortierte Liste: <strong>Bubblesort</strong> Im Falle der umgekehrt sortierten Liste (n,n-1,...,2,1)<br />

werden maximal viele Vertauschungen ausgeführt: um das erste (und größte) Element n ganz<br />

nach rechts zu bewegen, werden n − 1 Vertauschungen vorgenommen.<br />

Laufzeit Θ(n 2 ) für Listen der Länge n<br />

Bester Fall<br />

Falls die Liste bereits sortiert ist wird <strong>Bubblesort</strong> die Liste nur einmal durchgehen um<br />

festzustellen, dass die Liste bereits sortiert ist, weil keine benachbarten Elemente vertauscht<br />

werden mussten. Daher benötigt <strong>Bubblesort</strong> Θ(n) Schritte um eine bereits sortierte Liste zu<br />

bearbeiten.<br />

Falls die Elemente der Liste bereits nah den Stellen sind, die sie nach der Sortierung<br />

bekommen sollen, ist die Laufzeit erheblich besser als Θ(n 2 ).


Erstellt von Mario Mayr<br />

Hasen und Schildkröten<br />

Die Positionen der Elemente vor dem Sortieren entscheiden maßgeblich den Sortieraufwand.<br />

Große Elemente am Anfang wirken sich nicht gravierend aus, da sie schnell nach unten<br />

getauscht werden, kleine Elemente am Ende bewegen sich jedoch eher langsam nach oben.<br />

Darum spricht man bei diesen Elementen von Hasen und Schildkröten.<br />

Beispiel<br />

Eine Reihe von fünf Zahlen soll aufsteigend sortiert werden.<br />

Die fett gedruckten Zahlen werden jeweils verglichen. Ist die linke größer als die rechte, so<br />

werden beide vertauscht; das Zahlenpaar ist dann blau markiert. Im ersten Durchlauf wandert<br />

somit die größte Zahl ganz nach rechts. Der zweite Durchlauf braucht somit die letzte und<br />

vorletzte Position nicht mehr zu vergleichen. --> Dritter Durchlauf: kein Vergleich<br />

letzte/vorletzte/vorvorletzte....<br />

55 07 78 12 42 1. Durchlauf<br />

07 55 78 12 42<br />

07 55 78 12 42<br />

07 55 12 78 42<br />

07 55 12 42 78 2. Durchlauf<br />

07 55 12 42 78<br />

07 12 55 42 78<br />

07 12 42 55 78 3. Durchlauf<br />

07 12 42 55 78<br />

07 12 42 55 78 4. Durchlauf<br />

07 12 42 55 78 Fertig sortiert.<br />

Formaler Algorithmus<br />

Der Algorithmus sieht im Pseudocode so aus:<br />

prozedur bubbleSort( A : Liste sortierbarer Elemente ) definiert als:<br />

n := Länge( A ) - 1<br />

wiederhole<br />

vertauscht := falsch<br />

für jedes i in 0 bis n wiederhole:<br />

falls A[ i ] > A[ i + 1 ] dann<br />

vertausche( A[ i ], A[ i + 1 ] )<br />

vertauscht := wahr<br />

falls ende<br />

für ende<br />

n := n - 1<br />

während vertauscht<br />

prozedur ende<br />

Quelle: Wikipedia


Erstellt von Mario Mayr<br />

Quicksort<br />

Quicksort ist ein schneller, rekursiver, nicht-stabiler Sortieralgorithmus, der nach dem<br />

Prinzip Teile und herrsche arbeitet. Der Algorithmus hat den Vorteil, dass er über eine sehr<br />

kurze innere Schleife verfügt (was die Ausführungsgeschwindigkeit stark erhöht) und ohne<br />

zusätzlichen Speicherplatz auskommt (abgesehen von dem für die Rekursion zusätzlichen<br />

benötigten Platz auf dem Aufruf-Stack).<br />

Eine zufällige Permutation des Umfangs n, in der jedes Element von 1 bis n lediglich einmal<br />

vorkommt, wird mit Quicksort sortiert.<br />

Prinzip<br />

Quicksort wählt ein Element aus der zu sortierenden Liste aus (Pivotelement) und zerlegt die<br />

Liste in zwei Teillisten, eine untere, die alle Elemente kleiner und eine obere, die alle<br />

Elemente gleich oder größer dem Pivotelement enthält. Diese Teillisten werden rekursiv<br />

sortiert. Anschließend wird das Ergebnis zusammengesetzt. Es besteht (in der Reihenfolge)<br />

aus der unteren Liste, dem Pivotelement und der oberen Liste.<br />

Eine direkte Implementation dieses Prinzips hat allerdings den Nachteil, dass sie zum einen<br />

zusätzlichen Speicher benötigt und zum anderen unnötig viele Operationen durchführt.<br />

Deshalb wird ein Verfahren verwendet, das als teilen oder auch partitionieren bezeichnet<br />

wird. Dieses arbeitet in-place.<br />

Hierbei wird nach der Wahl des Pivotelementes zunächst ein Element vom Anfang der Liste<br />

beginnend gesucht, das größer als (oder gleichgroß wie) das Pivotelement und damit für die<br />

untere Liste zu groß ist. Entsprechend wird vom Ende der Liste beginnend ein kleineres<br />

Element als das Pivotelement gesucht. Die beiden Elemente werden dann vertauscht und<br />

landen damit in der jeweils richtigen Liste. Der Vorgang wird fortgesetzt, bis sich die untere<br />

und obere Suche treffen. Auf diese Art und Weise entstehen die beiden Teillisten aus der<br />

Gesamtliste. Sie haben auch schon die richtige Position. Es müssen nur noch die Teillisten<br />

sortiert werden.


Erstellt von Mario Mayr<br />

Laufzeit<br />

Die Laufzeit des Algorithmus hängt im wesentlichen von der Wahl des Pivotelementes ab.<br />

Im Worst Case (schlechtesten Fall) wird das Pivotelement stets so gewählt, dass es das größte<br />

oder das kleinste Element der Liste ist. Dies ist etwa der Fall, wenn als Pivotelement stets das<br />

Element am Ende der Liste gewählt wird und die zu sortierende Liste bereits sortiert vorliegt.<br />

Die zu untersuchende Liste wird dann in jedem Rekursionsschritt nur um eins kleiner und die<br />

benötigte Rechenzeit ist asymptotisch genau n 2 .<br />

Im Best Case (bester Fall) wird das Pivotelement stets so gewählt, dass die entstehenden<br />

Teillisten beide gleich groß sind. In diesem Fall ist die Laufzeit des Algorithmus durch<br />

O(n·log(n)) nach oben beschränkt. Auch für den durchschnittlichen Fall gilt diese obere<br />

Schranke.<br />

Wahl des Pivot-Elemts:<br />

erstes, letztes oder mittleres Element wählen -> sehr ineffizient<br />

Median -> beste Variante aber aufwendiger (Effizientz = ?)<br />

Random -> einfach und effizient<br />

Der Worst Case tritt nur sehr selten auf und im mittleren Fall ist Quicksort schneller, da die<br />

innerste Schleife von Quicksort nur einige wenige, sehr einfache Operationen enthält.<br />

( Heute ist Quicksort für ein breites Spektrum von praktischen Anwendungen die bevorzugte<br />

Sortiermethode, weil er schnell ist und, sofern Rekursion zur Verfügung steht, einfach zu<br />

implementieren ist )<br />

Quicksort setzt jedoch voraus, dass effizient (d.h. mit Aufwand O(1)) über einen Index auf die<br />

Elemente zugegriffen werden kann. Dies ist jedoch meist nur bei Arrays der Fall


Erstellt von Mario Mayr<br />

Quicksort an einem Beispiel<br />

Um ein Array zu sortieren, wählt QuickSort zunächst eine spezielle Zahl im Array, die wir<br />

Pivot nennen wollen. Wie das Pivot gewählt wird, sehen wir uns später an. Dann werden alle<br />

Zahlen, welche kleiner als das Pivot sind, auf die linke Seite des Arrays gebracht, alle Zahlen,<br />

welche größer als das Pivot sind, werden auf die rechte Seite des Arrays gebracht. Als<br />

Beispiel betrachten wir das Array mit den Zahlen<br />

7 10 8 6 11 4 9 3 1 5 2 12<br />

Der Übersichtlichkeit halber haben wir die Zahlen kleiner als 7, welches wir als Pivotelement<br />

wählen, blau markiert. Die Zahlen größer 7 haben wir rot markiert. Dass die 7 gerade am<br />

Anfang des Arrays steht, betrachten wir zunächst als Zufall. Als ersten Schritt ändert<br />

QuickSort das Array nun folgendermaßen:<br />

3 2 5 6 1 4 7 9 11 8 10 12<br />

Nun sind alle Zahlen, welche kleiner sind als 7, links von ihr. Alle Zahlen welche größer sind<br />

als die 7, sind rechts von ihr. Dabei interessiert es uns im Moment gar nicht, in welcher<br />

Reihenfolge die Zahlen links und rechts von der 7 stehen. Alles was wir fordern, ist, dass alle<br />

blauen Zahlen links von der 7 sind, die roten Zahlen rechts von der 7.<br />

Als nächstes sortieren wir den Teil links von der 7 wieder mit QuickSort. Als Pivot wählen<br />

wir die 3. Wieder markieren wir die Zahlen kleiner als drei mit blau, die grösseren Zahlen mit<br />

rot. Die Zahl 7 und die grösseren Zahlen betrachten wir im Moment nicht, deshalb markieren<br />

wir sie gelb:<br />

3 2 5 6 1 4 7 9 11 8 10 12<br />

QuickSort bringt die blauen und die roten Zahlen wieder in die richtige Ordnung. Man erhält<br />

dann das Folgende:<br />

1 2 3 6 5 4 7 9 11 8 10 12<br />

Das wird fortgesetzt bis der linke Teil des Arrays sortiert ist. Das sieht dann wie folgt aus:<br />

1 2 3 4 5 6 7 9 11 8 10 12<br />

Nun wird rechts von der 7 sortiert. Wieder wird ein Pivot gewählt, hier die 9, und die<br />

kleineren und grösseren Zahlen auf die jeweils richtige Seite gebracht.


Erstellt von Mario Mayr<br />

Wir können nun bereits den grundsätzlichen Algorithmus mit PseudoCode beschreiben:<br />

procedure QuickSort( int[] Array, int L, int R )<br />

if ( L < R)<br />

{<br />

mitte := partitioniere(Array, L, R)<br />

QuickSort(Array, L, mitte-1)<br />

QuickSort(Array, mitte+1, R)<br />

}<br />

endprocedure<br />

Dabei wählt die Prozedur partitioniere ein Pivot, und bringt die Zahlen, die kleiner sind, auf<br />

die linke Seite von dem Pivot und die grösseren Zahlen auf die rechte Seite des Pivots. Sie<br />

partioniert das Array. Als Rückgabewert gibt partitioniere die neue Position des Pivots<br />

zurück.<br />

Wir müssen uns noch zwei Dinge überlegen:<br />

1. Wie partitionieren wir ein Array?<br />

2. Wie wählen wir das Pivot?<br />

Wir wenden uns zunächst der Wahl des Pivot-Elements zu.<br />

Wahl des Pivot-Elements (schon beschrieben in LAUFZEIT)<br />

Die Wahl des Pivotelements ist offensichtlich wichtig. Bei einer QuickSortAnalyse? bemerkt<br />

man, dass man am Besten das Element als Pivot wählt, welches am Schluss genau in der<br />

Mitte zu liegen kommt. In diesem Fall ist die Laufzeit von Quicksort O(n log(n)). Leider ist<br />

dieses Element nicht ganz einfach zu finden. Die schlechteste Wahl des Pivots ist das<br />

Element, welches ganz am linken oder ganz am rechten Rand des zu sortierenden Intervals<br />

endet. Falls man immer dieses Element wählt so ist die Laufzeit von Quicksort O(n^2), also<br />

wesentlich schlechter als O(n log(n)).<br />

Man sollte deshalb nicht immer das linke Element als Pivot wählen, so wie wir das in<br />

unseren Beispielen getan haben. Falls nämlich das Array bereits sortiert ist, oder nahezu<br />

sortiert ist, führt dies zu dem einem äußerst langsamen "QuickSort".<br />

Als Kompromiss wird oft ein zufälliges Element als Pivot-Element genommen. Eine<br />

DetaillierteQuickSortAnalyse? zeigt, dass wir dann erwarten können, dass QuickSort in O(n<br />

log(n)) läuft. Noch besser kann es sein, von drei zufällig gewählten Elementen jeweils das<br />

mittlere zu nehmen.<br />

In unserem Text fahren wir fort und nehmen das linke Element als Pivot, obwohl das in jeder<br />

praktischen Implementation schlecht ist.


Erstellt von Mario Mayr<br />

Partitionierung<br />

Wir wenden uns jetzt der Frage zu, wie man bei gewählten Pivot das Array partitioniert. Also<br />

der Frage: wie kommt man effizient von<br />

7 10 8 6 11 4 9 3 1 5 2 12<br />

auf<br />

2 5 6 1 4 3 7 9 11 8 10 12<br />

Die effiziente Implementation dieses Schrittes ist übrigens der Grund, weshalb QuickSort<br />

instabil sortiert. StabilesSortieren ist fast unmöglich mit QuickSort.<br />

Der Einfachheit halber nehmen wir an, dass das Pivot am linken Rand des Arrays steht. Falls<br />

dies nicht so ist, so kann man zuerst das linke Element des Arrays mit dem Pivotelement<br />

vertauschen.<br />

Die Partitionierung macht man nun üblicherweise folgendermaßen. Man verwendet zwei<br />

Zeiger, einen links und einen rechts auf das Array:<br />

7 10 8 6 11 4 9 3 1 5 2 12<br />

^ ^<br />

L R<br />

Nun geht man mit dem linken Zeiger soweit nach rechts, bis er auf eine rote Zahl zeigt. Mit<br />

dem rechten Zeiger geht man soweit nach links, bis er auf eine blaue Zahl zeigt. Danach sieht<br />

die Situation wie folgt aus:<br />

7 10 8 6 11 4 9 3 1 5 2 12<br />

^ ^<br />

L R<br />

Jetzt vertauscht man die Elemente auf welche die Zeiger zeigen:<br />

7 2 8 6 11 4 9 3 1 5 10 12<br />

^ ^<br />

L R<br />

immer: weiter und vertauschen<br />

...<br />

Und ein letzes Mal vertauschen:<br />

7 2 5 6 1 4 3 9 11 8 10 12<br />

^ ^<br />

L R<br />

Jetzt ziehen wir nochmals weiter (aufpassen, Reihenfolge!)<br />

7 2 5 6 1 4 3 9 11 8 10 12<br />

^<br />

LR<br />

Und stoppen hier, weil beide Zeiger auf dasselbe Element zeigen. Nun vertauschen wir noch<br />

das Pivotelement mit dem Element, auf welches gezeigt wird, und sind fertig:<br />

3 2 5 6 1 4 7 9 11 8 10 12<br />

^<br />

LR


Erstellt von Mario Mayr<br />

In PseudoCode könnte das Ganze so aussehen:<br />

procedure partitioniere( int[] Array, int L, int R )<br />

L0 := L // Wählt als Pivot-Element Array[L0]. Dies<br />

// ist für praktische Anwendungen<br />

// SCHLECHT !!! (siehe Wahl des<br />

// Pivot-Elements)<br />

While ( L < R )<br />

{<br />

if ( Array[R] > Array[L0] )<br />

{<br />

R := R – 1<br />

}<br />

Else<br />

{<br />

if ( Array[L]

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!