L'Algorithme de Tri Shiverssort Adaptatif: Articles Scientifiques
L'Algorithme de Tri Shiverssort Adaptatif: Articles Scientifiques
L'Algorithme de Tri Shiverssort Adaptatif: Articles Scientifiques
Introduction
Le problème du tri des données est l’un des problèmes les plus anciens et les plus
étudiés dans le domaine de l’informatique. En effet, l’usage d’algorithmes de tri est
1. Maître de conférences à l’Université Gustave Eiffel, LIGM (UMR 8049), CNRS, ENPC, ESIEE
Paris, UPEM, F77454, Marne-la-Vallée, France.
1024 – Bulletin de la Société informatique de France, numéro 15, avril 2020, pp. 5–21
6 VINCENT JUGÉ ◦◦◦◦••••
omniprésent, puisqu’il est souvent nécessaire en tant que brique de base de nom-
breux autres algorithmes. Par conséquent, dès les années 1940, de multiples algo-
rithmes de tri ont été élaborés, chacun jouissant de diverses propriétés d’optimalité,
concernant aussi bien leur complexité temporelle (et, plus précisément, en nombre
de comparaisons et de déplacements d’éléments requis) que spatiale. Ainsi, chaque
décennie vient avec ses nouveaux algorithmes de tri, chacun utilisant une approche
différente ou consistant en l’adaptation de structures de données dédiées, et ce dans
le but d’améliorer les algorithmes préexistants : on peut ainsi citer le tri fusion [8],
le tri rapide [10], le tri par tas [20], ou encore les algorithmes SmoothSort [6] et
SplaySort [14].
L’un de ces algorithmes est TimSort, inventé en 2002 par Tim Peters [16]. Cet
algorithme a très vite été remarqué pour sa capacité à trier efficacement des don-
nées réelles, et est bientôt devenu un algorithme de tri des bibliothèques standard de
langages de programmation tels que Python et Java. L’irruption d’un tel algorithme
conçu de manière peu ou prou artisanale, et qui affichait cependant de meilleures
performances que bien d’autres algorithmes pourtant considérés comme optimaux, a
donc ravivé l’intérêt pour l’étude des algorithmes de tri.
Comprendre précisément les raisons du succès de TimSort est une tâche de
longue haleine. Ces raisons sont, entre autres, le fait que TimSort soit particulière-
ment adapté à l’architecture des ordinateurs (par exemple dans la gestion du cache)
et à des modèles de distribution de données réalistes. Un tel modèle, qui met en
évidence à quel point TimSort est adapté dès qu’il s’agit de traiter des données
pré-triées, se base sur la notion de sous-suites monotones [3, 7] (ci-après appelées
runs), c’est-à-dire de sous-suites croissantes ou bien strictement décroissantes,
comme illustré en Figure 1.
Outre le fait qu’il s’agisse d’un tri fusion naturel, TimSort inclut également de
nombreuses optimisations, soigneusement conçues et paramétrées en s’appuyant sur
des jeux de tests exhaustifs. Cette méthode de conception a donné à TimSort sa struc-
ture générale, que l’on peut sommairement découper en trois parties peu ou prou
indépendantes : (i) un tri par insertion un peu compliqué, utilisé pour traiter de pe-
tits runs (de longueur 32 ou moins) ; (ii) une stratégie simple pour décider quels
grands runs (de longueur 33 ou plus) fusionner ; (iii) l’usage d’une procédure as-
sez complexe pour fusionner ces runs. Les première et troisième composantes sont
celles qui sont les plus compliquées, et dont les paramètres ont été choisis suite à des
tests empiriques. Comprendre précisément pourquoi elles sont efficaces et comment
les améliorer semble donc difficile. En revanche, la deuxième composante est assez
simple. C’est donc là que repose une grande partie du potentiel de modification et
d’amélioration de TimSort.
Éléments de contexte.
Peu après son invention, l’algorithme TimSort a été largement adopté comme stan-
dard dans de multiples langages de programmation. La communauté scientifique a
donc cherché à répliquer ce succès, en élaborant des algorithmes eux aussi très per-
formants sur des données déjà partiellement ordonnées. Cependant, et en raison de
sa conception artisanale et hors du milieu académique, TimSort s’est avéré moins
facile à analyser que prévu.
Ainsi, on pourrait espérer que TimSort, comme tout algorithme de tri décent, ne
nécessite que O(n log(n)) comparaisons pour trier des tableaux de taille n. De ma-
nière surprenante, ce résultat n’a été démontré qu’en 2015, plus d’une décennie après
que TimSort avait déjà été largement déployé. Pire encore, et à cause de l’absence
d’une analyse théorique et systématique de cet algorithme, certains bugs des implé-
mentations de TimSort dans les langages Java et Python n’ont été découverts que très
récemment [1, 5].
Parallèlement, et depuis l’invention de TimSort, plusieurs algorithmes de tri fusion
naturels ont été élaborés, chacun étant censé jouir de bornes de complexité faciles à
démontrer : ShiversSort [18], MinimalSort [3, 19], α -MergeSort [4], PowerSort [15],
etc. Ces algorithmes ont souvent de nombreuses propriétés communes avec TimSort.
Quelques-unes d’entre elles sont mentionnées dans la Table 1.
Par exemple, et hormis MinimalSort, tous ces algorithmes sont stables. Cela signi-
fie que ces algorithmes préservent l’ordonnancement initial des éléments que l’ordre
considère comme égaux. Il s’agit d’une propriété très importante des tris fusion. En
effet, elle signifie que seuls des runs adjacents seront fusionnés, ce qui permet d’ef-
fectuer ces fusions directement sur le tableau à trier plutôt qu’en utilisant des listes
chaînées. Cette propriété est également cruciale dès lors qu’il s’agit de trier des ta-
bleaux dont les éléments sont de types composites (en Java, il s’agira de tous les
types non primitifs), qui pourraient être triés de deux manières différentes en fonc-
tion de diverses mesures de comparaison.
1024 – Bulletin de la Société informatique de France – numéro 15, avril 2020
8 VINCENT JUGÉ ◦◦◦◦••••
nH ). Afin de les distinguer, il nous faut donc mettre au point des mesures de com-
plexité plus fines. De surcroît, et à l’exception notable de TimSort, il s’avère que tous
ces algorithmes ne sont en fait caractérisés que par la stratégie utilisée pour décider
quels runs fusionner. La sous-routine choisie pour fusionner effectivement deux runs
y est donc implicite, et son implémentation laissée au libre choix du programmeur.
Cette situation nous pousse donc à travailler dans le modèle de coût suivant.
Pour fusionner deux runs adjacents de longueurs m et n, un algorithme de fusion
naïf requiert de l’ordre de m + n comparaisons et déplacements d’éléments. Or, quel
que soit l’algorithme de fusion utilisé, et dans le pire des cas, on aura toujours besoin
de m + n déplacements d’éléments. Par conséquent, dans tout l’article, nous mesure-
rons la complexité des algorithmes en terme de coût des fusions [2, 4, 9, 15] : le coût
d’une fusion entre deux runs de longueurs m et n est défini comme l’entier m + n,
et on identifie la complexité d’un algorithme à la somme des coûts des différentes
fusions que l’algorithme aura exécutées pour trier un tableau.
On peut alors démontrer que le coût des fusions de n’importe quel algorithme de
tri fusion naturel est d’au moins nH , même dans le meilleur des cas. Cette remarque
nous amène à identifier MinimalSort et PowerSort comme les deux seuls algorithmes
dont le coût des fusions est optimal. Et, puisque PowerSort est stable, cet algorithme
est donc un candidat naturel pour succéder à TimSort en tant qu’algorithme de tri
standard dans les langages Python et Java. Néanmoins, et même s’il admet des im-
plémentations similaires à celles de TimSort, la stratégie qu’il utilise pour décider
quels runs fusionner est nettement plus complexe.
La question de l’existence d’un algorithme de tri fusion naturel simple dont la
structure semblable à celle de TimSort et dont le coût des fusions serait optimal (à un
terme additif linéaire en n près) reste donc ouverte.
Une première étape, sur le chemin de cet objectif, consiste à définir une notion
adéquate permettant de caractériser ce qui fait qu’un algorithme est simple et sem-
blable à TimSort. La stratégie de TimSort consiste à découvrir les runs à la volée, à la
manière d’un algorithme glouton, et à les stocker dans une pile : si un run couvre les
éléments en positions i, i + 1, . . . , j dans le tableau, la pile contiendra la paire (i, j).
Puis TimSort ne procède qu’à des fusions entre runs stockés dans le dessus de la pile,
et les décisions qu’il prend ne sont basées que sur les longueurs de ces runs situés
au-dessus de la pile. Procéder ainsi s’avère très avantageux au vu de l’architecture
des ordinateurs actuels, par exemple parce que cela nous permet de tirer largement
parti de l’existence de mémoire cache dans les processeurs.
C’est sur la base de ce constat qu’a été inventée la notion d’algorithme k-
aware [4]. Un tri fusion naturel est k-aware si les runs qu’il fusionne figurent tous
parmi les k premiers runs de la pile, et si les décisions qu’il prend ne sont basées
que sur les longueurs de ces k premiers runs. La 4e colonne de la Table 1 indique
quels algorithmes sont k-aware pour un paramètre k fini, auquel cas elle indique
également la plus petite valeur de k possible.
1024 – Bulletin de la Société informatique de France – numéro 15, avril 2020
10 VINCENT JUGÉ ◦◦◦◦••••
Le lecteur aura pu noter que les cas n◦ 1 et n◦ 2 ont tous deux pour conséquence
la fusion entre les runs R2 et R3 . Nous avons décidé de séparer ces deux cas afin de
simplifier l’analyse de complexité effectuée c.
Présentons maintenant quelques-uns des algorithmes k-aware mentionnés dans la
Table 1. Bien qu’apparentés à ShiversSort adaptatif, ils utilisent une stratégie dif-
férente, que l’on peut en fait obtenir en ne modifiant que la boucle principale de
ShiversSort adaptatif.
Voici tout d’abord l’algorithme TimSort, conçu par Tim Peters [16], et dont la
boucle principale est présentée ci-dessous :
Comme mentionné dans l’introduction, le coût des fusions de TimSort est en
O(n + nH ). Ce résultat repose sur les deux ingrédients suivants : (i) on s’abstient
de fusionner un run nouvellement arrivé au sommet de la pile tant qu’il n’est pas de
taille comparable avec celle des autres runs situés en haut de la pile, et (ii) on main-
tient un invariant (l’inégalité ri + ri+1 6 ri+2 pour tout i > 3) grâce auquel les tailles
des runs, quand on s’enfonce dans les profondeurs de la pile, croissent à vitesse
exponentielle.
1024 – Bulletin de la Société informatique de France – numéro 15, avril 2020
12 VINCENT JUGÉ ◦◦◦◦••••
Notons au passage que le cas ♥ avait été omis dans la version originale de Tim-
Sort, et que cette omission était passée inaperçue en raison de l’absence d’analyse
de complexité de l’algorithme. Cependant, et en l’absence du cas ♥, l’invariant que
TimSort était censé maintenir n’était en fait plus nécessairement vérifié, et la com-
plexité en mémoire de l’algorithme était supérieure à ce qui avait été prévu. Il en a
résulté l’apparition de multiples bugs, liés à des dépassements de mémoire, dans les
implémentations de TimSort en Python et en Java [1, 5].
Par ailleurs, une analyse fine de la complexité de TimSort, dans sa version actuelle,
permet de démontrer que la constante multiplicative cachée dans la notation O est
assez élevée, puisqu’elle est de 50% supérieure à la constante optimale. C’est l’objet
du résultat ci-dessous, démontré dans [1, 4].
Théorème 1. Le coût des fusions de TimSort, sur des tableaux de longueur n et
d’entropie H , est inférieur ou égal à 3/2 nH + O(n). En outre, il existe des ta-
bleaux de longueur n pour lesquels le coût des fusions de TimSort est de l’ordre de
3/2 n log2 (n) + O(n).
Puisque H 6 log2 (n), ces deux bornes coïncident bien l’une avec l’autre. Ainsi,
le coût des fusions de TimSort peut effectivement être de l’ordre de 3/2 nH + O(n),
et ce même dans le cas le plus défavorable, où l’on a déjà H = log2 (n) + O(1).
Afin de faire baisser cette constante de 3/2 à 1, il était donc important de s’inté-
resser à d’autres stratégies de fusion. Dès lors que l’on a un tel objectif en tête, un
premier espoir vient de l’algorithme ShiversSort, inventé par Olin Shivers [18]. On
peut obtenir cet algorithme en omettant les cas n◦ 1 et 2 de ShiversSort adaptatif,
c’est-à-dire en utilisant la boucle principale suivante :
Dans la suite, nous noterons r la longueur d’un run R, et ` l’entier blog2 (r)c. On
dira que ` est le niveau du run R. Nous adapterons librement ces notations en fonc-
tion du nom des runs considérés. Ainsi, nous utiliserons sans autre forme d’aver-
tissement les notations r0 et `0 pour désigner la longueur d’un run R0 et l’entier
blog2 (r0 )c. De surcroît, il nous arrivera fréquemment de nous intéresser aux runs
contenus dans la pile à un moment donné de l’exécution de l’algorithme. On notera
alors (R1 , . . . , Rh ) cette pile et h sa hauteur, le run Rk étant situé en kème position en
partant du haut. Puis, comme précédemment, on notera rk et `k la longueur de Rk et
l’entier blog2 (rk )c.
Forts de ces quelques notations, nous pouvons maintenant démontrer deux
lemmes concernant les niveaux des runs manipulés lors d’une exécution de l’algo-
rithme.
Lemme 4. Lorsque deux runs R et R0 sont fusionnés en un unique run R00 , on a
`0 6 max{`, `0 } + 1.
Démonstration. Sans perte de généralité, on peut supposer que r 6 r0 . On constate
alors que
00 0 0
2` 6 r00 = r + r0 6 2r0 < 2 × 2` +1 6 2` +2 ,
et donc que `00 6 `0 + 1.
Démonstration. Nous allons procéder par récurrence sur le nombre d’étapes (inser-
tions d’un run dans la pile ou fusions de deux runs) déjà effectuées par l’algorithme.
Tout d’abord, si h 6 2, il n’y a rien à démontrer ; cette situation se produit entre autres
au début de l’algorithme.
Démontrons maintenant que, si une pile P = (R1 , . . . , Rh ) satisfait (1) et est trans-
formée en une pile P = (R1 , . . . , Rh ) par une fusion des runs R1 et R2 , ou bien R2
et R3 , ou bien par l’insertion d’un nouveau run R1 , la nouvelle pile P satisfait elle
aussi (1). Pour ce faire, procédons par disjonction de cas :
— Si l’on vient de fusionner deux runs, alors on sait que h = h − 1, que Ri =
Ri+1 pour tout i > 3, et que le run R2 est égal à R3 (si l’on vient de fusionner
R1 et R2 ) ou bien au produit de la fusion des runs R2 et R3 (si ce sont ces
deux runs que l’on vient de fusionner). En vertu du Lemme 4, il s’ensuit que
`2 = `3 ou bien que `2 6 max{`2 , `3 } + 1 = `3 + 1 ; dans tous les cas, on a
bien `2 6 `3 + 1. Par ailleurs, puisque la pile P satisfait (1) on sait déjà que
`3 < `4 = `3 < `4 < . . . < `h . On en déduit que `2 6 `3 + 1 6 `3 , de sorte que
la pile P satisfait elle aussi (1).
1024 – Bulletin de la Société informatique de France – numéro 15, avril 2020
◦◦◦◦•••• L’ALGORITHME DE TRI SHIVERSSORT ADAPTATIF 15
Le Lemme 5 stipule que les longueurs des runs stockés dans la pile augmentent à
vitesse exponentielle (au fur et à mesure que l’on s’éloigne du sommet de la pile) ; la
seule exception éventuelle était le run situé au sommet de la pile, et sur la longueur
duquel nous n’avons aucun contrôle. Comme annoncé en pages 12 et 13, ce genre de
propriété s’est avéré fondamental dans les analyses de complexité d’algorithmes tels
que ShiversSort, TimSort et α -MergeSort.
La démonstration du Théorème 3 consiste dès lors en une évaluation soigneuse
du coût des fusions qu’exécute l’algorithme. À cette fin, nous allons distinguer trois
types de fusions : les fusions équilibrées, entre deux runs R et R0 tels que ` = `0 ; les
fusions déséquilibrées, entre deux runs R et R0 tels que ` 6= `0 et survenues dans la
boucle principale ; enfin, les fusions tardives, entre deux runs R et R0 tels que ` 6= `0
et survenues en dernière ligne de l’algorithme.
Dans la suite, le coût de chaque fusion équilibrée entre deux runs R et R0 sera
alloué équitablement aux éléments des deux runs concernés : chaque élément paiera
un coût de 1, ce qui permettra bien de couvrir le coût total de la fusion, égal à r + r0 .
Au contraire, le coût de chaque fusion déséquilibrée sera intégralement alloué aux
éléments du dernier run R• à avoir été inséré dans la pile : chacun de ses r• éléments
devra payer un coût de (r + r0 )/r• . Enfin, nous verrons comment traiter à peu de frais
le cas des fusions tardives.
Munis de cette politique d’allocation des coûts, nous pouvons dès à présent éva-
luer le coût des fusions équilibrées.
Lemme 6. Le coût total des fusions équilibrées est inférieur ou égal à n(H + 1).
blog2 (n)c − ` = blog2 (n)c − blog2 (r)c 6 log2 (n) − log2 (r) + 1 = log2 (n/r) + 1
fusions équilibrées : chaque élément paiera donc au plus log2 (n/r) + 1 au titre de
ces fusions équilibrées. Par conséquent, si le tableau se décompose initialement en
des runs de longueurs r1 , r2 , . . . , rρ , la somme des coûts des fusions équilibrées est
ρ
inférieure ou égale à ∑i=1 ri (log2 (n/ri ) + 1) = n(H + 1).
1024 – Bulletin de la Société informatique de France – numéro 15, avril 2020
16 VINCENT JUGÉ ◦◦◦◦••••
Nous allons maintenant évaluer le coût des fusions déséquilibrées. Dans ce but,
distinguons également deux types de piles : on dira qu’une pile P = (R1 , . . . , Rh ) est
stable si h 6 1 ou si h > 2 et `1 6 `2 , et qu’elle est instable si h > 2 et `1 > `2 . Les
piles stables jouissent de remarquables propriétés de stabilité (d’où leur nom) et sont
faciles à obtenir, comment le soulignent les deux résultats suivants.
Lemme 7. Soit F une opération de fusion exécutée durant la boucle principale de
ShiversSort adaptatif. Soit P et P les piles obtenues juste avant que F ne soit
exécutée et juste après que F a été exécutée. Si P est une pile stable, alors F est une
fusion équilibrée, et P est stable également.
Démonstration. Soit (R1 , . . . , Rh ) et (R1 , . . . , Rh−1 ) les runs respectivement contenus
dans les piles P et P. Sans perte de généralité, et quitte à ajouter tout en bas de la
pile P un run Rh+1 de longueur 2(r1 + . . . + rh ), qui n’influera ni sur le caractère
stable de P, ni sur la fusion F et le cas qui l’a provoquée, ni sur le fait que (1) soit
vraie, on suppose que h > 3.
Puisque P est stable, (1) nous assure déjà que `1 6 `2 6 `3 . On procède alors par
disjonction de cas, en fonction du cas qui a provoqué la fusion F.
— Si F a été provoquée par l’un des cas n◦ 1 ou 2, c’est que `1 > `3 ou `2 > `3 ,
donc que `1 6 `2 = `3 . Comme F est une fusion entre les runs R2 et R3 , cette
fusion est donc équilibrée. Elle produit un run R2 tel que `2 > `2 et, puisque
R1 = R1 , on en conclut que `1 = `1 6 `2 6 `2 . Cela signifie bien que P est
une pile stable.
— Si F a été provoquée par le cas n◦ 3, c’est que `3 > `1 > `2 , ce sans quoi on
aurait déjà déclenché le cas n◦ 1, et donc que `1 = `2 < `3 . Comme F est une
fusion entre les runs R1 et R2 , cette fusion est donc équilibrée. En outre, en
vertu du Lemme 4, elle produit un run R1 tel que `1 6 `1 + 1. Puisque `1 < `3
et que R2 = R3 , il s’ensuit que `1 6 `3 = `2 . Cela signifie ici aussi que P est
une pile stable.
Lemme 8. Soit F une opération de fusion exécutée durant la boucle principale de
ShiversSort adaptatif, et soit P la pile obtenue juste après que F a été exécutée. Si
F a été provoquée par l’un des cas n◦ 2 ou 3, alors P est une pile stable.
Démonstration. Soit P la pile obtenue juste avant que F ne soit exécutée, et soit
(R1 , . . . , Rh ) et (R1 , . . . , Rh−1 ) les runs respectivement contenus dans les piles P et
P. Comme dans la démonstration du Lemme 7, et quitte à ajouter tout en bas de la
pile P un run Rh+1 de longueur 2(r1 + . . . + rh ), on suppose que h > 3.
Tout d’abord, si P est stable, le Lemme 7 nous assure déjà que P est stable elle
aussi. On suppose donc ci-dessous que P est instable et que F n’a pas été provoquée
par le cas n◦ 1, ce qui signifie que `2 < `1 < `3 . Dans ces conditions, la fusion F n’a
pu être provoquée que par le cas n◦ 3. Ainsi, R1 est le produit de la fusion entre les
1024 – Bulletin de la Société informatique de France – numéro 15, avril 2020
◦◦◦◦•••• L’ALGORITHME DE TRI SHIVERSSORT ADAPTATIF 17
Par conséquent, les seules fusions déséquilibrées que doivent payer les éléments
de R sont certaines des k − 1 fusions initiales, provoquées par le cas n◦ 1, ainsi que
l’éventuelle fusion entre R1 et R2 qui aura pu s’ensuivre. Le coût total de ces fusions
déséquilibrées est donc inférieur ou égal à C0 = C + 2r.
Or, puisque l’on a vu que `1 < . . . < `h et que `k 6 `, on sait en fait que
ri < 2`i +1 6 2`+i+1−k 6 2i+1−k r pour tout i 6 k. On en déduit que
k k k
C 6 ∑ (k + 1 − i)ri 6 ∑ (k + 1 − i)2i+1−k r 6 ∑ (k + 1 − i)2i+1−k r = 8r,
i=1 i=1 i=−∞
ce qui conclut.
Implémentation en pratique.
Au vu de la présentation de ShiversSort adaptatif en page et des implémentations
actuelles de TimSort en Python et en Java, trois questions se posent naturellement.
(1) Quelle est la taille maximale de la pile P ?
Au vu de l’invariant (1), on sait que, dans une pile P de taille h > 3, on a
`h > `3 + (h − 3) > h − 3. Puisque `h 6 log2 (n), on en déduit que la pile P
sera de taille h 6 log2 (n) + 3.
1024 – Bulletin de la Société informatique de France – numéro 15, avril 2020
20 VINCENT JUGÉ ◦◦◦◦••••
En pratique, puisque les petits runs sont déjà fusionnés par une méthode
ad hoc, les tailles des piles déjà utilisées en Python et en Java sont en fait
suffisantes.
(2) On peut manifestement fusionner les cas n◦ 1 et 2 ; peut-on faire mieux ?
Il se trouve que la réponse est positive. En effet, on peut montrer que, à tout
moment de l’exécution de l’algorithme, les prochains runs fusionnés seront les
runs les plus à gauche (disons Ri et Ri+1 ) tels que `i 6 max{`i+1 , `i+2 } ; ici, par
convention, on a posé `ρ+1 = +∞, de sorte qu’un tel entier i existe toujours.
Par conséquent, on peut en fait omettre le cas n◦ 3, et ce sans changer les
fusions qu’exécutera l’algorithme ni l’ordre dans lequel elles seront exécutées.
(3) Comment tester efficacement si `i 6 ` j ?
Au vu de la remarque précédente, il nous faudrait en fait tester si
`i 6 max{`i+1 , `i+2 }. Dans ce cas, il suffit en fait d’exécuter le programme
suivant, dont on laisse au lecteur le plaisir de démontrer qu’il nous fournit un
résultat correct.
Toutes ces remarques illustrent la facilité que l’on aurait à remplacer l’utilisation
de TimSort par celle de ShiversSort adaptatif. Ainsi, parmi les 908 lignes de l’im-
plémentation actuelle de TimSort en Java (version 13), il suffirait de remplacer les
lignes 405 à 412 par le code minimaliste suivant :
int n = stackSize - 3;
int x = runLen [n +1] | runLen [n +2];
if (n < 0 || ((~ x) & runLen [n ]) > x) {
break;
}
Références
[1] Nicolas Auger, Vincent Jugé, Cyril Nicaud et Carine Pivoteau. On the worst-case complexity of Tim-
sort. 26th Annual European Symposium on Algorithms (ESA), LIPIcs vol. 112, pages 4 :1–13, 2018.
[2] Nicolas Auger, Cyril Nicaud et Carine Pivoteau. Merge strategies : from merge sort to Timsort. Rap-
port technique HAL : hal-01212839, 2015.
[3] Jérémy Barbay et Gonzalo Navarro. On compressing permutations and adaptive sorting. Theoretical
Computer Science, pages 513 :109–123, 2013.
[4] Sam Buss et Alexander Knop. Strategies for stable merge sorting. 30th Annual ACM-SIAM Sympo-
sium on Discrete Algorithms (SODA), pages 1272–1290. SIAM, 2019.
[5] Stijn De Gouw, Jurriaan Rot, Frank de Boer, Richard Bubel et Reiner Hähnle. OpenJDK’s
Java.utils.Collection.sort() is broken : The good, the bad and the worst case. 27th International Confe-
rence on Computer Aided Verification (CAV), pages 273–289. Springer, 2015.
[6] Edsger Dijkstra. Smoothsort, an alternative for sorting in situ. Theoretical Foundations of Program-
ming Methodology, pages 3–17. Springer, 1982.
[7] Vladmir Estivill-Castro et Derick Wood. A survey of adaptive sorting algorithms. ACM Computing
Surveys, vol. 24, issue 4, pages 441–476, 1992.
[8] Herman Goldstine et John von Neumann. Planning and coding of problems for an electronic compu-
ting instrument. 1947.
[9] Mordecai Golin et Robert Sedgewick. Queue-mergesort. Information Processing Letters, vol. 48, is-
sue 5, pages 253–259, 1993.
[10] Tony Hoare. Algorithm 64 : Quicksort. Communications of the ACM, vol. 4, issue 7, page 321, 1961.
[11] Vincent Jugé. Adaptive Shivers sort : an alternative sorting algorithm. 31th Annual ACM-SIAM
Symposium on Discrete Algorithms (SODA). SIAM, 2020.
[12] Donald Knuth. The Art of Computer Programming, Volume 3 (2nd ed.) – Sorting and Searching.
Addison Wesley Longman Publish. Co., Redwood City, CA, USA, 1998.
[13] Heikki Mannila. Measures of presortedness and optimal sorting algorithms. IEEE Transactions on
Computers, vol. 34, issue 4, pages 318–325, 1985.
[14] Alistair Moffat, Gary Eddy et Ola Petersson. Splaysort : Fast, versatile, practical. Software : Practice
and Experience, vol. 26, issue 7, pages 781–797, 1996.
[15] J. Ian Munro et Sebastian Wild. Nearly-optimal mergesorts : Fast, practical sorting methods that
optimally adapt to existing runs. 26th Annual European Symposium on Algorithms (ESA 2018), LIPIcs
vol. 112, pages 63 :1–15, 2018.
[16] Tim Peters. TimSort description. http://svn.python.org/projects/python/trunk/Objects/
listsort.txt.
[17] Claude Shannon. A mathematical theory of communication. Bell system technical journal, vol. 27,
issue 3, pages 379–423, 1948.
[18] Olin Shivers. A simple and efficient natural merge sort. Rapport de recherche, Georgia Institute of
Technology, 2002.
[19] Tadao Takaoka. Partial solution and entropy. 34th International Symposium on Mathematical Foun-
dations of Computer Science (MFCS), pages 700–711. Springer Berlin Heidelberg, 2009.
[20] John Williams. Algorithm 232 : Heapsort. Communications of the ACM, vol. 7, pages 347–348,
1964.