Telechargé par Gedeon Ndong

Algorithmes de tri

publicité
Algorithmes de tri
1. Présentation
• Les algorithmes de tri constituent une classe étudiée
depuis longtemps en algorithmique. Ils sont
intéressants car, d’un point de vue pratique, la
nécessité de trier des données est présente dans la
plupart des domaines de l’informatique. De plus, d’un
point de vue pédagogique, ils permettent d’illustrer de
façon très parlante la notion de complexité
algorithmique abordée dans ce cours.
• Nous définissons d’abord la notion d’algorithme de tri
de façon générale, ainsi que les différents aspects
utilisés lorsqu’on veut les caractériser et les comparer.
Dans un deuxième temps, nous présentons en détails
les algorithmes de tri les plus connus.
1. Présentation
1.1 Définition
On définit un algorithme de tri de la façon
suivante :
• Algorithme de tri : algorithme dont le but est
d’organiser une collection d’éléments muni
d’une relation d’ordre.
Notez bien qu’il s’agit d’une collection, et non
pas d’un ensemble, ce qui signifie qu’il est
possible que le groupe d’éléments que l’on veut
trier contienne plusieurs fois la même valeur.
1. Présentation
1.1 Définition
Il est important de noter que le principe utilisé
pour le tri est complètement indépendant de la
nature de la relation d’ordre utilisée. On peut
utiliser le même algorithme pour trier des
entiers ou des chaînes de caractères. Tout ce qui
va changer, c’est que dans un cas on utiliser
l’opérateur C <=, alors que dans l’autre il faudra
créer une fonction capable de comparer des
chaînes de caractères.
1. Présentation
1.1 Définition
Dans le cas le plus simple du tri, on va utiliser la
relation d’ordre pour organiser les objets contenus
dans l’ensemble 𝑋 sur laquelle elle est définie. Mais
il est aussi possible que les éléments que l’on veut
trier soient de type complexe, en particulier de type
structuré. La valeur de 𝑋 associée à l’élément est
alors appelée une clé :
• Clé de tri : champ de l’élément à trier qui est
utilisée pour effectuer la comparaison.
1. Présentation
1.1 Définition
Dans le cas le plus simple du tri, on va utiliser la
relation d’ordre pour organiser les objets contenus
dans l’ensemble 𝑋 sur laquelle elle est définie. Mais
il est aussi possible que les éléments que l’on veut
trier soient de type complexe, en particulier de type
structuré. La valeur de 𝑋 associée à l’élément est
alors appelée une clé :
• Clé de tri : champ de l’élément à trier qui est
utilisée pour effectuer la comparaison.
1. Présentation
1.1 Définition
Autrement dit, la clé est la partie de l’élément sur laquelle la relation
d’ordre s’applique. C’est cette clé qui permet de comparer deux
éléments, et de décider lequel doit être placé avant l’autre dans la
séquence triée.
exemples :
• éléments simples :
o les éléments sont des entiers
o la clé correspond à l’élément tout entier
• éléments complexes :
o les éléments sont des étudiants caractérisés par un nom,
un prénom et un âge.
o si on effectue un tri par rapport à leur âge, alors l’âge est
la clé et les autres données ne sont pas considérées lors
du tri.
1. Présentation
1.1 Définition
On peut également définir des clés multiples, c’est-à-dire utiliser
une première clé pour ordonner deux éléments, puis une autre
clé en cas d’égalité, et éventuellement encore d’autres clés
supplémentaires.
exemple : dans l’exemple des étudiants, on pourrait ainsi
décider de les trier en fonction de leur âge d’abord, puis de
leur nom quand deux étudiants ont le même âge.
1. Présentation
1.2 Caractéristiques
On classe les différents algorithmes de tris suivant plusieurs
propriétés : complexité algorithmique, caractère en place,
caractère stable. Pour la complexité, on procède comme on l’a
fait précédemment : meilleur/moyen/pire des cas,
spatial/temporel. Les deux autres aspects sont spécifiques au
problème de tri.
• Caractère en place : se dit d’un tri qui trie la collection en la
modifiant directement, i.e. sans passer par une structure de
données secondaire.
En particulier, certains tris utilisent une copie de la collection, ce
qui implique une plus grande consommation de temps (puisqu’il
faut copier la collection) et de mémoire (puisqu’il faut stocker la
copie). Le fait de travailler directement sur la structure originale
est donc plus efficace de ce point de vue-là.
1. Présentation
1.2 Caractéristiques
• Caractère stable : se dit d’un tri qui ne modifie pas la position
relative de deux éléments de même clé.
exemple : supposons qu’on a deux étudiants 𝐴 et 𝐵 de même
âge, et que l’étudiant 𝐴 est placé avant l’étudiant 𝐵 dans la
collection initiale :
Pour des raisons pédagogiques, les différents algorithmes de
tri abordés dans ce cours seront appliqués à des tableaux
d’entiers de taille N, mais ils peuvent bien sûr être appliqués à
:
• N’importe quel type de données comparable autre que les
entiers (réels, structures…) ;
1. Présentation
1.2 Caractéristiques
• N’importe quelle structure séquentielle autre que les
tableaux (listes…).
De plus, on considèrera dans ce cours que l’objectif est de
trier les tableaux dans l’ordre croissant : le tri dans l’ordre
décroissant peut être facilement obtenu en inversant les
comparaisons effectuées dans les algorithmes.
2. Tri à bulles
2.1 Principe
Le principe du tri à bulle est de reproduire le déplacement d’une
bulle d’air dans un liquide : elle remonte progressivement à la
surface.
On appelle cycle la séquence d’actions suivante :
1. On part du premier élément du tableau.
2. On compare les éléments consécutifs deux à deux :
o Si le premier est supérieur au deuxième : ils sont échangés ;
o Sinon : on ne fait rien.
3. On passe aux deux éléments consécutifs suivants, jusqu’à
arriver à la fin du tableau.
On répète ce cycle jusqu’à ce que plus aucun échange ne soit
effectué.
2. Tri à bulles
2.1 Principe
exemple :
2. Tri à bulles
2.1 Principe
exemple :
Dans la figure ci-dessus :
• Les valeurs encadrées sont les deux éléments consécutifs
comparés.
• Les valeurs en gris sont celles que l’on vient d’échanger.
2. Tri à bulles
2.1 Principe
Il s’agit d’une version basique du tri à bulle, qui peut être
améliorée, car certaines des opérations qu’elle effectue sont
inutiles. En effet, on peut remarquer qu’à chaque cycle, le tri à
bulle fait remonter une valeur jusqu’à ce qu’elle atteigne sa place
définitive. Par exemple : à la fin du 1er cycle, la plus grande valeur
est dans la 1ère case en partant de la droite du tableau. À la fin du
2ème cycle, la 2ème plus grande valeur est dans la 2ème case en
partant de la droite du tableau, etc. Au ième cycle, il est donc
inutile d’effectuer les comparaisons après la case 𝑁−𝑖, puisque
ces valeurs sont forcément supérieures à toutes celles qui sont
placées avant la case 𝑁−𝑖.
2. Tri à bulles
2.1 Principe
Si on reprend l’exemple précédent, en représentant en rouge les
cases dont on sait que la valeur ne changera plus jamais, et en
supprimant les opérations inutiles on obtiendra ceci :
Le nombre de cycles ne
change pas, mais leur
Durée diminue.
2. Tri à bulles
2.2 Implémentation
Soit tri_bulle(int tab[]) la fonction qui trie le tableau
d’entiers tab de taille 𝑁 en utilisant le principe du tri à bulles.
2. Tri à bulles
2.2 Implémentation
Complexité temporelle :
• ligne 1 : affectation : 𝑂(1).
• ligne 2 : do :
o ligne 3 : affectation : 𝑂(1).
o ligne 4 : for :




