Pourquoi étudier des algorithmes de tri ?
Autant ne pas le cacher, ces algorithmes sont déjà implémentés (quelque soit le langage) dans des fonctions très performantes.
En Python, on utilise la fonction sort()
tab = [4, 8, 1, 2, 6]
tab.sort()
tab
Le meilleur de nos futurs algorithmes de tri sera moins efficace que celui de cette fonction sort()...
Malgré cela, il est essentiel de se confronter à l'élaboration manuelle d'un algorithme de tri.
Le tri par insertion est le premier des deux algorithmes de tri que nous allons étudier (nous étudierons aussi le tri par sélection).
Ces deux algorithmes ont pour particularité de :
Considérons la liste [7, 5, 2, 8, 1, 4]
Voici le fonctionnement de l'algorithme :

Explications :
Complétez l'algorithme ci-dessous
def tri(l) :
for k in range(1, len(l)):
i = k
while i>0 and l[i-1] > l[i] :
l[i], l[i-1] = l[i-1], l[i]
i = i -1
Vérification :
a = [7, 5, 2, 8, 1, 4]
tri(a)
print(a)
Observez l'animation ci-dessous et comparer avec la version initiale.

Complétez l'algorithme ci-dessous
def tri(l) :
for k in range(1,len(l)):
cle = l[k]
i = k-1
while i>=0 and l[i] > cle :
l[i+1] = l[i]
i = i -1
l[i+1] = cle
Vérification :
a = [7, 5, 2, 8, 1, 4]
tri(a)
print(a)
Pour pouvoir utiliser la fonction %timeit, nous allons modifier légèrement notre algorithme de tri : comme la fonction %timeit effectue un grand nombre d'appel à la fonction tri(), la liste serait triée dès le premier appel et les autres appels essaieraient donc de tri une liste déjà triée.
def tri(L) :
l = list(L) # pour ne pas modifier la liste passée en argument.
for k ...
a = [k for k in range(100,0,-1)] #on se place dans le pire des cas : une liste triée dans l'ordre décroissant
b = [k for k in range(200,0,-1)] #on se place dans le pire des cas : une liste triée dans l'ordre décroissant
%timeit tri(a)
%timeit tri(b)
En comparant les temps de tri des listes a et b, que pouvez-vous supposer sur la complexité du tri par insertion ?
Dénombrons le nombre d'opérations dans le pire des cas, pour une liste de taille $n$.
Si la liste est déjà triée, on ne rentre jamais dans la boucle while : le nombre d'opérations est dans ce cas égal à $n-1$, ce qui caractérise une complexité linéaire.
Est-on sûr que notre algorithme va s'arrêter ?
À l'observation du programme, constitué de deux boucles for imbriquées, il n'y a pas d'ambiguïté : on ne peut pas rentrer dans une boucle infinie. Le programme s'arrête forcément au bout de d'un nombre fixe d'opérations.
D'après nos calculs sur la complexité, ce nombre de tours de boucles est égal à $$\dfrac{n \times (n-1)}{2}$$
Ceci prouve que l'algorithme se terminera.
Il existe une notion théorique appelée variant de boucle. C'est une valeur entière positive qui décroit à chaque itération du programme. Lorsque cette valeur vaut 0, cela signifie que le programme s'arrête. Ici, le variant de boucle pourrait être la valeur i de la boucle while.
Les preuves de correction sont des preuves théoriques. La preuve ici s'appuie sur le concept mathématique de récurrence. Principe du raisonnement par récurrence : une propriété $P(n)$ est vraie si :
Ici, la propriété serait : « Quand $k$ varie entre 0 et longueur(liste) -1, la sous-liste de longueur $k$ est triée dans l'ordre croissant.» On appelle cette propriété un invariant de boucle (sous-entendu : elle est vraie pour chaque boucle)