Sortieralgorithmen - eine Einführung


Überblick


Einleitung
Merge-Sort
Bubble-Sort
Ripple-Sort
Minimum-Sort
Insertion-Sort
Quick-Sort

Geschwindigkeit
Kriterien

Quellen


Einleitung

Das Sortieren ist eines der wichtigsten Gebiete der Datenverarbeitung. Circa 25% aller Rechenzeit im kommerziellen Bereich wird auf das Sortieren von Daten verwendent [6].

An Beispielen zum Sortieren mangelt es nicht:

  • Statistiken
  • Telefonbücher, Wörterbücher
  • Listen, ...

Wie aber einer Rechenanlage beibringen, Daten zu sortieren? Natürlich durch einen Algorithmus. Im Streben, immer schnellere und effizientere Algorithmen zu entwickeln, sind heutzutage bereits viele Sortieralgorithmen bekannt. Einige von ihnen sollen in diesem Text vorgestellt werden.

Zuerst stellt sich aber die Frage, was ist Sortieren eigentlich? Natürlich fehlt es hier auch nicht an einer Definition:

"Unter Sortieren versteht man allgemein den Prozess des Anordnens einer gegebenen Menge von Objekten in einer bestimmten Ordnung." ([7], Kapitel 2, Seite 77)

Vereinbarungen

Im folgenden werden gewisse Vereinbarungen getroffen:

  • n ist zu lesen als Anzahl der Werte (oder Elemente) (das gilt sowohl für den Text als auch für das beigefügte Listing)
  • A(n) ist der Vergleichsaufwand eines bestimmten Algorithmus' für n Elemente
  • die Namen von Sortieralgorithmen werden entweder mit oder ohne Bindestrich vor Sort oder Suche geschrieben

Zum Anfang

Sortieren durch Mischen

Der bekannteste und nebenbei auch einer der einfacheren Sortieralgorithmen ist BubbleSort. Allerdings ist es für große zu sortierende Mengen aufgrund seiner Geschwindigkeit eher ungeeignet. Warum das so ist, läßt sich leicht zeigen:

Angenommen, das zu sortierende Array besteht aus n Elementen. Dann wird dieses Array auch (n-1)mal durchlaufen, wobei bei jedem Durchlauf verschieden viele Vergleiche durchgeführt werden. Abubble(n) sei die Anzahl der nötigen Vergleiche für n Elemente.

Abubble(n) = 0,5 · (n*n - n)

Beweis: Beim ersten Durchlauf wird das Array bis zum (n-1)ten Element durchlaufen (demzufolge finden (n-1) Vergleiche statt), beim zweiten bis zum (n-2)ten Element und beim letzten Durchlauf schließlich nur noch bis zum 1. Element (1 Vergleich) .

Abubble(n) ist demzufolge:

Abubble(n)     = (n-1) + (n-2) + (n-3) + ... + (n-(n-1))
                        = 0,5·n·(n+1) - n
                        = 0,5·n*n + 0,5*n - n
                        = 0,5·n*n - 0,5·n
                        = 0,5·(n*n - n)
        
Bei hohen n explodiert die Zahl der nötigen Vergleiche geradezu. Wenn sich n verdoppelt, vervielfacht sich die Anzahl der nötigen Vergleiche um mehr als das vierfache. Das läßt sich aber auch umgekehrt sagen:
Halbiert man n, so viertelt man Abubble: Abubble(0,5·n) = 0,25·Abubble(n)

Das kann man sich jetzt zunutze machen. Wenn man die n Elemente in zwei Teile teilt und sie getrennt sortiert, so braucht man nur halb soviel Zeit, als wenn man sie zusammen sortiert.

Hier hätten wir also die Idee zu einem für uns neuen Sortieralgorithmus:

Beschreibung: Wenn man n Elemente sortieren will, so teilt man das Feld, daß diese Elemente enthält, in der Mitte und sortiert beide Hälften getrennt voneinander. Anschließend besitzt man ein Feld, daß aus zwei Teilen besteht, welche beide sortiert sind. Das Feld an sich ist aber noch nicht sortiert. Also müssen wir zum Abschluß die beiden sortierten Hälften noch so miteinander mischen, daß das Feld vollkommen sortiert ist.

