Sortierverfahren

Quicksort

Der Quicksort-Algorithmus [Hoa 62] ist eines der schnellsten und zugleich einfachsten Sortier­verfahren. Das Verfahren arbeitet rekursiv nach dem Divide-and-Conquer-PrinzipErklärung.

Idee

Bild 1 zeigt schematisch die Vorgehens­weise von Quicksort anhand einer Eingabefolge von Nullen (weiß) und Einsen (grau). Zunächst wird die zu sortierende Folge a so in zwei Teilstücke b und c zerlegt, dass alle Elemente des ersten Stücks b kleiner oder gleich allen Elementen des zweiten Stücks c sind (divide). Danach werden die beiden Teilstücke sortiert, und zwar rekursiv nach demselben Verfahren (conquer). Wieder zusammen­gesetzt ergeben die Teilstücke die sortierte Folge (combine).

 

Divide and Conquer: Quicksort(n) 

Bild 1: Quicksort(n)

 

Die Auf­teilung geschieht, indem zunächst ein Vergleichs­element x gewählt wird. Alle Elemente der Folge, die kleiner als x sind, kommen in das erste Teilstück. Alle Elemente, die größer als x sind, kommen in das zweite Teilstück. Bei Elementen, die gleich x sind, ist es egal, in welches Teilstück sie kommen.

In dem folgenden Auf­teilungsverfahren kann es auch vorkommen, dass ein Element, das gleich x ist, zwischen den beiden Teilstücken verbleibt. Dieses Element steht dann schon an seiner richtigen Position.

 

Algorithmus Partition

Eingabe:

Folge a0, ..., an-1 mit n Elementen

Ausgabe:

Umordnung der Folge derart, dass alle Elemente a0, ..., aj kleiner oder gleich den Elementen ai, ..., an-1 sind (i > j)

Methode:

  1. wähle das mittlere Element der Folge als Vergleichselement x
  2. setze i = 0 und j = n-1
  3. solange ij wiederhole
    1. suche von links das erste Element ai mit aix
    2. suche von rechts das erste Element aj mit ajx
    3. wenn ij dann
      1. vertausche ai und aj
      2. setze i = i+1 und j = j-1

 

Quicksort behandelt nach dieser Auf­teilung der Folge die beiden Teilstücke a0, ..., aj und ai, ..., an-1 nach demselben Verfahren rekursiv weiter. Wenn ein im Verlauf des Verfahrens entstehendes Teilstück nur aus einem Element besteht, endet die Rekursion.

Programm

Die folgende Funktion quicksort sortiert ein Teilstück des Arrays a vom Index lo bis zum Index hi. Das Vergleichs­element x wird als das mittlere Element zwischen lo und hi gewählt 1).

Die Sortier­funktion ist in der Klasse QuickSorter gekapselt. Die Methode sort übergibt das zu sortierende Array an das Array a, setzt n auf dessen Länge und ruft quicksort mit dem unteren Index 0 und dem oberen Index n-1 auf.

Die Hilfs­funktion exchange(i, j) vertauscht die Array-Elemente a[i] und a[j].

Mit den Anweisungen

QuickSorter s=new QuickSorter();
s.sort(b);

wird ein Objekt vom Typ QuickSorter erzeugt und anschließend die Methode sort zum Sortieren eines Arrays b aufgerufen. Es folgt das Programm.

 

public class QuickSorter
{
    private int[] a;
    private int n;

    public void sort(int[] a)
    {
        this.a=a;
        n=a.length;
        quicksort(0, n-1);
    }

    private void quicksort (int lo, int hi)
    {
        int i=lo, j=hi;

        // Vergleichselement x
        int x=a[lo+(hi-lo)/2];

        //  Auf­teilung
        while (i<=j)
        {    
            while (a[i]<x) i++; 
            while (a[j]>x) j--;
            if (i<=j)
            {
                exchange(i, j);
                i++; j--;
            }
        }

        // Rekursion
        if (lo<j) quicksort(lo, j);
        if (i<hi) quicksort(i, hi);
    }

    private void exchange(int i, int j)
    {
        int t=a[i];
        a[i]=a[j];
        a[j]=t;
    }

}    // end class QuickSorter

Analyse

Der Algorithmus verläuft optimal, wenn jeder Auf­teilungsschritt im Verlauf der Rekursion jeweils etwa gleichlange Teilstücke erzeugt. In diesem günstigsten Fall benötigt Quicksort Θ(n log(n)) Schritte, denn die Rekursions­tiefe ist dann log(n) und in jeder Schicht sind n Elemente zu behandeln (Bild 2 a).

Der ungünstigste Fall tritt ein, wenn ein Teilstück stets nur aus einem Element und das andere aus den restlichen Elementen besteht. Dann ist die Rekursions­tiefe n-1 und Quicksort benötigt Θ(n2) Schritte (Bild 2 c).

