Académique Documents
Professionnel Documents
Culture Documents
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
CHAPITRE 8
LES ARBRES
8 Les arbres
Un arbre est une structure homogène composée d’éléments,
appelés nœuds, qui contiennent de l'information et de liens
(pointeurs) vers des éléments du même type.
D'une manière plus formelle, on peut dire qu'un arbre de
type de base T est :
Les structures d'arbres possèdent un double intérêt: d'une part les données qui interviennent
dans de nombreux problèmes sont naturellement structurées en arbres (hiérarchies d'objets,
imbrication d’objets, choix et décisions, arbres syntaxiques, etc.), d'autre part elles
permettent de représenter efficacement des ensembles d'objets ou des applications, on parlera
dans ce cas d'arbres de recherche (search trees).
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
8.1 Définitions
Un arbre est l’abstraction qui modélise une structure hiérarchique. Un arbre avec racine
est composé de deux ensembles N et A appelés respectivement l'ensemble des nœuds et
l'ensemble des arcs (liens) et d'un nœud particulier r appelé racine de l'arbre. Les éléments
de A sont des paires ( n1 , n2 ) d'éléments de N. Un arc ( n1 , n2) établit une relation entre
n1, appelé nœud parent, et n2, appelé nœud enfant de n1, A doit être tel que chaque nœud,
sauf la racine, a exactement un parent.
Noeuds niveau 0
racine
noeuds
Noeud père
Noeuds niveau 1
arc
Noeuds intérieurs
Hauteur de l’arbre=3 = hauteur racine
Noeud fils
Noeuds niveau 2
Noeuds niveau 3
feuilles
On appelle feuille de l'arbre les nœuds qui n'ont pas d'enfant et nœud intérieur les nœuds
qui ne sont ni des feuilles ni la racine.
Le degré d'un nœud est le nombre de ses enfants.
le niveau d'un nœud est le nombre d'arcs qu'il faut remonter pour atteindre la racine depuis
ce nœud (la racine est donc de niveau 0).
La hauteur d’un nœud ni est la longueur du plus long chemin (nombre d’arcs) depuis ni
jusqu’à une feuille.
La hauteur d’un arbre est la hauteur de la racine.
La profondeur d’un arbre est le nombre de nœuds qu’il faut parcourir partant de la racine,
pour atteindre le nœud le plus éloigné. C’est le nombre de nœuds contenu dans le sous-
arbre le plus haut, partant de la racine. La profondeur de la racine est égale à 1, donc on
compte la racine.
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
La profondeur d’un nœud est le nombre de nœuds qu’il faut parcourir, partant de la racine,
pour atteindre ce nœud.
Note: Systèmes de numérotation: unaire (1), binaire (0,1), ternaire (0,1,2), quaternaire
(0,1,2,3), quinaire (0,1,2,3,4), sénaire (0,1,2,3,4,5), septénaire (0,1,2,3,4,5,6), octal
(0,1,2,3,4,5,6,7), nonaire (0,1,2,3,4,5,6,7,8), décimal (0,1,2,3,4,5,6,7,8,9).
Un arbre est dit équilibré (ou à critère d'équilibre) si, à chaque niveau, la profondeur des
différents sous-arbres (ou branches) ne varie pas de plus d'un niveau.
Un arbre qui n'est pas équilibré est dit un arbre dégénéré.
Si l'ordre entre les sous-arbres enfants est pris en compte on parlera d'arbre ordonné (à ne
pas confondre avec un arbre trié).
Un arbre ordonné est un arbre où la position respective des sous-arbres reflète une relation
d'ordre. Par exemple, l’ordre des fils de n1 dans la figure suivante est, à partir de la
gauche, n2, puis n3 et enfin n4. Cet ordre de la gauche vers la droite peut être étendu de
manière à ordonner tous les nœuds d’un certain ordre de sorte que si m et n sont des frères
et si m est à la gauche de n, alors tous les descendants de m sont à la gauche de tous les
descendants de n.
n1
n2 n3 n4
n5 n6 n7
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
b) Tout nœud ni autre que la racine est relié par un arc à un autre nœud nj appelé le
père de ni.
Si p est le père du nœud n, on dit également que n est un fils de p. Un nœud peut avoir zéro
ou plusieurs fils, mais tout nœud autre que la racine a exactement un père. De plus, la
séquence des pères partant d’un nœud vers la racine est toujours unique.
Rappel: La profondeur d’un arbre est le nombre de nœuds qu’il faut parcourir partant de la
racine, pour atteindre le nœud le plus éloigné.
Le nombre maximum de nœuds qu'un arbre de profondeur "p" et de degré "d" peut
contenir, si tous les nœuds internes ont "d" descendants, est :
Nd(p)= di
N2(p)= 2i=2p-1
La profondeur d'un arbre binaire équilibré, ayant N nœuds, est toujours comprise entre
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
insertion : à gauche ou à droite d'un nœud terminal (cas d'un arbre binaire).
suppression : d'une feuille, d'un nœud intérieur.
recherche d'un élément.
parcours : en ordre, en pré-ordre, en post-ordre (cas d'un arbre binaire).
Il est bien entendu possible d'effectuer bien d'autres opérations sur des arbres (voir section
8.5). Par exemple combiner 2 arbres, extraire un sous-arbre d'un arbre, … etc. Ces
opérations dépendent étroitement du problème qu'elles sont sensées résoudre.
8.3.1 Exemple d’insertion d’éléments dans un arbre (cas d'un arbre de tri)
On suppose que les éléments de l’arbre sont des entiers.
Note:
Un arbre de tri est un arbre binaire dont la structure reflète une relation d'ordre entre les
éléments, basée sur une clé de tri. Principe d'insertion d'un élément dans un arbre de tri
(ordre croissant): on parcourt l'arbre, si la valeur de l'élément courant est supérieure à la
valeur de l'élément à insérer, on l'insère à gauche, sinon, on l'insère à droite.
struct Noeud
{
int info;
struct Noeud *gauche;
struct Noeud *droit;
};
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
void main(void)
{
TypNoeud * parbre; /* pointeur qui va référencer l’arbre */
TypNoeud * p, q;
int number; /* variable qui va contenir l’élément à insérer */
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
if(p==NULL)
printf("Insertion annulee\n"); /* si p pointe vers NULL, l’élément
courant n’existe pas */
else
if(p->gauche !=NULL) /* test pour voir si un élément a déjà été
inséré à gauche de l’élément courant*/
printf("Insertion non valide\n");
else /* le champ gauche de l’élément courant
n’a pas encore d’élément */
{
q=creer_noeud(); /*création du nouveau nœud */
if(q==NULL)
{
printf("\n Insertion échouée. Espace insuffisant.\n") ;
return() ;
}
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
else
{
q->info=x;
p->gauche=q; /* établissement du lien avec le père */
q->gauche=NULL;
q->droit=NULL;
}
}
}
if(p==NULL)
printf("Insertion annulee\n");
else
{
q=creer_noeud(); /* création du nouveau nœud */
if(q==NULL)
{
printf("\n Insertion échouée. Espace insuffisant.\n") ;
return() ;
}
else
{
q->info=x;
r=p->droit;
p->droit=q; /* établissement du lien avec le père */
q->gauche=NULL;
q->droit=r;
}
}
}
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
La suppression d'une feuille ne pose pas de problème; il suffit de mettre à Null le pointeur
vers l'élément à détruire et de récupérer la place occupée par l'élément détruit.
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
La suppression d'un nœud n'ayant qu'un seul descendant est à peine plus compliquée. Il
suffit de faire pointer l'ancêtre directement vers le descendant.
La suppression d'un nœud ayant deux descendants est plus délicate. Il faut d'abord trouver
l'élément le plus à droite du sous-arbre gauche (le plus grand élément inférieur à celui que
l'on veut détruire); ce sera une feuille ou un nœud à un seul descendant gauche. On
transfère alors le contenu de la feuille (ou du nœud à un seul descendant gauche) dans le
nœud que l'on voulait détruire et l'on détruit la feuille (ou le nœud à un seul descendant
gauche) à la place.
Le cas où l'élément le plus à droite du sous-arbre gauche est une feuille est illustré à la
figure 8.6, alors que celui où l'élément le plus à droite du sous-arbre gauche est un nœud à
un seul descendant est illustré à la figure 8.7.
Fig. 8.6. Suppression d'un nœud à deux descendants, échange avec une feuille.
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
pere=temp;
recherche(temp->gauche, val);
recherche(temp->droit, val);
}
}
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
L'impression d'un arbre de tri se fait à l'aide d'un parcours en ordre puisque la valeur
stockée dans le nœud racine se situe entre les valeurs du sous-arbre gauche et celles du
sous-arbre droit. L'évaluation d'un arbre syntaxique se fait avec un parcours en post-ordre
puisqu'il faut évaluer les deux opérandes avant de pouvoir traiter l'opérateur. A titre
d'exemple, le parcours en ordre d'un arbre binaire peut se faire de la manière suivante :
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
Exemple
la déclaration d'un arbre binaire ordonné doit spécifier le type de bases des
éléments qu'il contiendra (structure homogène).
primitives de manipulation:
1. initialiser un arbre à vide.
2. définir la racine d'un arbre vide.
3. regrouper un élément (racine) et deux sous-arbres (un des sous-arbres peut
être vide)
4. ajouter un élément (éventuellement tout un sous-arbre) à gauche (ou à
droite) d'un élément ne possédant pas de descendant gauche (ou droit,
respectivement).
5. indiquer si l'arbre est vide.
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
#include <stdio.h>
#include <stdlib.h>
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
if(number==p->info)
{printf("%d est un doublon\n", number);
ins_droit(p,number);
}
else
{
if(number <p->info)
ins_gauche(p,number);
else
ins_droit(p,number);
}
}
preordre(parbre); /* appel de la fonction d’affichage en pre-ordre
des elements de l’arbre */
}/* fin de la fonction principale */
TypNoeud * creer_arbre(int x)
{
TypNoeud * p;
p=creer_noeud();
if(p==NULL)
{
printf("\n Insertion échouée. Espace insuffisant.\n") ;
return(p) ;
}
else
{
p->info=x;
p->gauche=NULL;
p->droit=NULL;
return(p);
}
}/* fin de la definition de la fonction creer_arbre */
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
if(p==NULL)
printf("Insertion annulee\n");
else
if(p->gauche !=NULL)
printf("Insertion non valide\n");
else
{
q=creer_noeud();
if(q==NULL)
{
printf("\n Insertion échouée. Espace insuffisant.\n") ;
return() ;
}
else
{
q->info=x;
p->gauche=q;
q->gauche=NULL;
q->droit=NULL;
}
}
}
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
TypNoeud * creer_noeud(void)
{
return (TypNoeud *) malloc(sizeof(struct Noeud));
}
#define OPERATEUR 0
#define OPERAND 1
struct Noeud
{
short int utype;
union
{ /*le type union est utilisé pour définir le champ*/
char chinfo; /*info qui peut contenir un caractère (operateur)*/
float numinfo; /* ou un réel (opérande)*/
}info;
struct Noeud *left;
struct Noeud *right;
};
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
/*définition de la fonction oper qui effectue l’opération op1 symb op2 et retourne le
résultat sous forme de float */
float oper(char symb, float op1, float op2)
{
switch(symb) /*Le chois de l’opération en fonction du symbole*/
{
case '+': return(op1+op2);
case '-': return(op1-op2);
case '*': return(op1*op2);
case '/': return(op1/op2);
// case '$': return( expon(op1,op2));
default : printf("%s", "Operation illegale");
exit(1);
}return(0);
}/* fin de la définition de la fonction oper*/
Cours : Algorithmes & Structures de Données EN1, EM1, AR1 & GC1
8.8 Exercices
1- Ecrire un programme en c qui permet d’effectuer les opérations élémentaires suivantes
sur un arbre binaire ordonné de réels. Les fonctions à implémenter sont les suivants :
a) initialisation de l’arbre à vide
b) ajout d’un élément
c) suppression d’un élément
d) vérification de l’état de l’arbre (vide, non vide)
e) accès à la racine de l’arbre
f) recherche d’un élément donné dans l’arbre