Wie aber mischt man die beiden Hälften? Diese Frage soll weiter unten beantwortet werden. Zuerst wollen wir uns aber ein Beispiel ansehen:

Nehmen wir an, wir haben ein Feld, welches aus den folgenden Zahlen, in dieser Reihenfolge, besteht: 5 8 2 6 1 9 7 3

Zuerst teilen wir das Feld also in zwei Hälften: 5 8 2 6 und 1 9 7 3

Anschließend sortieren wir beide Hälften, und zwar jede für sich. Das Ergebnis sieht dann folgendermaßen aus: 2 5 6 8 und 1 3 7 9

Wie man sieht, sind beide Hälften fein säuberlich in sich sortiert. Insgesamt gesehen ist das gesamte Feld aber noch nicht fertig sortiert, da die 1 ja schließlich vor die 2 muß. Deshalb müssen wir die beiden Hälften miteinander mischen:

Um das zu tun, brauchen wir vier Zeiger:

  • start_links : zeigt auf den Beginn der linken Hälfte
  • ende_links : zeigt auf das Ende der linken Hälfte
  • start_rechts : zeigt auf den Beginn der rechten Hälfte
  • ende_rechts : zeigt auf das Ende der rechten Hälfte

Desweiteren brauchen wir zwei Läufer:

  • links : zeigt auf die akt. Position der linken Seite
  • rechts: zeigt auf die akt. Position der rechten Seite

Am Anfang ist links:=start_links und rechts:=start_rechts. Um die zwei Hälften (start_links bis ende_links und start_rechts bis ende_rechts) zu sortieren, müssen wir nun folgendes machen:

Vorbereitg.: Wir legen ein Hilfsfeld an, in das wir die beiden Hälften mischen.

Schritt (1)
Wir checken, ob die Zahl bei links kleiner als die bei rechts ist. Wenn ja, schreiben wir die Zahl bei links, ansonsten die bei rechts, in das Hilfsfeld und erhöhen anschließend entweder rechts oder links, abhängig davon, welche Zahl kleiner war.
Schritt (2)
Wir wiederholen (1) so lange, bis entweder links oder rechts ihre jeweilige Hälfte durchlaufen haben (links > ende_links oder rechts > ende_rechts)
Schritt (3)
In einer der Hälften sind nun noch Zahlen übrig. Diese schreiben wir zum Schluß auch noch in das Hilfsfeld.

Gegenüber BubbleSort ist dieser Sortieralgorithmus nun wie erwartet ungefähr doppelt so schnell. Jetzt aber nochmal das Mischverfahren am Beispiel:

Von unserem letzten Beispiel haben wir noch folgendes Feld übrig (s.o.) :

           2  5  6  8     1  3  7  9
           sl       el    sr       er
           l              r
        

Start_links (sl) zeigt auf die 2, das erste Element der linken Hälfte, start_rechts (sr) auf die 1, das erste Element der rechten Hälfte, ende_links (el) auf die 8 und ende_rechts (er) auf die 9 (jeweils das letzten Element der Seite). Die Läufer links (l) und rechts (r) zeigen auf die 2 und auf die 1.

Wie oben in (1) beschrieben, vergleichen wir die Zahlen an der Position links und an der Position rechts. Das wäre also die 2 und die 1. Die 1 ist bewiesenermaßen kleiner als die 2, also schreiben wir die 1 in das Hilfsfeld und erhöhen rechts:

           2  5  6  8     1  3  7  9
           l                 r
        

Nun machen wir weiter, indem wir die Zahlen bei links und rechts wieder vergleichen. Diesmal ist die 2 kleiner als die 3, also kommt die 2 in das Hilfsfeld, hinter die 1, und links wird erhöht.

Einige Durchläufe von (1) weiter, bietet sich uns das folgendes Bild:

           2  5  6  8     1  3  7  9
                    l              r

           Hilfsfeld :  1  2  3  5  6  7
        