ligne 4 : opérations élémentaires : 𝑂(1).
ligne 5 : if : opérations élémentaires : 𝑂(1).
répétitions : N fois dans le pire des cas.
total : 𝑁×𝑂(1) = 𝑂(𝑁).
o lignes 10 et 11 : opérations élémentaires 𝑂 1 .
o répétitions : N fois dans le pire des cas.
o total : 𝑁×𝑂(𝑁) = 𝑂(𝑁2).
• total : 𝑇(𝑁) = 𝑂(1) + 𝑂(𝑁2) = 𝑂(𝑁2).
2. Tri à bulles
2.2 Implémentation
Complexité spatiale :
• 4 variables simples : 𝑂(1).
• 1 tableau de taille N : 𝑂(𝑁).
• total : 𝑆(𝑁) = 𝑂(𝑁).
Propriétés :
• En place : oui (on trie en modifiant directement le tableau).
• Stable : oui (on ne permute deux éléments qu’après une
comparaison stricte).
3. Tri par sélection
3.1 Principe
Le principe du tri par sélection est de déterminer quel est le
maximum du tableau, de le déplacer à l’endroit qu’il devrait
occuper si le tableau était trié, puis de continuer avec les valeurs
restantes.
1. On cherche la plus grande valeur du tableau.
2. On déplace cette valeur dans le tableau afin de la placer au
bon endroit, c'est-à-dire à la dernière place (case 𝑁−1).
3. On cherche la 2ème plus grande valeur du tableau : on
cherche donc la plus grande valeur du sous-tableau allant de
la case 0 à la case 𝑁−2 incluse.
4. On déplace cette valeur dans le tableau afin de la placer au
bon endroit, c'est-à-dire à l’avant-dernière place (case 𝑁−2),
soit la 2ème case en partant de la fin du tableau.
3. Tri par sélection
1.
2.
3.
4.
3.1 Principe
On cherche la plus grande valeur du tableau.
On déplace cette valeur dans le tableau afin de la placer au bon endroit, c'est-à-dire à la dernière place
(case 𝑁−1).
On cherche la 2ème plus grande valeur du tableau : on cherche donc la plus grande valeur du sous-tableau
allant de la case 0 à la case 𝑁−2 incluse.
On déplace cette valeur dans le tableau afin de la placer au bon endroit, c'est-à-dire à l’avant-dernière
place (case 𝑁−2), soit la 2ème case en partant de la fin du tableau.
5. On cherche la 3ème plus grande valeur du tableau : on
cherche donc la plus grande valeur du sous-tableau allant de
la case 0 à la case 𝑁−3 incluse.
6. On déplace cette valeur dans le tableau afin de la placer au
bon endroit, c'est-à-dire à l’antépénultième place (case 𝑁−3),
soit la 3ème case en partant de la fin du tableau
7. On répète ce traitement jusqu’à ce que le sous-tableau à
traiter ne contienne plus qu’une seule case (la case 0), qui
contiendra obligatoirement le minimum de tout le tableau.
3. Tri par sélection
3.1 Principe
exemple :
3. Tri par sélection
3.1 Principe
Dans la figure ci-dessus :
• Les valeurs encadrées sont les éléments comparés.
• La valeur en gris est le maximum du sous-tableau.
• Les valeurs claires représentent la partie du tableau qui a été
triée.
3. Tri par sélection
3.2 Implémentation
Soit tri_selection(int tab[]) la fonction qui trie le
tableau d’entiers tab de taille 𝑁 en utilisant le principe du tri
par sélection.
3. Tri par sélection
3.2 Implémentation
Complexité temporelle :
• ligne 1 : for :
o lignes 1 et 2 : opérations élémentaires : 𝑂(1).
o ligne 3 : for :




