Aller au contenu

Partie 2 : Algorithmes de tri


Le tri est l'une des opérations les plus fondamentales en informatique. Trier des données permet ensuite de :

  • Faire une recherche dichotomique (beaucoup plus rapide)
  • Éliminer les doublons
  • Calculer la médiane
  • Afficher un classement, un palmarès

On étudie ici deux algorithmes simples qui trient un tableau en place (sans créer de nouveau tableau).


À chaque étape, on sélectionne le plus petit élément parmi ceux qui ne sont pas encore triés, et on le place à sa position définitive.

  1. Chercher le minimum du tableau [0 .. n-1] et l'échanger avec l'élément en position 0
  2. Chercher le minimum du tableau [1 .. n-1] et l'échanger avec l'élément en position 1
  3. Ainsi de suite jusqu'à la fin

Tableau initial : [64, 25, 12, 22, 11]

ÉtapePartie triéePartie non triéeMin trouvéÉchange
1[][64, 25, 12, 22, 11]11 (indice 4)64 ↔ 11
2[11][25, 12, 22, 64]12 (indice 2)25 ↔ 12
3[11, 12][25, 22, 64]22 (indice 3)25 ↔ 22
4[11, 12, 22][25, 64]25 (indice 3)pas d'échange

Résultat : [11, 12, 22, 25, 64]

def tri_selection(tab):
"""Trie le tableau tab en place par sélection."""
n = len(tab)
for i in range(n - 1):
i_min = i
for j in range(i + 1, n):
if tab[j] < tab[i_min]:
i_min = j
tab[i], tab[i_min] = tab[i_min], tab[i]
>>> t = [64, 25, 12, 22, 11]
>>> tri_selection(t)
>>> t
[11, 12, 22, 25, 64]

Invariant : « Après l'itération i, les éléments tab[0..i] sont triés et sont les i+1 plus petits éléments du tableau. »

  • Initialisation (i = 0) : tab[0] contient le minimum du tableau entier → vrai
  • Conservation : à l'étape i, on place le minimum de tab[i..n-1] en position i. Les éléments tab[0..i-1] sont déjà triés et tous ≤ tab[i], donc tab[0..i] est trié → vrai
  • Terminaison : quand i = n-2, tout le tableau est trié

La boucle interne parcourt :

  • n-1 éléments, puis n-2, puis n-3, ..., puis 1

Total de comparaisons : (n−1)+(n−2)+...+1=n(n−1)2(n-1) + (n-2) + ... + 1 = \frac{n(n-1)}{2}

La complexité est en O(n²), quel que soit le tableau (meilleur cas = pire cas).


Le tri par insertion fonctionne comme on trie un jeu de cartes dans sa main : on prend les cartes une par une et on insère chacune à sa bonne place dans la partie déjà triée.

  1. L'élément en position 0 est « trié » (il est seul)
  2. On prend l'élément en position 1 et on l'insère à sa place dans [0..0]
  3. On prend l'élément en position 2 et on l'insère à sa place dans [0..1]
  4. Ainsi de suite

Tableau initial : [5, 2, 4, 6, 1, 3]

ÉtapeCléPartie triéeInsertionRésultat
12[5]2 < 5, décaler[2, 5, 4, 6, 1, 3]
24[2, 5]4 < 5, 4 > 2[2, 4, 5, 6, 1, 3]
36[2, 4, 5]6 > 5, en place[2, 4, 5, 6, 1, 3]
41[2, 4, 5, 6]tout décaler[1, 2, 4, 5, 6, 3]
53[1, 2, 4, 5, 6]3 entre 2 et 4[1, 2, 3, 4, 5, 6]
def tri_insertion(tab):
"""Trie le tableau tab en place par insertion."""
for i in range(1, len(tab)):
cle = tab[i]
j = i - 1
while j >= 0 and tab[j] > cle:
tab[j + 1] = tab[j]
j = j - 1
tab[j + 1] = cle
>>> t = [5, 2, 4, 6, 1, 3]
>>> tri_insertion(t)
>>> t
[1, 2, 3, 4, 5, 6]

Invariant : « Après l'itération i, le sous-tableau tab[0..i] contient les mêmes éléments qu'initialement, mais triés en ordre croissant. »

  • Initialisation (i = 1) : tab[0..1] contient 2 éléments en ordre → vrai
  • Conservation : on insère tab[i] à sa bonne place dans tab[0..i-1] (déjà trié), donc tab[0..i] est trié → vrai
  • Terminaison : quand i = n-1, tab[0..n-1] est trié
CasSituationComparaisonsComplexité
MeilleurTableau déjà trién - 1O(n)
PireTableau trié à l'enversn(n−1)2\frac{n(n-1)}{2}O(n²)
MoyenTableau aléatoiren(n−1)4\frac{n(n-1)}{4}O(n²)

Le tri par insertion est plus rapide que le tri par sélection sur un tableau presque trié.


CritèreTri par sélectionTri par insertion
Complexité pire casO(n²)O(n²)
Complexité meilleur casO(n²)O(n)
Nombre d'échanges (pire cas)O(n)O(n²)
Stable (conserve l'ordre des égaux)NonOui
Adaptatif (plus rapide si presque trié)NonOui
En place (pas de mémoire supplémentaire)OuiOui

On peut mesurer le temps d'exécution pour comparer les algorithmes :

import time
import random
def mesurer_temps(fonction_tri, taille):
tab = [random.randint(0, 10000) for _ in range(taille)]
debut = time.time()
fonction_tri(tab)
fin = time.time()
return fin - debut
for n in [100, 1000, 5000, 10000]:
t_sel = mesurer_temps(tri_selection, n)
t_ins = mesurer_temps(tri_insertion, n)
print(f"n={n:6d} sélection: {t_sel:.4f}s insertion: {t_ins:.4f}s")

On observe que quand n est multiplié par 10, le temps est multiplié par environ 100 (confirmation du O(n²)).


Visualisation animée des tris : activez JavaScript pour utiliser cette visualisation.

  • Le tri par sélection cherche le minimum à chaque étape et le place en tête. Complexité : O(n²) dans tous les cas.
  • Le tri par insertion insère chaque élément à sa place dans la partie déjà triée. Complexité : O(n²) dans le pire cas, mais O(n) si le tableau est presque trié.
  • Les deux algorithmes trient en place (pas de tableau supplémentaire).
  • On prouve la correction d'un tri avec un invariant de boucle.
  • La terminaison est garantie par des boucles bornées.
  • Le tri par insertion est stable (conserve l'ordre des égaux), pas le tri par sélection.