web-dev-qa-db-fra.com

Quicksort vs heapsort

Le tri rapide et le tri sélectif effectuent tous les deux un tri sur place. Ce qui est mieux? Quelles sont les applications et les cas dans lesquels l'une ou l'autre est préférée?

75
avd

Cet article a une analyse.

Aussi, à partir de Wikipedia:

Le concurrent le plus direct du quicksort est le heapsort. Heapsort est généralement un peu plus lent que quicksort, mais le temps d'exécution le plus défavorable est toujours Θ (nlogn). Quicksort est généralement plus rapide, bien qu'il reste le risque de performances dans le pire des cas, sauf dans la variante introsort, qui passe à heapsort lorsqu'un mauvais cas est détecté. S'il est connu à l'avance que le heapsort va être nécessaire, son utilisation directe sera plus rapide que l'attente d'un introsort pour y basculer.

47
DVK

Heapsort est garanti O (N log N), ce qui est bien mieux que le pire des cas dans Quicksort. Heapsort n'a pas besoin de plus de mémoire pour qu'un autre tableau puisse mettre les données ordonnées comme le requiert Mergesort. Alors pourquoi les applications commerciales restent-elles avec Quicksort? Quel Quicksort a qui est si spécial par rapport aux autres implémentations?

J'ai moi-même testé les algorithmes et j'ai vu que Quicksort avait quelque chose de spécial. Il s'exécute rapidement, beaucoup plus rapidement que les algorithmes de tas et de fusion.

Le secret de Quicksort est le suivant: il ne fait presque pas d'échanges d'éléments inutiles. L'échange prend du temps.

Avec Heapsort, même si toutes vos données sont déjà ordonnées, vous allez échanger 100% des éléments pour ordonner le tableau.

Avec Mergesort, c'est encore pire. Vous allez écrire 100% des éléments dans un autre tableau et le réécrire dans celui d'origine, même si les données sont déjà ordonnées.

Avec Quicksort, vous n'échangez pas ce qui est déjà commandé. Si vos données sont complètement ordonnées, vous n'échangez presque rien! Bien qu'il y ait beaucoup de discussions sur le pire des cas, une petite amélioration du choix du pivot, autre que l'obtention du premier ou du dernier élément du tableau, peut l'éviter. Si vous obtenez un pivot de l'élément intermédiaire entre le premier, le dernier et le milieu, il suffit d'éviter le pire des cas.

Ce qui est supérieur dans Quicksort n'est pas le pire des cas, mais le meilleur des cas! Dans le meilleur des cas, vous faites le même nombre de comparaisons, d'accord, mais vous n'échangez presque rien. Dans le cas moyen, vous échangez une partie des éléments, mais pas tous les éléments, comme dans Heapsort et Mergesort. C'est ce qui donne à Quicksort le meilleur temps. Moins de swap, plus de vitesse.

L'implémentation ci-dessous en C # sur mon ordinateur, fonctionnant en mode release, bat Array.Sort de 3 secondes avec pivot central et de 2 secondes avec pivot amélioré (oui, il y a une surcharge pour obtenir un bon pivot).

static void Main(string[] args)
{
    int[] arrToSort = new int[100000000];
    var r = new Random();
    for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);

    Console.WriteLine("Press q to quick sort, s to Array.Sort");
    while (true)
    {
        var k = Console.ReadKey(true);
        if (k.KeyChar == 'q')
        {
            // quick sort
            Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            QuickSort(arrToSort, 0, arrToSort.Length - 1);
            Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
        }
        else if (k.KeyChar == 's')
        {
            Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            Array.Sort(arrToSort);
            Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
        }
    }
}

