Coquillage - Shellsort

Coquillage
Visualisation étape par étape de Shellsort
Shellsort avec interstices 23, 10, 4, 1 en action
Classer Algorithme de tri
Structure de données Déployer
Performances dans le pire des cas O( n 2 ) (la plus mauvaise séquence d'espacement connue dans le pire des cas)
O( n log 2 n ) (la plus mauvaise séquence d'espacement connue dans le pire des cas)
Le meilleur cas la performance O( n log n ) (la plupart des séquences de lacunes)
O( n log 2 n ) (la plus mauvaise séquence de lacunes connue)
Performances moyennes dépend de la séquence d'espacement
Complexité spatiale dans le pire des cas О( n ) total, O(1) auxiliaire
Les étapes de Shellsort.
Échanger des paires d'articles dans les étapes successives de Shellsort avec les écarts 5, 3, 1

Shellsort , également connu sous le nom de Shell sort ou méthode de Shell , est un tri par comparaison sur place . Il peut être vu comme étant soit une généralisation du tri par échange ( tri à bulles ) ou le tri par insertion ( tri par insertion ). La méthode commence par trier des paires d'éléments très éloignées les unes des autres, puis réduit progressivement l'écart entre les éléments à comparer. En commençant par des éléments éloignés, il peut déplacer certains éléments déplacés en position plus rapidement qu'un simple échange de voisins les plus proches. Donald Shell a publié la première version de ce type en 1959. Le temps d'exécution de Shellsort dépend fortement de la séquence d'intervalles qu'il utilise. Pour de nombreuses variantes pratiques, la détermination de leur complexité temporelle reste un problème ouvert .

La description

Shellsort est une optimisation du tri par insertion qui permet l'échange d'éléments éloignés les uns des autres. L'idée est d'arranger la liste des éléments de sorte que, en commençant n'importe où, en prenant chaque h ième élément, on produise une liste triée. Une telle liste est dite h- triée. Il peut également être considéré comme h listes entrelacée, toutes triées individuellement. Commencer par de grandes valeurs de h permet aux éléments de se déplacer sur de longues distances dans la liste d'origine, ce qui réduit rapidement de grandes quantités de désordre et laisse moins de travail pour les étapes de tri h plus petites . Si la liste est ensuite triée en k pour un entier plus petit k , alors la liste reste triée en h . Suivre cette idée pour une séquence décroissante de valeurs h se terminant par 1 est garanti pour laisser une liste triée à la fin.

En termes simplistes, cela signifie que si nous avons un tableau de 1024 nombres, notre premier écart ( h ) pourrait être 512. Nous parcourons ensuite la liste en comparant chaque élément de la première moitié à l'élément de la seconde moitié. Notre deuxième écart ( k ) est de 256, ce qui divise le tableau en quatre sections (à partir de 0,256,512,768), et nous nous assurons que les premiers éléments de chaque section sont triés les uns par rapport aux autres, puis le deuxième élément de chaque section, et ainsi de suite . En pratique, la séquence d'écarts peut être n'importe quoi, mais le dernier écart est toujours 1 pour terminer le tri (finissant effectivement avec un tri par insertion ordinaire).

Un exemple d'exécution de Shellsort avec les écarts 5, 3 et 1 est présenté ci-dessous.

un 1 un 2 un 3 un 4 un 5 un 6 un 7 un 8 un 9 un 10 un 11 un 12
Des données d'entrée 62 83 18 53 07 17 95 86 47 69 25 28
Après 5 tris 17 28 18 47 07 25 83 86 53 69 62 95
Après 3 tris 17 07 18 47 28 25 69 62 53 83 86 95
Après 1-tri 07 17 18 25 28 47 53 62 69 83 86 95

La première passe, 5-tri, effectue un tri par insertion sur cinq sous-tableaux distincts ( a 1 , a 6 , a 11 ), ( a 2 , a 7 , a 12 ), ( a 3 , a 8 ), ( a 4 , a 9 ), ( un 5 , un 10 ). Par exemple, il change le sous-tableau ( a 1 , a 6 , a 11 ) de (62, 17, 25) à (17, 25, 62). La passe suivante, 3-tri, effectue un tri par insertion sur les trois sous-tableaux ( a 1 , a 4 , a 7 , a 10 ), ( a 2 , a 5 , a 8 , a 11 ), ( a 3 , a 6 , un 9 , un 12 ). La dernière passe, 1-tri, est un tri par insertion ordinaire de l'ensemble du tableau ( a 1 ,..., a 12 ).

Comme l'illustre l'exemple, les sous-tableaux sur lesquels Shellsort opère sont initialement courts ; plus tard, ils sont plus longs mais presque commandés. Dans les deux cas, le tri par insertion fonctionne efficacement.

Shellsort n'est pas stable : il peut changer l'ordre relatif des éléments de valeurs égales. Il s'agit d'un algorithme de tri adaptatif dans la mesure où il s'exécute plus rapidement lorsque l'entrée est partiellement triée.

Pseudocode

Utilisation de la séquence d'espacement de Marcin Ciura, avec un tri par insertion interne.

# Sort an array a[0...n-1].
gaps = [701, 301, 132, 57, 23, 10, 4, 1]  // Ciura gap sequence

# Start with the largest gap and work down to a gap of 1
foreach (gap in gaps)
{
    # Do a gapped insertion sort for this gap size.
    # The first gap elements a[0..gap-1] are already in gapped order
    # keep adding one more element until the entire array is gap sorted
    for (i = gap; i < n; i += 1)
    {
        # add a[i] to the elements that have been gap sorted
        # save a[i] in temp and make a hole at position i
        temp = a[i]
        # shift earlier gap-sorted elements up until the correct location for a[i] is found
        for (j = i; j >= gap and a[j - gap] > temp; j -= gap)
        {
            a[j] = a[j - gap]
        }
        # put temp (the original a[i]) in its correct location
        a[j] = temp
    }
}

Séquences d'espacement

La question de décider quelle séquence d'espacement utiliser est difficile. Chaque séquence d'espaces contenant 1 donne un tri correct (car cela fait de la passe finale un tri par insertion ordinaire) ; cependant, les propriétés des versions ainsi obtenues de Shellsort peuvent être très différentes. Trop peu d'écarts ralentissent les passes, et trop d'écarts produisent un surcoût.

Le tableau ci-dessous compare la plupart des séquences de lacunes proposées publiées à ce jour. Certains d'entre eux ont des éléments décroissants qui dépendent de la taille du tableau trié ( N ). D'autres sont des séquences infinies croissantes, dont les éléments inférieurs à N doivent être utilisés dans l'ordre inverse.

OEIS Terme général ( k ≥ 1) Lacunes en béton
Complexité temporelle dans le pire des cas
Auteur et année de publication
[par exemple quand N = 2 p ] Coquille , 1959
Frank & Lazare, 1960
A000225 Hibbard , 1963
A083318 , préfixé par 1 Papernov & Stasevitch, 1965
A003586 Chiffres successifs de la forme ( 3- chiffres lisses ) Pratt , 1971
A003462 , pas plus de Knuth , 1973, d'après Pratt , 1971
A036569 Incerpi & Sedgewick , 1985, Knuth
A036562 , préfixé par 1 Sedgewick, 1982
A033622 Sedgewick, 1986
Inconnu Gonnet & Baeza-Yates , 1991
A108870 Inconnu Tokuda, 1992
A102549 Inconnu (dérivé expérimentalement) Inconnu Ciura, 2001

Lorsque la représentation binaire de N contient de nombreux zéros consécutifs, Shellsort utilisant la séquence d'écarts d'origine de Shell effectue des comparaisons Θ( N 2 ) dans le pire des cas. Par exemple, ce cas se produit pour N égal à une puissance de deux lorsque des éléments plus grands et plus petits que la médiane occupent respectivement des positions impaires et paires, puisqu'ils ne sont comparés qu'à la dernière passe.

Bien qu'il ait une complexité plus élevée que le O ( N  log  N ) qui est optimal pour les tris de comparaison, la version de Pratt se prête aux réseaux de tri et a la même complexité de porte asymptotique que le trieur bitonique de Batcher .

Gonnet et Baeza-Yates ont observé que Shellsort fait le moins de comparaisons en moyenne lorsque les ratios d'écarts successifs sont à peu près égaux à 2,2. C'est pourquoi leur séquence de ratio 2,2 et la séquence de Tokuda de ratio 2,25 s'avèrent efficaces. Cependant, on ne sait pas pourquoi il en est ainsi. Sedgewick recommande d'utiliser des espaces qui ont des plus grands diviseurs communs faibles ou qui sont deux à deux premiers entre eux .

En ce qui concerne le nombre moyen de comparaisons, la séquence de Ciura a les performances les plus connues ; les écarts de 701 n'ont pas été déterminés mais la séquence peut être encore étendue selon la formule récursive .

La séquence de Tokuda, définie par la formule simple , où , , peut être recommandée pour des applications pratiques.

Si la taille d'entrée maximale est petite, comme cela peut se produire si Shellsort est utilisé sur de petits sous-tableaux par un autre algorithme de tri récursif tel que quicksort ou merge sort , il est alors possible de tabuler une séquence optimale pour chaque taille d'entrée.

Complexité de calcul

La propriété suivante est vérifiée : après le tri en h 2 de tout tableau trié en h 1 , le tableau reste trié en h 1 . Chaque tableau trié en h 1 et trié en h 2 est également trié ( a 1 h 1 + a 2 h 2 ), pour tout entier non négatif a 1 et a 2 . La complexité au pire des cas de Shellsort est donc liée au problème de Frobenius : pour des entiers donnés h 1 ,..., h n avec pgcd = 1, le nombre de Frobenius g ( h 1 ,..., h n ) est le plus grand entier qui ne peut pas être représenté par un 1 h 1 + ... + a n h n avec un entier non négatif a 1 ,..., a n . En utilisant des formules connues pour les nombres de Frobenius, nous pouvons déterminer la complexité dans le pire des cas de Shellsort pour plusieurs classes de séquences de lacunes. Les résultats prouvés sont indiqués dans le tableau ci-dessus.

En ce qui concerne le nombre moyen d'opérations, aucun des résultats prouvés ne concerne une séquence d'intervalles pratique. Pour les écarts qui sont des puissances de deux, Espelid a calculé cette moyenne comme . Knuth a déterminé que la complexité moyenne du tri d'un tableau à N éléments avec deux écarts ( h , 1) était . Il s'ensuit qu'un Shellsort à deux passes avec h = ( N 1/3 ) fait en moyenne O ( N 5/3 ) comparaisons/inversions/temps d'exécution. Yao a trouvé la complexité moyenne d'un Shellsort à trois passes. Son résultat a été affiné par Janson et Knuth : le nombre moyen de comparaisons/inversions/temps d'exécution effectué lors d'un Shellsort à trois écarts ( ch , cg , 1), où h et g sont premiers entre eux, est dans la première passe, dans la seconde passe et dans la troisième passe. ψ ( h , g ) dans la dernière formule est une fonction complexe égale à asymptotiquement . En particulier, lorsque h = ( N 7/15 ) et g = Θ( N 1/5 ), le temps moyen de tri est O ( N 23/15 ).

Sur la base d'expériences, on suppose que Shellsort avec la séquence de lacunes de Hibbard s'exécute en un temps moyen de O ( N 5/4 ), et que la séquence de Gonnet et Baeza-Yates nécessite en moyenne 0,41 N  ln  N  (ln ln  N  + 1/6 ) l'élément se déplace. Les approximations du nombre moyen d'opérations précédemment proposées pour d'autres séquences échouent lorsque les tableaux triés contiennent des millions d'éléments.

Le graphique ci-dessous montre le nombre moyen de comparaisons d'éléments dans diverses variantes de Shellsort, divisé par la limite inférieure théorique, c'est-à-dire log 2 N !, où la séquence 1, 4, 10, 23, 57, 132, 301, 701 a été étendue selon la formule .

Shell trier le nombre moyen de comparaisons (anglais).svg

En appliquant la théorie de la complexité de Kolmogorov , Jiang, Li et Vitányi ont prouvé la borne inférieure suivante pour l'ordre du nombre moyen d'opérations/durée d'exécution dans un Shellsort p -pass : Ω( pN 1+1/ p ) lorsque p  ≤ log 2 N et ( pN ) lorsque p  > log 2 N . Par conséquent, Shellsort a des perspectives de fonctionner dans un temps moyen qui augmente asymptotiquement comme N log N uniquement lors de l'utilisation de séquences d'espaces dont le nombre d'espaces augmente proportionnellement au logarithme de la taille du tableau. On ne sait cependant pas si Shellsort peut atteindre cet ordre asymptotique de complexité moyenne des cas, qui est optimal pour les tris par comparaison. La limite inférieure a été améliorée par Vitányi pour chaque nombre de passes à où . Ce résultat implique par exemple la borne inférieure de Jiang-Li-Vitányi pour toutes les séquences d'incréments de passage et améliore cette borne inférieure pour des séquences d'incréments particulières. En fait, toutes les bornes (inférieure et supérieure) actuellement connues pour le cas moyen correspondent précisément à cette borne inférieure. Par exemple, cela donne le nouveau résultat que la limite supérieure de Janson-Knuth correspond à la limite inférieure résultante pour la séquence d'incréments utilisée, montrant que Shellsort à trois passes pour cette séquence d'incréments utilise des comparaisons/inversions/temps d'exécution. La formule nous permet de rechercher des séquences d'incréments qui donnent des limites inférieures inconnues ; par exemple une séquence d'incréments pour quatre passes dont la borne inférieure est supérieure à celle de la séquence d'incréments . La borne inférieure devient

La complexité dans le pire des cas de n'importe quelle version de Shellsort est d'un ordre supérieur : Plaxton, Poonen et Suel ont montré qu'elle croît au moins aussi rapidement que .

Applications

Shellsort effectue plus d'opérations et a un taux d' échec de cache plus élevé que quicksort . Cependant, comme elle peut être implémentée en utilisant peu de code et n'utilise pas la pile d'appels , certaines implémentations de la fonction qsort dans la bibliothèque standard C destinée aux systèmes embarqués l' utilisent à la place de quicksort. Shellsort est, par exemple, utilisé dans la bibliothèque uClibc . Pour des raisons similaires, dans le passé, Shellsort était utilisé dans le noyau Linux .

Shellsort peut également servir de sous-algorithme de tri introspectif , pour trier des sous-tableaux courts et éviter un ralentissement lorsque la profondeur de récursivité dépasse une limite donnée. Ce principe est utilisé, par exemple, dans le compresseur bzip2 .

Voir également

Les références

Bibliographie

Liens externes