On m'a posé cette question lors d'une interview. Ils sont tous deux O(nlogn) et pourtant la plupart des gens utilisent Quicksort au lieu de Mergesort. Pourquoi donc?
Quicksort a O (n2) le pire des cas d'exécution et O (nbûchen) durée moyenne d’affaires. Cependant, il est préférable de fusionner le tri dans de nombreux scénarios car de nombreux facteurs influent sur le temps d’exécution d’un algorithme et, en les prenant tous ensemble, le tri rapide l'emporte.
En particulier, le temps d'exécution souvent cité des algorithmes de tri fait référence au nombre de comparaisons ou au nombre de swaps nécessaires pour effectuer le tri des données. C’est en effet une bonne mesure de performance, d’autant plus qu’elle est indépendante de la conception matérielle sous-jacente. Cependant, d’autres choses - telles que la localité de référence (lisons-nous beaucoup d’éléments qui sont probablement en cache?) - jouent également un rôle important sur le matériel actuel. Quicksort en particulier nécessite peu d’espace supplémentaire et présente une bonne localisation en mémoire cache, ce qui le rend plus rapide que le tri par fusion dans de nombreux cas.
De plus, il est très facile d’éviter le temps d’exécution de 0 (n2) presque entièrement en utilisant un choix approprié du pivot - tel que le choisir au hasard (ceci est une excellente stratégie).
En pratique, de nombreuses implémentations modernes de quicksort (en particulier std::sort
de libstdc ++] sont en réalité introsort , dont le pire cas théorique est O (nbûchen), identique au tri par fusion. Pour ce faire, il limite la profondeur de récursivité et passe à un algorithme différent ( heapsort ) une fois dépassé le journal.n.
Comme de nombreuses personnes l’ont noté, les performances moyennes des cas de quicksort sont plus rapides que de mergesort. Mais Cela n’est vrai que si vous supposez un temps constant pour accéder à n’importe quel morceau de mémoire à la demande.
Dans RAM cette hypothèse n'est généralement pas trop mauvaise (ce n'est pas toujours vrai à cause des caches, mais ce n'est pas trop grave). Cependant, si votre structure de données est assez grosse pour vivre sur disque, alors quicksort est tué par le fait que votre disque moyen effectue quelque chose comme 200 recherches aléatoires par seconde. . Mais ce même disque n'a aucun problème à lire ou à écrire séquentiellement des mégaoctets de données par seconde. Ce qui est exactement ce que mergesort fait.
Par conséquent, si les données doivent être triées sur le disque, vous voulez vraiment, vraiment, utiliser certaines variantes de mergesort. (Généralement, vous triez rapidement les sous-listes, puis commencez à les fusionner au-delà d'un certain seuil.)
De plus, si vous devez faire quoi que ce soit avec des jeux de données de cette taille, réfléchissez bien à la façon d'éviter de chercher sur le disque. C'est pourquoi, par exemple, il est conseillé de supprimer les index avant d'effectuer des chargements de données volumineux dans des bases de données, puis de reconstruire l'index ultérieurement. Maintenir l'index pendant le chargement signifie chercher constamment sur le disque. En revanche, si vous supprimez les index, la base de données peut reconstruire l'index en triant d'abord les informations à traiter (à l'aide d'un mergesort bien sûr!), Puis en les chargeant dans une structure de données BTREE pour l'index. (Les BTREE sont naturellement conservés dans l’ordre, vous pouvez donc en charger un à partir d’un jeu de données trié avec peu de recherches sur le disque.)
À plusieurs reprises, comprendre comment éviter les recherches sur disque m'a permis de faire des tâches de traitement de données prenant des heures plutôt que des jours ou des semaines.
En fait, QuickSort est O (n2). Son cas moyen le temps d'exécution est O (nlog (n)), mais son cas le plus défavorable est O (n2), ce qui se produit lorsque vous l’exécutez sur une liste contenant quelques éléments uniques. La randomisation prend O (n). Bien sûr, cela ne change pas le pire des cas, cela empêche simplement un utilisateur malveillant de faire en sorte que votre tri prenne beaucoup de temps.
QuickSort est plus populaire parce que:
"Et pourtant, la plupart des gens utilisent Quicksort au lieu de Mergesort. Pourquoi?"
Une raison psychologique qui n’a pas été donnée est simplement que Quicksort porte un nom plus astucieux. c'est-à-dire un bon marketing.
Oui, Quicksort avec triple partitionnement est probablement l’un des meilleurs algorithmes de tri à usage général, mais il n’ya pas de doute que le tri "Rapide" semble beaucoup plus puissant que le tri "Fusionner".
Comme d'autres l'ont noté, le pire cas de Quicksort est O (n ^ 2), tandis que mergesort et heapsort restent en O (nlogn). En moyenne, cependant, tous les trois sont O (nlogn); ils sont donc comparables dans la grande majorité des cas.
Ce qui rend Quicksort meilleur en moyenne est que la boucle interne implique de comparer plusieurs valeurs avec une seule, alors que les deux autres termes sont différents pour chaque comparaison. En d'autres termes, Quicksort effectue la moitié moins de lectures que les deux autres algorithmes. Sur les processeurs modernes, les temps d’accès dominent largement les performances, de sorte que Quicksort finit par être un excellent premier choix.
J'aimerais ajouter que sur les trois algorithmes mentionnés jusqu'à présent (mergesort, quicksort et sort heap), seul le mergesort est stable. C'est-à-dire que l'ordre ne change pas pour les valeurs qui ont la même clé. Dans certains cas, cela est souhaitable.
Mais, à vrai dire, dans la pratique, la plupart des gens n’ont besoin que de bonnes performances moyennes et le tri rapide est ... rapide =)
Tous les algorithmes de tri ont leurs hauts et leurs bas. Voir article Wikipedia pour le tri des algorithmes pour un bon aperçu.
De l'entrée de Wikipedia sur Quicksort :
Quicksort est également en concurrence avec mergesort, un autre algorithme de tri récursif mais avec l'avantage du temps d'exécution le plus défavorable (nlogn). Mergesort est un type stable, contrairement au quicksort et au heapsort, et peut être facilement adapté pour fonctionner sur des listes chaînées et de très grandes listes stockées sur un support à accès lent, tel qu'un stockage sur disque ou un stockage connecté au réseau. Bien que quicksort puisse être écrit pour fonctionner sur des listes chaînées, il sera souvent pénalisé par de mauvais choix de pivot sans accès aléatoire. Le principal inconvénient de mergesort est que, lorsqu'il fonctionne sur des tableaux, il nécessite au mieux un espace auxiliaire Θ (n), alors que la variante de quicksort avec partitionnement sur place et récursivité utilise uniquement l'espace Θ (logn). (Notez que, lorsqu’il fonctionne sur des listes chaînées, mergesort ne nécessite qu’une petite quantité constante de mémoire auxiliaire.)
Mu! Quicksort n'est pas meilleur, il convient bien à un type d'application différent de celui de mergesort.
Mergesort vaut la peine d'être envisagé si la rapidité est essentielle, si les performances dans le pire des cas sont médiocres et si de l'espace supplémentaire est disponible . 1
Vous avez déclaré qu'ils "ils sont tous deux O(nlogn) […]". C'est faux. "Quicksort utilise environ n ^ 2/2 comparaisons dans le pire des cas." 1 .
Cependant, selon mon expérience, la propriété la plus importante est la mise en œuvre facile d'un accès séquentiel que vous pouvez utiliser lors du tri lorsque vous utilisez des langages de programmation avec le paradigme impératif.
1 Sedgewick, Algorithmes
Quicksort est l'algorithme de tri le plus rapide dans la pratique, mais présente un certain nombre de cas pathologiques qui peuvent le rendre aussi performant que O (n2).
Heapsort est garanti pour s'exécuter en O (n * ln (n)) et ne nécessite qu'un stockage supplémentaire limité. Mais il existe de nombreuses citations de tests dans le monde réel qui montrent que la pile de tractions est nettement plus lente que la tri rapide en moyenne.
L'explication de Wikipedia est la suivante:
En règle générale, le tri rapide est beaucoup plus rapide dans la pratique que les autres algorithmes Θ (nlogn), car sa boucle interne peut être efficacement mise en œuvre sur la plupart des architectures et, dans la plupart des données réelles, il est possible de faire des choix de conception minimisant la probabilité d'exiger un temps quadratique. .
Je pense qu'il y a également des problèmes avec la quantité de stockage nécessaire pour Mergesort (qui est Ω (n)), ce que les implémentations de quicksort n'ont pas. Dans le pire des cas, ils ont la même durée de temps algorithmique, mais mergesort nécessite davantage de stockage.
Je voudrais ajouter aux bonnes réponses existantes quelques calculs sur le comportement de QuickSort par rapport au meilleur des cas et sur sa probabilité, ce qui, je l’espère, aidera les gens à comprendre un peu mieux pourquoi le cas O (n ^ 2) n’est pas réel. préoccupation dans les implémentations plus sophistiquées de QuickSort.
Outre les problèmes d’accès aléatoire, deux facteurs principaux peuvent influer sur les performances de QuickSort. 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 ensemble de données ayant la même valeur sera trié n ^ 2 fois sur un tri rapide 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 traitent cela par des méthodes telles que l'utilisation d'un tri à 3 partitions. Ces méthodes s'exécutent sur un jeu de données de la même valeur en O(n) time. L'utilisation d'une telle implémentation signifie donc qu'une entrée avec un petit nombre de clés améliore réellement les performances et ne pose plus de problème.
2) Une sélection de pivot extrêmement mauvaise peut entraîner des performances optimales. Dans l'idéal, le pivot sera toujours tel que 50% des données sont plus petites et 50% plus grandes, de sorte que l'entrée sera divisée en deux à chaque itération. Cela nous donne n comparaisons et swaps fois les récurrences log-2 (n) pour O (n * logn) temps.
Dans quelle mesure la sélection d'un pivot non idéal affecte-t-elle le temps d'exécution?
Prenons le cas où le pivot est choisi de manière cohérente, de telle sorte que 75% des données se trouvent sur un côté du pivot. C'est toujours O (n * logn) mais maintenant la base du journal a été changée en 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 2.4. Donc, cette qualité de choix de pivot prend 2,4 fois plus longtemps que l’idéal.
À quelle vitesse cela empire-t-il?
Pas très vite jusqu'à ce que le choix du pivot devienne (toujours) très mauvais:
Lorsque nous approchons de 100% d'un côté, la partie journal de l'exécution approche n et l'exécution entière approche asymptotiquement de O (n ^ 2).
Dans une implémentation naïve de QuickSort, des cas tels qu'un tableau trié (pour le pivot du premier élément) ou trié de manière inverse (pour le pivot du dernier élément) généreront de manière fiable un temps d'exécution O (n ^ 2) dans le cas le plus défavorable. De plus, 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 une exécution dans le pire des cas. Les implémentations modernes l'évitent par diverses méthodes, telles que la randomisation des données avant tri, le choix de la médiane de 3 index choisis aléatoirement, etc. Cette randomisation faisant partie du mixage, nous avons 2 cas:
Quelle est notre probabilité de voir des performances terribles?
Les chances sont extrêmement faibles . Considérons une sorte de 5 000 valeurs:
Notre implémentation hypothétique choisira un pivot utilisant une médiane de 3 index choisis au hasard. Nous considérerons les pivots compris entre 25% et 75% comme "bons" et les pivots compris entre 0% et 25% ou 75% -100% comme étant "mauvais". Si vous regardez la distribution de probabilité en utilisant la médiane de 3 index aléatoires, chaque récursion a 11 chances sur 16 de se retrouver avec un bon pivot. Faisons deux hypothèses conservatrices (et fausses) pour simplifier les calculs:
Les bons pivots sont toujours exactement à 25%/75% et fonctionnent à 2,4 * cas idéal. Nous n'obtenons jamais une scission idéale ou une scission meilleure que 25/75.
Les mauvais pivots sont toujours les pires cas et ne contribuent essentiellement à la solution.
Notre implémentation QuickSort s'arrêtera à n = 10 et passera à un tri par insertion. Nous avons donc besoin de 22 partitions pivot 25%/75% pour décomposer la valeur de 5 000 entrées jusqu'à présent. (10 * 1.333333 ^ 22> 5000) Ou, nous avons besoin de 4990 pivots dans le cas le plus défavorable. Gardez à l’esprit que si nous accumulons 22 bons pivots à n’importe quel point , le tri s’achève, le pire des cas ou quoi que ce soit de près nécessite donc extrêmement malchance. Si nous avions besoin de 88 récursions pour atteindre les 22 pivots nécessaires pour trier n = 10, nous aurions alors 4 * 2.4 * cas idéal, soit environ 10 fois le temps d’exécution du cas idéal. Quelle est la probabilité que nous n'atteignons pas les 22 pivots requis après 88 récursions?
distributions de probabilité binomiales peut répondre à cela, et la réponse est environ 10 ^ -18. (n est 88, k est 21, p est 0,6875) Votre utilisateur est environ mille fois plus susceptible d’être frappé par la foudre au cours de la 1 seconde nécessaire pour cliquer sur [TRIER] que pour voir que le tri de 5 000 éléments est exécuté tout pire que 10 * cas idéal. Cette chance diminue à mesure que le jeu de données s'agrandit. Voici quelques tailles de tableaux et leurs chances correspondantes de fonctionner plus longtemps que 10 * idéal:
Rappelez-vous que ceci est avec 2 hypothèses conservatrices qui sont pires que la réalité. La performance réelle est donc encore meilleure et le solde de la probabilité restante est plus proche de l’idéal que pas du tout.
Enfin, comme d'autres l'ont déjà mentionné, même ces cas absurdement improbables peuvent être éliminés en passant à un type de tas si la pile de récursivité est trop profonde. Donc, 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 son exécution terminée en O (n * logn) time.
Quicksort n'est pas meilleur que mergesort. Avec O (n ^ 2) (dans le pire des cas, cela arrive rarement), le tri rapide est potentiellement beaucoup plus lent que le O(nlogn) du type de fusion. Quicksort a moins de frais généraux, donc avec les petits ordinateurs n et les ordinateurs lents, il est préférable. Mais les ordinateurs sont si rapides aujourd'hui que les frais généraux supplémentaires d'un fusionnement sont négligeables, et le risque d'un tri rapide très lent l'emporte largement sur les frais généraux insignifiants d'un test de fusion dans la plupart des cas.
De plus, un mergesort laisse les éléments avec des clés identiques dans leur ordre d'origine, un attribut utile.
Pourquoi Quicksort est bon?
Est-ce que Quicksort est toujours meilleur que Mergesort?
Pas vraiment.
Remarque: En Java, la fonction Arrays.sort () utilise Quicksort pour les types de données primitifs et Mergesort pour les types de données objet. Étant donné que les objets consomment de la mémoire, une légère surcharge pour Mergesort ne pose donc aucun problème en termes de performances.
Référence : Regardez les vidéos QuickSort de Semaine 3, Cours d'algorithmes de Princeton à Coursera
Contrairement au tri par fusion, le tri rapide n’utilise pas d’espace auxiliaire. Tandis que le tri par fusion utilise un espace auxiliaire O (n). Mais le tri par fusion a la complexité temporelle dans le pire des cas de O(nlogn) alors que la complexité dans le pire des cas de Tri rapide est O (n ^ 2), ce qui se produit lorsque le tableau est déjà trié.
La réponse inclinerait légèrement vers le tri rapide par rapport aux modifications apportées avec DualPivotQuickSort pour les valeurs primitives. Il est utilisé dans Java 7 pour trier dans Java.util.Arrays
It is proved that for the Dual-Pivot Quicksort the average number of
comparisons is 2*n*ln(n), the average number of swaps is 0.8*n*ln(n),
whereas classical Quicksort algorithm has 2*n*ln(n) and 1*n*ln(n)
respectively. Full mathematical proof see in attached proof.txt
and proof_add.txt files. Theoretical results are also confirmed
by experimental counting of the operations.
Vous pouvez trouver l'implémentation Java7 ici - http://grepcode.com/file/repository.grepcode.com/Java/root/jdk/openjdk/7-b147/Java/util/Arrays.Java
Lecture impressionnante sur DualPivotQuickSort - http://permalink.gmane.org/gmane.comp.Java.openjdk.core-libs.devel/2628
En fusion-tri, l'algorithme général est:
Au niveau supérieur, la fusion des 2 sous-tableaux triés implique de traiter avec N éléments.
Un niveau inférieur à celui-ci, chaque itération de l'étape 3 implique de traiter avec N/2 éléments, mais vous devez répéter ce processus deux fois. Donc, vous avez toujours affaire à 2 * N/2 == N éléments.
Un niveau en dessous, vous fusionnez 4 * N/4 == N éléments, etc. Chaque profondeur de la pile récursive implique la fusion du même nombre d'éléments, pour tous les appels de cette profondeur.
Considérons plutôt l'algorithme de tri rapide:
Au niveau supérieur, vous avez affaire à un tableau de taille N. Vous devez ensuite sélectionner un point pivot, le placer à la bonne position et l’ignorer complètement pour le reste de l’algorithme.
Un niveau inférieur à celui-ci correspond à deux sous-tableaux dont la taille combinée est N-1 (c.-à-d. Soustrayez le point de pivot précédent). Vous choisissez un point pivot pour chaque sous-tableau, ce qui donne lieu à 2 points pivot supplémentaires.
Un niveau en dessous, vous avez 4 sous-tableaux de taille combinée N-3, pour les mêmes raisons que ci-dessus.
Alors N-7 ... Alors N-15 ... Puis N-32 ...
La profondeur de votre pile récursive reste approximativement la même (logN). Avec fusion-sort, vous avez toujours affaire à une fusion de N éléments, à chaque niveau de la pile récursive. Avec le tri rapide cependant, le nombre d’éléments que vous traitez diminue au fur et à mesure que vous progressez. Par exemple, si vous regardez la profondeur à mi-chemin de la pile récursive, le nombre d'éléments que vous traitez est N - 2 ^ ((logN)/2)) == N - sqrt (N).
Clause de non-responsabilité: lors de la fusion-tri, comme vous divisez le tableau en 2 fragments identiques à chaque fois, la profondeur récursive est exactement logN. Sur tri rapide, comme il est peu probable que votre point de pivotement se trouve exactement au milieu du tableau, la profondeur de votre pile récursive peut être légèrement supérieure à celle de logN. Je n'ai pas fait le calcul pour voir le rôle important que jouent ce facteur et le facteur décrit ci-dessus dans la complexité de l'algorithme.
Quicksort a une complexité de cas moyenne supérieure, mais dans certaines applications, ce n'est pas le bon choix. Quicksort est vulnérable aux attaques par déni de service. Si un attaquant peut choisir l’entrée à trier, il peut facilement construire un ensemble prenant la complexité temporelle dans le pire des cas, soit o (n ^ 2).
La complexité moyenne des dossiers de Mergesort et la complexité des cas les plus défavorables sont identiques et ne souffrent donc pas du même problème. Cette propriété de fusion-tri en fait également le meilleur choix pour les systèmes temps réel - précisément parce qu'il n'y a pas de cas pathologique qui le ralentit beaucoup.
Je suis un plus grand fan de Mergesort que de Quicksort, pour ces raisons.
Bien qu'ils appartiennent tous les deux à la même classe de complexité, cela ne signifie pas qu'ils ont tous les deux la même exécution. Quicksort est généralement plus rapide que mergesort, simplement parce qu'il est plus facile de coder une implémentation étroite et que les opérations qu'elle effectue peuvent être plus rapides. C'est parce que le tri rapide est généralement plus rapide que les gens l'utilisent au lieu du mergesort.
Pourtant! Personnellement, je vais souvent utiliser mergesort ou une variante de quicksort qui se dégrade en mergesort lorsque le quicksort est médiocre. Rappelles toi. Quicksort est seulement O (n log n) sur moyen . C'est le pire des cas, c'est O (n ^ 2)! Mergesort est toujours O (n log n). Dans les cas où la performance ou la réactivité en temps réel est indispensable et où vos données d'entrée peuvent provenir d'une source malveillante, vous ne devez pas utiliser de tri rapide.
Le tri rapide correspond au cas le plus défavorable O (n ^ 2). Toutefois, le cas moyen moyen effectue systématiquement le tri par fusion. Chaque algorithme est O (nlogn), mais vous devez vous rappeler que lorsque nous parlons de Big O, nous ne tenons pas compte des facteurs de complexité inférieure. Le tri rapide présente des améliorations significatives par rapport au tri par fusion lorsqu'il s'agit de facteurs constants.
Le tri par fusion nécessite également la mémoire O(2n), tandis qu'un tri rapide peut être effectué sur place (ne nécessitant que O (n)). C'est une autre raison pour laquelle le tri rapide est généralement préféré au tri par fusion.
Info supplémentaire:
Le pire cas de tri rapide se produit lorsque le pivot est mal choisi. Prenons l'exemple suivant:
[5, 4, 3, 2, 1]
Si le pivot est choisi comme le plus petit ou le plus grand nombre du groupe, le tri rapide se fera dans O (n ^ 2). La probabilité de choisir l'élément qui se trouve dans le plus grand ou le plus petit des 25% de la liste est de 0,5. Cela donne à l’algorithme une chance sur deux d’être un bon pivot. Si nous utilisons un algorithme de choix de pivot typique (par exemple, choisir un élément aléatoire), nous avons 0,5 chance de choisir un bon pivot pour chaque choix de pivot. Pour les collections de grande taille, la probabilité de toujours choisir un pivot médiocre est de 0.5 * n. Sur la base de cette probabilité, le tri rapide est efficace pour le cas moyen (et typique).
C'est une assez vieille question, mais depuis que j'ai traité les deux récemment voici mon 2c:
Le tri par fusion nécessite en moyenne ~ N comparaisons log N. Pour les tableaux triés déjà (presque) triés, cela revient à 1/2 N log N, car lors de la fusion, nous sélectionnons (presque) toujours la partie "gauche" 1/2 N du nombre de fois, puis copions simplement les éléments 1/2 N à droite. De plus, je peux supposer que les entrées déjà triées font briller le prédicteur de branche du processeur, mais en devinant presque toutes les branches correctement, empêchant ainsi les blocages de pipeline.
Le tri rapide nécessite en moyenne environ 1,38 N log N comparaisons. Il ne tire pas grand profit des tableaux déjà triés en termes de comparaisons (toutefois, il en fait de même pour les échanges et probablement pour les prédictions de branche dans la CPU).
Mes points de repère sur un processeur assez moderne montrent ce qui suit:
Lorsque la fonction de comparaison est une fonction de rappel (comme dans l’implémentation de qsort () libc), quicksort est plus lent que mergesort de 15% pour une entrée aléatoire et de 30% pour un tableau déjà trié pour les entiers 64 bits.
D'un autre côté, si la comparaison n'est pas un rappel, mon expérience est que le tri rapide effectue une performance supérieure à 25% dans le fusionnement.
Cependant, si votre (grand) tableau a très peu de valeurs uniques, le tri par fusion commence à gagner dans tous les cas.
Donc, peut-être que le résultat final est le suivant: si la comparaison est coûteuse (par exemple, fonction de rappel, comparaison de chaînes, comparaison de nombreuses parties d'une structure aboutissant généralement à un "si" pour faire la différence) - il y a de fortes chances pour que vous soyez meilleur avec le genre de fusion. Pour des tâches plus simples, le tri rapide sera plus rapide.
Cela dit, tout ce qui a été dit précédemment est vrai: - Quicksort peut être N ^ 2, mais Sedgewick affirme qu'une bonne mise en œuvre randomisée a plus de chances qu'un ordinateur effectuant le tri soit frappé par la foudre que d'aller N ^ 2 - Mergesort requiert de l'espace supplémentaire
Lorsque j'ai expérimenté les deux algorithmes de tri, en comptant le nombre d'appels récursifs, quicksort a toujours moins d'appels récursifs que mergesort. C'est parce que quicksort a des pivots, et les pivots ne sont pas inclus dans les prochains appels récursifs. De cette manière, quicksort peut atteindre le cas de base récursif plus rapidement que mergesort.
Petits ajouts aux tris rapides et fusionnés.
En outre, cela peut dépendre du type d’éléments de tri. Si l'accès aux éléments, l'échange et les comparaisons ne sont pas des opérations simples, telles que la comparaison d'entiers dans la mémoire de plan, le tri par fusion peut être un algorithme préférable.
Par exemple, nous trions les éléments en utilisant le protocole réseau sur un serveur distant.
En outre, dans les conteneurs personnalisés tels que "liste liée", le tri rapide ne présente aucun avantage.
1. Fusionner le tri sur la liste chaînée, pas besoin de mémoire supplémentaire. 2. L'accès aux éléments en tri rapide n'est pas séquentiel (en mémoire)
C'est difficile à dire. Le pire de MergeSort est n (log2n) -n + 1, ce qui est exact si n est égal à 2 ^ k (je l'ai déjà prouvé). Et pour tout n, il est compris entre (n lg n - n + 1) et (n lg n + n + O (lg n)). Mais pour quickSort, son meilleur est nlog2n (n est égal à 2 ^ k) .Si vous divisez Mergesort par quickSort, il vaut un lorsque n est infini.So c'est comme si le pire cas de MergeSort était meilleur que le meilleur cas de QuickSort, pourquoi utilisons-nous quicksort? Mais rappelez-vous, MergeSort n'est pas en place, il nécessite un espace mémoire de 2n.Et MergeSort doit également faire de nombreuses copies n'incluez pas dans l'analyse d'algorithme.En un mot, MergeSort est vraiment plus rapide qu'un quicksort dans le jeu, mais en réalité, vous devez tenir compte de l'espace mémoire, le coût de la copie d'un tableau, la fusion est plus lente que le tri rapide. expérience où on m'a donné 1000000 chiffres dans Java par classe aléatoire, et il a fallu 2610ms par mergesort, 1370ms par quicksort.
Toutes choses étant égales par ailleurs, je m'attendrais à ce que la plupart des gens utilisent ce qui est le plus commodément disponible, et cela a tendance à être qsort (3). En dehors de cela, quicksort est connu pour être très rapide sur les tableaux, tout comme mergesort est le choix courant pour les listes.
Ce que je me demande, c'est pourquoi il est si rare de voir radix ou un tri de seau. Ils sont O (n), au moins sur des listes chaînées et tout ce qu'il faut, c'est une méthode de conversion de la clé en un nombre ordinal. (les cordes et les flotteurs fonctionnent très bien.)
Je pense que la raison est liée à la façon dont l’informatique est enseignée. J'ai même dû démontrer à mon conférencier en analyse algorithmique qu'il était effectivement possible de trier plus rapidement que O (n log (n)). (Il avait la preuve que vous ne pouvez pas la comparaison trier plus vite que O (n log (n)), ce qui est vrai.)
Dans d'autres nouvelles, les flottants peuvent être triés sous forme de nombres entiers, mais vous devez inverser les nombres négatifs par la suite.
Edit: En fait, voici un moyen encore plus vicieux de trier les floats-as-integers: http://www.stereopsis.com/radix.html . Notez que cette astuce peut être utilisée quel que soit l'algorithme de tri que vous utilisez réellement ...
Prenez en compte la complexité du temps et de l’espace. Pour le tri par fusion: Complexité temporelle: O(nlogn), Complexité de l'espace: O (nlogn)
Pour le tri rapide: complexité temporelle: O (n ^ 2), complexité spatiale: O (n)
Maintenant, ils gagnent tous les deux dans un scénario chacun. Mais, en utilisant un pivot aléatoire, vous pouvez presque toujours réduire la complexité temporelle du tri rapide à O (nlogn).
Ainsi, le tri rapide est préféré dans de nombreuses applications au lieu du tri par fusion.
Le tri rapide est un algorithme de tri sur place, il convient donc mieux aux tableaux. Le tri par fusion nécessite un stockage supplémentaire de O (N) et convient mieux aux listes chaînées.
Contrairement aux tableaux, dans la liste des éléments favoris, nous pouvons insérer des éléments au milieu avec O(1) espace et O(1) temps. Par conséquent, l'opération de fusion dans le tri par fusion peut être implémentée sans aucune modification. espace supplémentaire. Cependant, l'allocation et la désallocation d'espace supplémentaire pour les tableaux ont un effet négatif sur le temps d'exécution du tri par fusion. Le tri par fusion favorise également la liste chaînée au fur et à mesure de l’accès séquentiel aux données, sans beaucoup d’accès aléatoire à la mémoire.
Le tri rapide, en revanche, nécessite beaucoup d’accès aléatoire à la mémoire et, avec un tableau, nous pouvons accéder directement à la mémoire sans la traversée requise par les listes chaînées. De plus, le tri rapide, lorsqu'il est utilisé pour les tableaux, a une bonne localité de référence car les tableaux sont stockés de manière contiguë dans la mémoire.
Bien que la complexité moyenne des deux algorithmes de tri soit égale à O (NlogN), les utilisateurs de tâches ordinaires utilisent généralement un tableau pour le stockage. Pour cette raison, un tri rapide devrait être l'algorithme de choix.
EDIT: Je viens de découvrir que fusionner le pire/meilleur/moyen cas est toujours nlogn, mais le tri rapide peut varier de n2 (pire cas lorsque les éléments sont déjà triés) à nlogn (moyen/meilleur cas lorsque pivot divise toujours le tableau en deux moitiés).