ligne 3 : opérations élémentaires : 𝑂(1).
ligne 4 : if : opérations élémentaires : 𝑂(1).
répétitions : N fois au pire.
total : 𝑁×𝑂(1) = 𝑂(𝑁).
o lignes 6, 7 et 8 : opérations élémentaires : 𝑂(1).
o répétitions : N fois au pire.
o total : 𝑁×𝑂(𝑁) = 𝑂(𝑁2).
• total : 𝑇(𝑁) = 𝑂(𝑁2).
3. Tri par sélection
3.2 Implémentation
Complexité spatiale :
• 4 variables simples : 𝑂(1).
• 1 tableau de taille N : 𝑂(𝑁).
• total : 𝑆(𝑁) = 𝑂(𝑁).
Propriétés :
• En place : oui (on trie en modifiant directement le tableau).
• Stable : non (quand le maximum est placé à la fin, on modifie
forcément sa position relativement à d’autres éléments dont
la clé possède la même valeur).
4. Tri par insertion
4.1 Principe
Le tri par insertion effectue un découpage du tableau en deux
parties :
• Une partie pas encore triée ;
• Une partie déjà triée.
Initialement, la partie triée est constituée d’un seul élément : le
premier élément du tableau. Le principe de l’algorithme consiste
alors à répéter le traitement suivant :
1. Sélectionner le premier élément de la partie non-triée ;
2. L’insérer à la bonne place dans la partie triée ;
3. Jusqu’à ce qu’il n’y ait plus aucun élément dans la partie
non-triée.
4. Tri par insertion
4.1 Principe
Dans la figure ci-dessus, la valeur en gris est l’élément de la partie non-triée
qu’on insère dans la partie triée.
4. Tri par insertion
4.2 Implémentation
Soit void tri_insertion(int tab[]) la fonction qui trie le tableau
d’entiers tab de taille 𝑁 en utilisant le principe du tri par insertion.
4. Tri par insertion
4.2 Implémentation
Complexité temporelle :
• ligne 1 : for :
o lignes 2 et 3 : opérations élémentaires : 𝑂(1).
o ligne 4 : while :
 lignes 5 et 6 : opérations élémentaires : 𝑂(1).
 répétitions : 𝑁−1 au pire.
 total : 𝑁×𝑂(1) = 𝑂(𝑁).
