web-dev-qa-db-fra.com

Quels algorithmes sont utilisés dans C ++ 11 std :: sort dans différentes implémentations STL?

La norme C++ 11 garantit que std::sort a complexité O (n logn) dans le pire des cas . Ceci est différent de la garantie cas moyen en C++ 98/03, où std::sort pourrait être implémenté avec Quicksort (peut être combiné avec un tri par insertion pour le petit n), qui a O (n ^ 2) dans le pire des cas (pour une entrée spécifique, telle qu'une entrée triée).

Y a-t-il eu des changements dans std::sort implémentations dans différentes bibliothèques STL? Comment est le C++ 11 std::sort implémenté dans différentes STL?

46
Alexey Voytenko

Parcourir les sources en ligne pour libstdc ++ et libc ++ , on peut voir que les deux bibliothèques utilisent la gamme complète des algorithmes de tri bien connus à partir d'une boucle principale d'intro-tri:

Pour std::sort, Il existe une routine d'aide pour insertion_sort (Un algorithme O(N^2) mais avec une bonne constante de mise à l'échelle pour le rendre compétitif pour les petites séquences), plus un boîtier spécial pour sous-séquences de 0, 1, 2 et 3 éléments.

Pour std::partial_sort, Les deux bibliothèques utilisent une version de heap_sort (O(N log N) en général), car cette méthode a un invariant Nice qui conserve une sous-séquence triée (elle a généralement un constante de mise à l'échelle plus grande pour le rendre plus cher pour un tri complet).

Pour std::nth_element, Il existe une routine d'aide pour selection_sort (Encore un algorithme O (N ^ 2) avec une bonne constante de claquage pour le rendre compétitif pour les petites séquences). Pour un tri régulier, insertion_sort Domine généralement selection_sort, Mais pour nth_element L'invariant d'avoir les plus petits éléments correspond parfaitement au comportement de selection_sort.

19
TemplateRex

La question est de savoir comment [~ # ~] stl [~ # ~] dire std::sort Le pire des cas est O (N log ( N)) , même s'il s'agit essentiellement d'un QuickSort . Le tri de STL est IntroSort . IntroSort est essentiellement un QuickSort, la différence introduite change la complexité du pire des cas.


Le pire cas de QuickSort est O (N ^ 2)

Quel que soit le partitionnement que vous choisissez, il existe une séquence sur laquelle QuickSort s'exécutera O (N ^ 2) . Le partitionnement que vous choisissez ne fait que diminuer la probabilité que le pire des cas se produise. ( Sélection de pivot aléatoire , Médiane des trois, etc. )

EDIT: Merci à la correction de @ maxim1000. Tri rapide avec algorithme de sélection de pivot Médiane des médianes a O (N log (N)) pire complexité, mais en raison de la frais généraux, il introduit qu'il n'est pas utilisé dans la pratique. Il montre comment un bon algorithme de sélection peut théoriquement changer la complexité du pire des cas grâce à la sélection pivot.


Que fait IntroSort?

IntroSort limite la ramification de QuickSort. C'est le point le plus important, cette limite est 2 * (log N) . Lorsque la limite est atteinte, IntroSort peut utiliser n'importe quel algorithme de tri présentant la pire complexité de O (N log (N)).

La ramification s'arrête lorsque nous avons des sous-problèmes O (log N). Nous pouvons résoudre chaque sous-problème O (n log n). (Les minuscules n représentent les tailles des sous-problèmes).

La somme de (n log n) est notre pire cas de complexité, maintenant.

Pour le pire des cas de QuickSort; supposons que nous avons un tableau déjà trié, et nous sélectionnons toujours le premier élément de ce tableau comme pivot. À chaque itération, nous ne supprimons que le premier élément. Si nous allions de cette façon jusqu'à la fin, ce serait O (N ^ 2) évidemment. Avec IntroSort, nous arrêtons QuickSort, lorsque nous atteignons une profondeur log (N) , puis nous utilisons HeapSort pour le tableau non trié restant.

16 -> 1  /**N**/
   \
    > 15 -> 1 /**N - 1**/
         \
          > 14 -> 1 /**N - 2**/
               \
                > 13 -> 1 /**N - log(N)**/  
                     \
                      > 12 /**(HeapSort Now) (N - log(N)) log (N - log(N))**/

Résumez-les;

Jusqu'à l'arrêt de la branche, les opérations N + (N - 1) + ... + (N - log(N)) sont effectuées. Au lieu d'utiliser gauss pour résumer, nous pouvons simplement dire N + (N - 1) + ... + (N - log(N)) < N log(N).

La partie HeapSort est (N - log(N)) log(N - log(N)) < N log(N)

Complexité globale < 2 N log(N).

Étant donné que les constantes peuvent être omises, la complexité la plus défavorable de IntroSort est O (N log (N)) .


Informations ajoutées: [~ # ~] gcc [~ # ~] Le code source de l'implémentation STL est ici . Sort la fonction est à la ligne 5461 .

Correction: * Microsoft .NET * sort L'implémentation est IntroSort depuis 2012. Les informations associées sont ici .

25
Cahit Gungor