Les algorithmes de tri de complexité font leur travail uniquement en comparant les éléments du domaine deux à deux. On peut prouver que cette complexité est optimale pour ce type d'algorithmes.
Pour faire mieux, il faut donc abandonner la simple comparaison. Si le domaine est relativement petit et relativement compact, on peut faire correspondre les éléments du domaine à une suite d'entiers telle que la différence entre le plus grand et le plus petit est relativement petite. Autrement dit, on ne doit pas avoir trop de trous parmi les entiers de cet intervalle, à savoir des entiers qui ne correspondent pas à un élément du domaine.
Un exemple relativement typique est une application nécessitant de trier des personnes vivantes par année de naissance. Ici, il y a rarement plus de 100 ans de différence entre le dernier et le premier né et jamais plus de 200. C'est un exemple de domaine très compact.
L'algorithme de tri que nous allons présenter s'appelle tri par casier (en Anglais : bucket sort). C'est un algorithme qui nécessite un peu d'espace mémoire supplémentaire pour faire son travail. Si la séquence à trier est une liste, il nécessite un vecteur temporaire de la taille du domaine à trier, dans notre exemple, environ 100. Si la séquence est un vecteur, il nécessite deux vecteurs temporaires de cette taille. C'est souvent un prix raisonnable à payer pour un algorithme très rapide.
L'algorithme sur les listes contient quatres étapes :
Toutes ces étapes ont clairement une complexité .
Voici le code :
triparcasier.lisp
L'exemple suivant montre la différence en temps d'exécution entre ce code et la fonction primitive sort sur une liste de taille ne contenant que des éléments 5.
Pour trier un vecteur, les choses se compliquent un peu. Il est bien sûr possible de convertir le vecteur en liste, puis de trier la liste, et enfin re-convertir la liste en vecteur. Cette méthode est souvent plus rapide que trier le vecteur avec un algorithme :
Mais, on peut faire mieux. En effet, le problème de cette solution est la quantité de mémoire allouée (mais on remarque que sort alloue de la mémoire aussi). De plus, cette mémoire correspond à des cellules de listes, qui doivent être parcourues par le ramasse-miettes. Nous allons montrer comment s'en sortir avec deux vecteurs temporaires de la taille du domaine.
Pour comprendre cette technique, imaginons que le vecteur à trier soit celui indiqué dans la figure 24.5.
Comme pour les listes, la première étape consiste à calculer le plus petit et le plus grand éléments. Dans notre exemple, le plus petit élément est 2 et le plus grand 7.
La deuxième étape calcule le nombre d'éléments de chaque valeur du domaine. Dans notre exemple, il y a éléments de valeur 2, éléments de valeur 3, éléments de valeur 4, éléments de valeur 5, éléments de valeur 6 et éléments de valeur 7.
Puis, on crée deux vecteurs de la taille du domaine qui indiquent autant d'intervalles que d'éléments du domaine. Chaque intervalle signifie le début et la fin des indices dans le vecteur où seront ultérieurement stockés les éléments correspondant à l'intervalle. La figure 24.6 montre ces deux vecteurs pour notre exemple.
Finalement, les éléments du vecteur sont déplacés vers leurs places définitives. Pour cela, chaque intervalle calculé pendant l'étape précédente est considéré à la fois comme un endroit où sont stockés des éléments non encore à leur place et comme un espace libre pour stocker les éléments à leur place définitive. À chaque itération de cette étape, un élément non encore à sa place est considéré. Sa place finale est calculée par le vecteur de gauche à l'indice donné par l'élément même. L'élément est échangé avec celui qui occupait sa place finale et le début de l'intervalle est incrémenté. Cette procédure se poursuit tant qu'il existe un intervalle non vide. Les premières itérations de cette étape sont illustrées par les figures 24.7 et 24.8.
La complexité de cet algorithme est clairement , car chaque itération est de complexité et à chaque itération, un élément est positionné à sa place finale.
Voici le code :
triparcasier-vecteur.lisp
Afin d'apprécier la différence de performance, voici un exemple de tri d'un vecteur contenant un million d'éléments, d'abord avec la fonction sort prédéfinie, puis avec la fonction définie ci-dessus :
La fonction de tri linéaire est plus de fois plus rapide que la fonction sort prédéfinie.
Irene DURAND