o ligne 7 : affectation : 𝑂(1).
o répétitions : 𝑁−1 au pire.
o total : (𝑁−1) ×𝑂(𝑁) = 𝑂(𝑁2).
• total : 𝑇(𝑁) = 𝑂(𝑁2).
5. Tri fusion
5.1 Principe
Le tri fusion fonctionne sur le principe diviser-pour-régner :
plusieurs petits problèmes sont plus faciles à résoudre qu’un seul
gros problème.
Le tri se déroule en trois étapes :
1. Division : le tableau est coupé en deux (à peu près) en son
milieu ;
2. Tri : chaque moitié est triée séparément récursivement ;
3. Fusion : les deux moitiés triées sont fusionnées pour
obtenir une version triée du tableau initial.
5. Tri fusion
5.1 Principe
exemple :
5. Tri fusion
5.1 Principe
exemple :
5. Tri fusion
5.2 Implémentation
Chaque phase de l’algorithme est implémentée dans une
fonction différente.
Soit void division(int tab[], int tab1[], int
taille1, int tab2[], int taille2) la fonction qui
divise le tableau d’entiers tab en deux parties stockées dans les
tableaux tab1 et tab2.
Les tailles de tab1 et tab2 sont respectivement taille1 et
taille2, et elles sont passées en paramètres (i.e. elles sont
déjà calculées).
5. Tri fusion
5.2 Implémentation
5. Tri fusion
5.2 Implémentation
Complexité temporelle :
• ligne 1 : for :
• ligne 2 : affectation : 𝑂(1).
o répétitions : 𝑁/2 fois dans le pire des cas.
o total : 𝑁/2 × 𝑂(1) = 𝑂(𝑁).
• ligne 3 : for :
o ligne 4 : affectation : 𝑂(1).
o répétitions : 𝑁/2 fois dans le pire des cas.
o total : 𝑁/2 × 𝑂(1) = 𝑂(𝑁).
• total : 𝑇(𝑁) = 𝑂(𝑁) + 𝑂(𝑁) = 𝑂(𝑁).
5. Tri fusion
5.2 Implémentation
Complexité spatiale :
• 3 variables simples : 𝑂(1).
• 1 tableau de taille N : 𝑂(𝑁).
• 2 tableau de taille 𝑁/2 : 𝑂(𝑁).
• total : 𝑆(𝑁) = 𝑂(𝑁).
5. Tri fusion
5.2 Implémentation
Soit void fusion(int tab[], int tab1[], int
taille1, int tab2[], int taille2) la fonction qui
fusionne deux tableaux d’entiers triés tab1 et tab2 en un seul
tableau trié tab.
On connaît les tailles de tab1 et tab2, qui sont respectivement
taille1 et taille2.
5. Tri fusion
5.2 Implémentation
5. Tri fusion
5.2 Implémentation
Complexité temporelle :
• ligne 1 : affectation : 𝑂(1).
• ligne 2 : while :
o ligne 3 : if :
 lignes 4 et 5 : affectation : 𝑂(1).
 lignes 6 et 7 : affectation : 𝑂(1).
 total : 𝑂(1).
