Algorithmes de tri
Imaginons un tableau de n nombres dans un ordre quelconque. Comment faire pour le trier de façon systématique ? Attention, on ne veut pas créer un nouveau tableau, cela risquerait d'utiliser trop de place en mémoire, il faut juste modifier le tableau existant. On appelle cela un tri sur place.
Il existe de nombreux algorithmes de tris, étudions certains de ces algorithmes « simples » mais peu efficaces (donc rarement utilisé en pratique).
Tri par sélection

Une façon simple de trier un paquet de cartes consiste à séparer le paquet en deux parties : une partie déjà triée et un partie qui reste à trier. A chaque étape, on choisit la plus petite carte parmi celles qui reste à trier et on l'échange avec la première d'entre elles. La partie déjà triée augmente alors d'une carte et on recommence. C'est l'idée du tri par sélection.
Algorithme
L'algorithme du tri par sélection pour trier un tableau T de n valeurs consiste donc à itérer sur les positions i du tableau de la position 0 jusqu'à n (exclus) :
-
Le sous-tableau
T[:i]contenant les valeurs deTjusqu'à l'indicei(exclus) est déjà trié. -
On cherche
i_min, l'indice du minimum dans le sous-tableauT[i:]qui contient les autres valeurs qui ne sont pas encore triées. -
On échange
T[i]etT[i_min].

Programme Python
Traduit en Python, l'algorithme peut écrire de la façon suivante :
def tri_selection(T):
"""Trie le tableau T en place par sélection"""
n = len(T)
for i in range(n): # on suppose T trié jusqu'à i exclus
# On cherche l'indice de la plus petite valeur après i
i_min = i
for j in range(i+1, n):
if T[j] < T[i_min]:
i_min = j
# On échange la valeur en i avec la plus petite valeur trouvée
T[i], T[i_min] = T[i_min], T[i]
return T
Quelques remarques :
-
On peut bien sûr écrire
for i in range(n-1)puisque à la fin de la boucle le dernier élément (positionn-1) est de toute façon le plus grand, inutile de continuer à trier. -
On peut aussi écrire
for j in range(i, n), ce qui ne change rien puisque la conditionif T[j] < T[i_min]est fausse. -
Python permet l'échange des deux valeurs en une seule instruction :
T[i], T[i_min] = T[i_min], T[i]. Dans d'autres langages où ce n'est pas possible il faut passer par une variable temporaire :temp = T[i]; T[i] = T[i_min]; T[i_min] = temp; -
La dernière instruction
return Tn'est pas nécessaire carTest de typelist, un type muable, donc sa valeur est modifiée.
Terminaison
Les boucles for terminent toujours donc on sait que le tri par sélection termine, inutile de chercher un variant de boucle dans ce cas.
Correction
L'invariant de boucle du tri par sélection est le suivant : « Tous les éléments jusqu'à i (exclus) sont triés en ordre croissant et inférieurs à tous les éléments de i à la fin ».
On va faire un raisonnement par récurrence pour vérifier que cet invariant est correct :
-
Initialisation : il est clairement vérifié au départ puisqu'il n'y a aucun élément avant l'indice
0. -
Conservation : supposons qu'il est vérifié jusqu'à une valeur de
i: Tous les éléments jusqu'ài(exclus) sont triés en ordre croissant et inférieurs à tous les éléments deià la fin. Le prochain passage dans la bouclefor i in range(n)consiste à chercher la plus petite valeur entre tous les éléments deijusqu’à la fin et de la mettre en positioni. Cette nouvelle valeur mise eniest bien supérieure à tous les éléments qui la précèdent et inférieure à tous ceux qui la suivent. On peut toujours dire que « tous les éléments jusqu'ài+1(exclus) sont triés en ordre croissant et inférieurs à tous les éléments dei+1à la fin », l'invariant est donc toujours vérifié. -
Terminaison : une fois que la boucle
for i in range(n)termine, l'invariant de boucle nous assure que tous les éléments jusqu'àn(exclus) sont triés en ordre croissant. On a donc bien prouvé la correction de l'algorithme.
Cet invariant prouve la correction de l'algorithme : si l'invariant est vrai et que la boucle termine, alors le tableau complet est trié.
Complexité
Étudions maintenant la complexité (ou coût) temporelle de l'algorithme pour un tableau de grande taille \(n\). La boucle for i in range(n) se répète donc \(n\) fois. Et à chacune de ces répétitions, la boucle for j in range(i+1, n) va répéter \(n-1\) fois uen comparaison (if T[j] < T[i_min]) et éventuellement une affectation (i_min = j) quand i vaut 0, puis répéter \(n-2\) fois la comparaison quand i vaut 1, puis \(n-3\) quand i vaut 2, etc., jusqu'à ne plus s'exécuter quand i vaut n - 1. Au total, le tri par sélection effectue \((n-1) + (n-2) + (n-3) + ... + 2 + 1\) = \(n \times (n-1) \over 2\) comparaisons et quelques affectations supplémentaires. Seul l'ordre de grandeur nous intéresse ici et on voit bien que c'est de l'ordre de \(n \times n\), autrement dit que la complexité du tri par sélection est quadratique en \(O(n^2)\).
Cours
Tri par Selection :
-
Algorithme : À chaque étape, on cherche le plus petit élément parmi les éléments restants à trier et on l'échange avec la valeur en première position des éléments à trier.
-
Terminaison : Les boucles
forterminent. -
Correction/Invariant de boucle : Les éléments jusqu'à
i(exclus) sont triés en ordre croissant et inférieurs à tous les éléments deià la fin. -
Complexité (ou coût) : Quadratique, en O(n²).
Tri par insertion

