Implementierung des Quicksort-Algorithmus in Java

Implementierung des Quicksort-Algorithmus in Java

1. Überblick

In diesem Tutorial werden wir den QuickSort-Algorithmus im Detail untersuchen und uns auf seine Java-Implementierung konzentrieren.

Wir werden auch die Vor- und Nachteile diskutieren und dann die zeitliche Komplexität analysieren.

2. QuickSort-Algorithmus

Quicksort is a sorting algorithm, which is leveraging the divide-and-conquer principle.  Es hat eine durchschnittliche Komplexität vonO(n log n)und ist einer der am häufigsten verwendeten Sortieralgorithmen, insbesondere für große Datenmengen.

It’s important to remember that Quicksort isn’t a stable algorithm.  Ein stabiler Sortieralgorithmus ist ein Algorithmus, bei dem die Elemente mit denselben Werten in der sortierten Ausgabe in derselben Reihenfolge erscheinen wie in der Eingabeliste.

Die Eingabeliste wird durch ein Element namens Pivot in zwei Unterlisten unterteilt. eine Unterliste mit Elementen, die kleiner als der Pivot sind, und eine andere mit Elementen, die größer als der Pivot sind. Dieser Vorgang wird für jede Unterliste wiederholt.

Schließlich werden alle sortierten Unterlisten zusammengeführt, um die endgültige Ausgabe zu bilden.

2.1. Algorithmusschritte

  1. Wir wählen ein Element aus der Liste aus, das als Pivot bezeichnet wird. Wir werden es verwenden, um die Liste in zwei Unterlisten zu unterteilen.

  2. Wir ordnen alle Elemente um den Pivot herum neu an - die Elemente mit dem kleineren Wert werden davor und alle Elemente, die größer als der Pivot sind, danach. Nach diesem Schritt befindet sich der Zapfen in seiner Endposition. Dies ist der wichtige Partitionsschritt.

  3. Wir wenden die obigen Schritte rekursiv auf beide Unterlisten links und rechts vom Drehpunkt an.

Wie wir sehen können,quicksort is naturally a recursive algorithm, like every divide and conquer approach.

Nehmen wir ein einfaches Beispiel, um diesen Algorithmus besser zu verstehen.

Arr[] = {5, 9, 4, 6, 5, 3}
  1. Nehmen wir an, wir wählen der Einfachheit halber 5 als Drehpunkt

  2. Wir setzen zuerst alle Elemente unter 5 an die erste Position des Arrays: \ {3, 4, 5, 6, 5, 9}

  3. Wir wiederholen es dann für das linke Unterarray \ {3,4} und nehmen 3 als Drehpunkt

  4. Es gibt keine Elemente weniger als 3

  5. Wir wenden Quicksort auf das Sub-Array rechts vom Pivot an, d. H. {4}

  6. Dieses Subarray besteht nur aus einem sortierten Element

  7. Wir fahren mit dem rechten Teil des ursprünglichen Arrays \ {6, 5, 9} fort, bis wir das endgültig geordnete Array erhalten

2.2. Auswahl des optimalen Pivots

Der entscheidende Punkt in QuickSort ist die Auswahl des besten Pivots. Das mittlere Element ist natürlich das beste, da es die Liste in zwei gleiche Unterlisten aufteilt.

Das mittlere Element aus einer ungeordneten Liste zu finden, ist jedoch schwierig und zeitaufwendig. Aus diesem Grund wird das erste Element, das letzte Element, der Median oder ein beliebiges anderes zufälliges Element als Dreh- und Angelpunkt herangezogen.

3. Implementierung in Java

Die erste Methode istquickSort(), bei der das zu sortierende Array, der erste und der letzte Index als Parameter verwendet werden. Zunächst überprüfen wir die Indizes und fahren erst dann fort, wenn noch Elemente zu sortieren sind.

Wir erhalten den Index des sortierten Pivots und rufen damit rekursiv die Methodepartition()mit denselben Parametern wie die MethodequickSort()auf, jedoch mit unterschiedlichen Indizes:

