Vous êtes sur la page 1sur 72

Structures de Données

Chapitre 5 : les arbres binaires


de recherche
Pr. KASMI Mohammed Amine
Plan
• Définition d’un arbre
• Vocabulaire des arbres
• L’usage des arbres
• Arbres binaires de recherche (ABR)
• Traitements sur les arbres binaires de
recherche
• Exercices
1- Définition de l’arbre
• Un arbre est structure de données qui contient un
ensemble de nœuds reliés entre eux par des arcs
(chemins).
• Un arbre contient un nœud particulier r appelé racine et
une séquence des sous-arbres de racines respectives r1,r2,
… , rk.
• La racine r est reliée aux ri par des arcs orientés du haut en
bas (un seul sens de r vers ri)
r
a
r1 r2
b c
r3 r4
d e 3
2- Vocabulaire et propriétés
• L’arbre dans la figure ci-dessous a une racine « a »
• Les ri (b,c,d) sont les fils de la racine de l’arbre (a)
• Les fils du nœud g sont : j, k, l, m et n . Le père de j est g
• Les nœuds qui n’ont pas de fils sont appelés feuilles
• Les feuilles de cette arborescence sont : h, i, f, c, j , o, p, q,
l, m, n. r

b c d

ri e f g

4
2- Vocabulaire et propriétés
■ Une séquence de nœuds partant d’un nœud 'a' en suivant
les arcs jusqu’au nœud 'p' s’appelle un chemin de a à p.
■ La longueur d’un chemin est le nombre d’arcs qui se
trouvent sur le chemin(ex : entre a et p, la longueur = 4)
■ Il existe un seul et unique chemin pour aller à un nœud
de l’arbre à partir de la racine
a1

a2

a3

a4
5
2- Vocabulaire et propriétés
■ La hauteur d’un nœud est la longueur du plus long
chemin partant de ce nœud et aboutissant à une feuille.
■ Les hauteurs respectives des nœuds : b, c, d sont 2, 0, 3
■ La hauteur de toute feuille est donc 0.
■ La hauteur de l’arbre est la hauteur de sa racine.
■ L’arité d’un nœud est le nombre de ses fils.

6
3 - Usages des arbres
Les arbres sont utiles pour représenter les données
de manière hiérarchique
Exemple 1
un arbre généalogique :
3 - Usages des arbres
● Les arbres sont utiles pour représenter les données de
manière hiérarchique
● Exemple 2 : organigramme d’une entreprise
3 - Usages des arbres
● Les arbres sont utiles pour représenter les données de
manière hiérarchique
● Exemple 3 : Abrorescence du S.E

les systèmes de type UNIX


