Les tris
Cours · algorithmique (école d'ingénieur), chapitre 14 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun
Objectifs du chapitre
- Programmer, justifier par leurs invariants et analyser quatre tris classiques : par insertion, par fusion, par segmentation (tri rapide) et par tas.
- Connaître leurs complexités dans le pire cas, en moyenne et dans le meilleur cas, et savoir les comparer expérimentalement.
- Comprendre pourquoi aucun tri par comparaisons ne peut faire mieux que .
Trier un tableau, c'est réorganiser ses valeurs par ordre croissant. Le tri est partout : pour chercher par dichotomie (chapitre 12), pour détecter des doublons, pour afficher, pour fusionner des fichiers… C'est aussi un formidable terrain d'exercice : les quatre algorithmes de ce chapitre illustrent chacun une méthode du livre.
Le tableau est trié (par ordre croissant au sens large) si . Trier , c'est permuter ses valeurs de sorte qu'il soit trié.
Notation : pour deux sous-tableaux, on écrit si toute valeur du premier est inférieure ou égale à toute valeur du second.
14.1 Tri par insertion
C'est le tri du joueur de cartes : on prend les cartes une à une et on insère chacune à sa place parmi celles déjà en main, qui sont triées.
Invariant.
: « est trié (et contient les premières valeurs initiales) ».
- Initialisation : est vraie, un tableau d'une case est trié.
- Progression : on insère dans le préfixe trié par la recherche arrière du chapitre 12 : et après insertion.
- Arrêt : , le tableau entier est trié.
static void triInsertion(int[] T) { int n = T.length;
// I(k) : T[0:k] est trié (et contient les k premières valeurs initiales)
for (int k = 1; k < n; k++) { // I(1) : un 1-préfixe est trié
int x = T[k], j = k;
// J(j) : T[0:j] et T[j+1:k+1] triés, T[j+1:k+1] > x, case j libre
while (j > 0 && T[j-1] > x) { T[j] = T[j-1]; j--; }
T[j] = x; // I(k+1)
}
// I(n) : T[0:n] est trié
}
| valeur insérée | après insertion (puis le reste) | décalages | |
|---|---|---|---|
| 1 | 2 | 1 | |
| 2 | 9 | 0 | |
| 3 | 1 | 3 | |
| 4 | 6 | 1 | |
| 5 | 3 | 3 |
En gras : le préfixe trié de l'invariant. Le nombre total de décalages, , est exactement le nombre d'inversions du tableau initial (couples tels que ).
Complexité.
L'insertion de fait au plus décalages.
- Pire cas (tableau trié par ordre décroissant) : chaque valeur traverse tout le préfixe, décalages : .
- Meilleur cas (tableau déjà trié) : aucun décalage, un seul test par valeur : .
- En moyenne (valeurs dans un ordre aléatoire) : chaque valeur traverse la moitié du préfixe, environ décalages : .
À retenir
Le tri par insertion est quadratique, mais il est très rapide sur les petits tableaux et sur les tableaux « presque triés » (son temps de calcul est proportionnel à plus le nombre d'inversions). Les tris efficaces des bibliothèques l'utilisent pour finir le travail sur les petits sous-tableaux.
14.2 Tri par fusion
Le tri par fusion (merge sort, John von Neumann, 1945) est l'exemple type du « diviser pour régner » (chapitre 13) :
- couper en deux moitiés et ;
- trier récursivement chaque moitié ;
- fusionner les deux moitiés triées.
static void triFusion(int[] T) { triFusion(T, 0, T.length, new int[T.length]); }
static void triFusion(int[] T, int i, int j, int[] B) { // trie T[i:j]
if (j - i <= 1) return; // 0 ou 1 valeur : T[i:j] trié
int k = (i + j) / 2;
triFusion(T, i, k, B); // T[i:k] trié
triFusion(T, k, j, B); // T[k:j] trié
fusionner(T, i, k, j, B); // T[i:j] trié
}
14.2.1 La fusion
Deux sous-tableaux triés et sont donnés. On construit leur fusion triée dans un tableau de travail , en prenant à chaque étape la plus petite des deux valeurs de tête.
Invariant de la fusion.
: « est la fusion triée de et , et , ». Comme , chaque tour place une valeur.
static void fusionner(int[] T, int i, int k, int j, int[] B) {
int p = i, q = k, r = i;
// I(p,q,r) : B[i:r] est la fusion triée de T[i:p] et T[k:q],
// et B[i:r] <= T[p:k], B[i:r] <= T[q:j]
while (p < k && q < j)
if (T[p] <= T[q]) B[r++] = T[p++]; // <= : le tri est stable
else B[r++] = T[q++];
while (p < k) B[r++] = T[p++]; // reste de T[i:k]
while (q < j) B[r++] = T[q++]; // reste de T[k:j]
for (r = i; r < j; r++) T[r] = B[r];
}
La suite du cours est sur Adloun
Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.
Lire la suite du coursEn accès libre, sans inscription.