Zuletzt wurde die 8 mit der 7 verglichen. Die 7 wurde in das Hilfsfeld geschrieben und rechts wurde erhöht. Nun vergleichen wir die 8 mit der 9 und schreiben die 8 in das Hilfsfeld. Anschließend erhöhen wir links um 1. Wir haben jetzt unsere Abbruchbedingung erreicht ("l" hat die gesamte linke Hälfte einmal durchlaufen) und schreiben demzufolge die Zahlen, die in der rechten Hälfte übriggeblieben sind (von rechts bis ende_rechts) in das Hilfsfeld.

Damit wäre die Mischung abgeschlossen.

Das grobe Ablaufschema sieht jetzt ungefähr so aus:

  1. teile das Feld in zwei Teile: Mitte := Anzahl der Elemente \ 2
  2. sortiere linken und rechten Teil, was durch die Prozedur BubbleSort passieren kann, der wir das gesamte Feld übergeben, sowie den Anfang und das Ende des zu sortierenden Bereiches
  3. mische die beiden Teile zusammen (siehe oben)

Wir haben also die Anzahl der nötigen Vergleiche zum Sortieren eines Feldes halbiert, indem wir die Elemente aufteilten und getrennt sortierten. Eine Fortführung dieser Idee besteht darin, auf die Zuhilfenahme eines anderen Sortieralgorithmus' ganz zu verzichten, und statt dessen die beiden Hälften nochmal zu teilen und so weiter. Falls das so lange gemacht wird, bis die Teile nur noch ein Element groß sind, werden sie beim anschließendem Mischen in einer aufsteigenden Reihenfolge sortiert. Dieses Verfahren heißt Mergesort.

Der Geschwindigkeitsvorteil, der dabei erzielt wird, ist gegenüber dem einmaligen Teilen - Sortieren - Mischen enorm.


Zum Anfang

Weitere Sortieralgorithmen

Neben dem obengenannten Sortieralgorithmus gibt es noch weitere Algorithmen, namentlich

Diese sechs (fünf ohne Mischsort, das ja bereits ausführlich beschrieben wurde) Algorithmen sollen in diesem Kapitel ganz kurz vorgestellt und erläutert werden. Dabei wird auf eine detaillierte Beschreibung (sowie einem Struktogramm) verzichtet. Im Anhang befindet sich ein Programm, welches sämtliche Sortieralgorithmen, die hier vorgestellt werden, als Prozeduren beinhaltet.

Zum Anfang

Bubble-Sort

Heißt übersetzt etwa soviel wie Blasensortieren, was dadurch kommt, daß während des Sortierens die großen Werte wie Blasen aufsteigen. Ansonsten gehört es, wie auch RippleSort, zum "Sortieren durch Austauschen". Das Aufsteigen der großen Werte wird dadurch erreicht, daß die Elemente von vorne bis zum Ende durchlaufen werden und das aktuelle Element mit dem nachfolgenden vertauscht wird, sollte letzteres kleiner sein. Im Anschluß befindet sich die größte Zahl des Feldes am Ende. Diese Aktion wird wiederholt (allerdings nicht mehr bis zum Ende, sondern nur noch bis dahin, wo die sortierten großen Werte sind) bis das gesamte Array sortiert ist, oder das Array einmal durchlaufen wurde, ohne das ein Tausch stattfand.

Zum Anfang

Ripple-Sort

Ripple-Sort ist eine Art Bubble-Sort, allerdings wandern hier die kleinen Zahlen nach vorne (daher auch der Name: kleine Welle). Der Algorithmus durchläuft das Array von 1 bis n-1. An jeder Stelle kontrolliert er, ob ein noch kleineres Element folgt. Ist dem so, wird dieses Element mit dem Element, wo RippleSort gerade "steht", vertauscht. Folgt nach dem kleineren Element ein noch kleineres Element, wird wieder getauscht, ansonsten wird zum nächsten Element gegangen.

RippleSort sehr ähnlich ist Selection-Sort. Der grundlegende Ablauf ist derselbe, allerdings wird gezielt nach dem kleinsten Element gesucht, und dann erst vertauscht.

