Algorithmes de Tri
Algorithmes de Tri
Algorithmes de Tri
L2 - S3
1. PRESENTATION
Le tri est sans doute un des problèmes les plus fondamentaux de l’algorithmique. Après le tri beaucoup
d’autres problèmes deviennent facile à résoudre tels que l’unicité ou la recherche.
Dans ce qui suit, on décrit les principaux algorithmes de tri puis on analysera leur complexité temporelle.
2.1. Principe
Itérativement, le tri par sélection consiste à chercher le plus petit élément puis de le mettre au début.
Exemple
Liste initiale 42 17 13 28 14
1ère itération 13 17 42 28 14
2ème itération 13 14 42 28 17
3ème itération 13 14 17 28 42
4ème itération 13 14 17 28 42
2.2. Implémentation
public void selectionSort(int[] A, int n)
{
for (int i=0;i<n-1;i++)
{ int imin=i;
for (int j=i+1;j<n;j++)
if (A[j]<A[imin]) imin=j;
swap(A,i,imin);
}
}
2.3. Complexité
Le pire et le meilleur cas sont pareils, puisque pour trouver le plus petit élément, (𝑛 − 1) itérations sont
nécessaires, pour le 2ème plus petit élément, (𝑛 − 2) itérations sont effectuées… jusqu’à l’avant dernier plus
petit élément qui nécessite 1 itération. Le nombre total d’itérations est donc :
𝑛(𝑛 − 1)
𝑖= = 𝑂(𝑛 )
2
3.1. Principe
Itérativement, on insère le prochain élément dans la partie qui a été déjà triée précédemment. La partie de
départ qui est triée est le premier élément.
Exemple
Liste initiale 42 17 13 28 14
1ère itération 17 42 13 28 14
2ème itération 13 17 42 28 14
3ème itération 13 17 28 42 14
4ème itération 13 14 17 28 42
3.2. Implémentation
public void insertionSort(int[] A, int n)
{
for (int i=1;i<n;i++)
{ int temp=A[i],j=i;
while (j>0 && A[j-1]>temp)
{ A[j]=A[j-1];j--;}
A[j]=temp;
}
}
3.3. Complexité
Comme nous n’avons pas nécessairement à scanner toute la partie déjà triée, le pire et le meilleur cas sont
différents.
Meilleur cas : si le tableau est déjà trié, chaque élément est toujours inséré à la fin de la partie triée ; nous
n’avons à déplacer aucun élément. Comme nous avons à insérer (𝑛 − 1) éléments, chacun
générant seulement une comparaison, la complexité est 𝑂(𝑛).
Pire cas : si le tableau est inversement trié, chaque élément est inséré au début de la partie triée. Dans
ce cas, tous les éléments de la partie triée doivent être déplacés à chaque itération. La ième
itération génère (i-1) comparaisons et échanges de valeurs :
𝑛(𝑛 − 1)
(𝑖 − 1) = = 𝑂(𝑛 )
2
4.1. Principe
Parcourir le tableau en comparant deux à deux les éléments successifs et permuter s’ils ne sont pas dans
l’ordre.
Exemple
Liste initiale 42 17 13 28 14
1ère itération 13 42 17 14 28
2ème itération 13 14 42 17 28
3ème itération 13 14 17 42 28
4ème itération 13 14 17 28 42
4.2. Implémentation
public void bubbleSort(int[] A, int n)
{
for (int i=0;i<n-1;i++)
for (int j=n-1;j>i;j--)
if (A[j]<A[j-1]) swap(A,j,j-1);
}
4.3. Complexité
Globalement, c’est la même complexité que le tri par sélection. Le meilleur et le pire cas sont pareils avec une
complexité de 𝑂(𝑛²).
5.1. Principe
Cet algorithme divise en deux parties égales le tableau. Après que ces deux parties soient triées (de manière
généralement récursive), elles sont fusionnées pour l’ensemble des données.
Exemple
42 17 13 28 14
42 17 13 28 14
42 17 13 28 14
28 14
14 28
17 42 13 14 28
13 14 17 28 42
5.2. Implémentation
public void mergeSort(int[] A, int deb , int fin)
{
int mil;
if (deb<fin) {
mil=(deb+fin)/2; (* DIVIDE *) .......................................................... 𝑶(𝟏)
mergeSort(A,deb,mil); (* CONQUER *) ....................................................... 𝑻(𝒏/𝟐)
mergeSort(A,mil+1,fin);(* CONQUER *) ....................................................... 𝑻(𝒏/𝟐)
fusion(A,deb,mil,fin); (* COMBINE *) ....................................................... 𝑶(𝒏)
}
}
5.3. Complexité
La complexité peut être exprimée par récurrence :
O(1) si n 1
𝑇(𝑛) = 𝑇(𝑛) = 𝑂(𝑛𝑙𝑜𝑔 𝑛)
2T (n / 2) O( n ) si n 1
Le tableau A sera divisé par 2 jusqu’à obtention de tableaux de taille 1, ainsi :
Taille de A : 𝑛 , 𝑛/2 , 𝑛/4, …., 𝑛/2 avec 𝑛/2 = 1 𝑝 = 𝑙𝑜𝑔 (𝑛)
Puisqu’à chaque étape, une (ou plusieurs) opération(s) de fusion de l’ordre 𝑂(𝑛) est exécutée sur les sous
tableaux obtenus, on obtient donc 𝑂(𝑛𝑙𝑜𝑔 𝑛).
6.1. Principe
L’idée de cet algorithme est de diviser le tableau en deux parties séparées par un élément appelé pivot de
telle manière que les éléments de la partie gauche soient tous inférieurs ou égaux à cet élément et ceux de la
partie droite soient tous supérieurs à ce pivot. Cette étape fondamentale du tri rapide s’appelle le
partitionnement.
Choix du pivot : Le choix idéal serait que ça coupe le tableau exactement en deux parties égales, mais
cela n’est pas toujours possible. On peut prendre le premier ou le dernier ou de
manière aléatoire,…
Partitionnement :
On parcourt de gauche à droite jusqu’à rencontrer un élément supérieur au pivot.
Dans ce qui suit (Exemple & implémentation) on choisit l’élément se trouvant au milieu du tableau comme
pivot.
Exemple
6.2. Implémentation
public void quickSort(int[] A, int deb, int fin)
{
int ipivot;
if (deb<fin)
{
ipivot=partition(A,deb,fin);
quickSort(A,deb,ipivot-1);
quickSort(A,ipivot+1,fin);
}
}
6.3. Complexité
Cas favorable :
La meilleure chose qui puisse arriver, c'est qu'à chaque fois que la fonction Partition() est appelée, elle divise
exactement le tableau (ou le sous-tableau) en 2 parties égales.
À la première passe, les 𝑛 éléments du tableau sont comparés avec la valeur pivot pour les balancer à la
droite ou à la gauche. Il y a donc 𝑛 comparaisons. À la seconde passe il y a 2 fonctions Partition() qui
effectuent leur rôle chacune sur leur moitié de tableau. Chaque fonction doit comparer les 𝑛/2 éléments du
sous-tableau pour effectuer le balancement. Donc de fait, il y a encore 𝑛 comparaisons pour cette passe. Il en
sera de même pour les autres itérations.
Cas défavorable :
La pire chose qui puisse arriver, c'est qu'à chaque appel à la fonction Partition(), à cause des circonstances
de la disposition des données, celle-ci place la totalité du sous-tableau à droite ou à gauche (excluant bien
sûr l'élément pivot qui est alors à sa place définitive). Dès lors le tri rapide se transforme en un tri à bulles.
À la première passe, il y aura donc 𝑛 comparaisons. À la seconde passe il y a déjà une valeur ordonnée et un
sous-tableau de 𝑛 − 1 éléments, il y aura donc 𝑛 − 1 comparaisons effectuées par la fonction Partition(). À la
3ème passe, il y aura 𝑛 − 2 comparaisons, etc.
Pour effectuer le tri au complet, il aura donc fallu en tout 𝑛 passes. D'où la complexité:
𝑛 + (𝑛 − 1) +. . . +2 + 1 = 𝑛(𝑛 + 1)/2
Soit donc O(𝑛²) = pire cas de Quicksort. Mais il reste que la complexité en moyenne est O(𝑛log2𝑛).