• Aucun résultat trouvé

Tri par fusion

On a examin´e deux algorithmes de tri dont le coˆut est quadratique dans le nombre d’´el´ements `a trier. Peut-on faire mieux ? On va appliquer une strat´egie diviser pour r´egner dont on a d´ej`a vu un exemple dans le cadre de la recherche dichotomique. Une fa¸con d’appli-quer cette strat´egie donne lieu au tri par fusion (mergesort, en anglais) qui a ´et´e propos´e par Von Neumann autour de 1945.

— Si le tableau a taille 1, le tableau est tri´e.

— Sinon, on s´epare le tableau en deux parties ´egales et on les trie. — Ensuite on fait une fusion des deux tableaux tri´es.

Tri et permutations 57

Le coeur de l’algorithme est la fonction de fusion de deux ensembles ordonn´es. L’id´ee naturelle est de parcourir en parall`ele les deux ensembles par ordre croissant (par exemple) et de s´electionner `a chaque pas le minimum entre les deux. Si l’on repr´esente les ensembles comme des listes (section 10.1) il est facile de construire la liste fusion en place (sans allocation de m´emoire). Cependant, si l’on repr´esente les ensembles comme des tableaux il est beaucoup plus compliqu´e (mais possible) de travailler en place. Une solution simple, consiste `a utiliser un tableau auxiliaire. Avant de commencer la fusion on copie les deux tableaux ordonn´es dans le tableau auxiliaire et ensuite on ´ecrit la solution dans le tableau de d´epart. Une mise en oeuvre en C pourrait ˆetre la suivante.

1 v o i d f u s i o n (int t [] , int i , int j , int k ){

2 a s s e r t (( i <= j ) & & ( j < k )); 3 p r i n t f(" f u s i o n (% d ,% d ,% d )\ n " , i , j , k ); 4 int aux [ k + 1 ] ; 5 int p ; 6 for( p = i ; p <= k ; p + + ) { 7 aux [ p ]= t [ p ];} 8 p = i ; 9 int q = j +1; 10 int r = i ; 11 w h i l e ( r <= k ){ 12 if ( p > j ){ 13 t [ r ]= aux [ q ]; q ++; r ++; 14 c o n t i n u e;} 15 if ( q > k ){ 16 t [ r ]= aux [ p ]; 17 p ++; 18 r ++; 19 c o n t i n u e;}

20 if (( p <= j ) && ( aux [ p ] <= aux [ q ] ) ) {

21 t [ r ]= aux [ p ];

22 p ++;

23 r ++;

24 c o n t i n u e;}

25 if (( q <= k ) && ( aux [ p ] > aux [ q ] ) ) {

26 t [ r ]= aux [ q ];

27 q ++;

28 r ++;

29 c o n t i n u e; } } }

30 v o i d t r i f u s i o n (int t [] , int i , int j ){

31 a s s e r t ( i <= j ); 32 if ( i < j ){ 33 int m =( i + j ) / 2 ; 34 t r i f u s i o n ( t , i , m ); 35 t r i f u s i o n ( t , m +1 , j ); 36 f u s i o n ( t , i , m , j ) ; } }

Efficacit´e du tri par fusion

Quelle est l’efficacit´e du tri par fusion ? Soit C(n) une borne sup´erieure au temps n´ecessaire pour trier par fusion un tableau de taille n. On pose la r´ecurrence suivante :

C(1) = 1

C(n) = 2 · C(n/2) + n

qui veut dire qu’un probl`eme de taille n g´en`ere deux sous-probl`emes de taille n/2 et effectue un travail de combinaison (la fusion) dont le coˆut est proportionnel `a n.

Pour simplifier le raisonnement, supposons que n = 2k. On a : 20 probl`emes de taille 2k 21 probl`emes de taille 2k−1 · · ·

2k probl`emes de taille 20

La somme du travail de combinaison `a chaque niveau est constant et ´egal `a n. Comme on a k = log2n niveaux, le travail total est de l’ordre de nlog2n.

Remarque 7 La solution de relations de r´ecurrence est un sujet tr`es vaste (c’est la version discr`ete des ´equations diff´erentielles !). Par exemple, on sait traiter toutes les r´ecurrences de la forme :

C(n) = a · C(n/b) + nc .

Calcul du nombre d’inversions

On va ´etudier une application remarquable de la fonction de fusion. On dispose d’un tableau t non-ordonn´e de n ´el´ements. Une inversion est un couple (i, j) tel que :

1 ≤ i < j ≤ n t[i] > t[j]