Zum Anfang

MinimumSuche

Die Minimumsuche ist einer der einfachsten Sortieralgorithmen. Das Verfahren ist ganz einfach: Es wird ein Hilfsfeld angelegt, das genauso groß wie das originale Array ist. Anschließend wird das Originalarray n-mal vollständig durchlaufen. Bei jedem Durchlauf wird nach dem kleinsten Element Ausschau gehalten, das dann in das Hilfsfeld geschrieben wird. Damit dieser Wert beim nächsten Durchlauf nicht nochmal gefunden werden kann, wird er mit dem größtmöglichsten Wert überschrieben.

Zum Anfang

Insertion-Sort

Insertion-Sort (Sortieren durch Einfügen) ist ein Algorithmus, der genauso arbeitet, wie man selbst Daten, z.B. Karten, sortieren würde. Es durchläuft nämlich das Array von vorne bis hinten und sucht bei jeder Stelle das nachfolgende kleinste Element. Wenn ein kleineres existiert, werden die Werte vom aktuellen Element bis zum kleinsten Element um eins nach hinten verschoben, und das kleinste Element wird davor eingefügt.

Zum Anfang

QuickSort

QuickSort ist, wie der Name schon sagt, der schnellste gebräuchliche Sortieralgorithmus. Er funktioniert nach dem Prinzip Teile-und-Herrsche. Aus der Mitte des aktuellen Feldbereiches (zu Beginn das gesamte Feld), wird eine Testzahl herausgepickt. Anschließend werden alle anderen Zahlen, je nach Größe durch Vertauschen vor oder hinter die Mitte eingeordnet. Anschließend ruft sich QuickSort selbst auf (rekursiv) um die beiden Hälften nach eben beschriebenem Verfahren zu sortieren ... Die Abbruchsbedingung ist erreicht, wenn der Feldbereich nur noch ein Element groß ist.

Zum Anfang

Weitere Sortieralgorithmen

Neben den eben vorgestellten Algorithmen gibt es natürlich noch weitere, z.B.:

  • Heap-Sort
  • Binary-Insertion-Sort
  • Radix-sort
  • Bucket-Sort
  • Distribution-Sort
  • Shell-Sort
  • Shaker-Sort
  • ...

Zum Anfang

Geschwindigkeitsvergleiche

Wie man sich denken kann, sind alle Algorithmen unterschiedlich schnell. Im folgenden werden verschiedene Tabellen aufgelistet, die die Zeit angeben, welche die Algorithmen für ein bestimmtes n braucht.

Alle Messungen wurden unter folgenden Bedingungen durchgeführt:

  • compiliertes ACE-Programm
  • System: Amiga 500, 7 MHz

Algorithmus sortiert zufällig invers sortiert n
Minimumsuche 0.10 0.10 0.10 25
InsertSort 0.04 0.08 0.10
RippleSort 0.06 0.06 0.08
BubbleSort 0.06 0.08 0.10
MischSort 0.04 0.06 0.06
QuickSort 0.02 0.02 0.02
Minimumsuche 0.38 0.38 0.38 50
InsertSort 0.42 0.64 0.86
RippleSort 0.41 0.58 0.80
BubbleSort 0.42 0.60 0.82
MischSort 0.24 0.34 0.44
QuickSort 0.06 0.10 0.04
Minimumsuche 1.5 1.5 1.5 100
InsertSort 1.64 2.52 3.44
RippleSort 1.64 2.36 3.20
BubbleSort 1.70 2.48 3.28
MischSort 0.90 1.30 1.70
QuickSort 0.10 0.20 0.12
Minimumsuche 5.93 5.94 5.93 200
InsertSort 11.74 18.28 24.66
RippleSort 11.72 16.70 22.88
BubbleSort 12.04 17.90 23.5
MischSort 6.20 9 11.90
QuickSort 0.34 0.62 0.40
Minimumsuche 36.90 36.86 37.04 500
InsertSort 18.34 28.60 38.58
RippleSort 18.34 25.90 35.78
BubbleSort 18.84 28 36.74
MischSort 9.60 14.12 18.54
QuickSort 0.42 0.78 0.46
Minimumsuche 147.56 147.34 148.06 1000
InsertSort 73.56 113.54 154.78
RippleSort 73.56 98.76 143.50
BubbleSort 75.52 111.24 147.40
MischSort 38.12 56.46 74.04
QuickSort 0.90 1.68 0.98

