Structures de données

publicité
Structures de données
Dr. Youssef Bou Issa
[email protected]
http://youssef-bouissa.fr/
Algorithmes
• Un algorithme est une suite finie
d’instructions
– pouvant être exécutées de façon automatique.
• Un algorithme prend en entrée des données
et produit un résultat.
Algorithmes
• On peut dire qu’une recette de cuisine est un
algorithme
– l’entrée étant les ingrédients
– et la sortie le plat cuisiné.
Algorithmes
• On peut distinguer trois façons de décrire un
algorithme.
– Une première méthode consiste à en donner le
principe.
– Une deuxième méthode consiste à utiliser du
pseudo-code ;
– Une troisième méthode consiste à traduire
l’algorithme dans un langage de programmation.
• Précision des 3 façons
Caractéristiques d’un algorithme
• Sa simplicité :
– un algorithme simple est facile à comprendre, à
implémenter et généralement à prouver.
• La qualité du résultat obtenu :
– par exemple, une recette pour faire une mousse
au chocolat peut donner un résultat plus ou moins
agréable à déguster.
Caractéristiques d’un algorithme
• Le temps pris pour effectuer l’algorithme :
– Il n’est pas matériellement possible de résoudre
un problème à l’aide d’un algorithme nécessitant
quelques milliards d’années pour s’exécuter.
– On peut aussi préférer un algorithme qui
s’exécutera en une seconde plutôt qu’en une
heure.
Caractéristiques d’un algorithme
• La place prise en mémoire :
– cette place va dépendre à la fois de l’algorithme et
des structures de données retenues ;
– il ne faut pas dépasser les capacités de la mémoire
de la machine sur laquelle l’algorithme
s’exécutera.
Caractéristiques d’un algorithme
• le fait de déclarer un tableau exige de
réquisitionner des emplacements mémoires
– inutilisables par d'autres programmes comme
votre système d'exploitation.
– Tester l'égalité entre deux variables va nécessiter
de réquisitionner temporairement le processeur
Caractéristiques d’un algorithme
• parcourir un tableau de 10 000 cases
• en testant chacune des cases exigera donc de réquisitionner
au moins 10 000 emplacements mémoires
• d'interrompre 10 000 fois le processeur pour faire nos tests.
• Tout cela ralentit les performances de
l'ordinateur et saturer la mémoire
• D'où l'intérêt de pouvoir comparer la complexité
de différents algorithmes
– pour ne conserver que les plus efficaces,
– ou de prédire cette complexité.
Notion de complexité
• étant donné un algorithme, nous appelons
opérations élémentaires :
– un accès en mémoire pour lire ou écrire la valeur
d’une variable ou d’une case d’un tableau ;
– une opération arithmétique entre entiers ou entre
réels : addition, soustraction,
– multiplication, division, calcul du reste dans une
division entière ;
– une comparaison entre deux entiers ou deux
réels.
Notion de complexité
(ordre de grandeur)
• On considère f(n)>0, g(n)>0
On dit que f(n) a un ordre de grandeur au plus égal
à celui de g(n)
S’il existe un entier k tel que :
f(n)<=k.g(n)
on écrira f = O(g) (notation de Landau).
Exemple :
f(n) = n2
g(n) = 3 n2 + 5 n + 4
ont même ordre de grandeur (voir schéma)
Notion de complexité
(ordre de grandeur)
• Exemple f(n) : l'algorithme qui lit un tableau et
teste chacune de ses cases :
– si le tableau a 100 cases, l'algorithme effectuera
100 tests ;
– si le tableau à 5000 cases, l'algorithme effectuera
5000 tests ;
– si le tableau a n cases, l'algorithme effectuera n
tests ! On dit que sa complexité est en O(n).
Notion de complexité
(ordre de grandeur)
• un autre exemple g(n): un algorithme qui parcourt lui
aussi un tableau à n éléments.
• Pour chaque case, il effectue deux tests :
– le nombre est-il positif ?
– Le nombre est-il pair ?
• Combien va-t-il effectuer de tests ?
– deux fois plus que le premier algorithme, c'est à dire 2*n.
f(n)<=2g(n)
– l'algorithme f a toujours une complexité proportionnelle à
g et donc ils sont tous les deux en O(n).
Notion de complexité
(Ordre de grandeur)
• Est-ce que tous les algorithmes ont une
complexité en O(n)? NON
• Un facteur 2, 3, 20,…
– peut être considéré comme négligeable.
– tout facteur constant peut être considéré comme
négligeable.
Notion de complexité
(Ordre de grandeur)
• Reprenons encore notre algorithme : il parcourt
toujours un tableau à n éléments,
– mais à chaque case du tableau, il reparcourt tout le
tableau depuis le début
– pour savoir s'il n'y aurait pas une autre case ayant la
même valeur.
• Combien va-t-il faire de tests ?
– Pour chaque case, il doit faire n tests
– Et comme il y a n cases, il devra faire en tout n*n tests
• Le facteur n'est cette fois pas constant
• cet algorithme a une complexité en O(n2)
Notion de complexité
(Ordre de grandeur)
Légende de gauche à droite:
Complexité en O(en) (e : exponentielle)
Complexité en O(n2)
Complexité en O(n*log(n))
Complexité en O(n)
Complexité en O(log(n))
Notion de récursivité
• Ecrire en C la fonction itérative qui calcule la
factorielle d’un nombre
• Considérons la fonction factorielle (!).
• Elle est définie par :
– 0! = 1
– n! = 1 x 2 x … x (n – 1) x n si n > 0
Notion de récursivité
• Si l’on remarque que le produit
1 x 2 x … x (n – 1) est égal à (n – 1)!
• On peut définir la factorielle de la façon suivante :
• n! = 1, si n = 0
• n! = (n – 1)! x n, si n > 0
• Une telle définition est dite récursive car la
factorielle est appelée dans sa propre définition.
Notion de récursivité
• Considérons alors l’exemple :
3! = (2!) x 3
= ((1!) x 2) x3
= (((0!) x 1) x 2) x 3
= ((1 x 1) x 2) x 3
=6
• Une fonction récursive est formée d’une relation de
récurrence et d’un ou plusieurs cas de base.
Par exemple, dans la définition récursive de la
factorielle :
– la relation de récurrence est « n! = (n – 1)! X n, si n > 0 » ;
– le cas de base est « 0! = 1 ».
Définition d’une fonction récursive
• Démarche pour définir une fonction récursive:
1-établir la relation de récurrence ;
2-établir les cas de base ;
3-vérifier que l’application de la relation de
récurrence convergera vers l’un des cas de base ;
4- écrire en C la définition de la fonction qui
– devra tester si l’un des cas de base a été atteint,
– et relancer l’application de la relation de
récurrence, si ce n’est pas le cas.
Définition d’une fonction récursive
• Appliquons cette démarche à la définition de
la fonction fac,qui, appliquée à un nombre
entier n (n ≥ 0) calcule la factorielle de n.
1. Etablir la relation de récurrence.
fac(n) = n x fac(n – 1), si n > 0
2. Etablir les cas de base.
fac(n) = 1, si n = 0
Définition d’une fonction récursive
3. Vérifier que l’application de la relation de
récurrence convergera vers l’un des cas de
base :
– A chaque appel de la fonction fac on soustrait 1 à
n qui deviendra donc égal à 0 au (n + 1)e appel,
– ce qui arrêtera la récursion puisque le cas de base
sera atteint,
– pourvu que la condition n <= 0 ait été respectée
lors de l’appel initial.
Définition d’une fonction récursive
4. Ecrire en C la définition de la fonction qui devra tester si
l’un des cas de base a été atteint, et relancer l’application
de la relation de récurrence, si ce n’est pas le cas.
Int fac(int n) {
if (n <= 0)
return 1;
else
return fac(n - 1) * n;
}
• Quel est la complexité de cet algorithme?
Définition d’une fonction récursive (2)
• Ecrire la fonction itérative somme qui calcule
la somme des entiers compris entre i et j.
• Appliquons maintenant notre démarche de
récursivité à la définition de cette fonction
Définition d’une fonction récursive (2)
1. Etablir la relation de récurrence.
La somme des entiers compris entre i et j (i < j) est égale à
i plus la somme des entiers compris entre i + 1 et j :
somme(i, j) = i + somme(i + 1, j), si i < j
2. Etablir les cas de base.
La somme des entiers compris entre i et i est égale à i :
somme(i, j) = i, si i = j
Définition d’une fonction récursive (2)
3. Vérifier que l’application de la relation de
récurrence convergera vers l’un des cas de
base.
A chaque appel de la fonction somme on ajoute
1 à i qui deviendra égal à j au (j – i + 1)e appel,
ce qui arrêtera la récursion puisque le cas de
base sera atteint, pourvu que la condition i <=
j ait été respectée lors de l‟appel initial.
Définition d’une fonction récursive (2)
• 4. Ecrire en C la définition de la fonction qui devra tester si
l’un des cas de base a été atteint, et relancer l’application
de la relation de récurrence, si ce n’est pas le cas.
int somme(int i, int j)
{
if (i >= j)
return i;
else
return i + somme(i + 1, j);
}
Quel est la complexité de cet algorithme?
Recherche d’un élément dans un
tableau
• Ecrire en C la fonction qui cherche une valeur
donnée dans un tableau trié.
• Quel est la complexité de cet algorithme?
Recherche dichotomique
(principe)
• La recherche dichotomique est une méthode
de type « diviser pour régner ».
• On considère le tableau ci-dessous
• Et la valeur recherchée est 25
Recherche dichotomique
(principe)
• On coupe le tableau en 2
• Le nombre recherché 25 est supérieur à 17, il
ne peut donc appartenir qu’au sous tableau
de droite.
Recherche dichotomique
(principe)
• On coupe le sous-tableau de droite en deux :
• Le nombre recherché 25 est inférieur ou égal à
42, il ne peut donc appartenir qu’au sous
tableau de gauche.
Recherche dichotomique
(principe)
• On coupe le sous-tableau de gauche en deux :
• Le nombre recherché 25 est inférieur ou égal
à 25, il ne peut donc appartenir qu’au soustableau de gauche qui n’a qu’une seule case :
Recherche dichotomique
algorithme itératif
void recherche_dichotomique(int tab[],int finle,int r){
int i=0;
int j=finle-1;
int m;
while(i!=j){
m=(i+j)/2;
if(tab[m]>=r){j=m;}
else{i=m+1;}
}
printf("la valeur recherchée est dans la case %d",i);
}
Recherche dichotomique
algorithme récursif
int chercher(int r, int tab[], int i, int j)
{
int m;
if (i == j)
return tab[i] == r;
else
{
m = (i + j) / 2;
if (r <= tab[m])
return chercher(r, tab, i, m);
else
return chercher(r, tab, m + 1, j);
}
}
Comparaison du Nombre d’itérations
dans les algorithmes de recherche
x
y1
y2
Calcul de la complexité en ordre de
grandeur
• Pour calculer la complexité, il faut chercher en
premier lieu une relation (de récurrence)
entre le « coût » de la nième itération et le
coût de l’itération précédente.
• Dans notre cas, on peut remarquer que :
C(n)=C(n/2)+1
* 1= cout d’une itération
Calcul de la complexité en ordre de
grandeur
O(ln (n) )
Algorithmes de tri
• Tri à bulles
– premier algorithme de tri auquel on pense
intuitivement
– comparer deux à deux les éléments d'un tableau
ou d'une liste à trier et d'échanger leur position
s'ils sont mal placés
Tri à bulles
• Principe (1/3) :
– comparer deux valeurs adjacentes et d'inverser leur
position si elles sont mal placées.
– si un premier nombre x est plus grand qu'un deuxième
nombre y
– on souhaite trier l'ensemble par ordre croissant,
– alors x et y sont mal placés et il faut les inverser.
– Si, au contraire, x est plus petit que y,
– alors on ne fait rien
– et on compare y à z: l'élément suivant.
Tri à bulles
• Principe (2/3) :
– C'est donc itératif.
– Et on parcourt ainsi la liste jusqu'à ce qu'on ait
réalisé n-1 passages (n représentant le nombre de
valeurs à trier)
– ou jusqu'à ce qu'il n'y ait plus rien à inverser lors
du dernier passage.
Tri à bulles
• Principe (3/3)
– Au premier passage, on place le plus grand
élément de la liste au bout du tableau, au bon
emplacement.
– Pour le passage suivant, nous ne sommes donc
plus obligés de faire une comparaison avec le
dernièr élément ;
– Donc à chaque passage, le nombre de valeurs à
comparer diminue de 1.
Tri à bulles
• Illustration du principe :
Considérons les éléments suivants :
6035142
• Nous voulons trier ces valeurs par ordre
croissant.
Tri à bulles
Illustration du principe :
Premier passage :
6035142
0635142
0365142
0356142
0351642
0351462
0351426
sage
// On compare 6 et 0 : on inverse
// On compare 6 et 3 : on inverse
// On compare 6 et 5 : on inverse
// On compare 6 et 1 : on inverse
// On compare 6 et 4 : on inverse
// On compare 6 et 2 : on inverse
// Nous avons terminé notre premier pas
Tri à bulles
• Deuxième passage :
• refaire un passage mais en omettant la dernière
case.
0 3 5 1 4 2 6 // On compare 0 et 3 : on laisse
0 3 5 1 4 2 6 // On compare 3 et 5 : on laisse
0 3 5 1 4 2 6 // On compare 5 et 1 : on inverse
0 3 1 5 4 2 6 // On compare 5 et 4 : on inverse
0 3 1 4 5 2 6 // On compare 5 et 2 : on inverse
0 3 1 4 2 5 6 // Nous avons terminé notre
deuxième passage
Tri à bulles
0 3 1 4 2 5 6 // On compare 0 et 3 : On laisse
0 3 1 4 2 5 6 // On compare 3 et 1 : On inverse
0 1 3 4 2 5 6 // On compare 3 et 4 : On laisse
0 1 3 4 2 5 6 // On compare 4 et 2 : On inverse
0 1 3 2 4 5 6 // Nous avons terminé notre
3eme passage
Tri à bulles
0132456
0132456
0132456
0123456
passage
// On compare 0 et 1 : On laisse
// On compare 1 et 3 : On laisse
// On compare 3 et 2 : On inverse
// Nous avons terminé notre 4eme
Tri à bulles
0 1 2 3 4 5 6 // On compare 0 et 1 : On laisse
0 1 2 3 4 5 6 // On compare 1 et 2 : On laisse
0 1 2 3 4 5 6 // Nous avons terminé notre passa
ge
l'algorithme s'arrête
• il n'y a plus eu d'échange lors du dernier
passage
Tri à bulles
#include <stdio.h>
#include <stdlib.h>
#define N 8
int main()
{
int i,j;
int tab[8] = {15, 10, 23, 2, 8, 9, 14, 16};
printf("Avant:");
for(i = 0; i < N; i++) printf("%d, ",tab[i]);
Tri à bulles
for (i=0 ; i<N ; i++)
{
int j=0;
for (j=0 ; j<(N-i-1) ; j++)
{
if (tab[j]>=tab[j+1]) {
int tampon = tab[j];
tab[j] = tab[j+1];
tab[j+1] = tampon;
//on échange les 2 valeurs, en utilisant une case tampon
}
}
}
Tri à bulles
printf("\nAprès:");
for(i = 0; i < N; i++) printf("%d, ",tab[i]);
printf("\n");
system("PAUSE");
return 0;
}
Complexité du tri à bulles
• la complexité du tri à bulles est en O(n²).
Tri par selection
• Principe :
– rechercher le plus grand élément le placer en fin
de tableau, recommencer avec le second plus
grand, le placer en avant-dernière position et ainsi
de suite jusqu'à avoir parcouru la totalité du
tableau.
Tri par selection
• Soit le tableau d'entiers suivant :
6281537940
1. L'élément le plus grand se trouve en 7ème
position (si on commence à compter à partir de
zéro)
6281537940
2. On échange l'élément le plus grand (en 7ème
position) avec le dernier :
6281537049
Tri par selection
• Le dernier élément du tableau est désormais
forcément le plus grand.
• On continue donc en considérant le même
tableau, en ignorant son dernier élément :
6241537089
• Et ainsi de suite, en ignorant à chaque fois les
éléments déjà triés.
Tri par selection
Void tri_selection (int tab[],int finle)
{
Int i,j,imax,temp;
For(j=finle-1;j>=0;j--){
imax=0;
For(i=1;i<=j;i++){
If(tab[imax]<tab[i]){imax=i;}
Temp=tab[j];
Tab[j]=tab[imax];
Tab[imax]=temp;
}
}
Le tri par insertion
• Principe
– insère un élément dans une liste d'éléments déjà
triés
• Exemple : jeu de cartes :
– main gauche : des cartes triées de la plus petite à
la plus grande
– Main droite : une carte
– Main gauche : 1 3 6 8 ---main droite 5
– il faut la placer après (1 3) et avant (6 8)
Le tri par insertion
• Principe
Le tri par insertion
• Principe :
– Pour trier entièrement un ensemble de cartes:
• placer toutes ses cartes dans la main droite (la main
gauche est donc vide),
• et d'insérer les cartes une à une dans la main gauche.
Le tri par insertion
• Dans un tableau, on a du
décaler certaines cartes :
– 6 était en position 2 avant
l'insertion, elle est en position
3 après.
– De même, la carte 8 a été
décalée.
– il faut décaler d’une case vers
la droite toutes les cartes plus
grandes que la carte à insérer.
Le tri par insertion
• on décale la carte la plus à
droite (8),
• puis celle juste à gauche (6),
• jusqu'au moment où on tombe
sur une carte plus petite que
celle qu'on veut insérer, qu'il ne
faut pas décaler.
• Une fois qu'on a fait ces
décalages, on peut insérer la
carte, à la position à laquelle on
s'est arrêté de décaler
Le tri par insertion
(Implementation)
• La fonction qui insere
void inserer(int element_a_inserer, int tab[], int finle_gauche)
{
int j;
for (j = finle_gauche; j > 0 && tab[j-1] > element_a_inserer; j--)
On part de la fin de la main gauche, donc de finle_gauche, et on descend (j--) tant que les cartes
sont plus grandes que la carte à insérer
Le test j > 0 vérifie qu’on ne sort pas du tableau
tab[j] = tab[j-1];
//la boucle s’arrete
tab[j] = element_a_inserer;
On insère alors cet élément juste après la case j-1, donc en j.
} La boucle s’arrête quand la carte tab[j-1] devient plus petite que l’élément à
insérer
Le tri par insertion
(implémentation)
Le tri par insertion
(implémentation)
• Remarque :
– finle_gauche est la finle de la main gauche,
– mais ce n’est pas la finle du tableau tab :
– comme on rajoute un élément, on a besoin que le
tableau tab ait au moins une case de plus.
– Donc finle réelle de tab est toujours strictement
supérieure à finle_gauche,
– c’est pour cela qu’on a écrit dans
tab[finle_gauche]
Le tri par insertion
(implémentation)
void tri_insertion(int tab[], int finle)
{ int i;
for(i = 1; i < finle; i++)
Element à
inserer
inserer(tab[i], tab, i);
}
L’idée, c’est que je considère que toutes les cartes
avant i sont triées, et que toutes les cartes après i
ne sont pas triées, tab[i] compris. i est donc la
limite entre la main gauche et la main droite.
Le tri par insertion
(implémentation)
void tri_insertion(int tab[], int finle)
• L’idée, c’est que je considère que
{ int i;
toutes les cartes avant i sont
for(i = 1; i < finle; ++i)
triées,
inserer(tab[i], tab, i);
• et que toutes les cartes après i ne
}
sont pas triées, tab[i] compris.
• i est donc la limite entre la main
gauche et la main droite.
void inserer(int element_a_inserer, int tab[],
• Donc les deux mains ne sont pas
int finle_gauche)
séparées:
– elles sont ensembles dans un seul
tableau :
• la main gauche est le début du
tableau, qui est déjà trié,
• et la main droite le reste du tableau.
Le tri par insertion
(implémentation)
Le tri par insertion
(implémentation)
Code complet (tri par insertion)
#include <stdio.h>
#include <stdlib.h>
void affichage();
void inserer();
void tri_insertion();
int main(int argc, char *argv[])
{
int tab[]={15,20,35,10,60,5,3};
int finle=7;
affichage(tab,finle);
printf("avant");
tri_insertion(tab,finle);
printf("apres");
affichage(tab,finle);
system("PAUSE");
return 0;
}
void inserer(int element_a_inserer, int tab[], int finle_gauche)
{
int j;
for (j = finle_gauche; j > 0 && tab[j-1] > element_a_inserer; j--)
tab[j] = tab[j-1];
//la boucle s’arrete
tab[j] = element_a_inserer;
}
void tri_insertion(int tab[], int finle){
int i;
for(i = 1; i < finle; i++)
inserer(tab[i], tab, i);
}
void affichage(int tab[],int finle){
int i=0;
for (i=0;i<finle;i++)printf("tab[%d]=%d\n",i,tab[i]);
}
Les pointeurs
• Un pointeur est une variable qui contient
l’adresse d’une autre variable.
• Exemple:
int x, y, *p;
x=4;
p=&x //p contient l’adresse de x;
Les pointeurs
Les pointeurs
int x, y, *p;
x=4;
p=&x //p contient l’adresse de x;
Printf(‘’ adresse de x % d’’, p);
Printf(‘’ adresse de x % d’’, &x);
Printf(‘’ adresse de x % d’’, *p);
Printf(‘’ adresse de x % d’’, x);
Les pointeurs
•
•
•
•
x=4;
P=&x;
y=*p;
(*p)++;// incrementer le contenu de l’adresse
p par 1. incrementer x par 1;
• Printf(‘’ valeur de x est %d,x) //x=5;
• Printf(‘’ la valeur de y est %d’’, y);//y=4
Les pointeurs
• Exemple 2 :
int age = 10;
printf("La variable age vaut : %d", age);
printf("L'adresse de la variable age est : %p",
&age);
Les pointeurs
int age = 10;
int *pointeurSurAge;
// 1) signifie "Je crée un pointeur"
pointeurSurAge = &age;
// 2) signifie "pointeurSurAge contient l'adresse
de la variable age"
Les pointeurs
Les pointeurs
int nombre = 0;
scanf("%d", &nombre);
• Même écriture :
int nombre = 0;
int *pointeur ;
pointeur= &nombre;
scanf("%d", pointeur);
Les pointeurs
• Est-il possible qu’une fonction retourne deux
valeurs?
Les pointeurs, passage par adresse
void decoupeMinutes(int* pointeurHeures, int*
pointeurMinutes);
void decoupeMinutes(int* pointeurHeures, int*
pointeurMinutes)
{
*pointeurHeures = *pointeurMinutes / 60;
*pointeurMinutes = *pointeurMinutes % 60;
}
Les pointeurs, passage par adresse
int main(int argc, char *argv[])
{
int heures = 0, minutes = 90;
// On envoie l'adresse de heures et minutes
decoupeMinutes(&heures, &minutes);
// Cette fois, les valeurs ont été modifiées !
printf("%d heures et %d minutes", heures, minutes);
return 0;
}
Tableaux pointeurs et allocation
dynamique
•
•
•
•
void exemple2()
{
float *f;
printf(" f: %x",f);
• f=f+1;/* augmente f de 4 octets en fait */
• /* si un float est codé sur 4 octets */
• printf(" f: %x",f);
•
}
Tableaux pointeurs et allocation
dynamique
void exemple3()
{
int tab[3]={5,15,20};
int *p;
p=&tab[0]; /* <=> p=tab; */
printf ( "*p=%d",*p);
printf ( "adresse element 0: %x\n",p);
p++;
/* p pointe maintenant sur le deuxième élément du tableau */
printf ( "*p=%d",*p);
printf ( "adresse element 1: %x\n",p);
/* tab++; n'est pas possible car tab est un pointeur constant */
}
Tableaux pointeurs et allocation
dynamique
void exemple4()
{
int i;
char ch1[80], ch2[80];
char *s1, *s2;
/* lecture de la chaîne de caractères ch1 */
scanf("%s", ch1); /* passage par adresse, ch1 est déjà une adresse*/
s1=ch1;
printf("s1=%c\n",*s1);
while(*s1!=0) {
*s2 = *s1;
s1++;
s2++;}
}
}
Tableaux pointeurs et allocation
dynamique
int *tab;
tab=malloc(4*sizeof(int));
tab[0]=5;
tab[1]=13;
tab[2]=15;
tab[3]=25;
int i;
int *p;
p=&tab[0];
for(i=0;i<4;i++){
printf("%d,",*p);
p++;
}
Structure de données
typedef struct
{
char nom;
float x, y;
} point;
Structure de données
•
•
•
•
point a;
a.nom='a';
a.x=5;
a.y=6;
•
•
•
•
point b;
b.nom='b';
b.x=5;
b.y=10;
Structure de données
point milieu(point a, point b){
point m;
m.nom='m';
m.x=(a.x+b.x)/2;
m.y=(a.y+b.y)/2;
return m;
}
Structure de données
• point m=milieu(a,b);
•
• printf("le point m milieu de a et b a pour
coordonnees x=%f et y=%f",m.x,m.y);
Listes chaînées
Listes chaînées
• Plusieurs façons d’implémenter des
conteneurs :
– Tableaux
– Listes chaînées
Listes chaînées
• Tableau :
– Éléments placés de façon contiguë en mémoire
– éléments de celui-ci sont placés de façon contiguë
en mémoire
– Pour supprimer un élément au milieu du tableau,
il faut recopier les éléments temporairement, réallouer de la mémoire pour le tableau, puis le
remplir à partir de l'élément supprimé.
Liste chaînée
• liste chaînée
– les éléments de la liste sont répartis dans la
mémoire et reliés entre eux par des pointeurs.
– On peut ajouter et enlever des éléments d'une
liste chaînée à n'importe quel endroit, à n'importe
quel instant, sans devoir recréer la liste entière.
Listes chaînées
Listes chaînées
• Dans une liste chaînée,
– la finle est inconnue au départ, la liste peut avoir autant
d'éléments que votre mémoire le permet.
– impossible d'accéder directement à l'élément i de la liste
chainée.
– Pour ce faire, il faudra traverser les i-1 éléments précédents de
la liste.
• Pour déclarer une liste chaînée,
– il suffit de créer le pointeur qui va pointer sur le premier
élément de la liste chaînée, aucune finle n'est donc à spécifier.
– Il est possible d'ajouter, de supprimer, d'intervertir des éléments
d'un liste chaînée sans avoir à recréer la liste en entier, mais en
manipulant simplement leurs pointeurs.
Listes chaînées
• Chaque élément d'une liste chaînée est
composé de deux parties :
– la valeur que vous voulez stocker,
– l'adresse de l'élément suivant, s'il existe.
S'il n'y a plus d'élément suivant, alors l'adresse
sera NULL, et désignera le bout de la chaîne.
•
Listes chaînées
Définition d’une liste chaînée dans le
langage C
#include <stdlib.h>
typedef struct element element;
struct element
{ int val; struct element *nxt; };
typedef element* llist;
Listes chaînées
• On crée le type element qui est une structure
– contenant un entier (val)
– et un pointeur sur élément (nxt), qui contiendra l'adresse de
l'élément suivant.
• Ensuite, il nous faut créer le type llist (pour linked list =
liste chaînée) qui est en fait un pointeur sur le type
element.
• Lorsque nous allons déclarer la liste chaînée,
– nous devrons déclarer un pointeur sur element,
– l'initialiser à NULL, pour pouvoir ensuite allouer le premier
élément.
Déclaration d’une liste chaînée
#include <stdlib.h>
typedef struct element element;
struct element
{
int val;
struct element *nxt;
};
typedef element* llist;
int main(int argc, char **argv)
{
/* Déclarons 3 listes chaînées de façons différentes mais équivalentes */
llist ma_liste1 = NULL;
element *ma_liste2 = NULL;
struct element *ma_liste3 = NULL;
return 0;
}
Listes chaînées
Ajouter un élément
Listes chaînées
Ajouter en tête
llist ajouterEnTete(llist liste, int valeur)
{ /* On crée un nouvel élément */
element* nouvelElement =malloc(sizeof(element));
/* On assigne la valeur au nouvel élément */
nouvelElement->val = valeur;
/* On assigne l'adresse de l'élément suivant au nouvel élément */
nouvelElement->nxt = liste;
/* On retourne la nouvelle liste ca veut dire le pointeur sur le premier élément */
return nouvelElement; }
Listes chaînées
Ajouter en fin de liste
Ajouter en fin
llist ajouterEnFin(llist liste, int valeur) {
/* On crée un nouvel élément */
element* nouvelElement = malloc(sizeof(element));
/* On assigne la valeur au nouvel élément */
nouvelElement->val = valeur;
/* On ajoute en fin, donc aucun élément ne va suivre */
nouvelElement->nxt = NULL;
if(liste == NULL) {
/* Si la liste est videé il suffit de renvoyer l'élément créé */
return nouvelElement;
} else {
/* Sinon, on parcourt la liste à l'aide d'un pointeur temporaire et on indique que le dernier
élément de la liste est relié au nouvel élément */
element* temp=liste;
while(temp->nxt != NULL)
{ temp = temp->nxt; }
temp->nxt = nouvelElement;
return liste; } }
Listes chaînées
Utilisation de la fonction ajouter en fin
ma_liste1=ajouterEnFin(ma_liste1, 5);
• Exercice 1 :
Afficher la liste chaînée : Vous devrez parcourir
la liste jusqu'au bout et afficher toutes les
valeurs qu'elle contient.
void afficherListe(llist liste)
{
element *tmp = liste;
/* Tant que l'on n'est pas au bout de la liste */
while(tmp ->nxt!= NULL)
{
/* On affiche */
printf("%d ", tmp->val);
/* On avance d'une case */
tmp = tmp->nxt;
}
}
Listes chaînées
Dans la fonction main
llist ma_liste1=NULL;
ma_liste1=ajouterEnFin(ma_liste1, 5);
ma_liste1=ajouterEnFin(ma_liste1, 10);
ma_liste1=ajouterEnFin(ma_liste1, 15);
afficherListe(ma_liste1);
• Exercice 2 :
En utilisant trois fonctions que nous avons vues :
• ajouterEnTete
• ajouterEnFin
• afficherListe
Vous devez écrire la fonction main permettant de remplir
et afficher la liste chaînée ci-dessous. Vous ne devrez
utiliser qu'une seule boucle for.
10 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 10
Listes chaînées
• int main()
• {
•
llist ma_liste = NULL;
•
int i;
•
•
for(i=1;i<=10;i++)
•
{
•
ma_liste = ajouterEnTete(ma_liste, i);
•
ma_liste = ajouterEnFin(ma_liste, i);
•
}
•
afficherListe(ma_liste);
•
•
return 0;
• }
Listes chaînées
• Exercice 3 :
Écrivez une fonction qui renvoie 1 si la liste est
vide, et 0 si elle contient au moins un élément.
int estVide(lliste liste)
{
if(liste == NULL)
{ return 1; }
else { return 0; }
}
Dans la fonction main
if(estVide(ma_liste))
{
printf("La liste est vide");
}
else
{
afficherListe(ma_liste);
}
Listes chaînées
• Exercice 3 :
Ecrire un programme qui supprime le premier
element de la liste chainee
Exercice 4 :
Ecrire un programme qui supprime le dernier
element de la liste chainee
Supprimer en tête de liste
llist supprimerElementEnTete(llist liste)
{
if(liste != NULL)
{
/* Si la liste est non vide, on se prépare à renvoyer l'adresse de
l'élément en 2ème position */
element* aRenvoyer = liste->nxt;
/* On libère le premier élément */
free(liste);
/* On retourne le nouveau début de la liste */
return aRenvoyer;
}
else
{
return NULL;
}
}
Supprimer en fin de liste (1/2)
llist supprimerElementEnFin(llist liste)
{
/* Si la liste est vide, on retourne NULL */
if(liste == NULL)
return NULL;
/* Si la liste contient un seul élément */
if(liste->nxt == NULL)
{
/* On le libère et on retourne NULL (la liste est maintenant vide) */
free(liste);
return NULL;
}
Supprimer en fin de liste (2/2)
/* Si la liste contient au moins deux éléments */
element* tmp = liste;
element* ptmp = liste;
/* Tant qu'on n'est pas au dernier élément */
while(tmp->nxt != NULL)
{
/* ptmp stock l'adresse de tmp */
ptmp = tmp;
/* On déplace tmp (mais ptmp garde l'ancienne valeur de tmp */
tmp = tmp->nxt;
}
/* A la sortie de la boucle, tmp pointe sur le dernier élément, et ptmp sur
l'avant-dernier. On indique que l'avant-dernier devient la fin de la liste
et on supprime le dernier élément */
ptmp->nxt = NULL;
free(tmp);
return liste;
}
• Exercice 5 :
• Ecrire un programme qui recherche si une
valeur N existe dans la liste chainee
Rerchercher un element dans la liste
llist rechercherElement(llist liste, int valeur)
{
element *tmp=liste;
/* Tant que l'on n'est pas au bout de la liste */
while(tmp != NULL)
{
if(tmp->val == valeur)
{
/* Si l'élément a la valeur recherchée, on renvoie son adresse */
return tmp;
}
tmp = tmp->nxt;
}
return NULL;
}
Exercice 6
• Ecrire un programme qui calcule le nombre
d’occurences d’une valeur dans une liste
chainee d’entiers.
Nombre d’occurences d’une valeur
int nombreOccurences(llist liste, int valeur)
{
int i = 0;
/* Si la liste est vide, on renvoie 0 */
if(liste == NULL)
return 0;
/* Sinon, tant qu'il y a encore un élément ayant la val = valeur */
while((liste = rechercherElement(liste, valeur)) != NULL)
{
/* On incrémente */
liste = liste->nxt;
i++;
}
/* Et on retourne le nombre d'occurrences */
return i;
}
• Exercice 7 :
• ecrire un programme qui recherche la valeur
de la ieme position d’une liste chainee
Recherche du ième élément
Int element_i(llist liste, int indice)
{
int i;
/* On se déplace de i cases, tant que c'est possible */
for(i=0; i<indice && liste != NULL; i++)
{
liste = liste->nxt;
}
/* Si l'élément est NULL, c'est que la liste contient moins de i éléments */
if(liste == NULL)
{
return NULL;
}
else
{
/* Sinon on renvoie l'adresse de l'élément i */
return liste->valeur;
}
}
• Exercice 8 : Compter le nombre d'éléments
d'une liste chaînée
Compter le nombre d'éléments d'une
liste chaînée
int finleListe(llist liste)
{
int i=0;
element *tmp = liste;
/* Tant que l'on n'est pas au bout de la liste */
while(tmp!= NULL)
{
i++;
tmp = tmp->nxt;
}
return i;
}
Listes doublement chaînées
• Une liste doublement chainée contient :
– Une valeur(ici un simple entier)
– Un pointeur vers l'élément suivant (NULL si
l'élément suivant n'existe pas)
– Un pointeur vers l'élément précédent (NULL si
l'élément précédent n'existe pas)
Listes doublement chaînées
Représentation en C
• Typedef struct node
• {
• int valeur;
• struct node *p_next;
• struct node *p_prev;
• };
Représentation en C
typedef struct Dlist
{
size_t length; //size_t : entier positif
struct node *p_debut;
struct node *p_fin;
} Dlist;
Déclaration d'une liste vide
• Dlist *list = NULL; /* Déclaration d'une liste
vide */
Allouer une nouvelle liste
Dlist *dlist_new(void)
{
Dlist *p_new = malloc(sizeof(Dlist));
if (p_new != NULL)
{
p_new->length = 0;
p_new->p_debut = NULL;
p_new->p_fin = NULL;
}
return p_new;
}
Ajouter en fin de liste doublement
chaînée
Ajouter en fin de liste doublement
chaînée(1/2)
Dlist *dlist_append(Dlist *p_list, int valeur)
{
if (p_list != NULL) /* On vérifie si notre liste a été allouée */
{
struct node *p_new = malloc(sizeof *p_new); /* Création d'un
nouveau node */
if (p_new != NULL) /* On vérifie si le malloc n'a pas échoué */
{
p_new->valeur = valeur; /* On 'enregistre' notre donnée */
p_new->p_next = NULL; /* On fait pointer p_next vers NULL */
if (p_list->p_fin == NULL) /* Cas où notre liste est vide (pointeur
vers fin de liste à NULL) */
Ajouter en fin de liste doublement
chaînée(2/2)
{
p_new->p_prev = NULL; /* On fait pointer p_prev vers NULL */
p_list->p_debut = p_new; /* On fait pointer la tête de liste vers le nouvel élément */
p_list->p_fin = p_new; /* On fait pointer la fin de liste vers le nouvel élément */
}
else /* Cas où des éléments sont déjà présents dans notre liste */
{
p_list->p_fin->p_next = p_new; /* On relie le dernier élément de la liste vers notre nouvel
élément (début du chaînage) */
p_new->p_prev = p_list->p_fin; /* On fait pointer p_prev vers le dernier élément de la liste */
p_list->p_fin = p_new; /* On fait pointer la fin de liste vers notre nouvel élément (fin du
chaînage: 3 étapes) */
}
p_list->length++; /* Incrémentation de la finle de la liste */
}
}
return p_list; /* on retourne notre nouvelle liste */
}
Ajout en début de liste doublement
chaînée (1/2)
Dlist *dlist_prepend(Dlist *p_list, int valeur)
{
if (p_list != NULL)
{
struct node *p_new = malloc(sizeof *p_new);
if (p_new != NULL)
{
p_new->valeur = valeur;
p_new->p_prev = NULL;
if (p_list->p_fin == NULL)
{
p_new->p_next = NULL;
p_list->p_debut = p_new;
p_list->p_fin = p_new;
}
Ajout en début de liste doublement
chaînée (2/2)
else
{
p_list->p_debut->p_prev = p_new;
p_new->p_next = p_list->p_debut;
p_list->p_debut = p_new;
}
p_list->length++;
}
}
return p_list;
}
Insérer un élément dans une liste
doublement chaînée en position
Insérer un élément dans une liste
doublement chaînée en position
• Si nous sommes (cette position) en fin de liste
– (p_temp->p_next == NULL),
– nous utilisons notre fonction dlist_append
• Sinon, si nous sommes (cette position) en début de
liste
– (p_temp->p_prev == NULL)
– nous utilisons notre fonction dlist_prepend
• Sinon, nous devons créer un nouvel élément
– réaliser notre chaînage
– stoquer la valeur
Insérer un élément dans une liste
doublement chaînée en position
Dlist *dlist_insert(Dlist *p_list, int valeur, int position)
{
if (p_list != NULL)
{
struct node *p_temp = p_list->p_debut;
int i = 1;
while (p_temp != NULL && i <= position)
{
if (position == i)
{
if (p_temp->p_next == NULL)
{
p_list = dlist_append(p_list, valeur);
}
else if (p_temp->p_prev == NULL)
{
p_list = dlist_prepend(p_list, valeur);
}
Insérer un élément dans une liste
doublement chaînée en position
else
{
struct node *p_new = malloc(sizeof *p_new);
struct node *tempprecedent;
struct node *tempsuivant;
if (p_new != NULL)
{
p_new->valeur = valeur;
tempsuivant =p_temp->p_next;
tempsuivant ->p_prev = p_new;
tempprecedent =p_temp->p_prev;
tempprecedent ->p_next = p_new;
p_new->p_prev = tempprecedent;
p_new->p_next = tempsuivant;
p_list->length++;
}
}
}
else
{
p_temp = p_temp->p_next;
(je sauvegarde l’élément suivant dans une variable temporaire a1)
(je sauvegarde l’élément précédent dans une variable temporaire a2);
Affichage d’une liste chainée double
•
void affichage(Dlist *p_list){
•
•
if (p_list != NULL) /* On vérifie si notre liste a été allouée */
{
•
node *temp=p_list->p_debut;
•
•
•
•
•
•
while(temp!=NULL){
printf("%d,",temp->valeur);
temp=temp->p_next;
}
printf("\n");
}
•
}
Les piles et les files
définitions
• Une pile permet de réaliser ce que l'on nomme une
LIFO (Last In First Out)
– Il est possible de comparer cela à une pile d'assiettes.
• Lorsqu'on ajoute une assiette en haut de la pile,
• on retire toujours en premier celle qui se trouve en haut de la pile,
• c'est-à-dire celle qui a été ajoutée en dernier, sinon tout le reste
s'écroule.
• Une file, quant à elle, permet de réaliser une FIFO (First
In First Out)
– ce qui veut dire que les premiers éléments ajoutés à la file
seront aussi les premiers à être récupérés.
Structure de la pile
• Tout d'abord, nous allons commencer par définir notre
structure qui constituera notre pile.
• Pour notre exemple, nous allons créer une pile
d'entiers (int).
• notre pile sera basée sur une liste chaînée (simple).
• Chaque élément de la pile pointera vers l'élément
précédent.
• La liste pointera toujours vers le sommet de la pile.
Voici donc la structure qui constituera notre pile
Structure de la pile
typedef struct pile
{
int donnee;
/* La donnée que notre pile stockera. */
struct pile *precedent;
/* Pointeur vers l'élément précédent de la pile.
*/
}pile;
Rappel pointeurs, indicateur des piles
void test(int a, int b){
a=a+2;
b=b+2;
}
---------------------------------int a=1;
int b=1;
test(a,b);
printf("test a=%d,b=%d",a,b);
---------------------Resultat a reste 1 et b reste 1;
Modifions la fonction précédante
void test(int *a, int *b){
*a=*a+2;
*b=*b+2;
}
--------------int a=1;
int b=1;
test(&a,&b);
printf("test a=%d,b=%d",a,b);
---------------------Résultat : a=3 et b=3
Comparons alors :
• int *a=1;
• int *b=1;
• test(a,b);
----------------------------------• int **a=10;
• int **b=20;
• test(*a,*b);
• Retournons aux piles
Structure de la pile
Structure de la pile
Fonction push
• void pile_push(pile *(*p_pile), int donnee)
• Ne retourne pas de valeur : différence avec ce
que nous avons vu avec la manipulation des
listes chaînées.
• Donnée : La valeur à ajouter dans la pile
Fonctionnement
1. On crée un nouvel élément de type Pile.
2. On vérifie que l'élément a bien été créé.
3. On assigne à la donnée de cet élément la donnée que
l'on veut ajouter.
4. On fait pointer cet élément sur le sommet de la pile.
5. On fait pointer le sommet de la pile sur cet élément.
1. On crée un nouvel élément de type
Pile
• Pile *p_nouveau = malloc(sizeof
(*p_nouveau));
2. On vérifie que l'élément a bien été
créé
• if (p_nouveau != NULL)
• {
• }
3. On assigne à la donnée de cet
élément la donnée que l'on veut
ajouter
• p_nouveau->donnee = donnee;
4. On fait pointer cet élément sur le
sommet de la pile
• p_nouveau->precedent = p_pile;
5. On fait pointer le sommet de la pile
sur cet élément
• p_pile = p_nouveau;
En assemblant toutes les etapes
void pile_push(pile **p_pile, int donnee)
{
pile *p_nouveau = malloc(sizeof (pile));
if (p_nouveau != NULL)
{
p_nouveau->donnee = donnee;
p_nouveau->precedent = *p_pile;
*p_pile = p_nouveau;
}
}
Retrait d’un element
• int pile_pop(Pile *p_pile);
Retrait d’un element
1. Vérifier si la pile n'est pas vide.
2. Si elle ne l'est pas, stocker dans un élément temporaire l'avantdernier élément de la pile.
3. Stocker dans une variable locale la valeur étant stockée dans le
dernier élément de la pile.
4. Supprimer l’élément sommet, de la pile.
5. Faire pointer la pile vers notre élément temporaire.
6. Retourner la valeur dépilée.
1. Vérifier si la pile n'est pas vide
• if (p_pile != NULL)
• {
• }
2-Si elle ne l'est pas, stocker dans un élément
temporaire l'avant-dernier élément de la pile
• Pile *temporaire = (*p_pile)->precedent;
3-Stocker dans une variable locale la valeur
étant stockée dans le dernier élément de la
pile
•
ret = (*p_pile)->donnee;
4. Supprimer le dernier élément
• free(*p_pile), *p_pile = NULL;
5. Faire pointer la pile vers notre élément
temporaire
*p_pile = temporaire;
6. Retourner la valeur dépilée
• return ret;
Depiler
int pile_pop(pile **p_pile){
int ret = -1;
if (p_pile != NULL)
{
pile *temporaire = (*p_pile)->precedent;
ret = (*p_pile)->donnee;
free(*p_pile);
*p_pile = temporaire;
}
return ret;
}
• Exercice :
• créer une fonction pile_peek retournant la
valeur du dernier élément, mais sans le
dépiler comme le ferait la fonction pile_pop
solution
• int pile_peek(Pile *p_pile)
• {
• int ret = -1; /* Variable de retour. */
• if (p_pile != NULL) /* Si la pile n'est pas vide. */
• {
•
ret = p_pile->donnee; /* On stocke dans la
variable ret la valeur du dernier élément. */
• }
• return ret;
• }
• Exercice 2 : Ecrire un programme qui empile 3
valeurs de variables saisies par le clavier. En
utilisant la fonction push
• Calculer la moyenne de ces 3 elements en
utilisant la fonction pop.
Les files
• typedef struct file
• {
• int donnee;
• struct file *suivant;
• };
Ajout d’un element
•
1. On crée un nouvel élément de type File.
•
2. On vérifie que le nouvel élément a bien été créé.
•
3. On fait pointer cet élément vers NULL.
•
4. On assigne à la donnée de cet élément la donnée que l'on veut ajouter.
•
5. Si la file est vide, alors on fait pointer la file vers l'élément que l'on vient de
créer.
•
6. Sinon, on crée un élément temporaire de type File pointant vers notre file.
•
7. On parcourt entièrement la file.
•
8. On fait pointer l'élément temporaire vers le nouvel élément créé.
void file_enqueue(File **p_file, int donnee)
{
File *p_nouveau = malloc(sizeof *p_nouveau);
if (p_nouveau != NULL)
{
p_nouveau->suivant = NULL;
p_nouveau->donnee = donnee;
if (*p_file == NULL)
{
*p_file = p_nouveau;
}
else
{
File *p_tmp = *p_file;
while (p_tmp->suivant != NULL)
{
p_tmp = p_tmp->suivant;
}
p_tmp->suivant = p_nouveau;
}
}
}
int file_dequeue(File **p_file)
{
int ret = -1;
/* On teste si la file n'est pas vide. */
if (*p_file != NULL)
{
/* Création d'un élément temporaire pointant vers le deuxième élément
de la file. */
File *p_tmp = (*p_file)->suivant;
/* Valeur à retourner */
ret = (*p_file)->donnee;
/* Effacement du premier élément. */
free(*p_file), *p_file = NULL;
/* On fait pointer la file vers le deuxième élément. */
*p_file = p_tmp;
}
return ret;
}
Vidage de la file
void file_clear(File **p_file)
• {
•
/* Tant que la file n'est pas vide. */
•
while (*p_file != NULL)
•
{
•
/* On enlève l'élément courant. */
•
file_dequeue(p_file);
•
}
• }
• Exercice :
• faire une fonction file_peek retournant la
valeur du premier élément de la file sans
l'enlever de la liste.
int file_qeek(File *p_file)
{
int ret = -1;
if (p_file != NULL)
{
ret = p_file->donnee;
}
return ret;
}
Arbre binaire
Définition d’un arbre binaire
• Définition du type ARBRE, pointeur sur un
noeud
typedef struct Arbre{
int val ;
struct Arbre * gauche;
struct Arbre * droite;
} Arbre;
Arbre binaire de recherche ABR
• Ce type d’arbre binaire permet une recherche
ayant une complexité de l’ordre de Log(n)
• Définition : considérons un élément de l’arbre:
– Tous les éléments du sous arbre gauche sont
inférieurs.
– Tous les éléments du sous arbre droit sont supérieurs
– Ce principe s’applique récursivement à chaque
élément.
• Chaque sous arbre est aussi un ABR
Inserer un element dans un arbre
binaire de recherche
Arbre *inserer(int x, Arbre *a)
{if(a==NULL){
a=malloc(sizeof(Arbre));
a->val=x;
a->droite=NULL;
a->gauche=NULL;
return a; }
if (x< a->val)
{
a->gauche=inserer(x,a->gauche);
}
else if ( x>a->val){
a->droite=inserer(x, a->droite);
}}
Parcours en profondeur
• Principe : parcourir récursivement les fils dans
l’ordre
– fils gauche
– puis fils droite.
Parcours en profondeur
void Parcours(ARBRE *a)
{
if (a != NULL)
{
Parcours (a->gauche);
Parcours (a->droite);
}
}
Parcours préfixé
• Parcours préfixé : affichage de la valeur d’un
noeud avant les valeurs figurant dans ses
sous-arbres
• Printf : 12
• Gauche
– Printf 1
– Gauche
• Printf 91
• Gauche NULL
• Droite NULL
– Droite
• Printf 67
• Gauche null
• Droite null
• Droite
– Printf 7
– Gauche NULL
– Droite
• Printf 82
• Gauche
– Printf 61
– Gauche null
– Droite null
• Droite null
Parcours préfixé
•
•
•
•
•
•
•
•
•
void ParcoursPrefixe(Arbre *a)
{
if (a != NULL)
{
printf("%d-",a->val);
ParcoursPrefixe (a->gauche);
ParcoursPrefixe(a->droite);
}
}
Parcours infixé
• Parcours infixé : affichage de la valeur d’un
noeud après les valeurs figurant dans son
sous-arbre gauche et avant les valeurs
figurant dans son sous-arbre droit
Parcours infixé
•
•
•
•
•
•
•
•
•
void ParcoursInfixe(Arbre *a)
{
if (a != NULL)
{
ParcoursInfixe(a->gauche);
printf("%d-",a->val);
ParcoursInfixe(a->droite);
}
}
Parcours infixé
•
Gauche
de l’element 12
– Gauche de l’element 1
•
•
•
Gauche de l’element 91 (NULL)
Printf 91
Droite NULL
– Printf 1
– Droite de l’element 1
•
•
•
•
•
Gauche NULL
Printf 67
Droite NULL
Printf 12
Droite de l’element 12
– Gauche de 7 = NULL
– Printf 7
– Droite de l’element 7
•
Gauche de l’element 82
–
–
–
•
•
Gauche NULL
Printf 61
Droite NULL
Printf 82
Droite NULL
Parcours postfixé
• Parcours postfixé : affichage de la valeur d’un
noeud après les valeurs figurant dans ses
sous-arbres
Parcours postfixé
•
Gauche de 12->1
– Gauche de 1->91
•
•
•
Gauche de 91 : NULL
Droite NULL
Printf : 91
– Droite de 1->67
•
•
•
Gauche NULL
Droite NULL
Printf 67
– Printf 1
•
Droite de 12->7
– Gauche de 7 : NULL
– Droite de 7: 82
•
Gauche de 82: 61
–
–
–
•
•
Droite NULL
Printf 82
– Printf 7
•
Printf 12
Gauche de 61 : NULL
Droite de 61 : NULL
Printf : 61
Parcours postfixé
•
•
•
•
•
•
•
•
•
void Parcourspostefixe(Arbre *a)
{
if (a != NULL)
{
Parcourspostefixe(a->gauche);
Parcourspostefixe(a->droite);
printf("%d-",a->val);
}
}
Exercices arbre binaire
• Exercice 1 :
A-Ecrire une fonction qui retourne le nombre de
noeuds total d’un arbre binaire
B-Trouver la somme des elements des noeuds.
C-Trouver la moyenne des elements d’un arbre
binaire de recherche
D-Trouver la hauteur d’un arbre binaire.
Profondeur de l’arbre
#include <Math.h>
Int hauteur (ARBRE *a)
{
if (a != NULL)
{
Return 1+
Math.max (hauteur(a->gauche),hauteur(a->droite));
} else return 0;
}
Nombre de noeuds
•
•
•
•
•
int taille(Arbre a) {
if(a==Null ) {return 0; }
else {
return 1 + taille(a->gauche) +taille(a->droite);
}
• 1+taille(a->gauche)+taille(a->droite);
• 1+(1+taille(a->gauche)+taille(a>droite))+taille(a->droite)
•
•
•
•
•
•
•
•
•
void Parcours(ARBRE *a)
{
if (a != NULL)
{
Printf(“%d”,a->val);
Parcours (a->droite);
Parcours (a->gauche);
}
}
•
•
•
•
int somme(Arbre a) {
if(a==Null ) {return 0; }
else {
return a->val + somme(a->gauche) + somme
(a->droite);
• }
moyenne
• Float moyenne ( arbre *a){
//If (finle(a)!=0){
If (arbre!=NULL){
• Return somme(a)/finle(a);
• }else
• {
• return 0;
• }
moyenne
Int somme(ARBRE a){
If(arbre==NULL){ return 0;
}else
{
Return a->val+somme(a->gauche)+somme(a>droite);
}
• Float moyenne( Arbre a)
• {return somme(a)/finle(a);
Téléchargement