Implémentation de l’algorithme Quicksort en Java

Implémentation de l'algorithme Quicksort en Java

1. Vue d'ensemble

Dans ce didacticiel, nous allons explorer l’algorithme QuickSort en détail, en nous concentrant sur son implémentation Java.

Nous discuterons également de ses avantages et inconvénients, puis analyserons sa complexité temporelle.

2. Algorithme QuickSort

Quicksort is a sorting algorithm, which is leveraging the divide-and-conquer principle. Il a une complexité moyenne deO(n log n) et c'est l'un des algorithmes de tri les plus utilisés, en particulier pour les volumes de Big Data.

It’s important to remember that Quicksort isn’t a stable algorithm. Un algorithme de tri stable est un algorithme où les éléments avec les mêmes valeurs apparaissent dans le même ordre dans la sortie triée qu'ils apparaissent dans la liste d'entrée.

La liste d'entrées est divisée en deux sous-listes par un élément appelé pivot; une sous-liste avec des éléments inférieurs au pivot et une autre avec des éléments supérieurs au pivot. Ce processus se répète pour chaque sous-liste.

Enfin, toutes les sous-listes triées se fusionnent pour former la sortie finale.

2.1. Étapes de l'algorithme

  1. Nous choisissons un élément de la liste, appelé pivot. Nous allons l'utiliser pour diviser la liste en deux sous-listes.

  2. Nous réorganisons tous les éléments autour du pivot - ceux qui ont une valeur inférieure sont placés devant celui-ci et tous les éléments supérieurs au pivot qui le suit. Après cette étape, le pivot est dans sa position finale. C'est l'étape de partition importante.

  3. Nous appliquons les étapes ci-dessus de manière récursive aux deux sous-listes situées à gauche et à droite du pivot.

Comme nous pouvons le voir,quicksort is naturally a recursive algorithm, like every divide and conquer approach.

Prenons un exemple simple pour mieux comprendre cet algorithme.

Arr[] = {5, 9, 4, 6, 5, 3}
  1. Supposons que nous choisissions 5 comme pivot de la simplicité

  2. Nous allons d'abord placer tous les éléments inférieurs à 5 dans la première position du tableau: \ {3, 4, 5, 6, 5, 9}

  3. Nous allons ensuite le répéter pour le sous-tableau de gauche \ {3,4}, en prenant 3 comme pivot

  4. Il n'y a pas d'éléments inférieurs à 3

  5. Nous appliquons quicksort sur le sous-tableau à droite du pivot, c'est-à-dire {4}

  6. Ce sous-tableau est constitué d'un seul élément trié

  7. Nous continuons avec la partie droite du tableau d'origine, \ {6, 5, 9} jusqu'à ce que nous obtenions le tableau ordonné final

2.2. Choisir le pivot optimal

Le point crucial dans QuickSort est de choisir le meilleur pivot. L'élément central est, bien sûr, le meilleur, car il diviserait la liste en deux sous-listes égales.

Mais trouver l'élément central à partir d'une liste non ordonnée est difficile et prend du temps. C'est pourquoi nous considérons comme pivot le premier élément, le dernier élément, la médiane ou tout autre élément aléatoire.

3. Implémentation en Java

La première méthode estquickSort() qui prend comme paramètres le tableau à trier, le premier et le dernier index. Tout d'abord, nous vérifions les index et continuons uniquement s'il y a encore des éléments à trier.

Nous obtenons l'index du pivot trié et l'utilisons pour appeler récursivement la méthodepartition() avec les mêmes paramètres que la méthodequickSort(), mais avec des indices différents:

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);
    }
}

Continuons avec la méthodepartition(). Pour plus de simplicité, cette fonction prend le dernier élément comme pivot. Ensuite, vérifie chaque élément et l’échange avant le pivot si sa valeur est plus petite.

À la fin du partitionnement, tous les éléments inférieurs au pivot se trouvent à gauche de celui-ci et tous les éléments supérieurs au pivot se trouvant à droite. Le pivot est à sa position finale triée et la fonction renvoie cette position:

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. Analyse d'algorithmes

4.1. La complexité du temps

Dans le meilleur des cas, l'algorithme divisera la liste en deux sous-listes de taille égale. Ainsi, la première itération de la liste complète den nécessiteO(n). Le tri des deux sous-listes restantes avec les élémentsn/2 prend chacun2*O(n/2). En conséquence, l'algorithme QuickSort a la complexité deO(n log n).

Dans le pire des cas, l'algorithme ne sélectionnera qu'un seul élément à chaque itération, doncO(n) + O(n-1) + … + O(1), qui est égal àO(n2).

En moyenne, QuickSort a une complexité deO(n log n), ce qui le rend adapté aux gros volumes de données.

4.2. QuickSort vs MergeSort

Voyons dans quels cas nous devrions choisir QuickSort plutôt que MergeSort.

Bien que Quicksort et Mergesort aient une complexité temporelle moyenne deO(n log n), Quicksort est l'algorithme préféré, car il a une complexité d'espace deO(log(n)). Mergesort, en revanche, nécessite un stockage supplémentaire deO(n), ce qui le rend assez coûteux pour les baies.

Quicksort nécessite d'accéder à différents index pour ses opérations, mais cet accès n'est pas directement possible dans les listes chaînées, car il n'y a pas de blocs continus; par conséquent, pour accéder à un élément, nous devons parcourir chaque nœud depuis le début de la liste liée. De plus, Mergesort est implémenté sans espace supplémentaire pourLinkedLists.

Dans ce cas, les frais généraux supplémentaires pour Quicksort et Mergesort sont généralement préférés.

5. Conclusion

Quicksort est un algorithme de tri élégant très utile dans la plupart des cas.

Il s’agit généralement d’un algorithme «en place», avec une complexité temporelle moyenne deO(n log n).

Un autre point intéressant à mentionner est que la méthodeArrays.sort() de Java utilise Quicksort pour trier les tableaux de primitives. La mise en œuvre utilise deux pivots et fonctionne bien mieux que notre solution simple, c'est pourquoi pour le code de production, il est généralement préférable d'utiliser des méthodes de bibliothèque.

Comme toujours, le code pour l'implémentation de cet algorithme se trouve suron our GitHub repository.