Algorithmes de tri

publicité
Algorithmes de tri (1/2)
 Problématique : étant donné une structure linéaire (tableau, liste,
etc) contenant des valeurs d'un type ordonné, il faut trier les
éléments en ordre croissant (ou décroissant).
 Il existe des dizaines d'algorithmes répondant à ce problème,
utilisant quelques principes de base auxquels on ajoute des
variantes
 On peut distinguer :
 les tris internes qui trient les éléments dans la structure qui les contient
(tableau, liste, ...) ou au pire utilise une structure annexe de même type
 les tris externes utilisés lorsque la quantité d'éléments à trier est telle qu'ils ne
tiennent pas en mémoire vive et qu'il faut stocker des éléments dans des
fichiers ou des tables de bases de données
Licence Informatique - Semestre 2 - Algorithmique et Programmation
1
Algorithmes de tri (2/2)
 Propriétés des algorithmes de tri :
 complexité en temps et en espace
 stabilité : l'algorithme ne modifie pas l'ordre initial des éléments égaux par
comparaison
 tri sur place ou pas : l'algorithme n'utilise pas de mémoire supplémentaire
(hormis éventuellement quelques indices de boucle ou booléens), les
éléments sont triés directement dans la structure de données
 La plupart des algorithmes de tri utilisent la comparaison
d'éléments 2 à 2
 on montre que le nombre minimum de comparaisons à effectuer est nlog(n) si
n est la taille de la structure. Les algorithmes en O(n2) sont donc peu
efficaces, ceux au pire en O(nlog(n)) sont optimaux
 il existe des tris linéaires, mais ils n'utilisent pas la comparaison et sont limités
à des cas très particuliers (tris de nombres dont on connait des propriétés
statistiques)
Licence Informatique - Semestre 2 - Algorithmique et Programmation
2
Tri à bulle (1/7)
 Principe du tri à bulle (bubble sort) : l'élément le plus grand
remonte au bout du tableau, comme une bulle de champagne,
puis on recommence pour que le deuxième plus grand remonte,
etc. L'écriture de l'algorithme s'inspire de l'algorithme séquentiel
qui vérifie si un tableau est trié.
 Algorithme : i désigne la dernière case à traiter
fonction sans retour triBulle(entier[] tab){
entier i,j,temp;
début
pour (i allant de tab.longueur-2 à 1 pas -1) faire
pour (j allant de 0 à i pas 1) faire
si (tab[j] > tab[j+1]) alors
temp <- tab[j];
tab[j] <- tab[j+1];
tab[j+1] <- temp;
finsi
finpour
finpour
fin
Licence Informatique - Semestre 2 - Algorithmique et Programmation
3
Tri à bulle (2/7)
 Exemple : trier en ordre croissant le tableau suivant
701 | 17 | 2 | 268 | 415 | 45 | 45 | 102
premier tour de la première boucle (sur i)
j=0
17 | 701 | 2 | 268 | 415 | 45 | 45 | 102
j=1
17 | 2 | 701 | 268 | 415 | 45 | 45 | 102
j=2
17 | 2 | 268 | 701 | 415 | 45 | 45 | 102
j=3
17 | 2 | 268 | 415 | 701 | 45 | 45 | 102
j=4
17 | 2 | 268 | 415 | 45 | 701 | 45 | 102
j=5
17 | 2 | 268 | 415 | 45 | 45 | 701 | 102
j=6
17 | 2 | 268 | 415 | 45 | 45 | 102 | 701
Licence Informatique - Semestre 2 - Algorithmique et Programmation
4
Tri à bulle (3/7)
deuxième tour de la première boucle (sur i)
2 | 17 | 268 | 45 | 45 | 102 | 415 | 701
troisième tour de la première boucle (sur i)
2 | 17 | 45 | 45 | 102 | 268 | 415 | 701
quatrième tour de la première boucle (sur i)
2 | 17 | 45 | 45 | 102 | 268 | 415 | 701
Problème : l'algorithme va faire 3 tours supplémentaires
de la boucle sur i, inutilement
Licence Informatique - Semestre 2 - Algorithmique et Programmation
5
Tri à bulle (4/7)
 Complexité du tri à bulle en fonction de la taille n du tableau :
 la remontée du plus grand élément utilise (n-1) comparaisons, la remontée du
deuxième plus grand utilise (n-2), etc. La complexité sera donc toujours
proportionnelle à (n-1)+(n-2)+...+1 = n*(n-1)/2 soit O(n2).
 Le tri à bulle est en place et l'algorithme donné ici est stable (il
serait instable si on utilisait ≥ au lieu de >)
 Une amélioration possible du tri bulle consiste à utiliser une