(Die Zahl in der letzten Spalte benennt die Anzahl der Elemente. Die Spalten "sortiert", "zufällig" und "invers sortiert" enthalten die Werte, die der jeweilige Algorithmus gebraucht hat, um solch ein Feld zu sortieren. Sämtliche Zeitangaben sind in Sekunden. Zu den Zeiten muß aber noch gesagt werden, daß sie vom "wahren" Wert um bis zu einer Sekunde abweichen können. Das liegt daran, daß die Werte vom Computerprogramm selbst gemessen wurden und der Ablauf des Programms durch im Hintergrund laufende Programme verlangsamt worden sein könnte. Alle Meßreihen wurden aber unter gleichen Bedingungen durchgeführt und bei einigen Werten wurde von mehreren Meßversuchen der Mittelwert genommen. Ansonsten sollte sich eine eventuelle negative Beeinflussung größtenteil ausgleichen. Desweiteren sind ein Großteil der Algorithmen im Rahmen der Aufgabenstellung nicht optimiert.)

Beim Ansehen der Tabelle merkt man sofort den gewaltigen Vorsprung, den QuickSort (aus diesem Grund auch von seinem "Erfinder" C. A. R. Hoare so benannt) besitzt. Alle anderen Algorithmen sind für etwas größere n drastisch langsamer. Trotzdem ist der Gebrauch von QuickSort nicht in allen Anwendungsfällen vorzuziehen. Für wenige Zahlenwerte reicht BubbleSort oder ein ähnlich einfacher Algorithmus vollkommen aus. Der Zeitunterschied fällt dann gar nicht ins Gewicht. Das ist übrigens typisch für komplexere Sortieralgorithmen. Der erhöhte Aufwand zahlt sich meist erst bei höheren n richtig aus.


Zum Anfang

Auswertung

Minimumsuche
Der langsamste Algorithmus, der im Rahmen dieses Projektes untersucht wurde. Negativ ist der recht hohe Zeitaufwand und der Speicherbedarf, da ja ein Hilfsfeld angelegt werden muß, welches die selbe Größe wie das Originalfeld hat. Auffallen tut, daß der Zeitaufwand für eine bestimmte Anzahl von Elementen immer konstant bleibt, unabhängig von der Anordnung der Elemente innerhalb des Feldes. (Anzahl der Vergleiche: n*n, Anzahl der Verschiebung von Elementen: n und weitere n für das Rückschreiben vom Hilfsfeld)
InsertSort
Wie auch bei Ripple-, Bubble- und Mischsort kann an diesem Algorithmus eine Fallunterscheidung bzgl. der Geschwindigkeit durchgeführt werden. Alle vier Algorithmen besitzen einen besten Fall, der bei allen dann eintritt, wenn das Feld schon sortiert ist. Der schlechteste Fall ist jeweils dann, wenn das Feld invers sortiert ist.
Negativ bei InsertSort ist, daß jedes Mal, wenn ein Element an einer Stelle eingefügt werden soll, die nachstehenden Elemente verschoben werden müssen. Im Gegensatz zur Minimumsuche braucht hier aber kein extra Feld angelegt werden, wodurch der Speicherbedarf nur von der Anzahl der Elemente beeinflußt wird.
BubbleSort
Ein sehr bekannter Sortieralgorithmus, der aber auch seine kleinen Macken hat. Ist das Feld zum Beispiel schon von Element 1 bis n-1 sortiert und Element n beinhaltet den kleinsten Wert, so benötigt BubbleSort n Durchläufe, um dieses Element nach vorne zu bringen. Sollte sich aber das größte Element am Anfang des Feldes befinden und der Rest ist bereits sortiert, so benötigt BubbleSort nur einen einzigen Durchlauf, um dieses Element nach hinten zu bringen.
QuickSort
Der schnellste Algorithmus der hier vorgestellten. Das ist sicherlich das positivste an QuickSort. Der Speicherbedarf ist aufgrund der Rekursivität etwas höher als BubbleSort (da Speicherung der lokalen Variablen, Rücksprungadresse auf Stack), erreicht aber noch lange nicht das Ausmaß von MinimumSort.
Der Idealfall bei QuickSort ist gegeben, wenn das Testfeld (üblicherweise die Mitte oder der Mittelwert von drei zufällig gewählten Elementen) von mittlerer Größe ist. Sollte das Testfeld aber insofern aus der Rolle fallen, daß es eines der größten (oder kleinsten) Elemente ist und sich das so fortsetzt, so wird QuickSort zu "Slowsort" ([7], Seite 101). In diesem Fall kommt QuickSort an die Größenordnung von n*n, anstatt n*(log n), wie es eigentlich "üblich" ist.