static public void QuickSort(int[] arr, int left, int right)
{
    int begin = left
        , end = right
        , pivot
        // get middle element pivot
        //= arr[(left + right) / 2]
        ;

    //improved pivot
    int middle = (left + right) / 2;
    int
        LM = arr[left].CompareTo(arr[middle])
        , MR = arr[middle].CompareTo(arr[right])
        , LR = arr[left].CompareTo(arr[right])
        ;
    if (-1 * LM == LR)
        pivot = arr[left];
    else
        if (MR == -1 * LR)
            pivot = arr[right];
        else
            pivot = arr[middle];
    do
    {
        while (arr[left] < pivot) left++;
        while (arr[right] > pivot) right--;

        if(left <= right)
        {
            int temp = arr[right];
            arr[right] = arr[left];
            arr[left] = temp;

            left++;
            right--;
        }
    } while (left <= right);

    if (left < end) QuickSort(arr, left, end);
    if (begin < right) QuickSort(arr, begin, right);
}
87
Marquinho Peli

Pour la plupart des situations, avoir un rapport rapide ou un peu plus rapide n'est pas pertinent ... vous ne voulez tout simplement jamais que cela ralentisse parfois. Bien que vous puissiez Tweak QuickSort pour éviter la lenteur des situations, vous perdez l'élégance du QuickSort de base. Donc, pour la plupart des choses, je préfère en fait HeapSort ... vous pouvez l'implémenter dans toute son élégance simple, et ne jamais obtenir un tri lent.

Dans les situations où vous voulez VRAIMENT la vitesse maximale dans la plupart des cas, QuickSort peut être préféré à HeapSort, mais aucun ne peut être la bonne réponse. Pour les situations où la vitesse est critique, il convient d'examiner de près les détails de la situation. Par exemple, dans certains de mes codes critiques pour la vitesse, il est très courant que les données soient déjà triées ou presque triées (elles indexent plusieurs champs liés qui se déplacent souvent de haut en bas ensemble OR = se déplacer vers le haut et vers le bas l'un en face de l'autre, donc une fois que vous triez par un, les autres sont triés ou triés en sens inverse ou fermés ... l'un ou l'autre peut tuer QuickSort. Pour ce cas, je n'ai implémenté ni ... à la place, J'ai implémenté SmoothSort de Dijkstra ... une variante de HeapSort qui est O(N) lorsqu'il est déjà trié ou presque trié ... ce n'est pas si élégant, pas trop facile à comprendre, mais rapide. .. lire http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF si vous voulez quelque chose d'un peu plus difficile à coder.

14
Brian Kennedy

