Ch. 04 · Leçon 2
Méthodes de tri : sélection, bulles, insertion
Ce que vous saurez faire
- Décrire les trois méthodes de tri du programme officiel
- Implémenter chaque tri en Analyse et Python
- Comparer leurs complexités et leurs invariants
- Choisir la méthode adaptée selon le contexte
Plan de cours
Trois recettes pour ranger un paquet de cartes
Trier un tableau, c'est la grande fresque du programme officiel. Trois méthodes au programme — sélection, bulles, insertion — chacune avec sa propre intuition et son propre invariant. Maîtriser les trois est obligatoire.
Concepts clés
- Sélection : sélectionner le minimum restant et le placer.
- Bulles : échanger les voisins mal ordonnés jusqu'à stabilisation.
- Insertion : insérer chaque élément à sa place dans la zone triée.
- Tous O(N²) : adaptés aux petits tableaux (ou quasi-triés pour insertion).
Avant de commencer
- Q1. Si je vous donne 10 cartes mélangées, comment les trieriez-vous ?
- Q2. Combien de comparaisons faut-il au minimum pour trier 5 éléments différents ?
- Q3. Connaissez-vous une méthode de tri plus rapide que les trois ci-dessus ?
1. Le contenu du cours
1.1 Analogie : trois façons de trier des cartes en main
Sélection. Vous parcourez toutes les cartes pour trouver la plus petite, vous la mettez en première position. Vous recommencez sur les cartes restantes. Etc.
Bulles. Vous comparez les cartes 2 par 2 et échangez si elles sont dans le mauvais ordre. Vous repassez sur toutes les paires jusqu'à ce qu'aucun échange ne soit nécessaire.
Insertion. Vous prenez les cartes une par une et vous les insérez à leur place dans la zone déjà triée (en décalant celles qui doivent céder).
Les trois trient, mais leur rythme et leur élégance diffèrent.
1.2 Tri par sélection
Principe.
- À l'étape
i, on cherche le minimum parmi les éléments restants (de l'indiceiàN-1). - On l'échange avec l'élément en position
i. - On répète pour
i = 0, 1, …, N-2.
Invariant. À la fin de l'étape i, les i+1 premiers éléments sont les i+1 plus petits du tableau, triés.
Algorithme
Procédure tri_selection(VAR T : tableau, N : entier)
Variables : i, j, indice_min : entier
Début
Pour i de 1 à N - 1 Faire
indice_min ← i
Pour j de i + 1 à N Faire
Si T[j] < T[indice_min] Alors
indice_min ← j
FinSi
FinPour
Si indice_min ≠ i Alors
echanger(T[i], T[indice_min])
FinSi
FinPour
Fin
FinProcédure
Python
def tri_selection(T):
n = len(T)
for i in range(n - 1):
indice_min = i
for j in range(i + 1, n):
if T[j] < T[indice_min]:
indice_min = j
if indice_min != i:
T[i], T[indice_min] = T[indice_min], T[i]
Trace avec T = [5, 2, 8, 1, 9] :
| Étape | i | min trouvé | T après échange |
|---|---|---|---|
| 0 | 0 | indice 3 (val 1) | [1, 2, 8, 5, 9] |
| 1 | 1 | indice 1 (val 2 déjà en place) | [1, 2, 8, 5, 9] |
| 2 | 2 | indice 3 (val 5) | [1, 2, 5, 8, 9] |
| 3 | 3 | indice 3 (val 8 déjà en place) | [1, 2, 5, 8, 9] |
Résultat : [1, 2, 5, 8, 9] ✓
Complexité
- Comparaisons : (N-1) + (N-2) + … + 1 = N(N-1)/2 ≈ N² / 2.
- Échanges : au plus N-1 (un par étape).
- Complexité globale : O(N²).
1.3 Tri à bulles
Principe.
- On parcourt le tableau et compare chaque paire de voisins.
- Si
T[i] > T[i+1], on échange. - À la fin du parcours, le plus grand est en dernière position (il a « bullé » vers la droite).
- On recommence sur les éléments restants jusqu'à plus aucun échange.
Invariant. Après le k-ième parcours, les k plus grands éléments sont à leur place finale.
Algorithme
Procédure tri_bulles(VAR T : tableau, N : entier)
Variables : i, j : entier, echange : booléen
Début
Répéter
echange ← faux
Pour j de 1 à N - 1 Faire
Si T[j] > T[j + 1] Alors
echanger(T[j], T[j + 1])
echange ← vrai
FinSi
FinPour
Jusqu'à NON echange
Fin
FinProcédure
Python
def tri_bulles(T):
n = len(T)
while True:
echange = False
for j in range(n - 1):
if T[j] > T[j + 1]:
T[j], T[j + 1] = T[j + 1], T[j]
echange = True
if not echange:
break
Trace avec T = [5, 2, 8, 1, 9] (1er passage) :
| j | T avant | Comparaison | T après |
|---|---|---|---|
| 0 | [5,2,8,1,9] | 5 > 2 ✓ | [2,5,8,1,9] |
| 1 | [2,5,8,1,9] | 5 > 8 ✗ | [2,5,8,1,9] |
| 2 | [2,5,8,1,9] | 8 > 1 ✓ | [2,5,1,8,9] |
| 3 | [2,5,1,8,9] | 8 > 9 ✗ | [2,5,1,8,9] |
Fin du passage : [2, 5, 1, 8, 9]. Le 9 est arrivé à sa place finale. On recommence.
Complexité
- Pire cas (tableau inversé) : N(N-1)/2 comparaisons → O(N²).
- Meilleur cas (tableau déjà trié) : N-1 comparaisons → O(N) (un seul passage sans échange).
- Optimisation : la version avec drapeau
echangepermet la sortie anticipée, indispensable pour atteindre le meilleur cas O(N).
1.4 Tri par insertion
Principe.
- On considère le tableau divisé en deux zones : gauche triée, droite non triée.
- Au départ, la zone triée contient seulement
T[0]. - À chaque étape, on prend l'élément suivant de la zone non triée et on l'insère à sa place dans la zone triée, en décalant les éléments plus grands vers la droite.
Invariant. Après l'étape i, les i+1 premiers éléments sont triés (mais ne sont pas forcément les i+1 plus petits du tableau total).
Algorithme
Procédure tri_insertion(VAR T : tableau, N : entier)
Variables : i, j : entier, cle : entier
Début
Pour i de 2 à N Faire
cle ← T[i]
j ← i - 1
Tant que (j >= 1) ET (T[j] > cle) Faire
T[j + 1] ← T[j] { décalage }
j ← j - 1
FinTantQue
T[j + 1] ← cle { insertion }
FinPour
Fin
FinProcédure
Python
def tri_insertion(T):
n = len(T)
for i in range(1, n):
cle = T[i]
j = i - 1
while j >= 0 and T[j] > cle:
T[j + 1] = T[j]
j = j - 1
T[j + 1] = cle
Trace avec T = [5, 2, 8, 1, 9] :
| i | cle | T avant | Décalages | T après |
|---|---|---|---|---|
| 1 | 2 | [5,2,8,1,9] | 5 décalé | [2,5,8,1,9] |
| 2 | 8 | [2,5,8,1,9] | aucun (8 > 5) | [2,5,8,1,9] |
| 3 | 1 | [2,5,8,1,9] | 8, 5, 2 décalés | [1,2,5,8,9] |
| 4 | 9 | [1,2,5,8,9] | aucun (9 > 8) | [1,2,5,8,9] |
Complexité
- Pire cas (tableau inversé) : N(N-1)/2 → O(N²).
- Meilleur cas (tableau déjà trié) : N-1 comparaisons sans aucun décalage → O(N).
- Cas réel : très efficace pour les tableaux quasi-triés (par exemple, vous ajoutez un nouvel élément à un tableau déjà trié).
1.5 Tableau comparatif
| Critère | Sélection | Bulles | Insertion |
|---|---|---|---|
| Comparaisons (pire) | N(N-1)/2 | N(N-1)/2 | N(N-1)/2 |
| Comparaisons (meilleur) | N(N-1)/2 (toujours) | N-1 | N-1 |
| Échanges (pire) | N-1 | N(N-1)/2 | — |
| Stable* | Non | Oui | Oui |
| Complexité | O(N²) | O(N²) | O(N²) |
| Idéal pour | Quand échange coûte cher | Apprentissage (intuitif) | Tableaux quasi-triés |
* Un tri est stable s'il préserve l'ordre relatif des éléments de même valeur. Sélection : non. Bulles et insertion : oui.
1.6 Trace comparative — T = [3, 1, 2]
| N° | Instruction | methode | etat |
|---|---|---|---|
| 01 | ··· | ? | ? |
| 02 | ··· | ? | ? |
| 03 | ··· | ? | ? |
| 04 | ··· | ? | ? |
| 05 | ··· | ? | ? |
| 06 | ··· | ? | ? |
2. Exercices pratiques
Niveau Débutant — Type bac courant
Écrire un programme qui :
- Lit un entier
NpuisNentiers dans un tableauT. - Trie
Tpar insertion (croissant). - Lit une valeur
valet indique si elle est présente (par dichotomie), et à quelle position.
Voir le corrigé
def tri_insertion(T):
for i in range(1, len(T)):
cle = T[i]
j = i - 1
while j >= 0 and T[j] > cle:
T[j + 1] = T[j]
j -= 1
T[j + 1] = cle
def recherche_dicho(T, val):
deb = 0
fin = len(T) - 1
while deb <= fin:
mil = (deb + fin) // 2
if T[mil] == val:
return mil
elif T[mil] < val:
deb = mil + 1
else:
fin = mil - 1
return -1
# Programme principal
N = int(input("N = "))
T = []
for i in range(N):
T.append(int(input(f"T[{i}] = ")))
tri_insertion(T)
print("Tableau trié :", T)
val = int(input("Valeur à chercher : "))
indice = recherche_dicho(T, val)
if indice >= 0:
print(f"Trouvée à l'indice {indice}")
else:
print("Absente")
Composabilité. L'intérêt de trier d'abord : on peut ensuite utiliser la dichotomie pour toutes les recherches suivantes en O(log N) au lieu de O(N).
Niveau Intermédiaire — Type bac difficile
Modifier les trois tris pour qu'ils retournent (en plus de modifier le tableau) le nombre de comparaisons et le nombre d'échanges effectués.
Comparer les trois méthodes sur un tableau de 10 éléments aléatoires, puis sur un tableau déjà trié.
Voir le corrigé
import random
def tri_selection_compte(T):
n = len(T)
cmp = 0
echg = 0
for i in range(n - 1):
indice_min = i
for j in range(i + 1, n):
cmp += 1
if T[j] < T[indice_min]:
indice_min = j
if indice_min != i:
T[i], T[indice_min] = T[indice_min], T[i]
echg += 1
return cmp, echg
def tri_bulles_compte(T):
n = len(T)
cmp = 0
echg = 0
while True:
a_echange = False
for j in range(n - 1):
cmp += 1
if T[j] > T[j + 1]:
T[j], T[j + 1] = T[j + 1], T[j]
echg += 1
a_echange = True
if not a_echange:
break
return cmp, echg
def tri_insertion_compte(T):
n = len(T)
cmp = 0
echg = 0
for i in range(1, n):
cle = T[i]
j = i - 1
while j >= 0:
cmp += 1
if T[j] > cle:
T[j + 1] = T[j]
echg += 1
j -= 1
else:
break
T[j + 1] = cle
return cmp, echg
# Comparaison
random.seed(42)
tableau_aleat = [random.randint(1, 100) for _ in range(10)]
tableau_trie = sorted(tableau_aleat)
for nom, methode in [("Sélection", tri_selection_compte),
("Bulles", tri_bulles_compte),
("Insertion", tri_insertion_compte)]:
T = tableau_aleat.copy()
cmp, echg = methode(T)
print(f"{nom} sur aléatoire : {cmp} comparaisons, {echg} échanges")
T = tableau_trie.copy()
cmp, echg = methode(T)
print(f"{nom} sur déjà trié : {cmp} comparaisons, {echg} échanges")
Résultats typiques sur N = 10 :
| Méthode | Aléatoire (cmp / echg) | Déjà trié (cmp / echg) |
|---|---|---|
| Sélection | 45 / 6 | 45 / 0 |
| Bulles | 80-100 / 20-25 | 9 / 0 (1 seul passage) |
| Insertion | 25-30 / 20-25 | 9 / 0 |
Conclusions.
- Sélection : nombre fixe de comparaisons quel que soit l'ordre initial. Avantage : peu d'échanges. Inconvénient : N(N-1)/2 même si déjà trié.
- Bulles et insertion : adaptatifs. Beaucoup plus rapides sur tableau quasi-trié.
- Insertion est généralement le meilleur des 3 sur des données réelles (souvent quasi-triées).
Niveau Avancé — Hors bac (bonus)
Le tri par insertion classique trouve la position d'insertion par parcours séquentiel des éléments triés. On peut accélérer cette recherche par dichotomie.
Question 1. Pourquoi la complexité globale reste O(N²) malgré cette amélioration ?
Question 2. Implémenter le tri par insertion dichotomique en Python.
Question 3. Mesurer le nombre de comparaisons sur un tableau de 100 éléments aléatoires, comparé au tri par insertion classique.
Voir le corrigé
Question 1 — Pourquoi O(N²) malgré la dicho ?
La recherche de la position devient O(log N) (gain). Mais le décalage des éléments pour insérer la clé reste O(N) au pire cas. Au total : N étapes × (log N + N) = O(N²).
Le gain est sur les comparaisons, pas sur les déplacements en mémoire.
Question 2 — Implémentation
def position_insertion(T, deb, fin, cle):
"""Dichotomie : retourne la position où insérer cle dans T[deb..fin] trié."""
while deb <= fin:
mil = (deb + fin) // 2
if T[mil] < cle:
deb = mil + 1
else:
fin = mil - 1
return deb
def tri_insertion_dicho(T):
cmp = 0
for i in range(1, len(T)):
cle = T[i]
# On utilise position_insertion qui fait log(i) comparaisons
pos = position_insertion(T, 0, i - 1, cle)
# cmp ≈ log2(i) à chaque tour
cmp += max(1, (i).bit_length()) # approximation
# Décaler T[pos..i-1] vers la droite
for k in range(i, pos, -1):
T[k] = T[k - 1]
T[pos] = cle
return cmp
Question 3 — Mesure
import random
random.seed(42)
tableau = [random.randint(1, 1000) for _ in range(100)]
# Classique
T = tableau.copy()
cmp_classique, _ = tri_insertion_compte(T)
print(f"Insertion classique : {cmp_classique} comparaisons")
# Dichotomique
T = tableau.copy()
cmp_dicho = tri_insertion_dicho(T)
print(f"Insertion dichotomique : {cmp_dicho} comparaisons")
Résultats typiques pour N = 100 :
- Insertion classique : ~2500 comparaisons.
- Insertion dichotomique : ~525 comparaisons (≈ N · log₂(N) = 100 × 7).
Gain réel : ~5x sur les comparaisons. Mais les déplacements restent identiques (~2500). Donc le gain en temps total est plus faible (~30%).
Leçon. Pour vraiment passer en O(N log N), il faut des algorithmes plus sophistiqués comme le tri fusion ou le tri rapide (hors programme officiel 4ème, mais à connaître pour la suite de vos études).
⚠️ Erreurs fréquentes au bac
Avant le quiz, mémorisez ces pièges classiques qui font perdre des points :
- Erreur 1. Confondre tri par sélection (cherche le min, le place) et tri à bulles (échange voisins). Mécanismes différents.
- Erreur 2. Échange à 2 lignes au lieu de 3 :
T[i] = T[j]; T[j] = T[i]perd la valeur initiale. Utiliser une variabletempouT[i], T[j] = T[j], T[i]en Python. - Erreur 3. Oublier la borne
n-1-idans la 2e boucle du tri à bulles optimisé. La passein'a plus besoin de comparer lesiderniers (déjà triés). - Erreur 4. Trier sans appliquer en place ni retourner un nouveau tableau. Le tri « se perd ».
3. Quiz de vérification
Méthodes de tri (5 questions)
Le tri par SÉLECTION fonctionne par :
Complexité du tri à BULLES dans le meilleur cas (tableau déjà trié) avec optimisation drapeau :
Pour ajouter un nouvel élément à un tableau déjà trié, le tri le plus efficace est :
Combien de comparaisons effectue le tri par sélection sur un tableau de 10 éléments, QUEL QUE SOIT l'ordre initial ?
Quelle propriété N'est PAS conservée par le tri par sélection ?
En résumé
Avant la dernière leçon (4.3 Complexité algorithmique), vous devez :
- ✅ Implémenter les trois tris de tête, en algorithmique et en Python,
- ✅ Tracer chacun sur un petit tableau (5 éléments) sans erreur,
- ✅ Distinguer les invariants : zone triée à gauche (insertion), plus grands à droite (bulles), minimum placé (sélection),
- ✅ Choisir entre les trois selon la situation (déjà trié, peu d'échanges autorisés, etc.).
La prochaine leçon prend du recul : comment mesurer l'efficacité d'un algorithme, et pourquoi le bond O(N²) → O(N log N) → O(log N) change tout pour les grandes données.