Chapitre 4.4 - Tri par sélection
0. Préambule⚓︎
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()
:
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 sélection est le premier des deux algorithmes de tri que nous allons étudier (nous étudierons aussi le tri par insertion).
Ces deux algorithmes ont pour particularité de :
- ne pas nécessiter la création d'une nouvelle liste. Ils modifient la liste à trier en place.
- ne pas faire intervenir de fonctions complexes (comme la recherche d'un minimum par exemple)
0. Introduction⚓︎
En vidéo⚓︎
1. Animation⚓︎
Considérons la liste [5, 4, 2, 1]
Voici le fonctionnement de l'algorithme :
2. Principe⚓︎
Comme dans tous les autres algorithmes de tri que nous allons étudier, nous allons travailler en place. Cela signifie que nous ne travaillons que sur la liste initiale, sans en créer de nouvelles. Le tri sera fait en permutant des éléments.
Très très grossièrement, l'idée de l'algorithme est la suivante :
- on cherche le minimum de toute la liste, et on le place au tout début de la liste.
- on cherche maintenant le minimum de toute la liste SAUF le 1er terme, et on le place en 2ème position.
- on continue ainsi jusqu'à la fin.
Pour réaliser ceci, le travail va se faire en manipulant les indices des éléments de la liste.
Description de l'algorithme
Le travail se fait essentiellement sur les indices.
- du premier élément jusqu'à l'avant-dernier :
- on considère que cet élément est l'élément minimum, on stocke donc son indice dans une variable indice du minimum.
- on parcourt les éléments suivants, et si on repère un élémént plus petit que notre mininum on met à jour notre indice du minimum.
- une fois le parcours fini, on échange l'élément de travail avec l'élément minimum qui a été trouvé.
3. Implémentation de l'algorithme⚓︎
Tri par sélection
Vérification :
4. Complexité de l'algorithme⚓︎
4.0. De quoi parle-t-on ?⚓︎
4.1. Calcul du nombre d'opérations⚓︎
La ligne 3 indice_min = i
va s'exécuter
Mais intéressons-nous à la ligne 5 (le test) : combien de fois va-t-elle s'exécuter ?
À chaque tour de la première boucle (qui en comporte
- La 1ère fois, de 1 à
, provoquera opérations. - La 2ème fois, de 2 à
, provoquera opérations. - ...
- La
-ième fois, à , provoquera 1 opération.
Or
Dans cette expression, un terme joue un rôle fondamental :
Ici,
Complexité du tri par sélection
Le tri par sélection a une complexité quadratique.
4.2 Influence de la taille de la liste sur le temps d'exécution⚓︎
Considérons qu'une liste de taille
Voici donc un ordre de grandeur de ce que devraient être les temps nécessaires pour trier une liste de taille
Taille de la liste | Temps |
---|---|
4.3 Vérification expérimentale⚓︎
Exercice
Analyser le code suivant :
Q1. Essayer de confirmer les résultats théoriques du tableau précédent. On pourra travailler par exemple avec une liste initiale de taille 1000.
Q2. Recommencer avec une liste déjà triée. Que constate-t-on ?
Q1.
>>> mesures(10**3)
temps moyen pour trier une liste de taille 1000 : 0.03579235076904297
>>> mesures(2*10**3)
temps moyen pour trier une liste de taille 2000 : 0.13821134567260743
>>> mesures(10**4)
temps moyen pour trier une liste de taille 10000 : 3.3528685569763184
On retrouve (à peu près, mais plutôt bien) le facteur 4 quand la taille de la liste double, et le facteur 100 quand la taille de la liste est multipliée par 10.
Q2. Changeons la ligne lst = list(range(n, 0, -1))
en lst = list(range(n))
:
>>> mesures(10**3)
temps moyen pour trier une liste de taille 1000 : 0.038380765914916994
>>> mesures(2*10**3)
temps moyen pour trier une liste de taille 2000 : 0.13413033485412598
>>> mesures(10**4)
temps moyen pour trier une liste de taille 10000 : 3.213682508468628
Les mesures sont identiques : l'état initial de la liste n'a pas d'influence.
5. Preuve de la terminaison de l'algorithme⚓︎
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 à
Ceci prouve que l'algorithme se terminera.
6. Preuve de la correction de l'algorithme⚓︎
Est-on sûr que notre algorithme va bien trier notre liste ?
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é
(par exemple) est vraie- Pour tout entier naturel
, si est vraie alors est vraie.
Ici, la propriété serait : « Quand longueur(liste) -1
, la sous-liste de longueur
- quand
vaut 0, on place le minimum de la liste en position 0, la sous-liste [ ] est donc triée. - si la sous-liste de
éléments [ ] est triée, l'algorithme rajoute en dernière position de la liste le minimum de la sous-liste restante, dont tous les éléments sont supérieurs au maximum de la sous-liste de éléments. La sous-liste de éléments [ ] est donc elle aussi triée.
7. Bonus : comparaison des algorithmes de tri⚓︎
Une jolie animation permettant de comparer les tris :
(on peut y constater que le tri par sélection met toujours autant de temps pour trier la liste, quelque soit son état initial)
Issue de ce site.