Les joueurs de cartes utilisent souvent le tri par insertion de façon naturelle quand ils trient leurs cartes en main en les prenant une par une de la gauche vers la droite et en plaçant chaque carte à sa place entre celles qui sont déjà triées sur la gauche.
Algorithme
L'algorithme du tri par insertion pour trier un tableau T de n valeurs consiste donc à itérer sur les positions i du tableau de la position 0 jusqu'à n (exclus) :
-
Le sous-tableau
T[:i], contenant les valeurs deTjusqu'à l'indicei(exclus), est déjà trié. -
On cherche à insérer la valeur
valeur_insertion = T[i]à sa place dans la partie triéeT[:i]. -
On commence à
j = i. -
Tant que
T[j-1] > valeur_insertion(et quej > 0) :-
On décale
T[j-1]d’une position vers la droite :T[j] = T[j-1]. -
On continue vers la gauche :
j = j - 1.
-
-
On insère
valeur_insertionà sa position enj.

Programmation Python
Traduit en Python, l'algorithme peut écrire de la façon suivante :
def tri_insertion(T):
"""Trie le tableau T en place par insertion"""
n = len(T)
for i in range(n): # On suppose T trié jusqu'à i exclu
valeur_insertion = T[i]
# On décale les éléments plus grands que valeur_insertion
j = i
while j > 0 and valeur_insertion < T[j - 1] :
T[j] = T[j - 1]
j = j - 1
# On insère valeur_insertion à sa place
T[j] = valeur_insertion
return T
Quelques remarques :
-
On peut écrire
for i in range(1, n):puisque la première valeur du tableau,T[0], ne peut pas être insérée avant. -
Attention à ne pas oublier la condition
j > 0pour ne pas repartir à la fin du tableau enT[-1],T[-2], etc. -
La dernière instruction
return Tn'est pas nécessaire carTest de typelist, un type muable, donc sa valeur est modifiée.
Terminaison
La boucle externe for i in range(n) termine. La boucle interne while j > 0 ... décrémente j à chaque itération et s'arrête dès que j <= 0 ou que valeur_insertion >= T[j - 1]. Ici j est un variant de boucle qui décroît de 1 à chaque itération, la condition j <= 0 sera nécessairement atteinte. Donc l'algorithme termine toujours
Correction
L'invariant de boucle est le suivant : « À la fin de chaque itération de la boucle externe for i in range(n), le sous-tableau T[:i] contient les mêmes éléments que le sous-tableau initial triés par ordre croissant ».
On va faire un raisonnement par récurrence pour vérifier que cet invariant est correct :
-
Initialisation : il est clairement vérifié au départ, puisqu'un tableau vide est trié.
-
Conservation : si l'invariant est vrai avant l'itération
i, alors après l'insertion deT[i]à sa place dansT[:i](qui est trié), le sous-tableauT[:i+1]est trié. -
Terminaison : quand la boucle termine, le sous-tableau T[:n], c'est-à-dire tout le tableau, est trié
Cet invariant prouve la correction de l'algorithme : si l'invariant est vrai et que la boucle termine, alors le tableau complet est trié.
Complexité
Dans le pire des cas, le tableau est trié en ordre décroissant. À chaque itération de la boucle for i in range(n), on doit décaler tous les éléments pour placer T[i] tout au début du tableau, on effectue donc i comparaisons et décalages. On répète cette opération pour toutes les valeurs de i allant de 0 à n-1, le nombre total d'opérations est alors : \(0 + 1 + 2 + 3 + ... + (n-1)\) = \({n \times (n-1)} \over 2\). La complexité du tri par insertion est quadratique en \(O(n^2)\)..
Par contre, dans le meilleur des cas, quand le tableau est déjà trié, la boucle interne ne s'exécute jamais et donc la complexité est linéaire ou en \(O(n)\), c'est donc bien meilleur que le tri par sélection.
Cours
Tri par insertion:
Algorithme : À chaque étape, on insère un élément à sa place dans la partie triée en décalant les éléments plus grands vers la droite.
Terminaison : La boucle for termine. Le variant de boucle j assure que la boucle while termine.
Correction/Invariant de boucle : Les éléments jusqu'à i (exclus) sont triés en ordre croissant et inférieurs à tous les éléments de i à la fin.
Complexité (ou coût) : Quadratique, en O(n²) en moyenne mais linéaire en O(n) dans le meilleur des cas.
Comparaison
On peut noter que les deux tris par sélection et par insertion ont tous les deux une complexité quadratique, mais que le tri par insertion est plus efficace dans le cas d'un tableau déjà trié ou presque trié. Par contre, dans le cas d'un tableau en désordre, le tri par insertion à le désavantage de faire beaucoup plus d'échanges de valeurs dans le tableau quand on décale les éléments pour insérer une valeur à sa place.
| Critère | Tri par sélection | Tri par insertion |
|---|---|---|
| Complexité moyenne | O(n²) | O(n²) |
| Complexité meilleur cas | O(n²) | O(n) |
| Nombre d'échanges | O(n) | O(n²) |
| Efficace sur tableau presque trié | Non | Oui |