Quicksort vs heapsort

Le sorting rapide et le sorting rapide font tous les deux un sorting sur place. Ce qui est mieux? Quelles sont les applications et les cas dans lesquels l’un ou l’autre est préféré?

http://www.cs.auckland.ac.nz/~jmor159/PLDS210/qsort3.html a des parsings.

Aussi, de Wikipedia:

Le concurrent le plus direct du sorting est rapide. Heapsort est généralement un peu plus lent que le sorting rapide, mais le temps d’exécution le plus défavorable est toujours Θ (nlogn). Quicksort est généralement plus rapide, bien qu’il rest une possibilité de performance dans le pire des cas, sauf dans la variante introsort, qui bascule en cas de panne. Si l’on sait à l’avance que l’horticulture sera nécessaire, son utilisation directe sera plus rapide que l’attente de l’introspection.

Heapsort est garanti O (N log N), ce qui est beaucoup mieux que le pire des cas dans Quicksort. Heapsort n’a pas besoin de plus de mémoire pour qu’une autre baie place les données ordonnées comme l’exige Mergesort. Alors, pourquoi les applications commerciales sont-elles compatibles avec Quicksort? Qu’est-ce que Quicksort a de si spécial par rapport aux autres implémentations?

J’ai testé les algorithmes moi-même et j’ai vu que Quicksort avait quelque chose de spécial. Il fonctionne rapidement, beaucoup plus rapidement que les algorithmes Heap et Merge.

Le secret de Quicksort est: il ne fait presque pas de swaps d’éléments inutiles. Swap prend du temps.

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

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

Avec Quicksort, vous n’échangez pas ce qui est déjà commandé. Si vos données sont complètement commandées, vous échangez presque rien! Bien qu’il y ait beaucoup de problèmes dans le pire des cas, une petite amélioration sur le 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 les éléments premier, dernier et moyen, il est suffisant d’éviter le pire des cas.

Ce qui est supérieur à Quicksort n’est pas le pire, mais le meilleur des cas! Dans le meilleur des cas, vous faites le même nombre de comparaisons, ok, mais vous échangez presque rien. En moyenne, 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 moment. Moins d’échange, plus de vitesse.

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

static void Main(ssortingng[] 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); } 

Pour la plupart des situations, avoir de la rapidité ou un peu de rapidité n’est pas pertinent … vous ne voulez tout simplement pas que cela devienne parfois lent. Bien que vous puissiez modifier QuickSort pour éviter les situations lentes, 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 avec toute son élégance, et ne jamais obtenir un sorting lent.

Pour les situations où vous souhaitez obtenir la vitesse maximale dans la plupart des cas, QuickSort peut être préféré à HeapSort, mais aucune ne peut être la bonne solution. Pour les situations critiques pour la vitesse, il convient d’examiner de près les détails de la situation. Par exemple, dans certains de mes codes de vitesse critique, il est très courant que les données soient déjà sortingées ou presque sortingées (il indexe plusieurs champs connexes qui se déplacent souvent de haut en bas ou se déplacent en opposition l’un par rapport à l’autre). Ainsi, une fois que vous sortingez par un, les autres sont soit sortingés, soit sortingés en ordre inverse ou fermés … l’un ou l’autre peut tuer QuickSort). Dans ce cas, je n’ai pas implémenté … à la place, j’ai implémenté SmoothSort de Dijkstra … une variante de HeapSort qui est O (N) quand elle est déjà sortingée ou presque sortingée … ce n’est pas si élégant, pas trop facile à comprendre, mais vite … lisez http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF si vous voulez quelque chose d’un peu plus difficile à coder.

Les hybrides sur place Quicksort-Heapsort sont également intéressants, car la plupart d’entre eux n’ont besoin que de comparaisons n * log n dans le pire des cas (ils sont optimaux par rapport au premier terme des asymptotiques, ils évitent donc les scénarios les plus défavorables) de Quicksort), O (log n) extra-space et ils préservent au moins «la moitié» du bon comportement de Quicksort par rapport à un dataset 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 des comparaisons d’au plus 24 sqtt (n) à travers l’algorithme de Tarjan & co, ou 5 sqrt (n) dans l’araignée beaucoup plus compliquée. algorithme de production de Schonhage);
  • Partitionnez votre tableau 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 encoder un tas dans lequel chaque enfant gauche a une valeur supérieure à celle de son frère;
  • Extraire récursivement la racine du tas, tamiser la lacune laissée par la racine jusqu’à ce qu’elle atteigne une feuille du tas, puis remplir le lacune avec un élément approprié pris de l’autre partie du tableau;
  • Répétez sur la partie restante non ordonnée du tableau (si p est choisi comme la médiane exacte, il n’y a pas de récursivité du tout).

Comp. entre quick sort et le quick sort merge sort puisque les deux sont de type sorting en place, il y a une différence entre O(n^2) et O(n*log(n)) et pour une quantité moyenne de données, un sorting rapide sera plus utile. Comme il s’agit d’un algorithme randomisé, la probabilité d’obtenir des années correctes. 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érieure à 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 un sorting par insertion et pour une très grande quantité de données aller pour un sorting de tas.

Eh bien, si vous allez au niveau de l’architecture … nous utilisons la structure de données de la queue dans la mémoire cache. Ainsi, tout ce qui est disponible dans la queue sera sortingé. sort (en utilisant array) il peut arriver que le parent ne soit pas présent dans le sous-tableau disponible dans le cache et ensuite il doit le mettre en cache … ce qui prend beaucoup de temps. C’est le sorting rapide, c’est mieux !!