Zum Anfang

Kriterien für die Auswahl von Sortieralgorithmen

Ein Aspekt, der immer bei der Wahl von Sortieralgorithmen beachtet werden sollte (und muß), ist die Art der Speicherung der Werte. Da wäre zum einen die Möglichkeit, daß die Daten im Hauptspeicher (RAM) in Form eines Arrays liegen, andererseits könnten sie auch in einer Datei auf einem Festspeicher (z.B. Diskette) sein.

Bei kleinen n wäre es möglich, daß die Daten aus der Datei in den Speicher geladen und dort sortiert werden. Bei großen n macht sich das allerdings schlecht, da es dann leicht zu Speicherproblemen kommen kann. Man muß in dem Fall also einen Sortieralgorithmus benutzen, der sequentiell auf Daten zugreift. Das sind in der Regel Algorithmen, die das Prinzip des Mischens verwenden.

Aber auch im ersten Fall (Daten im Hauptspeicher) ist nicht jeder Algorithmus günstig (mal ganz abgesehen von Geschwindigkeitsunterschieden). Einige Verfahren, namentlich die Minimumsuche, legen nämlich ein zweites Array an, das manchmal genauso groß wie das originale Feld ist. Das das bei vielen Zahlen- oder sonstigen Werten sehr ungünstig sein kann, sei mit dem Hinweis auf den oft begrenzten Hauptspeicher bewiesen.


Zum Anfang

Anhang

Quellenangaben

        [1]     Owsnicki-Klewe, Bernd
                Algorithmen und Datenstrukturen
                (Inf & Ing; Bd. 5)
                Dr. Bernd Wißner, Augsburg
                1. Auflage, 1994
        [2]     Mehlhorn, Kurt
                Datenstrukturen und effiziente Algorithmen
                Band 1: Sortieren und Suchen
                Teuber, Stuttgart
                1986
        [3]     Oestereich, Bernd
                Algorithmen - Sortieren
                aus: c't, Seite 273-276
                Heft 11, 1988
        [4]     Dipl.-Ing. (FH) Schültz, Helbring
                Das "Bubble-Sort-Verfahren"
                (Ein Sortieralgorithmus unter der Lupe)
                aus: Elektronik, Seite 72-81
                Heft 11, Jahrgang 39, 25.05.1990
        [5]     Sortieralgorithmen in Pascal: "Ordnung muß sein"
                aus: Dos international, Seite 254-258
                Heft 12, Jahrgang 8, 1994
        [6]     Ottmann, Thomas
                Algorithmen und Datenstrukturen
                Bibliographisches Institut & F.A. Brockhaus AG
                2. Auflage, 1993
        [7]     Wirth, Niklaus
                Algorithmen und Datenstrukturen
                B.H. Teubner, Stuttgart
                4. Auflage, 1995
        [8]     Sedgewick, Robert
                Algorithmen
                Addison-Wesley
                1. Auflage, 1991
        

[1], [6], [7] und [8] besitzen jeweils ein oder mehr Kapitel, die sich mit dem Sortieren von Daten beschäftigen. Desweiteren kann verwiesen werden auf: c't : Heft 3 und 4, 1992