Les hybrides en place Quicksort-Heapsort sont également très intéressants, car la plupart d'entre eux n'ont besoin que de n * log n comparaisons dans le pire des cas (ils sont optimaux par rapport au premier terme de l'asymptotique, donc ils évitent les pires scénarios) de Quicksort), O (log n) extra-espace et ils préservent au moins "la moitié" du bon comportement de Quicksort par rapport à un ensemble de données déjà ordonné. Un algorithme extrêmement intéressant est présenté par Dikert et Weiss dans http://arxiv.org/pdf/1209.4214v1.pdf :

  • Sélectionnez un pivot p comme médiane d'un échantillon aléatoire d'éléments sqrt (n) (cela peut être fait dans au plus 24 comparaisons sqrt (n) via l'algorithme de Tarjan & co, ou 5 comparaisons sqrt (n) via l'araignée beaucoup plus compliquée -algorithme d'usine de Schonhage);
  • Partitionnez votre baie en deux parties comme dans la première étape de Quicksort;
  • Heapify la plus petite partie et utilisez O (log n) bits supplémentaires pour coder un tas dans lequel chaque enfant gauche a une valeur supérieure à son frère;
  • Extraire récursivement la racine du tas, tamiser le lacune laissé par la racine jusqu'à ce qu'il atteigne une feuille du tas, puis remplir le lacune avec un élément approprié prélevé de l'autre partie du tableau;
  • Répétez sur la partie non ordonnée restante du tableau (si p est choisi comme médiane exacte, il n'y a aucune récurrence).
5
Jack D'Aurizio

Eh bien, si vous passez au niveau de l'architecture ... nous utilisons la structure de données de la file d'attente dans la mémoire cache.Ainsi, tout ce qui est disponible dans la file d'attente sera trié.Comme dans le tri rapide, nous n'avons aucun problème à diviser le tableau en n'importe quelle longueur ... mais en tas trier (en utilisant un tableau), il peut arriver que le parent ne soit pas présent dans le sous-tableau disponible dans le cache, puis il doit le mettre dans la mémoire cache ... ce qui prend du temps. C'est quicksort est le meilleur !! ????

2
Manav Jain

Comp. entre quick sort et merge sort puisque les deux sont du type de tri sur place, il y a une différence entre le temps d'exécution du casier Wrost et le temps d'exécution du casier Wrost pour le tri rapide est O(n^2) et pour le tri en tas est toujours O(n*log(n)) et pour une quantité moyenne de données un tri rapide sera plus utile. Puisqu'il s'agit d'un algorithme randomisé donc la probabilité d'obtenir des ans corrects. en moins de temps dépendra de la position de l'élément pivot que vous choisissez.

Donc un

Bon appel: les tailles de L et G sont chacune inférieures à 3s/4

Mauvais appel: l'un des L et G a une taille supérieure à 3s/4

pour une petite quantité, nous pouvons opter pour le tri par insertion et pour une très grande quantité de données, opter pour le tri en tas.

2
vicky garg

Heap Sort est une valeur sûre lorsqu'il s'agit de très grandes entrées. L'analyse asymptotique révèle que l'ordre de croissance de Heapsort dans le pire des cas est Big-O(n logn), ce qui est mieux que Big-O(n^2) de Quicksort dans le pire des cas. Cependant, Heapsort est un peu plus lent en pratique sur la plupart des machines qu'un tri rapide bien implémenté. Heapsort n'est pas non plus un algorithme de tri stable.

La raison pour laquelle heapsort est plus lent dans la pratique que quicksort est due à la meilleure localité de référence (" https://en.wikipedia.org/wiki/Locality_of_reference ") dans quicksort, où les éléments de données sont relativement fermer les emplacements de stockage. Les systèmes qui présentent une forte localité de référence sont d'excellents candidats pour l'optimisation des performances. Le tri par tas, cependant, traite des sauts plus importants. Cela rend le tri rapide plus favorable pour les entrées plus petites.

1
Benn

Heapsort construit un tas puis extrait à plusieurs reprises l'élément maximum. Son pire cas est O (n log n).

Mais si vous voyiez le pire des cas de tri rapide , qui est O (n2), vous vous rendriez compte que le tri rapide ne serait pas un très bon choix pour les données volumineuses.

Le tri est donc une chose intéressante; Je crois que la raison pour laquelle tant d'algorithmes de tri vivent aujourd'hui est parce qu'ils sont tous "meilleurs" à leurs meilleurs endroits. Par exemple, le tri à bulles peut effectuer un tri rapide si les données sont triées. Ou si nous savons quelque chose sur les articles à trier, nous pouvons probablement faire mieux.

Cela peut ne pas répondre directement à votre question, pensais que j'ajouterais mes deux cents.

1
KMån

Heapsort a l'avantage d'avoir le pire cas de fonctionnement O (n * log (n)) donc dans les cas où le tri rapide est susceptible de mal fonctionner (généralement des ensembles de données triés en général), le heapsort est de loin préférable.

1
zellio

Pour moi, il y a une différence très fondamentale entre heapsort et quicksort: ce dernier utilise une récursivité. Dans les algorithmes récursifs, le tas croît avec le nombre de récursions. Cela n'a pas d'importance si n est petit, mais en ce moment je trie deux matrices avec n = 10 ^ 9 !!. Le programme prend près de 10 Go de RAM et toute mémoire supplémentaire fera que mon ordinateur commencera à basculer vers la mémoire du disque virtuel. Mon disque est un disque RAM, mais y échanger toujours fait énorme différence de vitesse. Donc, dans un statpack codé en C++ qui comprend des matrices de dimensions ajustables, avec la taille inconnu à l'avance du programmeur, et type de tri statistique non paramétrique je préfère le heapsort pour éviter les retards d'utilisation avec de très grandes matrices de données.

1
csevcik