variable booléenne drapeau qui permet de stopper le tri si plus
aucune permutation n'a lieu
Licence Informatique - Semestre 2 - Algorithmique et Programmation
6
Tri à bulle (5/7)
fonction sans retour triBulle(entier[] tab){
entier i,j,temp;
booleen b;
début
b <- faux;
i <- tab.longueur-2;
tantque ((non b) et (i > 0)) faire
b <- vrai;
pour (j allant de 0 à i pas 1) faire
si (tab[j] > tab[j+1]) alors
temp <- tab[j];
tab[j] <- tab[j+1];
tab[j+1] <- temp;
b <- faux;
finsi
finpour
i <- i-1;
fintantque
fin
 Remarques :
 la complexité au pire et en moyenne reste en O(n2) mais est linéaire au mieux
 cette amélioration n'est efficace que si le tableau est déjà « un peu » trié et
qu'il devient trié « assez vite »
Licence Informatique - Semestre 2 - Algorithmique et Programmation
7
Tri à bulle (6/7)
 Une autre amélioration possible du tri bulle est le tri
Boustrophedon (du nom des écritures qui changent de sens à
chaque ligne) :
 on parcourt le tableau de gauche à droite, en faisant remonter l'élément le
plus grand
 on redescend le tableau de droite à gauche, en faisant descendre le plus petit
 on recommence en ignorant les 2 éléments déjà triés
 la complexité au pire et en moyenne est toujours en O(n2) mais est réduite du
fait qu'on peut exploiter les bonnes positions des petits éléments comme des
plus grands
Licence Informatique - Semestre 2 - Algorithmique et Programmation
8
Tri à bulle (7/7)
Tri par la méthode du tri à bulle de tableaux d'entiers générés aléatoirement
Licence Informatique - Semestre 2 - Algorithmique et Programmation
9
Tri sélection (1/3)
 Principe du tri sélection (ou tri par extraction) : on parcourt le
tableau pour trouver le plus grand élément, et une fois arrivé au
bout du tableau on y place cet élément par permutation. Puis on
recommence sur le reste du tableau.
 Algorithme : i désigne la dernière case à traiter
fonction sans retour triSelection(entier[] tab){
entier i,j,temp,pg;
debut
i <- tab.longueur-1;
tanque (i > 0) faire
pg <- 0;
pour (j allant de 0 à i pas 1) faire
si (tab[j] > tab[pg]) alors
pg <- j;
finsi
finpour
temp <- tab[pg];
tab[pg] <- tab[i];
tab[i] <- temp;
i <- i-1;
fintantque
fin
Licence Informatique
- Semestre 2 - Algorithmique et Programmation
10
Tri sélection (2/3)
 Complexité du tri sélection : à chaque tour de la boucle tantque,
on fait i tours de boucle pour. La complexité sera donc dans tous
les cas proportionnelle à (n-1)+(n-2)+...+1 =n*(n-1)/2 et donc en
O(n2).
 Remarque : le tri sélection utilise une variable de plus que le tri bulle pour stocker
l'indice du plus grand élément, mais il utilise moins d'instructions (une seule
permutation par parcours du tableau)
 Le tri sélection est en place et l'algorithme donné ici est stable (il
serait instable si on utilisait ≥ au lieu de >)
 Le tri sélection peut être amélioré en positionnant à chaque
parcours du tableau le plus grand et le plus petit élément, selon le
même principe que celui utilisé pour le tri Boustrophédon.
 dans ce cas, on fera 2 fois moins de tours de boucle tantque (on trie 2
éléments à chaque tour) mais à chaque tour, on effectuera plus
d'opérations, la complexité reste donc quadratique mais s'améliore
Licence Informatique - Semestre 2 - Algorithmique et Programmation
11
Tri sélection (3/3)
Tri par la méthode du tri sélection de tableaux d'entiers générés aléatoirement
Licence Informatique - Semestre 2 - Algorithmique et Programmation
12
Tri insertion (1/2)
 Principe du tri insertion : on prend deux éléments qu'on trie dans
le bon ordre, puis un 3e qu'on insère à sa place parmi les 2
autres, puis un 4e qu'on insère à sa place parmi les 3 autres, etc.
C'est la méthode la plus utilisée pour trier des dossiers, des cartes
à jouer, ...
 Algorithme : i représente le nombre d'éléments triés
fonction sans retour triInsertion(entier[] tab){
entier i, j, val;
debut
pour (i allant de 1 à tab.longueur-1 pas 1) faire
val <- tab[i];
j <- i;
tantque ((j > 0) et (tab[j-1] > val)) faire
tab[j] <- tab[j-1];
j <- j-1;
fintantque
tab[j] <- val;
finpour
fin
Licence Informatique - Semestre 2 - Algorithmique et Programmation
13
Tri insertion (2/2)
 Complexité du tri insertion :
 au pire, si le tableau est trié en ordre inverse, à chaque tour de la boucle pour,
on fait i tours de boucle tantque. La complexité au pire sera donc
proportionnelle à 1+2+...+(n-1) =n*(n-1)/2 et donc en O(n2).
 au mieux, si le tableau est déjà trié, on fera une seule comparaison par
élément, sans permutation, sauf pour le premier élément. La complexité au
mieux est donc proportionnelle à (n-1) et linéaire.
 en moyenne, chaque élément sera inséré au milieu de ceux déjà triés, le
nombre de comparaisons sera donc de 0+1+3/2+...+n/2 soit n*(n+1)/4 et la
complexité moyenne est donc en O(n2).
 Le tri par insertion est en place et l'algorithme donné ici est stable
(il serait instable si on utilisait ≥ au lieu de >)
 Remarque : le tri insertion est donc environ 2 fois plus rapide que le tri sélection
Licence Informatique - Semestre 2 - Algorithmique et Programmation
14
Tris quadratiques
Comparaison des tris quadratiques sur des tableaux d'entiers générés
aléatoirement
Licence Informatique - Semestre 2 - Algorithmique et Programmation
15
Tri rapide (1/5)
 Principe du tri rapide (quicksort, tri de Hoare) : on prend le premier
élément (le pivot), on le met à sa place en plaçant à sa gauche les
éléments plus petits que lui et à sa droite les éléments plus grands
que lui. Puis on répète l'opération d'une part sur le sous tableau
de gauche, d'autre part sur le sous-tableau de droite.
 Algorithme : on partitionne le tableau autour de la valeur contenue
dans la première case, puis on appelle l'algorithme sur chacune
des sous-parties. Il nous faut donc :
 une fonction qui partitionne une certaine section [a,b] du tableau en plaçant au
début les éléments plus petits que tab[a], puis tab[a], puis les éléments plus
grands ou égaux à tab[a] et retourne l'indice de tab[a] dans le tableau
partitionné (au départ, a vaudra 0 et b vaudra tab.longueur-1)
 une fonction de tri proprement dit qui appelle la fonction précédente puis lance
des appels récursifs sur les sous-parties
Licence Informatique - Semestre 2 - Algorithmique et Programmation
16
Tri rapide (2/5)
fonction avec retour entier partition(entier[] tab, entier a, entier b){
entier pivot,i,temp;
début
pivot <- a;
pour (i allant de a+1 à b pas 1) faire
si (tab[i] < tab[pivot]) alors
temp <- tab[i];
tab[i] <- tab[pivot+1];
tab[pivot+1] <- tab[pivot];
tab[pivot] <- temp;
pivot <- pivot+1;
finsi
finpour
return pivot;
fin
// cette fonction trie les éléments de tab d'indices compris entre a et b inclus
fonction sans retour triRapide(entier[] tab, entier a, entier b){
entier pivot;
début
si (b > a) alors
pivot <- partition(tab,a,b);
triRapide(tab,a,pivot-1);
triRapide(tab,pivot+1,b);
finsi
fin
Licence Informatique - Semestre 2 - Algorithmique et Programmation
17
Tri rapide (3/5)
 Exemple d'exécution de la fonction de partitionnement :
70 | 17 | 2 | 268 | 415 | 45 | 45 | 102
i=1 et pivot=0
17 | 70 | 2 | 268 | 415 | 45 | 45 | 102
i=2 et pivot=1
17 | 2 | 70 | 268 | 415 | 45 | 45 | 102
i=3 et pivot=2
17 | 2 | 70 | 268 | 415 | 45 | 45 | 102
i=4 et pivot=2
17 | 2 | 70 | 268 | 415 | 45 | 45 | 102
i=5 et pivot=2
17 | 2 | 45 | 70 | 268 | 415 | 45 | 102
i=6 et pivot=3
17 | 2 | 45 | 45 | 70 | 268 | 415 | 102
i=7 et pivot=4
17 | 2 | 45 | 45 | 70 | 268 | 415 | 102
L'indice du pivot retourné est 4
Licence Informatique - Semestre 2 - Algorithmique et Programmation
18
Tri rapide (4/5)
 Complexité du tri rapide :
 la fonction de partitionnement est linéaire de la forme a*n où a est une
constante.
 au mieux, dans le cas où le partitionnement coupe toujours le tableau en deux
parties égales (à 1 près), la complexité c(n) de la fonction de tri est telle que
c(n) = a*n + 2*c(n/2) + b où b est une constante. Dans ce cas, on a c(n) =
O(n*log(n)).
 au pire, dans le cas où le partitionnement conduit systématiquement à ce
qu'une des parties soit vide, la complexité c(n) de la fonction de tri est telle
que c(n) = a*n + c(n-1) + b où b est une constante. Dans ce cas, on a c(n) =
O(n2).
 en moyenne, on peut montrer que c(n) = O(n*log(n)).
 Le tri rapide n'est pas optimum dans tous les cas, mais en
moyenne, il est bien plus rapide que les tris quadratiques
 Le tri rapide est en place, mais l'algorithme donné ici n'est pas
stable
Licence Informatique - Semestre 2 - Algorithmique et Programmation
19
Tri rapide (5/5)
 Améliorations possibles :
 quand le tableau est déjà « un peu trié », il vaut mieux choisir comme pivot
l'élément du milieu du tableau, pour équilibrer les parties
fonction avec retour entier partition(entier[] tab, entier a, entier b){
entier pivot,i,temp;
début
temp = tab[a];
tab[a] = tab[(a+b)/2];
tab[(a+b)/2] = temp;
pivot <- a;
...
fin
 quand le tableau n'est pas « un peu trié », on peut découvrir l'élément médian
(tel que la moitié des éléments du tableau sont plus petits, et la moitié plus
grands, à 1 près) avec un algorithme linéaire. Cette amélioration assure donc
une complexité au pire en O(n*log(n)) au tri rapide.
 quand les parties à trier sont petites, il peut être plus rapide d'utiliser un tri par
sélection pour les trier car ce tri est plus rapide sur de petits tableaux
 ...
Licence Informatique - Semestre 2 - Algorithmique et Programmation
20
Tri fusion (1/3)
 Principe du tri fusion : on découpe le tableau en deux, on trie
chacune des 2 parties, puis on fusionne les parties triées en
respectant l'ordre.
 Algorithme : il nous faut donc
 une fonction qui fusionne deux sections contigues du tableau. Le plus simple
est d'utiliser des tableaux supplémentaires (mais il est possible de faire du tri
fusion en place).
 une fonction de tri proprement dit qui lance des appels récursifs sur les sousparties puis appelle la fonction précédente
Licence Informatique - Semestre 2 - Algorithmique et Programmation
21
Tri fusion (2/3)
// cette fonction fusionne les parties de tab d'indices dans [a,b] et [b+1,c],
// en supposant que les éléments de ces parties soient triés en ordre croissant.
fonction sans retour fusion(entier[] tab,entier a, entier b, entier c){
entier i,j,k; entier[b-a+1] t1; int[c-b] t2;
début
pour (i allant de 0 à t1.longueur-1 pas 1) faire t1[i] <- tab[a+i]; finpour
pour (j allant de 0 à t2.longueur-1 pas 1) faire t2[j] <- tab[b+1+j]; finpour
i <- 0; j <- 0; k <- a;
tantque (k <= c) faire
si (i >= t1.longueur) alors
tab[k] <- t2[j]; j <- j+1;
sinon
si (j >= t2.longueur) alors
tab[k] <- t1[i]; i <- i+1;
sinon
si (t1[i] <= t2[j]) alors
tab[k] <- t1[i]; i <- i+1;
sinon
tab[k] <- t2[j]; j <- j+1;
finsi
finsi
finsi
k++;
fintantque
fin
Licence Informatique - Semestre 2 - Algorithmique et Programmation
22
Tri fusion (3/3)
fonction sans retour triFusion(entier[] tab, entier a, entier b){
début
si (b>a) alors
triFusion(tab,a,(a+b)/2);
triFusion(tab,((a+b)/2)+1,b);
fusion(tab,a,(a+b)/2,b);
finsi
fin
 Complexité du tri-fusion :
 la complexité de la fusion est linéaire de la forme a*n où a est une constante
 la complexité du tri est c(n) = c(n/2) + c(n/2) + a*n + b où b est une constante
donc c(n) = O(n*log(n)) dans tous les cas
 Le tri fusion est donc optimum en complexité, mais généralement,
il est moins rapide que le tri rapide, car il duplique le tableau. On
peut optimiser le tri en ne dupliquant qu'une des deux parties.
 Le tri fusion est stable mais la version proposée ici n'est pas en
place (il est possible d'en écrire des versions en place)
Licence Informatique - Semestre 2 - Algorithmique et Programmation
23
Tris quasilinéaires
Comparaison des tris quasilinéaires sur des tableaux d'entiers générés
aléatoirement
Licence Informatique - Semestre 2 - Algorithmique et Programmation
24
Téléchargement