TD3- Les fonctions de tri et la notion de complexité

publicité
TD3- Les fonctions de tri et la notion de complexité
Notions du TD
Tri de tableaux – Algorithmes de tri – Notion de complexité – Introduction des collections d'objet – Évaluation de la durée
d'exécution d'un algorithme
Corps du TD
Différentes applications exigent de trier des données. Il s'agit par exemple de classer des hedges funds en
fonction de leurs niveaux de performance, il s'agit de classer des titres en fonction de leur niveau de risque, il s'agit de
classer des noms par ordre alphabétique ou par les résultats qu'ils obtiennent... Sans prévoir une application directe, on
introduit les notions de tri et leur applications sur des tableaux et des collections de données. Les tableaux sont déjà des
collections de données, mais il existe d'autres formes de collections qui sont de taille variables. Celles-ci relèvent des
connaissances théoriques sur l'objet, qui n'ont pas encore été vues, mais qui le seront ensuite.
Dans un premier temps, on ne s'intéresse qu'aux tableaux, avant d'implémenter un algorithme pour le tri des
données pour le cas d'une collection. Dans tout ce qui suit, on ne s'intéresse qu'aux tableaux d'entiers. Il est bien évident
que le raisonnement serait le même pour n'importe quels types de tableaux.
Etape 1 : un premier algorithme de tri de données : le tri à bulles
Un algorithme de tri classique est le tri à bulles. S'il est loin d'être optimal, voire s'il est parmi les algorithmes de
tri les plus coûteux en temps, il est traditionnellement le premier algorithme de tri présenté en raison de sa simplicité
relative par rapport aux autres algorithmes.
On veut trier un tableau par ordre croissant. L'algorithme consiste à parcourir un tableau élément par élément.
On considère d'abord le premier élément du tableau, on le compare au deuxième élément du tableau : si le deuxième
élément du tableau est inférieur au premier élément, on échange ces deux premiers éléments. On considère ensuite le
deuxième élément par rapport au troisième élément. On échange ces éléments si le troisième élément est inférieur au
deuxième élément etc... Après un premier parcours du tableau, on parcourt une seconde fois le tableau, puis une
troisième etc... On continue de parcourir le tableau tant qu'il doit procéder à au moins un échange entre les éléments du
tableau.
Un exemple d'utilisation de cet algorithme, on considère le tableau suivant :
-1
-6
3
-2
7
On commence l'algorithme de tri. Au début, l'algorithme compare les deux premiers éléments. Il apparaît -1 > -6, donc il
Cours de C++ - Rémi Dorat
faut échanger les deux premières valeurs. Le tableau devient :
-6
-1
3
-2
7
L'algorithme compare ensuite la deuxième valeur du tableau à la troisième, -1<3. L'algorithme considère ensuite la
troisième valeur du tableau et la quatrième, comme 3<-2, on échange les valeurs, le tableau devient :
-6
-1
-2
3
7
Est ensuite comparée la quatrième valeur avec la cinquième. 3<7 : on n'échange pas les valeurs.
On a terminé un premier passage dans le tableau. Comme ce premier passage a donné lieu à des échanges,
on doit faire un autre passage dans le tableau.
On recommence en comparant la première valeur et la seconde valeur du tableau. Ces deux valeurs ne
donnent pas lieu à échange. On considère la deuxième valeur du tableau et la troisième : celles-ci donnent lieu à
échange :
-6
-2
-1
3
7
L'algorithme parcourt ensuite le tableau. -1<3, 3<7 : il n'y a plus d'échange à faire. Comme le passage dans le
tableau a donné lieu à un échange, on fait un nouveau passage dans le tableau. Ce passage ne conduit pas à un nouvel
échange. On sait donc que pour tout i du tableau, t[i]<t[i+1], et donc, on peut conclure, par transitivité, que l'ensemble du
tableau est classé.
La dénomination du tri à bulles vient du fait que les valeurs les plus faibles du tableau remontent vers le début
du tableau à l'instar des bulles de gaz qui remontent vers le haut des bouteilles.
On va procéder à l'implémentation de cet algorithme. Pour simplifier le problème, on commence par travailler
avec un tableau de 5 éléments.
1) Créer un tableau de 5 éléments en mettant des valeurs aléatoires dans le tableau.
2) Proposer un code qui permet d'afficher ce tableau. Proposer ce code comme une fonction
3) Proposer un code qui permet d'échanger deux éléments du tableau. Vérifier qu'il fonctionne avec les affichage
adéquats
4) Faire du code précédent une fonction qui prend le tableau en paramètre. Là aussi, vérifier qu'elle fonctionne. On
appelera cette fonction echange(int t[],int indice1,int indice2). Elle aura donc pour effet d'échanger les valeurs contenues
dans t[indice1] et t[indice2].
5) Implémenter l'algorithme de tri à bulle pour le tableau et vérifier qu'il fonctionne.
6) Proposer une fonction qui initialise un tableau avec des valeurs tirées au hasard.
Cours de C++ - Rémi Dorat
7) Pour afficher la date courante en milliseconde, on peut utiliser la fonction time(0). Par exemple : int n=time(0). On va
pouvoir évaluer le temps d'exécution de l'algorithme de tri, grâce à cette fonction, en prenant la date courante au début
de l'algorithme de tri, puis à la fin de cet algorithme et en faisant la différence entre ces deux dates. Faire une fonction
qui prend en paramètre un nombre d'essais, une taille de tableaux et donne le temps moyen pour classer le tableau en
utilisant l'algorithme de tri à bulle. Le temps moyen sera établi sur le nombre d'essais passé en paramètre.
Etape 2 : D'autres algortihmes de tri de données, plus de complexité pour plus d'efficacité
En supposant qu'une fonction échange ait été définie avec cette signature :
void echange(int t[],int i,int j)
(c'est la fonction telle qu'on a demandé de la définir à la question 4), on peut définir un autre algorithme de tri : le
quickksort ou tri rapide. Cet algorithme de tri est défini au travers de deux fonctions que l'on donne telles quelles :
//La fonction
considère T(premier),
détermine son
rang de
classement et
classe les
valeurs avant ou après ce rang
int fPivot(int T[],int premier,int dernier,int tailleT)
{
//Compteur est une variable qui correspond au rang auquel on va placer le pivot, soit
le nombre de valeurs inf au pivot
int compteur = premier;
int x = T[premier];
for(int i = premier + 1;i<=dernier;i++)
{
//si l'élément est inférieur au pivot
if(T[i] < x)
{
compteur = compteur + 1;
//on place l'élément à la position finale du pivot : si compteur ne bouge plus
alors l'élt finira à la place du pivot,
//sinon, cet élément sera inférieur au pivot
//L'instruction suivante garantie que toutes les place jusqu'au pivot sont
remplies par des valeurs inférieures.
echange(T,compteur, i);
}
}
echange(T,compteur, premier);
return compteur;
}
Cours de C++ - Rémi Dorat
//On passe en entrée le tableau et l'espace du tableau que l'on veut classer (souvent le
premier et le dernier indice utilisable
void triRapide(int T[],int premier,int dernier,int tailleT)
{
int varPivot;
//Condition d'arret qui correspond au fait d'avoir un unique élément
if(premier < dernier)
{
//partition du tableau en 2 parties dont chaque élément de la première est inf à
l'élt de la seconde
varPivot = fPivot(T, premier, dernier,tailleT);
triRapide(T, premier, varPivot - 1,tailleT);
triRapide(T, varPivot + 1, dernier,tailleT);
}
}
Pour trier un tableau t de taille n, il suffit d'utiliser l'instruction
triRapide(t,0,n-1,n);
une explication complète du fonctionnement de l'algorithme dépasse le cadre de ce cours et ne sera pas développée ici.
8) Reprendre cet algorithme tel quel, vérifier qu'il peut classer un tableau, d'abord sur un tableau de taille 5, puis sur un
tableau de taille 100.
9) Faire une fonction qui prend en paramètre un nombre d'essais, une taille de tableaux et donne le temps moyen pour
classer le tableau en utilisant l'algorithme de tri rapide.
10) En faisant évoluer la taille du tableau, tester le temps de tri moyen pour chacun des deux algorithmes et tracer une
courbe d'évolution ce temps de tri moyen. Conclure
Etape 3 : Introduction des vecteurs
Un vecteur est un objet. Sa manipulation relève des règles de la manipulation de variables objets, qui n'a pas
encore été vue. Un vecteur est une collection d'éléments qui n'est pas de taille fixe.
Pour utiliser les vecteurs, il faut inclure la librairie dans laquelle sont définies ces objets :
#include <vector>
L'instruction pour définir une variable vecteur :
Cours de C++ - Rémi Dorat
vector<int> v;
Dans ce cas, v est un vecteur d'entiers. On pourrait créer des vecteurs de double, etc...
Pour ajouter des valeurs :
v.push_back(7);
Cette instruction ajoute 7 comme dernier élément du vecteur v.
On peut ensuite modifier ou utiliser les éléments du vecteur :
vector<int> v;
v.push_back(7);
v.push_back(4);
v.push_back(-5);
for(int i=0;i<v.size();i++)
{
cout << v.at(i) << "\n";
}
v.at(0)=-2;
v.at(1)=v.at(0)-v.at(2);
v.at(2)=v.at(1)+6;
for(int i=0;i<v.size();i++)
{
cout << v.at(i) << "\n";
}
Dans ce cas, l'exécution du code permettra d'afficher
7
4
-5
-2
3
9
Dans ce code, on a introduit v.size() qui est la taille du vecteur et v.at(i) qui est le ième élément du vecteur qu'il est
possible d'accéder et d'utiliser dans des calculs ou qu'il est possible de modifier. A noter que pour faire passer un
vecteur à une fonction, il faut faire passer sa référence, son adresse et non pas le vecteur.
11) Proposer une solution de tri à bulle pour un objet vector : l'implémenter et la tester.
Cours de C++ - Rémi Dorat
Téléchargement