Im Mittel wird etwa eine Auf­teilung wie in Bild 2 b dargestellt zustande kommen.

 

Bild 2: Rekursionstiefe von Quicksort: (a) im besten Fall, (b) im Mittel, (c) im schlechtesten Fall 

Bild 2: Rekursionstiefe von Quicksort: (a) im besten Fall, (b) im Mittel, (c) im schlechtesten Fall

 

Die Wahl des Vergleichs­wertes x spielt die ent­scheidende Rolle dafür, welche Auf­teilung zustande kommt. Man könnte z.B. auch das erste Element der Folge als Vergleichs­element wählen. Dies würde aber dazu führen, dass das ungünstigste Verhalten des Algorithmus ausgerechnet dann eintritt, wenn die Folge zu Anfang bereits sortiert ist. Daher ist es besser, das mittlere Element der Folge zu wählen.

Am besten ist es natürlich, als Vergleichs­element dasjenige Element zu wählen, das von der Größe her in der Mitte der Elemente liegt (der Median). Dann würde die optimale Auf­teilung zustande kommen. Tatsächlich ist es möglich, den Median in linearer Zeit zu bestimmen (linearer Median­algorithmus). In dieser Variante würde Quicksort also auch im schlechtesten Fall nur O(n log(n)) Schritte benötigen.

Quicksort lebt jedoch von seiner Einfachheit. Außerdem zeigt es sich, dass Quicksort auch in der einfachen Form im Durchschnitt nur O(n log(n)) Schritte benötigt. Überdies ist die Konstante, die sich in der O-Notation verbirgt, sehr klein. Wir nehmen dafür in Kauf, dass Quicksort im (seltenen) schlechtesten Fall Θ(n2) Schritte benötigt.

Satz:  Die Zeit­komplexität von Quicksort beträgt

Θ(n log(n))  im Durchschnitt   und
Θ(n2)im schlechtesten Fall.

Zusammenfassung

Quicksort erweist sich in der Praxis als das schnellste Sortier­verfahren. Es hat eine Zeit­komplexität von Θ(n log(n)) im Durchschnitt. Im (seltenen) schlechtesten Fall ist es mit einer Zeit­komplexität von Θ(n2) allerdings genauso langsam wie Insertionsort.

Es gibt Sortier­verfahren, die auch im schlechtesten Fall in O(n log(n)) liegen, z.B. Heapsort und Mergesort. Diese sind jedoch im Durchschnitt um einen konstanten Faktor langsamer als Quicksort.

Es ist möglich, mit einer Variante von Quicksort auch im schlechtesten Fall eine Zeit­komplexität von O(n log(n)) zu erreichen (indem als Vergleichs­element der Median gewählt wird). Dieses Verfahren ist jedoch im Durchschnitt und im schlechtesten Fall um einen konstanten Faktor langsamer als Heapsort oder Mergesort; daher ist es für die Praxis nicht interessant.

Aufgaben

Quicksort ist in der angegebenen Implementierung einfach, elegant und schnell. Gleichwohl sind einige Optimierungen möglich, die bei bestimmten Eingabe­folgen oder sogar im Durchschnitt zu deutlichen Ver­besserungen führen.

Einige Vorschläge zu solchen Optimierungen sind in den Aufgaben zu Sortier­verfahren formuliert. Bearbeiten Sie diese Aufgaben und untersuchen Sie, wie sich diese Optimierungen praktisch auf die Laufzeit von Quicksort auswirken.

Literatur

[CLRS 01]   T.H. Cormen, C.E. Leiserson, R.L. Rivest, C. Stein: Introduction to Algorithms. 2. Auflage, The MIT Press (2001)

[Hoa 62]   C.A.R. Hoare: Quicksort. Computer Journal, Vol. 5, 1, 10-15 (1962)

[Sed 88]   R. Sedgewick: Algorithms. 2. Auflage, Addison-Wesley (1988)

[Lan 12]   H.W. Lang: Algorithmen in Java. 3. Auflage, Oldenbourg (2012)

Quicksort und andere Sortierverfahren, so etwa Heapsort, Mergesort und Shellsort, finden Sie auch in meinem Buch über Algorithmen.
Weitere(vertikaler Abstand) Themen des Buches: Textsuche, Graphenalgorithmen, Arithmetik, Codierung, Kryptografie, parallele Sortieralgorithmen.

Buch

[Weitere Informationen]


1)  Die Berechnung lo+(hi-lo)/2 anstelle der einfacheren Berechnung (lo+hi)/2 wird wegen der Gefahr eines Integer-Überlaufs gewählt, der entsteht, wenn lo+hi größer als 2.147.483.647 wird.

 

Weiter mit:   [Heapsort]   oder   [up]

 


H.W. Lang   mail@hwlang.de   Impressum   Datenschutz
Created: 12.09.1997   Updated: 05.02.2023
Diese Webseiten sind während meiner Lehrtätigkeit an der Hochschule Flensburg entstanden