Je voudrais ajouter aux excellentes réponses existantes quelques calculs sur la façon dont QuickSort fonctionne en s'écartant du meilleur cas et sur la probabilité, ce qui, j'espère, aidera les gens à comprendre un peu mieux pourquoi le cas O (n ^ 2) n'est pas réel dans les implémentations plus sophistiquées de QuickSort.
En dehors des problèmes d'accès aléatoire, deux facteurs principaux peuvent affecter les performances de QuickSort et ils sont tous deux liés à la façon dont le pivot se compare aux données en cours de tri.
1) Un petit nombre de clés dans les données. Un jeu de données de la même valeur sera trié en n ^ 2 fois sur un QuickSort vanilla à 2 partitions car toutes les valeurs, à l'exception de l'emplacement du pivot, sont placées d'un côté à chaque fois. Les implémentations modernes résolvent cela par des méthodes telles que l'utilisation d'un tri à 3 partitions. Ces méthodes s'exécutent sur un ensemble de données de même valeur en temps O (n). Ainsi, l'utilisation d'une telle implémentation signifie qu'une entrée avec un petit nombre de clés améliore réellement le temps de performance et n'est plus un problème.
2) Une sélection de pivot extrêmement mauvaise peut entraîner les pires performances. Dans un cas idéal, le pivot sera toujours tel que 50% les données sont plus petites et 50% les données sont plus grandes, de sorte que l'entrée sera divisée en deux à chaque itération. Cela nous donne n comparaisons et échange les temps log-2 (n) récursions pour le temps O (n * logn).
Dans quelle mesure la sélection de pivot non idéale affecte-t-elle le temps d'exécution?
Prenons un cas où le pivot est choisi de manière cohérente de telle sorte que 75% des données se trouvent d'un côté du pivot. C'est toujours O (n * logn) mais maintenant la base du journal est passée à 1 / 0,75 ou 1,33. La relation dans les performances lors du changement de base est toujours une constante représentée par log (2) / log (newBase). Dans ce cas, cette constante est de 2,4. Cette qualité de choix de pivot prend donc 2,4 fois plus de temps que l'idéal.
À quelle vitesse cela empire-t-il?
Pas très vite jusqu'à ce que le choix du pivot soit (systématiquement) très mauvais:
- 50% d'un côté: (cas idéal)
- 75% d'un côté: 2,4 fois plus longtemps
- 90% d'un côté: 6,6 fois plus de temps
- 95% d'un côté: 13,5 fois plus longtemps
- 99% d'un côté: 69 fois plus longtemps
Lorsque nous approchons de 100% d'un côté, la partie logarithmique de l'exécution approche n et l'exécution entière approche asymptotiquement O (n ^ 2).
Dans une implémentation naïve de QuickSort, des cas tels qu'un tableau trié (pour le pivot du 1er élément) ou un tableau trié inversement (pour le pivot du dernier élément) produiront de manière fiable un temps d'exécution O (n ^ 2) dans le pire des cas. En outre, les implémentations avec une sélection de pivot prévisible peuvent être soumises à une attaque DoS par des données conçues pour produire l'exécution dans le pire des cas. Les implémentations modernes évitent cela par une variété de méthodes, telles que la randomisation des données avant le tri, le choix de la médiane de 3 index choisis au hasard, etc. Avec cette randomisation dans le mélange, nous avons 2 cas:
- Petit ensemble de données. Le pire des cas est raisonnablement possible mais O (n ^ 2) n'est pas catastrophique car n est suffisamment petit pour que n ^ 2 soit également petit.
- Grand ensemble de données. Le pire des cas est possible en théorie mais pas en pratique.
Quelle est la probabilité de voir de terribles performances?
Les chances sont extrêmement faibles . Considérons une sorte de 5000 valeurs:
Notre implémentation hypothétique choisira un pivot en utilisant une médiane de 3 index choisis au hasard. Nous considérerons les pivots dans la plage de 25% à 75% comme étant «bons» et les pivots dans la plage de 0% à 25% ou 75% à 100% comme «mauvais». Si vous regardez la distribution de probabilité en utilisant la médiane de 3 indices aléatoires, chaque récursivité a 11/16 de chances de se retrouver avec un bon pivot. Faisons 2 hypothèses conservatrices (et fausses) pour simplifier les calculs:
Les bons pivots sont toujours exactement à une répartition de 25% / 75% et fonctionnent à 2,4 * cas idéal. Nous n'obtenons jamais une répartition idéale ou une répartition meilleure que 25/75.
Les mauvais pivots sont toujours le pire des cas et ne contribuent essentiellement à rien à la solution.
Notre implémentation QuickSort s'arrêtera à n = 10 et basculera vers un tri par insertion, nous avons donc besoin de 22 partitions pivotantes à 25% / 75% pour décomposer la valeur de 5 000 entrées jusque-là. (10 * 1.333333 ^ 22> 5000) Ou, nous avons besoin de 4990 pivots dans le pire des cas. Gardez à l'esprit que si nous accumulons 22 bons pivots à tout moment, le tri se terminera, donc le pire des cas ou quoi que ce soit à proximité nécessite une très mauvaise chance. S'il nous a fallu 88 récursions pour atteindre réellement les 22 bons pivots nécessaires pour trier vers le bas à n = 10, ce serait 4 * 2,4 * cas idéal ou environ 10 fois le temps d'exécution du cas idéal. Quelle est la probabilité que nous n'obtenions pas les 22 bons pivots requis après 88 récursions?
Les distributions de probabilités binomiales peuvent répondre à cela, et la réponse est d'environ 10 ^ -18. (n est 88, k est 21, p est 0,6875) Votre utilisateur a environ mille fois plus de chances d'être frappé par la foudre dans la seconde qu'il faut pour cliquer sur [TRIER] que pour voir que le tri de 5 000 éléments s'exécute plus mal de 10 * cas idéal. Cette chance diminue au fur et à mesure que l'ensemble de données augmente. Voici quelques tailles de tableau et leurs chances correspondantes de fonctionner plus longtemps que 10 * idéal:
- Tableau de 640 éléments: 10 ^ -13 (nécessite 15 bons points de pivot sur 60 essais)
- Tableau de 5000 éléments: 10 ^ -18 (nécessite 22 bons pivots sur 88 essais)
- Tableau de 40000 articles: 10 ^ -23 (nécessite 29 bons pivots sur 116)
N'oubliez pas que c'est avec 2 hypothèses conservatrices qui sont pires que la réalité. Les performances réelles sont donc encore meilleures, et le solde de la probabilité restante est plus proche que l'idéal.
Enfin, comme d'autres l'ont mentionné, même ces cas absurdement improbables peuvent être éliminés en passant à un tri par tas si la pile de récursivité va trop loin. Ainsi, le TLDR est que, pour de bonnes implémentations de QuickSort, le pire des cas n'existe pas vraiment car il a été conçu et l'exécution se termine en temps O (n * logn).
qsort
, Pythonlist.sort
etArray.prototype.sort
JavaScript de Firefox sont toutes des sortes de fusion gonflées. (GNU STLsort
utilise Introsort à la place, mais cela pourrait être dû au fait qu'en C ++, l'échange peut potentiellement gagner gros sur la copie.)