Académique Documents
Professionnel Documents
Culture Documents
Introduction
Le traitement des données pose le problème de leur classement (tri). C'est pour cela que différentes
méthodes de tris ont été développées. Un algorithme de tri sert à ordonner un ensemble d'éléments. Il
facilite l'accès à l'information et la recherche d'un élément dans une quantité très grande d'information.
Vu l'importance de l'opération de tri, plusieurs algorithmes performants ont été proposés. Les différents
algorithmes présentent des performances différentes, en terme de temps d'exécution et en terme d'espace
mémoire requis pour leur exécution. Une utilisation courante de l'informatique est la recherche parmi
des données présentant des caractéristiques communes, d'éléments satisfaisant certains critères. En
général, le critère de recherche porte sur la valeur d'une clé. Par exemple, chercher le numéro de
téléphone (tel) d'une personne dans un ensemble de personnes (une personne = nom + adresse + tel).
Nous allons voir dans ce chapitre les principaux algorithmes de tri et de recherche de données
I. Notion de complexité
Pour résoudre un problème, on a besoin des ressources de l’ordinateur (la mémoire pour stocker les
données et le temps des calculs pour les opérations à réaliser par le processeur).
La complexité d’un algorithme mesure la quantité de chacune de ces grandeurs physiques (temps de
calcul, taille de la mémoire occupée). L’analyse des algorithmes consiste à quantifier ces deux grandeurs
physiques dans le but de comparer plusieurs algorithmes résolvant le même problème. Le but de
l’analyse de la complexité de l’algorithme est d’être capable d’affirmer « sur toute machine et quel que
soit le langage de programmation, l’algorithme A1 est meilleur que A2 pour les données de grande
taille » ou « l’algorithme A est optimal pour résoudre le problème B en nombre d’opération X ».
1. Opération fondamentale
Pour certains algorithmes, on peut mettre en évidence certaines opérations telles que le temps
d’exécution de l’algorithme soit proportionnel au nombre de ses opérations. Ces opérations sont dites
fondamentales.
2. Principe d’invariance
Si t1(n) et t2(n) sont les temps nécessaires pour exécuter le même algorithme sur les machines M1 et M2,
on a la relation t1(n) ≤ c t2 (n) ou c est une constante réelle positive. Ceci implique que la complexité des
algorithmes sera exprimée à une constante multiplicative près. Si le nombre d’opérations fondamentales
est t(n) ou n est la taille des données manipulées par l’algorithme ; alors sur la machine M1, on a t1(n) =
c1t(n) et sur la machine M2, on a t2(n) = c2 t(n). Les constantes c1 et c2 dépendent de la puissance de la
machine.
• Le choix des opérations que l’on prend en compte ne doit pas dépendre du langage ni de la
structure de programmation.
AA/ Structure de données avancées/2022-2023
• La complexité dépend de la taille des données
• La complexité dépend pour une taille fixée des différentes données possibles.
En général, la complexité des algorithmes pour résoudre une classe de problèmes d’une taille données
n’est pas la même quel que soit l’instance du problème. On distingue donc la complexité dans le
meilleur des cas et dans le pire des cas.
Meilleur des cas : les données manipulées sont les plus favorables
Pire des cas : les données manipulées sont les plus défavorables
Complexité en moyenne : moyenne des complexités calculées sur tous les cas possibles.
Dans une séquence, le nombre d’opérations fondamentales s’ajoute. Dans un branchement conditionnel,
il faut majorer le nombre d’opérations fondamentales.
Dans une seule boucle, si ni est le nombre d’opérations fondamentales à l’itération no i, le nombre total
d’opération est la somme des ni (∑𝑚𝑎𝑥𝑖=1 ni) dans une procédure ou une fonction, il faut évaluer le
nombre d’opération fondamentale de la fonction et de la procédure en utilisant la même stratégie.
La complexité algorithmique est une notion qui concerne l'évaluation des performances des
algorithmes réalisant les mêmes procédés ou les mêmes fonctionnalités et permettant de comparer et de
déterminer si un algorithme "a" est meilleur qu'un algorithme "b" et s'il est optimal ou s'il ne doit pas
être utilisé.
{Début}
S:= 0 ; I : =1 ; N:=10 {"1"}
while I <= N do {"2"}
S = S +K[I] {"3"}
I = I+1 {"4"}
end do;
{FIN}
Le temps d’exécution nécessaire pour cet algorithme t(n), composé de plusieurs temps de chaque
instruction. Nous supposons que :
• t1 est le temps d’exécution entre le début et la lecture des différentes variables d'initiation {"1"}
• t2 représente le temps d’exécution de la comparaison {"2"}
• t3 est le temps d’exécution de l’action {"3"}
AA/ Structure de données avancées/2022-2023
• t4 est le temps d’exécution de l’action {"4"}
Sachant que le temps t2, t3, t4 sont bien définis et inchangés durant l'exécution de cette ligne. Par
ailleurs ces temps représentent une boucle qui se répète n fois. Donc le temps nécessaire pour l'exécution
de la boucle est donné par :
Évaluation temporelle
L'évaluation temporelle d'un algorithme peut avoir plusieurs possibilités parmi elles lorsqu'il s'agit d'une :
• Somme des temps : l'exécution de trois actions l'une après l'autre ou chaque action demande un
temps de traitement relatif,
• Maximum de temps exp : c'est dans le cas d'une boucle avec la condition si
Note : d'une manière générale, les performances asymptotiques des algorithmes dépendent
principalement du nombre de variables, n, et la taille du problème, en négligeant les termes de degré
inferieur par exemple :
La première ligne indique la position initiale des éléments du tableau avant le démarrage de l'algorithme
Dans la première ligne de la première itération, l'algorithme compare le premier élément du tableau T
(nombre 7) avec le deuxième (nombre 4). Les éléments comparés sont notés en gris. Dans ce cas, un
échange est effectué entre ces deux éléments. On procède de la même façon (c'est à dire le premier
élément avec les autres) jusqu'à la fin de la première itération. Lorsque l'on termine de parcourir le
tableau la première fois (première boucle), on est sûr d'avoir placé le premier élément à la bonne place.
On parcourt donc de nouveau le tableau pour placer le second élément, le troisième jusqu'au dernier.
Version C
#include <stdio.h>
#define N 5
int main ()
{ int i, j, v;
int T[N];
//LECTURE DES ELEMENTS DU TABLEAU
printf("entrez les elements du tableau \n");
for ( i=0; i <N; i++) {
scanf("%d", &T[i]);
}
AA/ Structure de données avancées/2022-2023
// TRI DES ELEMENTS DU TABLEAU
for (i=0; i<N-1; i++) {
for (j=i+1; j<N; j++) {
if (T[i]>T[j]){
v= T[i];
T[i]=T[j];
T[j]=v;
}
}
}
// AFFICHAGE DU TABLEAU TRIE
for ( i=0; i <N; i++) {
printf ("%d \t",T[i]);
}
}
Le principe de l'algorithme tri à Bulle (Bubble Sort) consiste à comparer successivement les
éléments adjacents, et les permuter si le premier élément est supérieur au second. L'opération est
relancée jusqu'à ce qu'il n'y a plus de permutation possible (tous les nombres sont triés).
Description
L'algorithme contient deux boucles imbriquées. La boucle interne (la boucle Pour) permet de comparer
les éléments adjacents du tableau puis les échanger s'ils sont désordonnés. La boucle externe, quant à
elle, entraîne la boucle interne à se répéter jusqu'à ce qu'il n'a plus aucune permutation à effectuer
(variable permut = Faux). Les éléments les plus grands remontent ainsi, peu à peu vers les dernières
places, ce qui explique la dénomination de tri à bulleLe tableau suivant montre le fonctionnement de cet
algorithme.
AA/ Structure de données avancées/2022-2023
La première ligne indique la position initiale des éléments du tableau T avant le démarrage de
l'algorithme. Chaque ligne indique le contenu du tableau T après chaque itération de la boucle.
Le tri par insertion est l'un des plus naturels : c'est le tri qu'on utilise intuitivement lorsqu'on souhaite
trier une liste d'objets manuellement, par exemple des dossiers, des cartes à jouer etc. On prend un
dossier et on le met à sa place parmi les dossiers déjà triés. Puis on recommence avec le dossier suivant.
Le principe de cet algorithme est de considérer que le tableau est divisé en deux parties de tailles
variables; une dans laquelle les données sont triées et l'autre contenant les données à trier. Au début de
l'algorithme la partie non triée contient les autres éléments (le reste du tableau). on prend un à un les
éléments de la partie non triée du tableau et on l’insère au bon endroit dans la partie triée du tableau en
déplaçant tous les éléments qui le précède. Dés que l'élément à insérer est plus grand qu'un des éléments
de la partie triée du tableau, il n'y a plus de déplacement, et l'élément est inséré dans la case laissé
vacante par les éléments qui ont été déplacé.
Chaque ligne indique le contenu du tableau T après chaque itération de la boucle Pour, et la valeur
conservée par la variable echange. La première ligne indique la position initiale des éléments du tableau
T avant le démarrage de l'algorithme. La partie triée (note en gris) contient une case, les cases restants ne
sont pas triées (partie non triée) Dans le première itération (i = 2), le nombre 4 a été inséré au début du
tableau. Cela nécessite le déplacement du nombre qui le précède (7). On procède de la même façon
jusqu'à la fin du tableau. Cet algorithme est utile lorsque l'ensemble des éléments n'est pas disponible
AA/ Structure de données avancées/2022-2023
immédiatement. Imaginons par exemple une application interactive où l’utilisateur fournit un élément à
la fois et doit maintenir un ensemble trié de ces éléments.
Tri rapide
Le tri rapide (en anglais QuickSort) est une méthode de tri inventée par C. Hoare basée sur la méthode
conception "diviser pour régner". Il n'est pas le plus rapide dans tous les cas, mais il a la particularité
d'être relativement rapide dans la plupart des situations.
Le principe de cet algorithme consiste à choisir un élément T[i] du tableau, appelé Pivot qui va servir à
partitionner ce tableau en deux parties. Toutes les valeurs d'une partie seront inférieures eu Pivot, les
autres supérieurs. Ensuite, de façon récursive, l'algorithme va recommencer le tri dans les deux sous
tableaux ainsi obtenus.
Le schéma suivant illustre le principe du tri rapide. Nous choisissons ici comme Pivot l'élément du
tableau dont l'indice est le plus petit.
//Procédure Tri
Procédure Tri(T : tableau d'entiers, D : entier, F : entier)
Variable Pos : entier
Début
Si D < F Alors
Pos ← Partition(T, D, F)
//trie le sous tableau à gauche
Tri(T, D, Pos - 1)
//Trie le sous tableau à droite
Tri(T, Pos + 1, F)
FinSi
FinProc
#include<stdio.h>
#include<stdio.h> #define N 10
#define N 10
void permuter (int *a, int *b);
void permuter (int *a, int *b); void tri_rapide(int tab[], int first, int last);
void tri_rapide(int tab[], int first, int last); int main ()
int main () {
{ int tab[N], nbr,i;
int tab[N], nbr,i; printf("Entrer les elements du tableau :\n");
printf("Entrer les elements du tableau :\n"); for (i=0;i<N;i++)
for (i=0;i<N;i++) {
{ printf("T[%d] :",i);
printf("T[%d] :",i); scanf("%d",&tab[i]);
scanf("%d",&tab[i]); }
} tri_rapide(tab,0,N-1);
tri_rapide(tab,0,N-1);
printf("Tableau trie :\n");
printf("Tableau trie :\n"); for(i=0;i<N;i++)
for(i=0;i<N;i++) {
{ printf("%d\t",tab[i]);
printf("%d\t",tab[i]); }
} printf("\n");
printf("\n"); return 0;
return 0; }
} void permuter (int *a, int *b)
AA/ Structure de données avancées/2022-2023
void permuter (int *a, int *b) {
{ int tmp;
int tmp; tmp=*a;
tmp=*a; *a=*b;
*a=*b; *b=tmp;
*b=tmp; }
} void tri_rapide(int tab[], int first, int last)
void tri_rapide(int tab[], int first, int last) {
{ int pivot,i,j;
int pivot,i,j; if(first<last)
if(first<last) {
{ pivot=first;
pivot=first; i=first;
i=first; j=last;
j=last; while(i<j)
while(i<j) {
{ while (tab[i]<=tab[pivot] && i<last )
while (tab[i]<=tab[pivot] && i<last ) i++;
i++; while(tab[j]>tab[pivot])
while(tab[j]>tab[pivot]) j--;
j--; if (i<j)
if (i<j) {
{ permuter(&tab[i], &tab[j]);
permuter(&tab[i], &tab[j]); }
} }
} permuter(&tab[pivot], &tab[j]);
permuter(&tab[pivot], &tab[j]); tri_rapide(tab,first, j-1);
tri_rapide(tab,first, j-1); tri_rapide(tab,j+1,last);
tri_rapide(tab,j+1,last); }
} }
}
Description
L'algorithme ci-dessus contient une fonction et une procédure qui prennent en entrée un tableau d'entiers
et deux entiers qui sont des bornes entre lesquelles il faut trier les valeurs. L'exécution de cet algorithme
commence par l'exécution de l'algorithme principal qui fait appelle à la procédure Tri. La fonction
Partition sert à partitionner le tableau en deux parties et à mettre le Pivot à sa place définitif. Elle
retourne en résultat la position finale du Pivot. Le tableau ci-dessous montre une étape de
partitionnement du tableau.
Chaque ligne représente une itération de la boucle TantQue du partitionnement du tableau. Dans cette
boucle les indices inf et sup sont respectivement incrémentés et décrémentés tant qu'ils sont inférieurs
AA/ Structure de données avancées/2022-2023
(supérieurs) au Pivot. Ensuite la boucle effectue un échange entre deux éléments du tableau. Les cellules
en gris représentent le Pivot et les valeurs en gras sont celles qui vont être permutées.
La procédure Tri est constituée de l'appel à la fonction Partition et de l'appel récursif à la procédure de
tri dans les deux portions du tableau.
Le tableau ci-dessous illustre le fonctionnement de cet algorithme.
2. Recherche dichotomique
L'algorithme de recherche dichotomique est un algorithme efficace et rapide pour la recherche d'un
élément dans un tableau trié.
AA/ Structure de données avancées/2022-2023
Le principe de cet algorithme consiste à diviser le tableau en deux parties et à comparer la valeur
recherchée avec l'élément situé au milieu du tableau (l'élément médian).
Si la valeur recherchée correspond à l'élément médian, alors l'algorithme s'arrête et renvoie l'indice de
cet élément. Sinon l'algorithme va répéter la recherche uniquement dans la partie qui peut contenir la
valeur recherchée.
return 0;
}
Description :
L'algorithme ci-dessus est une fonction qui prend en entrée, un tableau T d'entiers trié, la taille de ce
tableau N et un entier ValRech qui est la valeur recherchée.
Les variables D et F correspondent aux indices respectivement début et fin de l'intervalle de recherche
dans le tableau. M c'est l'indice médian de l'intervalle de recherche.
Le tableau ci-dessous montre le fonctionnement de cet algorithme. La valeur recherchée est 35.
Chaque ligne indique l'intervalle de recherche dans le tableau à chaque itération de la boucle TantQue.
La cellule grise représente l'élément médian de l'intervalle de recherche.
La première ligne indique la position initiale avant le démarrage de l'algorithme. Dans la ligne suivante,
l'algorithme compare le nombre 15 qu'est l'élément médian à la valeur 35 (15 < 35). Dans ce cas
l'algorithme continue la recherche entre les indices M +1 et F.
Lors de l'itération suivante la valeur recherchée a été trouvée, la fonction s'interrompt et renvoie l'indice
de cet élément.
Cette fonction dispose de quatre sorties possibles : trois à l'intérieur de la boucle, si la valeur est trouvée
et une à la fin si elle n'est pas trouvée. Par convention la valeur -1 est retournée si la valeur n'est pas
trouvée.
Remarque :
Pour utiliser la recherche dichotomique, il faut tout d'abord trier le tableau en utilisant l'un des
algorithmes de tri.