Pourquoi le tri rapide est-il meilleur que les autres algorithmes de tri dans la pratique?

31

Ceci est une rediffusion d'une question sur cs.SE par Janoma . Crédits complets et butin pour lui ou cs.SE.

Dans un cours d'algorithmes standard, nous apprenons que le tri rapide est O (n log n) en moyenne et O (n²) dans le pire des cas. Dans le même temps, d'autres algorithmes de tri sont étudiés qui sont O (n log n) dans le pire des cas (comme mergesort et heapsort ), et même le temps linéaire dans le meilleur des cas (comme bubbleort ) mais avec des besoins de mémoire supplémentaires.

Après un coup d'œil rapide sur certains temps de fonctionnement supplémentaires, il est naturel de dire que le tri rapide ne devrait pas être aussi efficace que d'autres.

De plus, considérez que les étudiants apprennent dans les cours de programmation de base que la récursivité n'est pas vraiment bonne en général car elle pourrait utiliser trop de mémoire, etc. vraiment bon car c'est un algorithme récursif.

Pourquoi, alors, le tri rapide surpasse-t-il les autres algorithmes de tri dans la pratique? Cela a-t-il à voir avec la structure des données du monde réel ? Cela a-t-il à voir avec le fonctionnement de la mémoire dans les ordinateurs? Je sais que certains souvenirs sont beaucoup plus rapides que d'autres, mais je ne sais pas si c'est la vraie raison de cette performance contre-intuitive (par rapport aux estimations théoriques).

Raphaël
la source
3
La réputation de tri rapide date d'une époque où le cache n'existait pas.
Programmeur
9
"Pourquoi quicksort surpasse-t-il les autres algorithmes de tri dans la pratique?" Bien sûr que c'est vrai? Montrez-nous la véritable implémentation à laquelle vous faites référence avec cette déclaration, et la communauté vous expliquera pourquoi cette implémentation spécifique se comporte comme elle le fait. Tout le reste mènera à des suppositions folles sur les programmes inexistants.
Doc Brown
1
@DocBrown: De nombreuses implémentations Quicksort (ou des variantes de celui-ci) sont choisies dans de nombreuses bibliothèques, sans doute parce qu'elles fonctionnent mieux (j'espère bien, c'est le cas). Il peut donc y avoir quelque chose dans l' algorithme qui rend Quicksort rapide, indépendamment de l' implémentation .
Raphael
1
Quelqu'un doit le dire pour être complet, alors je vais le faire: Quicksort n'est pas (généralement) stable. Pour cette raison, vous ne voudrez peut-être pas l'utiliser. De plus, pour cette raison, votre tri par défaut peut ne pas être un tri rapide même lorsque c'est ce que vous souhaitez.
RalphChapin
1
@Raphael: Souvent, ce qu'on appelle le tri rapide est en fait une variante comme le tri d'introduction (utilisé, afaik, dans la bibliothèque standard C ++), pas le tri rapide pur.
Giorgio

Réponses:

21

Je ne suis pas d'accord pour dire que le tri rapide est meilleur que les autres algorithmes de tri dans la pratique.

Dans la plupart des cas, Timsort - l'hybride entre le tri par fusion / insertion qui exploite le fait que les données que vous triez commencent souvent par un tri ou un tri inversé.

Le tri rapide le plus simple (pas de pivot aléatoire) traite ce cas potentiellement commun comme O (N ^ 2) (réduit à O (N lg N) avec des pivots aléatoires), tandis que TimSort peut gérer ces cas dans O (N).

Selon ces repères en C # comparant le tri rapide intégré à TimSort, Timsort est nettement plus rapide dans les cas principalement triés, et légèrement plus rapide dans le cas de données aléatoires et TimSort s'améliore si la fonction de comparaison est particulièrement lente. Je n'ai pas répété ces benchmarks et je ne serais pas surpris si quicksort battait légèrement TimSort pour une combinaison de données aléatoires ou s'il y avait quelque chose de bizarre dans le tri intégré de C # (basé sur quicksort) qui le ralentissait. Cependant, TimSort présente des avantages distincts lorsque les données peuvent être partiellement triées et est à peu près égal à quicksort en termes de vitesse lorsque les données ne sont pas partiellement triées.

TimSort a également l'avantage supplémentaire d'être un type stable, contrairement au quicksort. Le seul inconvénient de TimSort utilise la mémoire O (N) par rapport à la mémoire O (lg N) dans l'implémentation (rapide) habituelle.

dr jimbob
la source
18

Le tri rapide est considéré comme plus rapide car le coefficient est plus petit que tout autre algorithme connu. Il n'y a aucune raison ni preuve à cela, juste aucun algorithme avec un coefficient plus petit n'a été trouvé. Il est vrai que d'autres algorithmes ont également un temps O ( n log n ), mais dans le monde réel, le coefficient est également important.