On souhaite calculer le nombre d’inversions dans t qui est un nombre compris entre 0 et

n(n−1) 2 .

Exercice 3 Proposez un algorithme pour calculer le nombre d’inversions. Programmez une fonction C qui correspond `a l’algorithme d’en tˆete : int inversions(int n, int t[n]).

Comme il y a de l’ordre de n2 inversions, tout algorithme qui compte les inversions une par une prendra dans le pire des cas un temps quadratique en n. Pour ˆetre plus efficaces il nous faut donc une m´ethode pour compter plusieurs inversions en mˆeme temps. Il se trouve qu’il est possible de modifier la fonction fusion ci-dessus de fa¸con telle que l’algorithme de tri par fusion qui l’utilise calcule le nombre d’inversions. Consid´erons les 4 cas de la boucle while de la fonction fusion (section 7.1). Dans le deuxi`eme cas, on inverse l’´el´ement a[q] avec tous les ´el´ements a[p], . . . , a[j] et il faut donc ajouter au compteur j − p + 1 inversions. Il n’y a pas d’inversion dans les autres 3 cas. En supposant que l’addition d’entiers 32 bits se fait en temps constant, on a une efficacit´e comparable `a celle de l’algorithme du tri par fusion. Voici une application de la m´ethode `a la s´equence 7, 6, 5, 4, 3, 2, 1, 0 :

S´equences `a fusionner Nombre inversions 76, 54, 32, 10 4 · 1 = 4

6745, 2301 2 · 4 = 8 54670123 1 · 16 = 16

Total 28 =8·7 2

Tri et permutations 59

Bornes inf´erieures

Quelle est le coˆut minimal d’un algorithme de tri ? Il est clair que tout algorithme de tri doit examiner l’int´egralit´e de son entr´ee et que le coˆut de cette op´eration est lin´eaire.

Les algorithmes de tri qu’on a consid´er´e sont bas´es sur la comparaison d’´el´ements. Pour ce type d’algorithmes un simple argument combinatoire qui va suivre permet de conclure qu’on ne peut pas faire mieux que n log n.

On consid`ere un algorithme (d´eterministe) qui prend en entr´ee un tableau de n ´el´ements x0, . . . , xn−1. L’algorithme compare un nombre fini de fois et deux par deux les ´el´ements du tableau. A la fin de cette phase de comparaison, l’algorithme n’a plus acc`es au tableau et il calcule une permutation π sur {0, . . . , n − 1} telle que xπ(0) ≤ · · · ≤ xπ(n−1). Combien de comparaisons faut-il faire dans le pire des cas ? Le calcul de l’algorithme peut ˆetre visualis´e comme un arbre binaire enracin´e o`u on associe une comparaison `a chaque noeud interne et une permutation `a chaque feuille. L’arbre binaire doit avoir au moins une feuille pour chaque permutation sur {0, . . . , n − 1}, soit n! feuilles ; sinon, il est facile de voir qu’il y a une entr´ee sur laquelle l’algorithme n’est pas correct. Le pire des cas correspond au chemin le plus long de la racine `a une feuille. La longueur (on compte le nombre d’arˆetes qu’il faut traverser) de ce chemin est la hauteur de l’arbre. Il est ais´e de v´erifier qu’un arbre binaire de hauteur h peut avoir au plus 2h feuilles. On doit donc avoir 2h ≥ n!, soit :

h ≥ log2n! = Σi=1,...,nlog2i . On remarque que :

Σi=1,...,nlog2i ≥ Z n

1

log2xdx , et en calculant l’int´egrale on trouve une valeur de l’ordre de n log n.

L’argument pr´esent´e fait des hypoth`eses restrictives sur la forme de l’algorithme. Voici un exemple d’algorithme qui ne respecte pas ses restrictions et qui a une coˆut lin´eaire si le nombre d’´el´ements diff´erents qui peuvent apparaˆıtre dans la s´equence x0, . . . , xn−1est lin´eaire en n. Pour fixer les id´ees, on suppose que la s´equence est m´emoris´ee dans le tableau T et que 0 ≤ T [i] ≤ 10 · n, pour i = 0, . . . , n − 1. Dans ce cas, on peut en temps lin´eaire en n :

— allouer un tableau C avec 10 · n entiers initialis´es `a 0,

— parcourir le tableau T et pour chaque ´el´ements T [i] incr´ementer C[T [i]] et

— parcourir le tableau C et pour chaque ´el´ement C[i] ´ecrire C[i] fois i dans le tableau T .