3 - Usages des arbres
● Exemple 4 : Arbre syntaxique (exp: expression arithmétique
3 - Usages des arbres

● Exemple 5 : Intelligence Artificielle : arbres de


décision

les systèmes de type UNIX


3 - Usages des arbres
● Exemple 5 : Arbre DOM (Document Object
Model)
4- Arbres binaires de recherche: ABR
■ Un arbre binaire de recherche (ABR), ou simplement
arbre binaire, est un arbre dans lequel tout nœud
possède au plus deux fils.
■ Un arbre binaire complet est un arbre binaire dont tous
les nœuds internes ont deux fils, et dont tous les
chemins de la racine aux feuilles sont de longueur
égales.

FIG: A gauche, un arbre binaire À droite, un arbre binaire possédant


complet, il est donc parfaitement le même nombre de nœuds, mais
équilibré. complètement déséquilibré.
13
4- Représentation d’un ABR

■ Un ABR peut être représenté sous une forme chaînée et


chaque nœud de l’arbre contient :
■ Un ou plusieurs champs de données
■ Deux champs droit et gauche , ce sont des pointeurs
vers les deux fils du nœud,
NB: Lorsque les nœuds droit et gauche sont vides, les
champs correspondants valent NULL.
donnée

donnée donnée
14
4- Arbres binaires de recherche: ABR
■ Un ABR possède de plus une propriété fondamentale à
respecter sur les valeurs des nœud fils.
Exemple :
■ Soit x un nœud quelconque d’un ABR. Pour tout nœud
y du sous-arbre gauche de x , la valeur de y est
inférieure à la valeur de x , et pour chaque nœud z du
sous-arbre droit de x , la valeur de z est supérieure à la
valeur de x
x-> valeur = 5

y->valeur= 2 z-> valeur = 10


15
Exemple d’ABR
■ La figure suivante représente deux exemples d’ABR.
Elle montre que plusieurs ABR peuvent posséder la
même collection de valeurs. Les deux arbres (a) et
(b) ont des hauteurs différentes.
■ L’ABR résultat dépends de l’ordre d’insertion du
nœud dans l’arbre.

16
Traitements sur les
arbres binaires de
recherche

17
Etapes:
■ Nous allons traiter un ABR d’entiers.
1. Déclarer un nœud de l’ABR
2. Créer la racine d’un ABR
3. Insérer un nœud dans un ABR
4. Rechercher un nœud dans un ABR
5. Afficher les nœuds d’un ABR: les modes de
parcours d’un ABR
6. Supprimer un nœud
7. Supprimer l’arbre

18
1-Déclaration d’un nœud
■ La déclaration d’un nœud de l’arbre
est la suivante:

struct Noeud {
type valeur ; valeur
struct Nœud * gauche ;
struct Nœud * droit ; Nœud gauche Nœud droit

};
typedef struct Nœud Nœud;

19
Exemple de déclaration d’un nœud d’un
ABR d’entiers
■ La déclaration d’un nœud de l’arbre
est la suivante:

typedef struct Nœud { 5

int valeur ;
struct Nœud * gauche ; Nœud fils à droite
Nœud fils de gauche
struct Nœud * droite ;
} Nœud;

20
2-Création de la racine d’un ABR
■ La création de la racine d’un ABR d’entiers revient à
donner des valeurs initiales
racine racine
valeur Gauche Droit 5 NULL NULL

Nous allons suivre les étapes suivantes :


1. Déclarer et allouer de la mémoire pour le nœud racine
2. Mettre le nombre que l’on veut ajouter dans le champ
valeur
3. Mettre les pointeurs gauche et droit à NULL. Au départ
, il y’a un seul nœud
4. Retourner la racine de l’ABR
21
2-Création de la racine d’un
ABR
//Création de la racine d’un ABR
Noeud* creer_noeud ( int n){
Noeud* noeud=(Noeud*)malloc(sizeof(Noeud)) ;//étape1
noeud->valeur = n ; // étape2
noeud->gauche = NULL; //étape3
noeud->droite = NULL; //étape3
return noeud ; // étape4
}
• n est la valeur du nœud.

22
3-Insertion d’un nœud dans un ABR

■ Nous allons traiter 3 cas :


o Cas 1: la valeur du nouveau nœud se trouve déjà
dans l’arbre
o Cas 2: Insérer un nouveau nœud à gauche de l’ABR
o Cas 3: Insérer un nouveau nœud à droite de l’ABR

23
3-Insertion d’un noeud dans un ABR
■ Cas 1: la valeur du nouveau nœud se trouve déjà dans
l’arbre
4 5

3 8

2 4 9

24
3-Insertion d’un noeud dans un ABR
■ Cas 2: Insérer un nouveau nœud à gauche de l’ABR

1 5

3 8

2 4 9

1
25
3-Insertion d’un nœud dans un ABR
■ Cas 3: Insérer un nouveau nœud à droite de l’ABR

10 5

3 8

2 4 9

10

26
Explications (exemple du cas 3)
temp
racine
10 5
temp

3 8
temp

2 4 9

10

27
Etapes
■ Créer un nouveau nœud et lui allouer la mémoire
■ Rechercher de façon récursive le bon emplacement de la
valeur : comparer les valeurs à ajouter avec la valeur de
temp.
■ Si nouveau -> valeur < temp -> valeur :chercher dans
le sous arbre gauche
■ Si nouveau -> valeur > temp -> valeur :chercher dans
le sous arbre droit
■ Si nouveau -> valeur == temp -> valeur : on affiche le
message "valeur existe déjà"
■ Si la valeur ne figure pas dans l’arbre, on l’ajoute
28
3-Insertion d’un élément dans un ABR
Noeud* ajouter_noeud ( int n ,Noeud*racine ){
Noeud* nouveau = creer_noeud (n ) ; //étape 1
return ajout_recursif ( nouveau , racine, racine) ; //étape2
}
racine temp
1 5

3 8

2 4 9

1 29
Nœud *ajout_recursif(Noeud*nouv, Noeud*racine, Noeud*
temp ){
if ( racine == NULL) return nouv ; // arbre vide
if ( nouv->valeur == temp->valeur ) // valeur existe
printf("\n Valeur déjà existante\n"); return racine;
else if ( nouv->valeur < temp ->valeur ){
if (temp ->gauche==NULL) temp ->gauche = nouv;
else ajout_recursif( nouv , racine , temp->gauche ) ;
} racine temp
1 5

3 8

2 4 9
30
1
else { // nouveau->valeur > temp ->valeur
if ( tmp->droite == NULL)
temp->droite = nouv ;
else
ajout_recursif( nouv , racine , temp->droite) ;
}
return racine ; racine temp
}
10 5

3 8

2 4 9

10
31
4-La recherche d’une valeur dans un
ABR
4- La recherche d’une valeur dans un
ABR
■ Nous pouvons réaliser la recherche dans l’ABR de façon
itérative ou bien récursive

■ Dans les deux cas, on retourne l’adresse du nœud qui


contient la valeur recherchée et NULL sinon.

24
4- La recherche d’une valeur dans un
ABR
•A- Fonction itérative de recherche dans un ABR
■ La fonction itérative utilise une boucle tant que pour
parcourir tous le nœuds de l’arbre et comparer la valeur
recherchée avec les valeurs des nœuds à partir de la
racine. La fonction :
■ Retourne l’adresse du nœud qui contient la valeur
■ Continue la recherche de la valeur avec le fils droit ou
gauche en fonction du résultat de la comparaison de
la valeur recherchée avec le nœud traité.
■ Retourne NULL si la valeur ne se trouve pas dans
l’arbre.

24
4- La recherche d’une valeur dans un
ABR
•Exemple 1 : recherche la valeur 3 dans l’arbre
racine

5
racine

3 8

2 4 9

24
4- La recherche d’une valeur dans un
ABR
•Exemple 2 : recherche la valeur 7 dans l’arbre
racine

5
racine

3 8

racine
2 4 9
NULL

24
4- La recherche d’une valeur dans un

ABR
A- Fonction de recherche itérative dans un ABR

Noeud* rechercher_noeud_iter ( int n , Noeud* racine ){


while (racine!=NULL){
if ( racine->valeur == n)
return racine ;
else if (n < racine->valeur )
racine=racine->gauche ;
else
racine=racine->droite ;
}
return NULL;
}
24
4- La recherche d’une valeur dans un
ABR
•A- Fonction récursive de recherche dans un ABR
■ La fonction récursive consiste à comparer la valeur de la
racine avec la valeur recherchée. Le résultat de la
comparaison permet de :
■ Retourne l’adresse de la racine correspondante (dont
la valeur = la valeur recherchée)
■ Appeler la même fonction de recherche avec une
modification: remplacer la racine par le fils droit ou
gauche du nœud racine.
■ Retourne NULL si la valeur ne se trouve pas dans
l’arbre.

24
4- La recherche récursive d’une valeur dans un
ABR
•Exemple 1 : recherche la valeur 1 dans l’arbre
racine

5
racine

3 8
racine

2 4 9

24
4- La recherche récursive d’une valeur dans un ABR

•Exemple 2 : recherche la valeur 7 dans l’arbre


racine

5
racine

3 8

racine
2 4 9
NULL

24
B- Fonction de recherche récursive
Nœud * rechercher_noeud_rec ( int n , Noeud* racine ){
if ( racine->valeur == n) return racine ;
else if (n < racine->valeur ){
if ( racine->gauche != NULL)
return rechercher_noeud_rec(n , racine->gauche);
else return NULL;
} else {
if ( racine->droite!= NULL)
return rechercher_noeud_rec (n , racine->droite ) ;
else return NULL;
}
}
5-Affichage des nœuds d’une
arborescence
5-Affichage des nœuds d’un arbre

■ Pour afficher les valeurs des nœuds d’un arbre, nous


pouvons choisir l’un des 2 parcours suivants :
1. Parcours en profondeur (le plus utilisé)
2. Parcours en largeur.

43
5-Affichage des nœuds d’une arborescence

■ 5.2. Parcours en profondeur


■ Le parcours en profondeur consiste à explorer un
arbre de haut en bas puis de gauche à droite.
■ On distingue trois types de parcours en profondeur :
◻ le parcours préfixe traite le nœud courant puis le
sous-arbre gauche et enfin le sous-arbre droit ;
◻ le parcours infixe traite le sous-arbre gauche, puis
le nœud courant et enfin le sous-arbre droit ;
◻ le parcours postfixe traite le sous-arbre gauche, le
sous-arbre droit et enfin le nœud courant.
44
5-Affichage des nœuds d’une arborescence
■ Parcours préfixe : Le traitement du nœud courant est
effectué avant les deux appels récursifs des nœuds gauche
et droit
void affiche_prefixe (Noeud* racine ){
printf ( ",%d , " , racine->valeur);
if ( racine->gauche !=NULL)
affiche_prefixe ( racine->gauche ) ;
if ( racine->droite != NULL)
affiche_prefixe ( racine->droite) ;
}

45
Exemple
void affiche_prefixe (Noeud* racine ){
printf ( ",%d , " , racine->valeur);
if ( racine->gauche !=NULL)
affiche_prefixe ( racine->gauche ) ;
if ( racine->droite != NULL)
affiche_prefixe ( racine->droite) ;
}
racine

3 8

2 4 9 46
■ Parcours infixe:
■ Traitement du sous-arbre gauche , puis le nœud
courant et enfin le sous-arbre droit

void affiche_infixe (Noeud* racine ){


if ( racine->gauche != NULL)
affiche_infixe ( racine->gauche ) ;
printf ( ",%d , " , racine->valeur ) ; racine
if ( racine->droite != NULL) 5
affiche_infixe ( racine->droite) ;
3 8
}
2 4 9
47
■ Parcours postfixe: traitement du sous-arbre gauche , puis le
sous-arbre droit et enfin le nœud courant
void affiche_postfixe (Noeud* racine ){
if (racine->gauche != NULL)
affiche_postfixe ( racine->gauche ) ;
if (racine->droite != NULL)
affiche_postfixe (racine->droite) ;
printf ( ",%d , " , racine->valeur ) ;
} racine

3 8

48
2 4 9
Exercice1
■ Afficher les valeurs des nœuds de l’arbre suivant en
utilisant les 3 types de parcours : préfixe, postfixe et
infixe

49
Solution
■ 1. Parcours préfixe (Courant, Gauche, Droite) : r,a,c,h,d, i , j ,l,b,e,k, f .
■ 2. Parcours postfixe (Gauche, Droite, Courant) : h,c, i ,l, j ,d,a,k,e, f ,b, r .
■ 3. Parcours infixe (Gauche, Courant, Droite) : c,h,a, i ,d,l, j , r,k,e,b, f .

50
Exercice2
■ Afficher l’arbre ci-dessous en utilisant un parcours infixe

■ On ajoute la convention suivante : on ajoute une parenthèse


ouvrante à chaque fois qu’on rentre dans un sous-arbre et on
ajoute une parenthèse fermante lorsqu’on quitte ce sous-arbre.
51
Solution
■ Parcours infixe (Gauche, Courant, Droite) : ( ((a+b) x (c-d)) ÷ (e+f) )

52
5-Affichage des nœuds d’une arborescence

■ 5.1. Parcours en largeur

■ Le parcours en largeur d’abord consiste à explorer un


arbre de gauche à droite puis de haut en bas.

53
Suppression d’un noeud
Suppression d’un nœud de l’ABR
L’opération dépend du nombre de fils du nœud à supprimer.

Cas 1 : le noeud à supprimer n’a pas de fils, c’est une feuille. Il suffit de
supprimer le noeud de l’arbre et libérer la mémoire occupée par ce
nœud.
Suppression d’un noeud
Suppression d’un nœud de l’ABR
L’opération dépend du nombre de fils du nœud à supprimer.

Cas 2 : le nœud à supprimer a un fils et un seul(fils gauche ou droit). Le


noeud est supprimé de l’arbre comme dans le cas 1. Il est remplacé par
son fils unique dans le noeud père, si ce père existe. Sinon l’arbre est
réduit au fils unique du nœud supprimé.
Suppression d’un noeud
Suppression d’un nœud de l’ABR
L’opération dépend du nombre de fils du nœud à supprimer.

Cas 3 : Le nœud à supprimer a deux fils, un fils gauche et un fils droit.


C’est le cas le plus délicat car ici, on ne peut pas simplement supprimer ou
remplacer le nœud par son enfant. Dans ce cas, nous trouvons le plus petit
nœud dans le sous-arbre droit du nœud minnode. Remplacez la valeur du
nœud à supprimer par la valeur de minnode et appelez récursivement delete
sur ce nœud(minnode)
Algorithme de suppression d’un noeud

-Si racine == NULL, alors renvoie NULL.

-Si val > racine->val , alors , le fils droit devient le


résultat de la suppression sur le fils de droite
racine->droite = supp_noeud(racine->droite,X)

-Sinon, si val < racine->val , le fils gauche devient le


résultat de la suppression sur le fils gauche
racine->gauche = supp_noeud(racine->gauche, X)

-Sinon si racine->val == X, alors agissez selon les 3 cas:


Algorithme de suppression d’un noeud
-Sinon si racine->val == X, alors agissez selon les 3 cas:
--Si (racine->gauche == NULL && racine->droit == NULL)
alors supprimez racine et renvoyez NULL.
--Sinon si (racine->droit == NULL) alors
supprimer le noeud et remplacer le par son fils de gauche

--Sinon si (racine->gauche == NULL) alors


supprimer le noeud et remplacer le par son fils droit

--Sinon si (racine->gauche!=NULL && racine->droit !=NULL)


alors supprimer le noeud et remplacer le par le nœud minimum dans
le sous-arbre droit minnode. Ensuite supprimez récursivement
minnode du sous-arbre de droite.
Retournez le pointeur vers la racine d’origine.
Noeud* supp_noeud(Noeud* racine, int valeur) {
if (racine == NULL) return racine; /* arbre vide : pas de suppression */
if (valeur < racine->valeur) racine->gauche = supp_noeud(racine->gauche, valeur);
else if (valeur > racine->valeur) racine->droite = supp_noeud(racine->droite, valeur);
else { // si valeur == racine->valeur
if((racine->gauche==NULL)&&(racine->droite==NULL)){//le nœud n’as aucun fils
free(racine); racine = NULL;}
else if((racine->gauche!=NULL) && (racine->droite!=NULL)) { // le nœud a 2 fils
Noeud* temp = getmin(racine->droite);
racine->valeur = temp->valeur;
racine->droite = supp_noeud(racine->droite, temp->valeur); }
else { // si le nœud a un seul fils
if (racine->droite != NULL) {
Noeud* temp = racine;
racine = racine->droite;
free(temp); return racine;
} else // (racine->gauche != NULL) {
Noeud* temp = racine;
racine = racine->gauche;
free(temp); return racine; }
}
}
return racine;
Fonction de recherche du noeud min

Noeud* getmin( Noeud* racine) {

Noeud* courant = racine;

while (courant!=NULL && courant->gauche!=NULL) {

courant = courant->gauche;

return courant;

}
Suppression d'un arbre :
L'algorithme de suppression de l'arbre est simple : on
supprime les feuilles une par une. On répète l'opération
autant de fois qu'il y a de feuilles.
Cette opération est donc très dépendante du nombre de
nœud.
En fait cet algorithme est un simple parcours d'arbre.
En effet, lorsque nous devrons traiter la racine, nous
appellerons une fonction de suppression de la racine.
Avant de supprimer la racine, il faut supprimer les sous
arbres gauche et droit.
On en déduit donc que l'algorithme de suppression est
un parcours d'arbre postfixe.

61
Noeud* DetruireArbre(Noeud*T)
{ Noeud* SupNoeud;
SupNoeud=(Noeud*) malloc(sizeof(Noeud));
SupNoeud=T;
if(T!=NULL)
{
if(T->gauche!=NULL)
DetruireArbre(T->gauche);
if(T->droite!=NULL)
DetruireArbre(T->droite);
}
T=NULL;
free(SupNoeud);
return T;
} 62
Solution 2
void DetruireArbre(Noeud**T) //(Noeud*T )
{ Noeud* SupNoeud;
Noeud* Ng; Noeud* Nd;
SupNoeud=(Noeud*) malloc(sizeof(Noeud));
Ng=(Noeud*) malloc(sizeof(Noeud));
Nd=(Noeud*) malloc(sizeof(Noeud));
SupNoeud=*T;
if((*T)->gauche!=NULL && (*T)->droite!=NULL) {
Ng=(*T)->gauche;
Nd=(*T)->droite;
DetruireArbre(&Ng);
DetruireArbre(&Nd);
}
*T=NULL;
free(SupNoeud);
} 63
Arbres binaires de recherche équilibrés :
arbres AVL

● Rappel sur les Arbres binaires de recherche :


● « Ordre » sur les nœuds :
○ Les plus petits à gauche
○ Les plus grands à droite
● Pour accélérer les recherches d’un élément dans
l’arbre
○ Objectif : dichotomie (on ne parcourt que la moitié
de l’arbre)
Arbres binaires de recherche équilibrés :
● arbres AVL
● Rappel sur les Arbres binaires de recherche :

Si on cherche 7 : 7 > 3 : pas besoin de chercher dans le


sous-arbre gauche
Arbres binaires de recherche équilibrés :
arbres AVL
● Rappel sur les Arbres binaires de recherche :

● En général, la recherche dans un ABR coute Θ ( h), où


h est l’hauteur de l’arbre

● La hauteur minimale pour un arbre binaire avec n


nœuds est log( n )

● Donc dans le meilleur de cas, le cout de la recherche


dans un ABR est log( n) où n est le nombre des nœuds
de l’arbre
Identification du problème

On ne gagne rien au niveau de la recherche


• On est obligé de chercher dans le s.-a. droit
• Recherche en Θ ( n) forcément

Solution
• Obliger l’arbre à être relativement symétrique
• Hauteur du s.-a. gauche proche de la hauteur du s.-a.
droit

⇒ Arbres de recherche équilibrés


Arbres de recherche équilibrés (AVL)

Principe :
• Pour chaque nœud, les hauteurs du s.-a. gauche et du
s.-a. droit différent au plus de 1
- Modèle proposé par G.M. Adelson-Velsky et E.M.
Landis (d’où son nom)
Notion de facteur d'équilibrage d'un nœud
• Différence entre les hauteurs des sag et sad
• Un arbre est AVL si tous les nœuds ont un facteur de -1,
0 ou 1
Changements

● Cette fois, on a systématiquement la moitié, ou


près de la moitié de l’arbre de chaque côté de la
racine
● Et ceci pour tous les nœuds
● Chaque choix entre s.-a. gauche et s.-a. droit
élimine la moitié des nœuds restants
● On a un vrai parcours dichotomique
● Complexité Θ(log( n)) dans le pire des cas

Vous aimerez peut-être aussi