Notez que pour les petites insertions de données, le tri (celui qui est considéré comme O ( n 2 )) est plus rapide en raison de la nature des fonctions mathématiques. Cela dépend des coefficients spécifiques qui varient d'une machine à l'autre. (À la fin, seul l'assemblage fonctionne vraiment.) Donc, parfois, un hybride de tri rapide et de tri par insertion est le plus rapide en pratique, je pense.

Ramzi Kahil
la source
7
+ Droite. Les enseignants doivent être plus conscients (et j'étais enseignant) du fait que les facteurs constants peuvent varier par ordre de grandeur. Ainsi, la compétence de réglage des performances est vraiment importante, indépendamment du big-O. Le problème est qu'ils continuent d'enseigner le gprof uniquement parce qu'ils doivent dépasser ce point dans le programme, ce qui est à 180 degrés la mauvaise approche.
Mike Dunlavey
2
«Il n'y a aucune raison ni aucun avantage à cela»: bien sûr. Si vous creusez assez profondément, vous trouverez une raison.
Gilles 'SO- arrête d'être méchant'
2
@B Seven: pour simplifier beaucoup… pour un algorithme de tri O (n log n), il y a (n log n) itérations de la boucle de tri afin de trier n éléments. Le coefficient est la durée de chaque cycle de la boucle. Lorsque n est vraiment grand (au moins des milliers), le coefficient n'a pas autant d'importance que O () même si le coefficient est énorme. Mais lorsque n est petit, le coefficient est important - et peut être la chose la plus importante si vous ne triez que 10 éléments.
Matt Gallagher
4
@MikeDunlavey - un bon exemple est que la construction des pyramides est O (n) tandis que le tri de vos photos est O (n ln n) mais ce qui est plus rapide!
Martin Beckett
2
Il existe des algorithmes O (n log n) garantis tels que heapsort et mergesort, donc dans le pire des cas asymptotiques, Quicksort n'est même pas aussi rapide que le meilleur. Mais dans les performances réelles, certaines variantes de tri rapide fonctionnent très bien. Cependant, dire "le coefficient est plus petit" revient à dire "c'est plus rapide parce que c'est plus rapide". Pourquoi les facteurs constants sont-ils si petits? Une raison principale est que le tri rapide est très bon en termes de localité - il utilise très bien les caches. Mergesort a aussi une bonne localité, mais c'est très difficile à faire sur place.
Steve314
16

Quicksort ne surpasse pas tous les autres algorithmes de tri. Par exemple, le tri ascendant de tas ( Wegener 2002 ) surpasse le tri rapide pour des quantités raisonnables de données et est également un algorithme sur place. Il est également facile à mettre en œuvre (au moins, pas plus difficile que certaines variantes optimisées de tri rapide).

Ce n'est pas si connu et vous ne le trouvez pas dans de nombreux manuels, ce qui peut expliquer pourquoi il n'est pas aussi populaire que quicksort.

Doc Brown
la source
+1: J'ai effectué quelques tests et en effet, le tri par fusion était nettement meilleur que le tri rapide pour les grands tableaux (> 100 000 éléments). Le tri par segment de mémoire était légèrement pire que le tri par fusion (mais le tri par fusion nécessite plus de mémoire). Je pense que ce que les gens appellent le tri rapide est souvent une variante appelée tri d'introduction: le tri rapide qui revient au tri en tas lorsque la profondeur de récursivité dépasse une certaine limite.
Giorgio
@Giorgio: quicksort peut être modifié de certaines manières pour l'améliorer, voir par exemple ici: algs4.cs.princeton.edu/23quicksort Avez-vous essayé ces améliorations?
Doc Brown
Intéressant, pouvez-vous faire référence à un livre \ site pour en savoir plus? (de préférence un livre)
Ramzi Kahil
@Martin: vous parlez du tas ascendant? Eh bien, j'ai donné une référence ci-dessus. Si vous voulez une ressource gratuite, le wikipedia allemand a un article à ce sujet ( de.wikipedia.org/wiki/BottomUp-Heapsort ). Même si vous ne parlez pas allemand, je suppose que vous pouvez toujours lire l'exemple C99.
Doc Brown
7

Vous ne devez pas vous concentrer uniquement sur le pire des cas et uniquement sur la complexité du temps. Il s'agit plus de la moyenne que du pire, et c'est du temps et de l' espace.

Tri rapide:

  • a une complexité temporelle moyenne de Θ ( n log n );
  • peut être implémenté avec une complexité d'espace de Θ (log n );

Tenez également compte du fait que la grande notation O ne prend en compte aucune constante, mais dans la pratique, cela fait une différence si l'algorithme est quelques fois plus rapide. Θ ( n log n ) signifie que cet algorithme s'exécute dans K  n  log ( n ), où K est constant. Quicksort est l'algorithme de tri par comparaison avec le K le plus bas .

vartec
la source
1
@Gilles: il a un faible K, car c'est un algorithme simple.
vartec
5
WTF? Cela n'a aucun sens. La simplicité d'un algorithme n'a aucun rapport avec sa vitesse de course. Le tri par sélection est plus simple que le tri rapide, ce qui ne le rend pas plus rapide.
Gilles 'SO- arrête d'être méchant'
1
@ Gilles: le tri de sélection est O (n ^ 2) dans tous les cas (pire, moyen et meilleur). Donc, peu importe à quel point c'est simple. Quicksort est O (n log n) pour le cas moyen, et parmi tous les algos avec O (n log n) avg c'est le plus simple.
vartec
1
@Gilles: toutes choses étant égales par ailleurs, la simplicité favorise les performances. Supposons que vous comparez deux algorithmes qui prennent chacun (K n log n) des itérations de leurs boucles internes respectives: l'algorithme qui doit faire moins de trucs par boucle a un avantage en termes de performances.
comingstorm
1
@comingstorm: Formulé comme ça, votre déclaration est une tautologie, mais elle n'a rien à voir avec la "simplicité". Il existe, par exemple, des variantes plus compliquées de Quicksort (distinctions de casse!) Qui se traduisent par une durée d'exécution plus courte (à la fois en théorie et en pratique).
Raphael
5

Quicksort est souvent un bon choix car il est raisonnablement rapide et raisonnablement rapide et facile à mettre en œuvre.

Si vous souhaitez sérieusement trier de grandes quantités de données très rapidement, vous êtes probablement mieux avec une certaine variation sur MergeSort. Cela peut être fait pour profiter du stockage externe, peut utiliser plusieurs threads ou même des processus, mais ils ne sont pas triviaux à coder.

James Anderson
la source
1

Les performances réelles des algorithmes dépendent de la plate-forme, ainsi que du langage, du compilateur, de l'attention du programmeur aux détails de l'implémentation, de l'effort d'optimisation spécifique, etc. Ainsi, "l'avantage factoriel constant" de quicksort n'est pas très bien défini - c'est un jugement subjectif basé sur les outils actuellement disponibles, et une estimation approximative de "l'effort de mise en œuvre équivalent" par quiconque effectue réellement l'étude comparative des performances. .

Cela dit, je pense que le tri rapide fonctionne bien (pour une entrée aléatoire) car il est simple et parce que sa structure récursive est relativement compatible avec le cache. D'un autre côté, parce que son pire cas est facile à déclencher, toute utilisation pratique d'un quicksort devra être plus complexe que ce que sa description de manuel l'indiquerait: ainsi, des versions modifiées comme introsort.

Au fil du temps, à mesure que la plate-forme dominante change, différents algorithmes peuvent gagner ou perdre leur avantage relatif (mal défini). La sagesse conventionnelle sur les performances relatives peut très bien être à la traîne de ce changement, donc si vous n'êtes vraiment pas sûr de l'algorithme le mieux adapté à votre application, vous devez implémenter les deux et les tester.

tempête à venir
la source
J'imagine que la "constante la plus petite" à laquelle les autres se rapportent est celle de l'analyse formelle, c'est-à-dire du nombre de comparaisons ou d'échanges. Ceci est très bien défini, mais on ne sait pas comment cela se traduit par l'exécution. Un collègue fait actuellement des recherches à ce sujet, en fait.
Raphael
Mon impression était qu'il s'agissait de performances généralisées, mais je ne comptais pas non plus. Mais vous avez raison: si votre comparaison est particulièrement chère, vous pouvez consulter le nombre de comparaisons attendues ...
comingstorm
1
Pour la raison que vous dites, parler de performances globales (en termes de temps) ne signifie pas dans le cas général car trop de détails entrent en ligne de compte. La raison du comptage de certaines opérations uniquement n'est pas qu'elles sont coûteuses, mais qu'elles se produisent "le plus souvent" "dans le sens de la notation Landau (Big-Oh), donc en les comptant vous donne vos asymptotiques approximatives. Dès que l'on considère les constantes et / ou le runtime, cette stratégie est beaucoup moins intéressante.
Raphael
Une bonne implémentation de QuickSort sera compilée de telle sorte que vos valeurs de pivot restent dans un registre CPU aussi longtemps qu'elles sont nécessaires. C'est souvent suffisant pour battre un tri théoriquement plus rapide avec des temps Big-O comparables.
Dan Lyons
Différents algorithmes de tri ont des caractéristiques différentes en ce qui concerne le nombre de comparaisons et le nombre d'échanges qu'ils effectuent. Et @DanLyons note qu'un tri typique dans une bibliothèque effectue ses comparaisons via des fonctions fournies par l'utilisateur, et garder les valeurs dans les registres à travers de nombreux appels de fonction est assez délicat.
Pointy