Algorithmique 1 · C3 Recherche et tris · Chapitre 2 · 30 min
Tris élémentaires
Dérouler le tri par sélection et le tri par insertion, et comprendre leur invariant.
La leçon précédente s'est achevée sur une dette : la dichotomie exige un tableau trié. Il est temps de le trier. Les deux algorithmes de cette leçon ne sont pas les plus rapides — ils sont les plus instructifs, parce qu'on peut les dérouler entièrement à la main.
Ce que trier veut dire
Trier un tableau, c'est le réorganiser pour que T[0] ≤ T[1] ≤ … ≤ T[n − 1]. Deux
exigences, pas une seule :
- l'ordre : chaque case est inférieure ou égale à la suivante ;
- la permutation : le tableau final contient exactement les mêmes valeurs que le tableau initial, ni plus, ni moins.
La seconde est facile à oublier, et c'est pourtant elle qui interdit les fausses solutions. Un algorithme qui remplirait le tableau de zéros produirait une suite parfaitement croissante — et un résultat évidemment faux.
Les deux tris qui suivent travaillent en place : ils réorganisent le tableau existant
sans en créer un second. La seule opération dont ils disposent pour cela est l'échange de
deux cases — exactement le schéma tmp de la leçon 2.
Le tri par sélection
L'idée tient en une phrase : chercher le plus petit élément restant, et l'amener à sa place définitive.
Pour i de 0 à n − 2 Faire indiceMin ← i Pour j de i + 1 à n − 1 Faire Si T[j] < T[indiceMin] Alors indiceMin ← j FinSi FinPour tmp ← T[i] T[i] ← T[indiceMin] T[indiceMin] ← tmpFinPourDeux boucles imbriquées : celle de i compte les tours, celle de j cherche le minimum
dans ce qui reste. On mémorise l'indice du minimum, pas sa valeur — c'est l'indice qui
servira à échanger.
Animation · 9 étapes
Tri par sélection de [5, 2, 9, 1, 6]
- Le tableau de départ — Principe : à chaque tour, chercher le plus petit élément de la partie non triée et l'amener à sa place définitive.
- Tour 1 — le minimum est 1, en case 3 — Le trouver a demandé 4 comparaisons : il faut voir toutes les cases restantes pour être sûr d'avoir le plus petit.
- Tour 1 — on échange T[0] et T[3] — 1 est à sa place DÉFINITIVE : plus rien de plus petit ne reste. Le 5 qui occupait la case 0 est parti en case 3.
- Tour 2 — le minimum restant est 2, en case 1 — On ne regarde plus la case 0 : elle est définitive.
- Tour 2 — il est déjà à sa place — L'échange T[1] ↔ T[1] a bien lieu dans la plupart des écritures : il ne coûte rien et évite un test supplémentaire.
- Tour 3 — le minimum restant est 5, en case 3 — Deux comparaisons cette fois : la partie non triée rétrécit à chaque tour.
- Tour 3 — on échange T[2] et T[3] — Le 9 est renvoyé plus loin. Notez qu'il « saute » : le tri par sélection n'est pas stable.
- Tour 4 — le minimum restant est 6, en case 4 — Une seule comparaison suffit : il ne reste que deux cases.
- Tour 4 — on échange T[3] et T[4] : c'est fini — n − 1 tours suffisent : dès que les 4 premières cases sont définitives, la dernière l'est forcément, puisqu'il ne reste qu'une valeur.
L'invariant de ce tri — la propriété vraie à la fin de chaque tour — est le suivant : après le tour i, les cases 0 à i contiennent les i + 1 plus petites valeurs, à leur place définitive. Définitive est le mot fort : ces cases ne bougeront plus jamais.
Remarquez aussi qu'on s'arrête à n − 2 : quand les n − 1 premières cases sont
définitives, la dernière l'est forcément, puisqu'il ne reste qu'une valeur pour l'occuper.
Le tri par insertion
Autre idée, celle du joueur de cartes : prendre l'élément suivant et le glisser à sa place parmi ceux déjà rangés.
Pour i de 1 à n − 1 Faire valeur ← T[i] j ← i − 1 TantQue j ≥ 0 ET T[j] > valeur Faire T[j + 1] ← T[j] j ← j − 1 FinTantQue T[j + 1] ← valeurFinPourAnimation · 9 étapes
Tri par insertion de [5, 2, 9, 1, 6]
- Le tableau de départ — Principe : comme des cartes en main. La case 0 seule est déjà « triée » — un élément unique l'est toujours.
- On prend T[1] = 2 — On le retire mentalement du tableau et on cherche où le glisser à gauche.
- 2 se glisse avant 5 — 5 est décalé d'une case vers la droite pour libérer la place. Un décalage, une insertion.
- On prend T[2] = 9 — La partie gauche [2, 5] est triée. Où va 9 ?
- 9 reste où il est — Une seule comparaison (9 > 5) et c'est réglé : aucun décalage. Sur un tableau déjà trié, ce tri ne fait que n − 1 comparaisons.
- On prend T[3] = 1 — Le plus petit élément du tableau, tout au fond. Ça va coûter cher.
- 1 remonte jusqu'en tête — 2, 5 et 9 sont tous décalés d'un cran. Trois décalages pour une insertion : c'est le pire cas de ce tri, un tableau à l'envers.
- On prend T[4] = 6 — Dernier élément à insérer dans [1, 2, 5, 9].
- 6 se glisse entre 5 et 9 : c'est fini — Différence essentielle avec la sélection : ici la partie gauche était triée mais PAS définitive — le 9 vient encore de bouger au dernier tour.
La boucle démarre à 1, pas à 0 : une case seule est déjà triée, il n'y a rien à faire au premier élément. La boucle interne ne cherche pas, elle décale — chaque élément trop grand recule d'une case pour ouvrir un trou, et la valeur mise de côté vient s'y loger.
Un détail de la condition j ≥ 0 ET T[j] > valeur mérite l'attention : l'ordre des deux
tests n'est pas indifférent. Le premier protège le second. Quand j atteint −1, la machine
constate que j ≥ 0 est FAUX et n'évalue même pas T[j], ce qui serait un débordement
d'indice. Cette évaluation paresseuse est garantie par tous les langages courants, mais elle
vous impose de mettre le garde-fou en premier.
Comparer les deux sur le même tableau
Les deux animations partent du même tableau [5, 2, 9, 1, 6]. Rejouez-les côte à côte : le
comportement diffère profondément.
| Tri par sélection | Tri par insertion | |
|---|---|---|
| Invariant | les i premières cases sont définitives | les i premières cases sont triées entre elles |
| Opération de base | échanger deux cases | décaler d'une case |
| Nombre de comparaisons | toujours le même, quel que soit le tableau | dépend du désordre initial |
| Tableau déjà trié | même travail complet | n − 1 comparaisons, aucun décalage |
| Nombre d'échanges | au plus n − 1 | autant que de décalages |
La différence d'invariant est le point le plus important de la leçon. Dans le tri par sélection, une case placée ne bouge plus jamais : elle est définitive. Dans le tri par insertion, la partie gauche est triée mais pas définitive — regardez le 9 dans l'animation, il est encore déplacé au dernier tour.
Quiz · 1 question
Sur un tableau DÉJÀ trié, lequel des deux fait le moins de travail ?
- Le tri par sélection, car il fait peu d'échanges
- Le tri par insertion, car chaque élément est bien placé du premier coup
- Les deux font exactement le même travail
Réponse : Le tri par insertion compare chaque élément à son voisin de gauche, constate qu'il est plus grand, et n'effectue aucun décalage : n − 1 comparaisons au total. Le tri par sélection, lui, cherche quand même le minimum dans toute la partie restante à chaque tour : il ne profite d'aucun ordre préexistant.
Quiz · 1 question
Dans le tri par sélection, pourquoi mémorise-t-on l'indice du minimum plutôt que sa valeur ?
- Parce qu'un indice occupe moins de place en mémoire
- Parce que l'échange a besoin de savoir OÙ se trouve le minimum pour le déplacer
- Parce que la valeur pourrait changer pendant la boucle
Réponse : Connaître la valeur 1 ne dit pas où elle est. L'échange T[i] ↔ T[indiceMin] a besoin de la position. C'est une constante de l'algorithmique sur tableaux : on manipule des indices, les valeurs suivent.
Quiz · 1 question
Le tri par sélection sur 5 éléments effectue 4 + 3 + 2 + 1 = 10 comparaisons. Combien en fera-t-il sur 10 éléments ?
- 20 — deux fois plus
- 45 — somme des entiers
- 100 — n fois n
Réponse : 9 + 8 + … + 1 = 45, soit n(n − 1)/2 — la formule de la somme des entiers vue à la leçon 4. Doubler la taille a plus que doublé le travail : il a été multiplié par 4,5. C'est ce comportement que la leçon 8 va nommer.
À vous
Exercice de code
Complétez la recherche du minimum et l'échange. L'échange reprend exactement le schéma de la leçon 2.
Point de départ
// Complétez le tri par sélection.
// Rappel de l'invariant : après le tour i, les cases 0 à i sont à leur
// place DÉFINITIVE.
function triSelection(t) {
const a = [...t]; // on travaille sur une copie, l'original reste intact
for (let i = 0; i < a.length - 1; i++) {
let indiceMin = i;
for (let j = i + 1; j < a.length; j++) {
// à compléter : retenir l'indice du plus petit élément restant
}
// à compléter : échanger a[i] et a[indiceMin]
}
return a;
}
console.log(triSelection([5, 2, 9, 1, 6])); // attendu : [1, 2, 5, 6, 9]
console.log(triSelection([1, 2, 3])); // attendu : [1, 2, 3]
Solution
function triSelection(t) {
const a = [...t];
for (let i = 0; i < a.length - 1; i++) {
// On mémorise l'INDICE, pas la valeur : c'est lui qui servira à échanger.
let indiceMin = i;
for (let j = i + 1; j < a.length; j++) {
if (a[j] < a[indiceMin]) indiceMin = j;
}
const tmp = a[i];
a[i] = a[indiceMin];
a[indiceMin] = tmp;
}
// n - 1 tours suffisent : la dernière case est forcément à sa place.
return a;
}
console.log(triSelection([5, 2, 9, 1, 6]));
console.log(triSelection([1, 2, 3]));
À retenir
Flashcards · 2 cartes
- Quel est l'invariant du tri par sélection ?
- Après le tour i, les cases 0 à i contiennent les plus petites valeurs, à leur place DÉFINITIVE : elles ne bougeront plus.
- Quel est l'invariant du tri par insertion ?
- Après le tour i, les cases 0 à i sont triées entre elles — mais pas définitives : un élément plus petit peut encore venir s'y insérer.