o ligne 8 : affectation : 𝑂(1).
o répétitions : 2𝑁/2−1 = 𝑁−1 fois dans le pire des cas.
o total : (𝑁−1) × 𝑂(1) = 𝑂(𝑁).
5. Tri fusion
5.2 Implémentation
Complexité temporelle (suite) :
• ligne 9 : if :
o ligne 10 : for :
 lignes 11 et 12 : affectation : 𝑂(1).
 répétitions : 𝑁/2 fois dans le pire des cas.
 total : 𝑁/2 × 𝑂(1) = 𝑂(𝑁).
o ligne 13 : for :
 lignes 14 et 15 : affectation : 𝑂(1).
 répétitions : 𝑁/2 fois dans le pire des cas.
 total : 𝑁/2 × 𝑂(1) = 𝑂(𝑁).
o total : max(𝑂(𝑁), 𝑂(𝑁)) = 𝑂(𝑁).
• total : 𝑇(𝑁) = 𝑂(1) + 𝑂(𝑁) + 𝑂(𝑁) = 𝑂(𝑁).
5. Tri fusion
5.2 Implémentation
Complexité spatiale :
• 6 variables simples : 𝑂(1).
• 1 tableau de taille N : 𝑂(𝑁).
• 2 tableau de taille 𝑁/2 : 𝑂(𝑁).
• total : 𝑆(𝑁) = 𝑂(𝑁).
5. Tri fusion
5.2 Implémentation
Enfin, Soit void tri_fusion(int tab[], int
taille) la fonction qui trie récursivement un tableau d’entiers
tab de taille taille, grâce aux fonctions division et fusion.
5. Tri fusion
5.2 Implémentation
Complexité temporelle :
• complexité du cas d’arrêt 𝑛=1 :
o lignes 1, 2 et 3 : affectations : 𝑂(1).
o total : 𝑇(1) = 𝑂(1).
• complexité du cas général 𝑛>0 :
o lignes 1, 2 et 3 : affectations : 𝑂(1).
o ligne 4 : if :




ligne 5 : division : 𝑂(𝑁).
lignes 6 et 7 : tri_fusion : 𝑇 (𝑁/2).
ligne 8 : fusion : 𝑂(𝑁).
total : 2𝑂(𝑁) + 2𝑇(𝑁/2).
o o total : 𝑇(𝑁) = 2𝑇(𝑁/2) + 2𝑂(𝑁) + 𝑂(1) = 2𝑇(𝑁/2) + 𝑂(𝑁).
5. Tri fusion
5.2 Implémentation
Complexité temporelle (suite) :
• résolution de la récurrence :
o selon la formule :
 si 𝑇 𝑛 =𝑐𝑇(𝑛/𝑑) + Θ(𝑛k) pour 𝑐=𝑑k
 alors on a une complexité en 𝑂(𝑛klog𝑛).
o o ici, on a :
 𝑐=𝑑=2 et 𝑘=1.
 d’où 𝑇(𝑁) = 𝑂(𝑁log𝑁).