public void quickSort(int arr[], int begin, int end) {
    if (begin < end) {
        int partitionIndex = partition(arr, begin, end);

        quickSort(arr, begin, partitionIndex-1);
        quickSort(arr, partitionIndex+1, end);
    }
}

Fahren wir mit der Methodepartition()fort. Der Einfachheit halber nimmt diese Funktion das letzte Element als Drehpunkt. Überprüft dann jedes Element und tauscht es vor dem Pivot aus, wenn sein Wert kleiner ist.

Am Ende der Unterteilung befinden sich alle Elemente, die kleiner als der Drehpunkt sind, links und alle Elemente, die größer als der Drehpunkt sind, rechts davon. Der Drehpunkt befindet sich an seiner endgültigen sortierten Position und die Funktion gibt diese Position zurück:

private int partition(int arr[], int begin, int end) {
    int pivot = arr[end];
    int i = (begin-1);

    for (int j = begin; j < end; j++) {
        if (arr[j] <= pivot) {
            i++;

            int swapTemp = arr[i];
            arr[i] = arr[j];
            arr[j] = swapTemp;
        }
    }

    int swapTemp = arr[i+1];
    arr[i+1] = arr[end];
    arr[end] = swapTemp;

    return i+1;
}

4. Algorithmusanalyse

4.1. Zeitliche Komplexität

Im besten Fall teilt der Algorithmus die Liste in zwei gleich große Unterlisten auf. Die erste Iteration der vollständigen Liste mitn-Größe benötigt alsoO(n). Das Sortieren der verbleibenden zwei Unterlisten mitn/2 Elementen dauert jeweils2*O(n/2). Infolgedessen hat der QuickSort-Algorithmus die Komplexität vonO(n log n).

Im schlimmsten Fall wählt der Algorithmus in jeder Iteration nur ein Element aus, alsoO(n) + O(n-1) + … + O(1), wasO(n2) entspricht.

Im Durchschnitt hat QuickSort eine Komplexität vonO(n log n)und ist daher für große Datenmengen geeignet.

4.2. QuickSort vs MergeSort

Lassen Sie uns diskutieren, in welchen Fällen wir QuickSort anstelle von MergeSort wählen sollten.

Obwohl sowohl Quicksort als auch Mergesort eine durchschnittliche Zeitkomplexität vonO(n log n) aufweisen, ist Quicksort der bevorzugte Algorithmus, da er eine Raumkomplexität vonO(log(n))aufweist. Mergesort benötigt andererseitsO(n)zusätzlichen Speicher, was es für Arrays ziemlich teuer macht.

Quicksort muss für seine Operationen auf verschiedene Indizes zugreifen, dieser Zugriff ist jedoch in verknüpften Listen nicht direkt möglich, da es keine fortlaufenden Blöcke gibt. Um auf ein Element zuzugreifen, müssen wir daher jeden Knoten ab dem Anfang der verknüpften Liste durchlaufen. Außerdem wird Mergesort ohne zusätzlichen Speicherplatz fürLinkedLists. implementiert

In diesem Fall wird der Overhead für Quicksort und Mergesort im Allgemeinen bevorzugt.

5. Fazit

Quicksort ist ein eleganter Sortieralgorithmus, der in den meisten Fällen sehr nützlich ist.

Es handelt sich im Allgemeinen um einen "In-Place" -Algorithmus mit einer durchschnittlichen Zeitkomplexität vonO(n log n).

Ein weiterer interessanter Punkt ist, dass dieArrays.sort()-Methode von Java Quicksort zum Sortieren von Arrays von Grundelementen verwendet. Die Implementierung verwendet zwei Pivots und bietet eine viel bessere Leistung als unsere einfache Lösung. Daher ist es für Produktionscode normalerweise besser, Bibliotheksmethoden zu verwenden.

Wie immer liegt der Code für die Implementierung dieses Algorithmus überon our GitHub repository.