Algorithmes de tri et complexité Plan 1. RECHERCHE D'UN ELEMENT DANS UN TABLEAU ..................................................................................2 1.1. RECHERCHE SEQUENTIELLE D'UN ELEMENT .......................................................................................................2 1.2. RECHERCHE DICHOTOMIQUE DANS UN TABLEAU TRIE .........................................................................................2 1.3. COMPLEXITE D'UN ALGORITHME........................................................................................................................3 2. TRI PAR SELECTION DIRECTE ....................................................................................................................3 3. TRI PAR INSERTION ......................................................................................................................................5 4. TRI PAR PERMUTATION (TRI "BULLE") ....................................................................................................6 5. TRI PAR DEPLACEMENTS DECROISANTS (TRI DE SHELL) ....................................................................7 6. TRI PAR SEGMENTATION (QUICKSORT DE HOARE) ...............................................................................7 7. TRI PAR COMPTAGE.................................................................................................................................. 11 1. RECHERCHE D'UN ELEMENT DANS UN TABLEAU La nécessité de trier des données résulte du besoin de les retrouver rapidement. Par exemple, pour chercher l'adresse et le numéro de téléphone d'une personne, on parcours un fichier jusqu'à trouver l'enregistrement qui le concerne. On supposera par la suite que tous les éléments du fichier sont rangés dans un tableau en mémoire centrale (ce qui n'est pas toujours possible). 1.1. Recherche séquentielle d'un élément Pour rechercher séquentiellement un élément dans un tableau, on passe en revue tous les éléments de ce tableau, un à un, jusqu'à trouver la position de l'élément recherché (appelé encore indice). Un recherche séquentielle est plus ou moins rapide selon que l'élément recherché est situé au début ou en fin du tableau. En moyenne, on passe en revue la moitié des éléments. Si N est le nombre d'élément du tableau, le nombre moyen de comparaisons sera N/2. Exercice: écrire l'algorithme de recherche 1.2. Recherche dichotomique dans un tableau trié Lorsque le tableau est trié numériquement ou alphabétiquement, la méthode de recherche dichotomique permet de diviser le temps de recherche par 2 à chaque étape. L'algorithme est le suivant: 1) Initialiser la section de recherche à l'ensemble du fichier. 2) Comparer le nom cherché au nom de l'élément situé au milieu de la section de recherche 3) - si le nom cherché est plus petit que le nom de l'élément situé au milieu, on recommence la recherche dans la première moitié - si le nom cherché est plus grand que le nom de l'élément situé au milieu, la recherche se poursuit dans la seconde moitié. - sinon on a trouvé l'élément recherché Recommencer en (2) tant que l'élément cherché n'est pas trouvé ou que la section de recherche est constitué d'au moins un élément. Lorsqu'il y a par exemples 1024 éléments, on retrouve l'élément recherché après au plus 10 comparaisons (210 = 1024) alors qu'il en faut en moyenne 512 dans le cas d'une recherche séquentielle. Comme on le voit, l'algorithmique a une très grande importance dans la programmation. Notons dès à présent que les algorithmes utilisés dépendent beaucoup de l'organisation et des structures de données adoptées. 1.3. Complexité d'un algorithme Pour évaluer l'efficacité d'un algorithme, on introduit la notion du complexité comme étant l'ordre de grandeur des opérations à effectuer pour N données. Dans le cas de la recherche séquentielle dans un tableau de N éléments non triés, on a vu que le nombre moyen d'opérations est N/2. L'ordre de grandeur est donc en O(N) ce qui signifie que le temps de traitement est proportionnel au nombre d'élément. Dans le cas de la recherche dichotomique dans un tableau trié, l'ordre de grandeur est en O(Log2 N). La complexité des algorithmes permet de les comparer entre eux. Un algorithme de complexité O(N) est réputé meilleur qu'un algorithme en O(N2). Pour de grandes valeurs de N, cela parait évident et c'est souvent le cas. Pour des petites valeurs de N, ce n'est pas toujours vrai. 2. Tri par SELECTION DIRECTE On recherche l'élément le plus petit et on le range dans le second tableau. Cette méthode utilise 2 tableaux. Le tableau trié résultant n'est pas le tableau de départ qui conserve l'ordre initial des éléments à trier. Avec cette méthode, il faut se rappeler les éléments déjà pris. BETA ALPHA OMEGA ALPHA GAMMA BETA ALPHA OMEGA BETA -----------GAMMA Si N est le nombre d'éléments du tableau, cette méthode effectue N recherches du plus petit élément suivant dans le tableau à trier. Sachant que la recherche séquentielle d'un élément demande en moyenne N/2 comparaisons, cette méthode demandera N2/2 comparaisons. La complexité de cet algorithme est donc en O(N2) ce qui signifie que son temps de traitement est proportionnel au carré du nombre d'éléments à trier. Une amélioration consiste à n'utiliser qu'un seul tableau et procéder par permutation. Le début du tableau est considéré trié. On recherche le plus petit élément dans la section non triée du tableau. Cet élément est permuté avec le premier élément de la section non triée. La partie triée du tableau s'enrichit alors d'un élément. BETA OMEGA ALPHA GAMMA ALPHA OMEGA BETA GAMMA ALPHA BETA OMEGA GAMMA ALPHA BETA GAMMA OMEGA Exercice: écrire l'algorithme de tri par sélection directe avec un seul tableau. 3. Tri par INSERTION On n'utilise ici qu'un seul tableau. On considère à un instant donné que les P premiers éléments du tableau sont déjà triés. On recherche alors la place de l'élément numéro (P+1) parmi les P éléments déjà triés. L'élément est inséré à la place ainsi trouvée, ce qui a pour conséquence le déplacement des éléments plus grands que lui. +--------+ +--------+ +--------+ +--------+ ¦ BETA ¦ BETA _ ¦ <-+ ¦ ALPHA ¦ ALPHA +--------¦ Ã--------Â ¦ Ã--------Â Ã--------Â ¦ OMEGA ¦ OMEGA_ ¦ ¦ ¦ BETA ¦ BETA +--------¦ +--------¦ ¦ Ã--------Â Ã--------Â ¦ ALPHA ¦ ALPHA ¦ OMEGA_ ¦<--+ ¦ GAMMA Ã--------Â ¦ ¦ ? ¦ ¦ ?-+ ¦ ¦ +--------¦ ¦--------¦ +--------¦ ¦ GAMMA ¦ GAMMA ¦ GAMMA ¦ +--------+ ¦ +--------+ ¦ ¦ ?-+ +--------+ ¦ OMEGA ¦ ¦ ¦ ¦ +--------+ Notons que le déplacement des éléments, de la partie déjà triée, plus grands que l'élément à insérer peut s'effectuer au fur et à mesure des comparaisons. Si TAB[ i ] désigne l'élément numéro i du tableau, l'algorithme d'insertion s'écrit alors : EL_A_INSERER <- TAB[ P+1 ] INDICE <- P Tant que (TAB[ INDICE ] > EL_A_INSERER) ET ( INDICE >= 1) faire TAB[ INDICE+1 ] <- TAB[ INDICE ] INDICE <- INDICE-1 Fin (Tant que) TAB[ INDICE+1 ] <- EL_A_INSERER Exercice: écrire l'algorithme complet du tri par insertion. 4. Tri par PERMUTATION (Tri "Bulle") Dans le tri par permutation, appelé encore tri par inversion, l'idée est de passer en revue le minimum de fois possible les éléments du tableau. Ainsi à chaque parcours, on va comparer les paires d'éléments juxtaposés et les permuter si besoin de telle manière à rapprocher les éléments les plus petits vers le début du tableau et les éléments les plus grands vers la fin. +--------+ +--------+ +--------+ +--------+ ¦ BETA ¦ BETA ¦ BETA ¦ BETA ¦ ¦ ¦ ¦ +--------¦ +--------¦ +--------¦ +--------¦ ¦ OMEGA ¦ OMEGA ¦ ALPHA ¦ ALPHA ¦ ¦<--+ +--------¦ +--------¦ ¦ ALPHA ¦ ALPHA ¦ ¦ ¦<--+ ¦ +--------¦ +--------¦ ¦ OMEGA ¦ GAMMA ¦<--+ +--------¦ ¦--------¦ +--------¦ ¦ GAMMA ¦ GAMMA ¦ GAMMA ¦ +--------+ ¦ +--------+ ¦ ¦ ¦ +--------¦ ¦<--+ ¦ OMEGA +--------+ ¦ +--------+ Ici un deuxième passage en revue est nécessaire pour achever le tri: +--------+ +--------+ ¦ BETA ¦ ALPHA ¦<--+ +--------¦ ¦ ALPHA ¦ ¦<--+ ¦ Le nombre de passages K nécessaire +--------¦ pour obtenir un tableau trié étant ¦ BETA compris entre 1 et N ¦ +--------¦ +--------¦ de comparaisons ¦ GAMMA ¦ GAMMA étant ¦ ¦ de l'ordre à et le nombre chaque passage de N, on peut +--------¦ ¦--------¦ simplement dire ¦ OMEGA ¦ OMEGA du tri par permutation est compris ¦ +--------+ ¦ +--------+ que la complexité O(N) et O(N2). Une amélioration consiste à ne pas déposer systématiquement le deuxième élément d'une paire juxtaposée. En effet, il est possible que cet élément, qui appartient à la paire suivante, doit être une nouvelle fois permuté. L'élément qui n'est pas déposé crée ainsi un trou qui se déplace dans le tableau d'où le nom de tri "Bulle" donné à cet algorithme de tri. Exercice: écrire l'algorithme par permutation 1) sans tenir compte de cette amélioration, 2) en tenant compte de cet amélioration. 5. Tri par DEPLACEMENTS DECROISANTS (tri de SHELL) Dans le tri précédent, les éléments parcourent au pas à pas le chemin qui les mène à leur place définitive. L'idée de l'algorithme que proposa SHELL en 1959 est de déplacer les éléments plus rapidement dans le tableau. On ne compare plus une paire constituée de 2 éléments juxtaposées mais 2 éléments éloignés d'une certaine distance D. Les éléments TAB[ I ] et TAB[ I+D ] sont comparés et éventuellement permutés. L'algorithme du tri précédent est ainsi appliqué à D sous suites d'éléments du tableau à trier. La distance D, grande au début, est diminuée après chaque série de tris Bulle jusqu'à parvenir à la valeur unité. On s'arrange pour choisir des distances successives qui ne font pas intervenir des suites d'éléments déjà triés. La nouvelle valeur de D ne doit pas être en particulier un diviseur de l'ancienne valeur. On choisit en général des valeurs premières entre elles, exemple: 63, 31, 15, 7, 3 puis 1. Exercice: écrire l'algorithme de tri de SHELL. 6. Tri par SEGMENTATION (Quicksort de Hoare) Une valeur "pivot" est choisie parmi les valeurs à trier. Diverses manières de choisir la valeur du pivot existent. La suite d'éléments est réorganisée de telle manière à ce que les valeurs inférieures au pivot se trouve avant celui-ci et les valeurs supérieures après. Il reste ensuite à trier les 2 parties obtenues. Cet algorithme, récursif et de complexité O(N.Log2N), peut s'exprimer ainsi: Pour TRIER une section d'éléments d'indices compris entre KD et KF, faire : Choisir à priori un élément PIVOT, on prend le premier élément du tableau par exemple REPARTIR en fonction de ce pivot. La position du pivot change: il n'y a pas forcément autant d'éléments plus petits que d'éléments plus grands que le pivot. A la fin de la répartition la position du pivot est KP - TRIER la section d'indices compris entre 1 et KP - TRIER la section d'indices compris entre KP+1 et KF Pour REPARTIR une section par rapport à un pivot Au début la section des plus petits et la section des plus grands sont vides. Tant que des éléments n'appartiennent pas à une section des plus petits ou des plus grands, Faire : Parcourir la section des plus petits du premier vers le milieu et repèrer le premier élément plus grand ou égal que le pivot Parcourir la section des plus grands du dernier vers le milieu et repèrer le premier élément plus petit ou égal que le pivot - Permuter les 2 éléments précédemment repérés Exemple: Dans la suite d'éléments suivants, le premier élément est choisi pour pivot. Le premier élément plus grand que le pivot a pour valeur 20. Le premier élément, dans le parcours inverse, plus petit que le pivot a pour valeur 08. Ces 2 sont permutés. Pivot +---------------------------------------+ ¦ 09 ¦ 20 ¦ 14 ¦ 10 ¦ 06 ¦ 08 ¦ 60 ¦ 11 ¦ +---------------------------------------+ _ _ KD KF La section des plus petits possède à ce stade 2 éléments (09 et 20). La section des plus grands en comprend 3 (08, 60 et 11). Ils restent des éléments à répartir, on continue les parcours: l'indice KD augmente (parcours vers la droite) et l'indice KF diminue (parcours vers la gauche). Les éléments de valeurs 14 et 06 sont alors permutés. +---------------------------------------+ ¦ 09 ¦ 08 ¦ 06 ¦ 10 ¦ 14 ¦ 20 ¦ 60 ¦ 11 ¦ +---------------------------------------+ _ _ KF KD Il n'y a plus d'élément à répartir, on applique de manière récursive l'algorithme sur les sections 1..KF et KD..N. Remarque: Le fait de choisir pour pivot le premier élément de la section est pénalisant lorsque les éléments sont déjà prétriés. Le pivot peut être choisi de manière aléatoire parmi les éléments de la section à répartir. L'idéal serait de choisir le pivot de telle manière que les sous-sections créées soient de même longueur. Programme PASCAL Program TRI_RAPIDE; (*$M 16384, 0, 65536 *) Const NbMAX=999; Var TAB :ARRAY[ 1..NbMAX ] Of Integer; NbT, NN, TE :Integer; Procedure TRIER(KDeb, KFin :Integer); Var KK, KD, KF, PVT, ELT :Integer; Begin KD:=KDeb; KF:=KFin; PVT:=TAB[ KDeb ]; Repeat While TAB[ KD ] < PVT Do KD:=KD+1; While TAB[ KF ] > PVT Do KF:=KF-1; If KD <= KF Then Begin ELT:=TAB[ KD ]; TAB[ KD ]:=TAB[ KF ]; TAB[ KF ]:=ELT; KD:=KD+1; KF:=KF-1; End; Until KD > KF; If KDeb < KF Then TRIER(KDeb, KF); If KFin > KD Then TRIER(KD, KFIN); End; (* TRIER *) Begin (* LECTURE DES ELEMENTS A TRIER *) NN:=0; Write(' Nombre d''éléments ( < ',NbMAX,' ): '); ReadLN(NbT); WHile (NN < NbT) Do Begin NN:=NN+1; Write(' Entrer élément n°', NN, ' : '); ReadLN( TAB[ NN ]); End; If NbT > 1 Then TRIER(1, NbT); (* TRI *) For NN:=1 To NbT Do WriteLN(NN:3, TAB[ NN ]:4); (* RESULTAT *) End. 7. Tri par COMPTAGE Elements à trier non égaux (?clés unique) ... à vérifier On se sert d'un tableau auxiliaire de compteurs TCPT. Pour chaque éléments du tableau à trier on compte le nombre d'éléments qui lui sont inférieurs. La première étape est la création du tableau des compteurs? On proicède comme cidessous: Pour J de 1 à N Faire Pour I de j+1 à N Faire si TABT[ J ] < TABT[ I ] alors incrémenter TCPT[ I ] sinon incrémenter TCPT[ J ] Dans la 2ième étape, on utilise le tableau des compteurs pour mettre chaque élément à sa position correcte: Pour NC de 0 à N-1 Faire Recherche l'élément de compte NC: J=NC+1 Tant que TCPT[ J ] <> NC incrémenter J Permuter TABT[ NC+1 ] et TABT[ J ] Permuter TCPT[ NC+1 ] et TCPT[ J ]