5. Tri fusion
5.2 Implémentation
Complexité spatiale :
•
•
•
•
•
3 variables simples : 𝑂(1).
1 tableau de taille N : 𝑂(𝑁).
2 tableaux de taille 𝑁/2 : 𝑂(𝑁).
profondeur de l’arbre d’appels : log𝑁.
total : 𝑆(𝑁) = 𝑂(𝑁log𝑁).
Propriétés :
• En place : non, car on utilise plusieurs tableaux lors des
différentes phases. C’est essentiellement l’étape de fusion qui
empêche de faire un tri en place.
• Stabilité : oui (lors de la fusion, en cas de clés identiques, on
recopie d’abord la valeur du tableau gauche puis celle du
tableau droit).
6. Tri rapide
6.1 Principe
Le tri rapide est également appelé : tri de Hoare, tri par
segmentation, tri des bijoutiers…
L’algorithme est le suivant :
1. On choisit un élément du tableau qui servira de pivot.
2. On effectue des échanges de valeurs dans le tableau, de
manière à ce que :
o Les valeurs inférieures au pivot soient placées à sa gauche.
o Les valeurs supérieures au pivot soient placées à sa droite.
3. On applique récursivement ce traitement sur les deux
parties du tableau (i.e. à gauche et à droite du pivot).
6. Tri rapide
6.1 Principe
Remarque : on peut remarquer que le choix du pivot est
essentiel. L’idéal est un pivot qui partage le tableau en deux
parties de tailles égales. Mais déterminer un tel pivot coûte trop
de temps, c’est pourquoi il est en général choisi de façon
arbitraire. Ici par exemple, on prend la première case du tableau.
Entre ces deux extrêmes, des méthodes d’approximation peu
coûteuses existent pour faire un choix de compromis.
6. Tri rapide
6.1 Principe
exemple :
Dans figure ci-dessus, la valeur indiquée en gris correspond au
pivot sélectionné pour diviser le tableau en deux.
6. Tri rapide
6.2 Implémentation
Soit void tri_rapide(int tab[], int d, int f)
la fonction récursive qui trie le tableau d’entiers tab de taille 𝑁
en utilisant le principe du tri rapide.
Les paramètres d et f marquent respectivement le début et la
fin du sous-tableau en cours de traitement. Ces deux paramètres
valent donc respectivement 0 et N-1 lors du premier appel.
6. Tri rapide
6.2 Implémentation
6. Tri rapide
6.2 Implémentation
Complexité temporelle :
• complexité du cas d’arrêt 𝑛=1 :
o ligne 1 : affectation : 𝑂(1).
o total : 𝑇(1) = 𝑂(1).
• complexité du cas général 𝑛>0 :
o ligne 1 : affectation : 𝑂(1).
o ligne 2 : if :
 ligne 3 : affectations : 𝑂(1).
 ligne 4 : for :
 ligne 5 : if :

lignes 6, 7, 8 et 9 : affectation : 𝑂(1).
 répétitions : 𝑁−1 fois si le pivot est le min et se trouve au début ou est le
max et se trouve à la fin.
 total : (𝑁−1) 𝑂(1) = 𝑂(𝑁).
6. Tri rapide
6.2 Implémentation
Complexité temporelle (suite) :
 lignes 10, 11, et 12 : affectations : 𝑂(1).
 ligne 13 : if :
 ligne 14 : tri_rapide : 𝑇(𝑁−1).
 ligne 15 : if :
 ligne 16 : tri_rapide : 𝑇(1) = 𝑂(1).
 total : 𝑂(1) + 𝑂(𝑁) + 𝑂(1) + 𝑇(𝑁−1) + 𝑂(1).
o total : 𝑇(𝑁) = 𝑂(𝑁) + 𝑇(𝑁−1).
6. Tri rapide
6.2 Implémentation
Complexité temporelle (fin) :
• résolution de la récurrence :
o selon la formule :
 si 𝑇(𝑛) = 𝑇(𝑛−1) + Θ(𝑛𝑘)
 alors on a une complexité en O(𝑛𝑘+1).
o ici, on a :
 𝑘=1.
 d’où 𝑇(𝑁) = 𝑂(𝑁2).
6. Tri rapide
• remarques :
6.2 Implémentation
o complexité en moyenne : 𝑂(𝑁log𝑁).
o le temps dont l’algorithme a besoin pour trier le tableau dépend
fortement du pivot choisi :


Dans le pire des cas, le pivot se retrouve complètement au début ou à la fin du
tableau. On a donc un sous-tableau de taille 𝑁−1 et un autre de taille 0, et on
obtient une complexité en 𝑂(𝑁2).
Dans le meilleur des cas, le pivot sépare le tableau en deux sous-tableaux de taille
égale 𝑁/2. On retrouve alors la même complexité que pour le tri fusion : 𝑂(𝑁log𝑁).
6. Tri rapide
6.2 Implémentation
Complexité spatiale :
• 6 variables simples : 𝑂(1).
• 1 tableau de taille N : 𝑂(𝑁).
• profondeur de l’arbre d’appels (pire des cas) : 𝑁.
• total : 𝑆(𝑁) = 𝑂(𝑁2).
• remarque : si on considère que le même tableau est passé à
chaque fois, on a 𝑆(𝑁) = 𝑂(𝑁).
Propriétés :
• En place : oui (on travaille directement dans le tableau
initial).
• Stabilité : non (lors du déplacement des valeurs en fonction
du pivot, des valeurs de même clé peuvent être échangées).
Téléchargement