Heapsort construit un tas, puis extrait à plusieurs resockets l’élément maximal. Son pire cas est O (n log n).

Mais si vous renconsortingez le pire cas de sorting rapide , qui est O (n2), vous réaliseriez que le sorting rapide serait un choix moins bon pour les données volumineuses.

Cela fait donc du sorting une chose intéressante. Je crois que la raison pour laquelle tant d’algorithmes de sorting sont utilisés aujourd’hui est qu’ils sont tous «meilleurs» dans leurs meilleurs endroits. Par exemple, le sorting par bulles peut effectuer un sorting rapide si les données sont sortingées. Ou si nous soaps quelque chose sur les articles à sortinger, alors nous pouvons probablement faire mieux.

Cela pourrait ne pas répondre directement à votre question, pensais que j’appendais mes deux cents.

Heapsort a l’avantage d’avoir le pire cas d’exécution de O (n * log (n)), donc dans les cas où le taux de réponse rapide risque d’être médiocre (la plupart des ensembles de données sortingés en général)

Heap Sort est une valeur sûre pour traiter de très gros intrants. L’parsing asymptotique révèle l’ordre de croissance de Heapsort dans le pire des cas, Big-O(n logn) , ce qui est mieux que le Big-O(n^2) de Quicksort Big-O(n^2) . Cependant, Heapsort est un peu plus lent en pratique sur la plupart des machines qu’un sorting rapide bien implémenté. Heapsort n’est pas non plus un algorithme de sorting stable.

La raison pour laquelle le taux de réponse est plus lent dans la pratique que le taux de réponse rapide est dû à la meilleure localité de référence (” https://en.wikipedia.org/wiki/Locality_of_reference “) dans quicksort, où les éléments de données se trouvent dans des emplacements de stockage relativement proches. Les systèmes présentant une forte localisation de référence sont d’excellents candidats pour l’optimisation des performances. Le sorting de tas, cependant, traite de plus grands sauts. Cela rend les mesures rapides plus favorables pour les intrants plus petits.

Pour moi, il y a une différence très fondamentale entre hortic et quicksort: ce dernier utilise une récursivité. Dans les algorithmes récursifs, le tas augmente avec le nombre de récurrences. Cela n’a pas d’importance si n est petit, mais pour l’instant je sortinge deux masortingces avec n = 10 ^ 9 !!. Le programme prend près de 10 Go de RAM et toute mémoire supplémentaire fera en sorte que mon ordinateur commence à permuter vers la mémoire de disque virtuel. Mon disque est un disque RAM, mais le fait de le changer fait une énorme différence de vitesse . Donc, dans un pack de statistiques codé en C ++ qui inclut des masortingces de dimension ajustables, dont la taille est inconnue au programmeur, et un type de sorting statistique non paramésortingque, je préfère le compromis pour éviter les retards dans les utilisations avec de très grandes masortingces de données.

Pour répondre à la question initiale et répondre à certains des autres commentaires ici:

Je viens de comparer les implémentations de sélection, quick, merge et heap sort pour voir comment elles se superposent. La réponse est qu’ils ont tous leurs inconvénients.

TL; DR: Rapide est le meilleur type d’usage général (raisonnablement rapide, stable et surtout en place) Personnellement, je préfère le sorting par tas sauf si j’ai besoin d’un sorting stable.

Sélection – N ^ 2 – Ce n’est vraiment bon que pour moins de 20 éléments, alors il est surpassé. À moins que vos données ne soient déjà sortingées ou très, presque. N ^ 2 devient vraiment lent très vite.

Rapide, d’après mon expérience, n’est pas si rapide tout le temps. Les bonus pour l’utilisation du sorting rapide sont généralement assez rapides et stables. C’est aussi un algorithme sur place, mais comme il est généralement implémenté de manière récursive, il va prendre plus d’espace de stack. Il se situe également quelque part entre O (n log n) et O (n ^ 2). Le chronométrage sur certains types semble le confirmer, en particulier lorsque les valeurs se situent dans une fourchette étroite. C’est beaucoup plus rapide que le sorting par sélection sur 10 000 000 d’éléments, mais plus lent que la fusion ou le tas.

Le sorting par fusion est garanti O (n log n) car son sorting ne dépend pas des données. Il fait juste ce qu’il fait, quelles que soient les valeurs que vous lui avez données. Il est également stable, mais de très grandes quantités peuvent faire exploser votre stack si vous ne faites pas attention à l’implémentation. Il existe des implémentations complexes de sorting de fusion sur place, mais généralement, vous avez besoin d’un autre tableau dans chaque niveau pour fusionner vos valeurs. Si ces baies vivent sur la stack, vous pouvez rencontrer des problèmes.

Le sorting du tas est max O (n log n), mais dans de nombreux cas, il est plus rapide, en fonction de la distance à laquelle vous devez déplacer vos valeurs dans le segment de mémoire. Le tas peut facilement être implémenté sur place dans le tableau d’origine, il n’a donc pas besoin de mémoire supplémentaire, et il est itératif. L’ énorme inconvénient du sorting sur le tas est que ce n’est pas un sorting stable, ce qui signifie que c’est parfait si vous en avez besoin.