1. Structures de données arborescentes
Les arbres
1
Prof. M. Khalifa MANSOURI
Université Hassan II de Casablanca
ENSET de Mohammedia
Département Mathématiques et Informatique
2. Structures de données arborescentes
2
Pour faire fonctionner correctement et efficacement son
programme, un programmeur doit toujours chercher la bonne
méthode pour organiser les données. Ainsi, le choix de la structure
de données est déterminante.
Il existe quatre grandes classes de structures :
Les structures de données séquentielles (tableaux) ;
Les structures de données linéaires (liste chaînées) ;
Les arbres ;
Les graphes.
Introduction
3. Structures de données arborescentes
3
Inconvénient des structures séquentielles
En format contigu, les mises à jour sont fastidieuses et coûteuses
En format chaîné, les parcours sont de complexité linaire
Les structurations arborescentes permettent une amélioration
globale des accès aux informations.
Introduction
4. Structures de données arborescentes
4
Arbre généalogique
Organigramme d’une entreprise
Table des matières d’un livre
Les expressions arithmétiques
Tournois sportifs
Le décisionnel
Classification : par questionnaire binaire
Nœud= question, feuille = réponse, branche gauche
étiquetée par faux et branche droite par vrai
Recherche : par arbre binaire de recherche (ou ordonné)
Files de priorité : par arbre tournoi : gestion des tampons avec
priorité
Application des arbres :
Buts des Structures arborescentes
Organisation hiérarchique des informations : intuitive et
conforme à beaucoup de situations :
5. Structures de données arborescentes
5
Arbre = graphe purement hiérarchique
pas de cycle
un seul chemin d’un nœud à un autre
Arbre binaire = tout nœud a au plus deux fils
Liste = arbre dégénéré
Forêt = ensemble d’arbres
Classification des structures
Graphes
Arbres
Arbres binaires
Listes
Piles
Files
Forêt
Autres
6. Les Arbres en général
6
Définition récursive d’un arbre
Un arbre est:
Vide
Ou constitué d’un élément (information ou valeur) et d’un ou
plusieurs arbres
Définitions :
…
7. Les Arbres en général
7
Définitions, Terminologies :
A
B C
D
E
H J
I
A: racine
A,B,C,D,E,F,G,H,I,J: nœuds ou
sommets
B,C,E: sous arbres
D,F,G,H,I,J: sous arbres
Tout sous arbre est un arbre
D,E,F,G,H,I,J: Feuilles ou nœuds
terminaux
Un nœud peut être fils d’un autre:
B est fils de A, F est fils de B
Un nœud peut être père d’un autre:
A est père de B et C, B est père de
D,E,F et G
Tout chemin de la racine à une
feuille est une branche
F
G
8. Les Arbres en général
8
Définitions, Terminologies :
• R, A, B, C, D,E : nœuds ou sommets
• R racine, D - E sous-arbre, A – B – C sous-
arbre
• Un nœud peut être fils d’un autre
– C est un fils de A
• Un nœud peut être père d’un autre
– D est le père de E, A est le père de C
• B, C, E sont des nœuds terminaux ou
feuilles
• Tout chemin de la racine à une feuille est
une branche : R – A – C est une branche
R
A
B C
D
E
9. Les Arbres en général
9
Définitions, Terminologies :
• Nœuds d’un même niveau = issus d’un
même nœud avec même nb de
filiations : R=Niveau 0, A,D = Niveau 1
et B,C,E = Niveau 2
• Parcours en largeur = par niveau
R-A-D-B-C-E
• Taille d’un arbre = nb de nœuds = ici 6
• Hauteur d’un arbre = niveau maximum
= ici 2
• Arbre dégénéré = au plus un
descendant par nœud
R
A
B C
D
E
Niv 0
Niv 1
Niv 2
10. Les Arbres en général
1
Définitions, Terminologies :
Taille d’un arbre= nombre de nœuds: ici, 10
Chemin : une séquence n1 n2 · · ·nk de nœuds telle que, pour tout 1 <=i < k : ni+1
appartient à Succ(ni). On dit que ce chemin va de n1 à nk.
Longueur d’un chemin n1n2 · · ·nk est le nombre k de nœuds qui le composent.
Hauteur d’un arbre= Longueur du plus long chemin partant de la racine: ici, 3
Profondeur d’un nœud= longueur du chemin allant de la racine à ce nœud.
Arbre dégénéré: au plus un descendant par nœud.
Une liste chainée est arbre dégénéré
Définition récursive de la hauteur
la hauteur d’un arbre est égale à la hauteur de sa racine
la hauteur d’un arbre vide est nulle
la hauteur d’un nœud est égale au maximum des hauteurs de ses sous-arbres plus
un
11. Les Arbres en général
1
Définitions, Terminologies :
• Hauteur d’un nœud : Nombre d’arcs séparant
ce nœud à la feuille la plus loin accessible
depuis ce nœud.
• Hauteur de l’arbre : longueur de sa plus longue
branche
• Définition récursive de la hauteur d’un arbre :
la hauteur d’un arbre est égale à la hauteur de
sa racine
la hauteur d’un arbre ne contenant que la
racine est nulle : les feuilles ont une hauteur
nulle
La hauteur d’une arbre vide est fixée par
convention à -1
la hauteur d’un nœud est égale au maximum
des hauteurs de ses sous-arbres plus un
R
A
B C
D
E
15. Des cas particuliers
1
Arbres ordonnés: souvent, on fixe un ordre pour les fils pour augmenter l’efficacité des
algorithmes
Arbre n-aires: Souvent on fixe le nombre de fils pour limiter la complexité. Un arbre n-aires,
il possède au plus n fils.
Arbre binaire: c’est un cas particulier des arbres n-aires (n=2)
16. Les arbres binaires
1
Représentation graphique d’un arbre binaire
graphique
Parenthésée : R(A(B,C),D(,E))
chaînée
typedef struct _tnœud
{
struct _tnoeud *gauche;
telement info;
struct _tnoeud *droite;
}tnoeud;
racine
R
A
B C
D
E
17. Les arbres binaires
1
Représentation graphique d’un arbre binaire
1
2 3
4 5 6 7
8Nœud=
{
Information: <T>;
fils_gauche: nœud;
fils_droit:nœud;
}
Racine: pointeur
Élément à gauche: pointeur
Élément à droite pointeur
18. Les arbres binaires
1
Définition
Dans un arbre binaire, chaque nœud a au plus deux fils
Plus généralement, si chaque nœud a au plus n fils, l’arbre est n-aire
Un arbre binaire est soit vide ou contient un arbre gauche (fils
gauche) et un arbre droit (fils droit)
Un arbre binaire est vide ou composé d’un élément auquel sont
rattachés un sous-arbre gauche et un sous-arbre droit
19. Les arbres binaires
1
Définition d’arbre binaire :
• Notation parenthésée :
R ( A (B,C), D (, E))
• On peut manipuler un arbre
binaire avec trois primitives :
valeur d’un nœud,
fils gauche d’un nœud et
fils droit d’un nœud.
R
A
B C
D
E
21. Les arbres binaires
2
Manipulation :
On manipule un arbre à l’aide des éléments suivants:
Le contenu du nœud
L’adresse du nœud à gauche
L’adresse du nœud à droite
L’information pour signaler qu’un nœud (X) n’a pas de fils gauche (X->fg=NULL) ou n’a pas
de fils droit (X->fd=NULL) est importante
Arbre binaire complet :
• Chaque nœud non terminal a exactement deux fils
• Arbre binaire complet au sens large ou arbre parfait :
l’avant-dernier niveau est complet
les feuilles du dernier niveau sont groupées le plus à gauche
possible
22. Les arbres binaires
2
Structure de données :
typedef struct Element
{
<T> val; // T peut représenter n’importe quel type
Element *fg;
Element *fd;
} ArbreB;
23. Les primitives des arbres binaires
2
Allouer l’espace pour un nœud
Initialiser un nœud : sa valeur et ses fils
Changer la valeur d’un nœud
Récupérer la valeur d’un nœud
Changer le fils gauche d’un nœud
Changer le fils droit d’un nœud
Récupérer le fils gauche
Récupérer le fils droit
Tester si un nœud est une feuille
24. Les primitives des arbres binaires
2
Allouer l’espace mémoire pour un nœud
ArbreB *allouer_espace_noeud()
{
ArbreB *p;
p=(ArbreB*) malloc(sizeof(ArbreB));
return p;
}
main()
{
ArbreB *A;
A=allouer_espace_noeud();
printf("adresse de A : %d",A);
}
Programme main
25. Les primitives des arbres binaires
2
Initialiser un nœud
ArbreB *initialiser_noeud(ArbreB *p, int v)
{
p->val=v;
p->fg=NULL;
p->fd=NULL;
return p;
}
main()
{
ArbreB *A;
A=allouer_espace_noeud();
A=initialiser_noeud(A,1);
}
Programme main
26. Les primitives des arbres binaires
2
Vérifier si un nœud est vide
int estVide(ArbreB *p)
{
if(p==NULL) return 1;
return 0;
}
main()
{
ArbreB *A;
A=allouer_espace_noeud();
A=initialiser_noeud(A,1);
if (!estVide(A))
printf("n%i",A->val);
else
prinft(“noeud vide”);
}
Programme main
27. Les primitives des arbres binaires
2
Changer la valeur d’un nœud
void setValeur(ArbreB *p, int v)
{
if(p) p->val=v;
else
Printf(« opération non autorisée »);
}
main()
{
ArbreB *A;
A=allouer_espace_noeud();
A=initialiser_noeud(A,0);
setValeur(A,1);
}
Programme main
28. Les primitives des arbres binaires
2
Récupérer la valeur d’un nœud
int getValeur(ArbreB *p)
{
if(p) return p->val;
else
return -1;
}
main()
{
ArbreB *A;
A=allouer_espace_noeud();
A=initialiser_noeud(A,1);
int a= getValeur(A);
}
Programme main
29. Les primitives des arbres binaires
2
Changer le fils gauche resp le fils droit d’un nœud
void setFils_gauche(ArbreB *r,ArbreB *p_fils)
{
if(r) r->fg=p_fils;
else
printf("opération non autorisée");
}
void setFils_droit(ArbreB *r,ArbreB *p_fils)
{
if(r) r->fd=p_fils;
else
printf("opération non autorisée");
}
main()
{
ArbreB *A,*B,*C;
A=allouer_espace_noeud(); B=allouer_espace_noeud(); C=allouer_espace_noeud();
A=initialiser_noeud(A,1); B=initialiser_noeud(B,2); C=initialiser_noeud(C,3);
setFils_gauche(A,B);
setFils_droit(A,C);
}
Programme main
30. Les primitives des arbres binaires
3
Récupérer le fils gauche resp le fils droit d’un nœud
ArbreB * getFils_gauche(ArbreB *p)
{
if(p) return p->fg;
else
Return NULL;
}
ArbreB * getFils_droit(ArbreB *p)
{
if(p) return p->fd;
else
Return NULL;
}
main()
{
ArbreB *A,*B,*C;
A=allouer_espace_noeud(); B=allouer_espace_noeud(); C=allouer_espace_noeud();
A=initialiser_noeud(A,1); B=initialiser_noeud(B,2); C=initialiser_noeud(C,3);
setFils_gauche(A,B); setFils_droit(A,C);
ArbreB *p_fg=getFils_gauche(A); ArbreB *p_fd=getFils_droit(A);
}
Programme main
31. Les primitives des arbres binaires
3
Application 1
1
2 3
4 5 6 7
8
Créer l’arbre suivant :
En utilisant pour chaque nœud
un pointeur
En utilisant un seul pointeur
(pointeur de la racine)
Afficher l’arbre:
En utilisant le pointeur de
chaque nœud
En utilisant le pointeur de la
racine
32. Les primitives des arbres binaires
3
Calculer la taille d’un arbre: nombre de nœuds
int taille_arbre(ArbreB *A)
{
if (estVide(A))
return 0;
else
return
1+taille_arbre(getFils_gauche(A))+taille_arbre(getFils_droit(A));
}
33. Les parcours d’un arbre binaire
3
Parcours en profondeur
Préfixé
Infixé
Postfixé
Parcours en largeur
Par niveau
34. Les parcours d’un arbre binaire
3
• Trois parcours possibles :
– préfixé (préordre): (R,G,D)
• on traite la racine,
• puis le sous-arbre gauche,
• puis le sous-arbre droit
– infixé (projectif ou symétrique) : (G,R,D)
• on traite le sous-arbre gauche,
• puis la racine,
• puis le sous-arbre droit
– postfixé (ordre terminal) : (G,D,R)
• on traite le sous-arbre gauche,
• le sous-arbre droit,
• puis la racine!!!!
35. Les parcours d’un arbre binaire
3
Exemples de parcours :
• Préfixé : R-A-B-C-D-E
• Infixé : B-A-C-R-D-E
• Postfixé : B-C-A-E-D-R
R
A
B C
D
E
36. Les parcours d’un arbre binaire
3
Arbre binaire ordonné :
• La chaîne infixée des
valeurs est ordonnée
• Tous les éléments dans le
sous-arbre gauche d’un
nœud lui sont inférieurs
• Tous les éléments dans son
sous-arbre droit lui sont
supérieurs
12
6
5 8
15
18
40. Les parcours d’un arbre binaire
4
Parcours en profondeur
Infixé: implémentation récursive
Traiter le sous arbre gauche
Traiter la racine
Traiter le sous arbre droit
void inFixe(ArbreB *A)
{
if (!estVide(A))
{
inFixe(A->fg);
printf("%i ",A->val);
inFixe(A->fd);
}
}
4 2 5 8 1 6 3 7
44. Les parcours d’un arbre binaire
4
Parcours en profondeur
1 2 4 5 8 3 6 7
void preFixe_it_etape2(ArbreB *A)
{
Pile *pil;
Initialiser_pile();
int f;
ArbreB *pt;
pt=A;
while(!(estVide(pt))||pile!=NULL)
{
while(!(estVide(pt)))
{
printf("n%i",pt->val);
Empilier(pt);
pt=getFils_gauche(pt);
}
pt=Depiler(pt);
pt=getFils_droit(pt);
}
}
Prefixe: implémentation itérative (étape 2)
Il faut une pile pour
préserver les valeurs
successives de la racine et
passer au sous-arbre droit à
chaque retou.
45. Autres opérations sur les arbres binaires
4
Calcul de la taille d’un arbre binaire
int taille(tnoeud *racine)
{
if(racine == NULL) return 0;
else
return 1+ taille(racinegauche) + taille(racinedroite);
}
Nombre de feuilles d’un arbre binaire
int nbfeuille(tnoeud *racine)
{
if(racine == NULL) return 0 ;
if(feuille(racine) ) return 1 ;
return nbfeuille(racinegauche)
+ nbfeuille(racinedroite)
}
int feuille(tnoeud *nœud)
{
return !nœudgauche
&& !nœuddroite;
}
Avec :
46. Autres opérations sur les arbres binaires
Vérifier qu’un arbre n’est pas dégénéré
int nondegener(tnoeud *racine)
{
if(racine = = NULL) return 0;
if((racinegauche != NULL ) && (racinedroite != NULL)
return 1;
if(racinegauche = = NULL)
return nondegener(racinedroite);
return nondegener(racinegauche);
}
47. Autres opérations sur les arbres binaires
Recherche par association dans un arbre binaire
int rechAssoc(tnoeud *racine , telement val)
{
if(racine = = NULL) return 0 ;
if (racineinfo = = val ) return 1;
if (rechAssoc(racinegauche , val)) return 1;
return rechAssoc(racinedroite ,val) ;
}
48. Autres opérations sur les arbres binaires
Recherche par association dans un arbre binaire ordonné
int rechDichoAssoc(tnoeud *racine , telement val)
{
if(racine == NULL)
return 0;
if (racineinfo ==val )
return 1;
if (racineinfo < val )
return rechDichoAssoc(racinedroite ,val) ;
return rechDichoAssoc(racinegauche,val) ;
}
L’arbre est ordonné =>
On peut choisir le sous-arbre
dans lequel il faut rechercher val
49. Autres opérations sur les arbres binaires
Insertion dans un arbre binaire ordonné (1)
• L’insertion doit maintenir l’ordre
• Si racine = NULL, on crée un nœud racine avec
l’élément à insérer
• Sinon on parcourt l’arbre en recherchant le nœud
père de l’élément à insérer, puis on crée une feuille
à rattacher à ce nœud, du bon côté
• Ce nœud père est soit une feuille soit un nœud
avec un seul sous-arbre
• Il vérifie (pèreinfo <= val et pèredroite=NULL)
ou (pèreinfo > val et pèregauche=NULL)
50. Autres opérations sur les arbres binaires
Insertion dans un arbre binaire ordonné (2)
50
30
25 35
70
8060
33 65
• Insertion de 27
• Le nœud père est 25
• 27 doit être inséré en
sous-arbre droit
Insertion de 50
Le nœud père est 60
50 doit être inséré en
sous-arbre gauche
27 50
51. Autres opérations sur les arbres binaires
Insertion dans un arbre binaire ordonné (3)
void creeFeuille(tnoeud **feuille , telement val )
{
*feuille=(tnoeud *)malloc(sizeof(tnoeud);
(*feuille)info =val;
(*feuille)gauche=NULL;
(*feuille)droite=NULL;
}
void insertion(tnoeud **racine , telement val)
{
if(*racine == NULL) creeFeuille( racine,val);
else
if( val >= (*racine)info)
insertion(&((*racine)droite ),val);
else
insertion(&((*racine)gauche) , val) ;
}
Avec :
52. Les arbres binaires
5
Un arbre binaire est dit de recherche si :
Pendant l’insertion, je dois respecter un ordre
Le fils à gauche est inférieur à la racine
Le fils à droite est supérieur ou égal à la racine
Un arbre binaire est dit complet si :
Chaque nœud non terminal a exactement deux fils
Un arbre binaire est dit parfait (presque complet) :
Tous les niveaux sont complètement remplis sauf
éventuellement le dernier, et
dans ce cas les feuilles sont le plus à gauche possible
Arbre binaire équilibrée : La différence de hauteur entre 2 frères
ne peut dépasser 1
53. Les arbres binaires
5
Conclusion :
• Beaucoup d’applications : B-arbres, analyse
syntaxique, etc.
• Généralisation des listes
• La recherche est généralement
proportionnelle à la hauteur de l’arbre,
intéressant si l’arbre est bien géré, c’est-à-
dire dont la hauteur est proche du log de la
taille
• L’implantation chaînée est généralement la
plus adaptée
54. Les arbres binaires
5
Application :
Développer une application qui implémente la structure de données
suivantes pour permettre des recherches sur une liste dynamique
d’étudiants suivant deux clés : recherche par matricule et recherche par
moyenne.