D’après le cours de Denis Gautier - CITE SCOLAIRE J.H. FABRE – CARPENTRAS
1. Introduction
Les structures de type listes chaînées, piles et files vues précédemment sont efficaces lorsqu’il s’agit de structurer des données qui devront être parcourue séquentiellement.
Quand il s’agit d’accéder à une donnée qui est à une position quelconque, ces structures ne sont plus adaptées puisqu’il faut parcourir toutes les données précédentes avant d’atteindre la donnée recherchée. Le temps moyen d’accès à une donnée est en général proportionnel au nombre de données à parcourir.
Les structures arborescentes sont une autre famille de données chaînées qui réduisent le temps d’accès aux données en les organisant différemment. |
2. Structures arborescentes
Ces structures sont largement utilisées en informatique, pour l’arborescence des fichiers par exemple. Ci-dessous l’arborescence du système de fichier Unix que l’on retrouve sur les MAC, sur Linux, Android, etc..

3. Les arbres binaires
|

4. Définitions
Vocabulaire
|
Mesures
|
Si \( N \) désigne la taille d’un arbre ( son nombre de nœuds) et \(h\) sa hauteur, pour un arbre binaire, on a l’inégalité suivante :
\(h ≤ N ≤ 2^{h} -1 \)
Ci-dessous, un arbre binaire parfait où toutes les feuilles sont à la même profondeur : \( N = 2^{h} -1 \) ici \( 7 = 2^3 -1 \)

Cas particulier
|
5. Intéret d’un arbre
L’intérêt d’un arbre est de stocker de l’information. Ci-dessous l’arbre binaire du codage de la compression de Huffman où chaque feuille est étiquetée et chaque branche pondérée:

Codage des caractères :
-
R = 00
-
P = 01
-
E = 1
5.1. Travail à faire : Décoder le mot codé
Le mot codé est : 011001
5.2. Travail à faire : Construction d’un arbre et mesures
On considère le type abstrait Arbre
qui possède les opérations suivantes :
-
creer_arbre(v,g,d)
: cette opération renvoie un objetArbre
, dont la valeur du nœud estv
, la valeur du fils gaucheg
et celle du fils droit estd
. -
creer_arbre_feuille(v)
: cette opération renvoie un objetArbre
, dont la valeur du nœud estv
et le nœud n’a pas d’enfants.
On utilise ce type abstrait pour construire l’arbre suivant :
A = creer_arbre_feuille( 5) B = creer_arbre_feuille( 4) C = creer_arbre(3,B,A) D = creer_arbre_feuille( 2) E = creer_arbre(1,D,C)
-
Dessiner l’arbre correspondant.
Correction1__ / \ 2 3 / \ 4 5 |
-
Quelle est la taille de l’arbre
-
Quelle est la hauteur de l’arbre
-
Qui est le père du nœud 4 ?
-
Combien a-t-il de frères ?
-
Combien d’enfants possède la racine ?
5.3. Travail à faire : Construction à partir d’un tableau
On peut décrire un arbre sous la forme d’un tableau avec un numéro de nœud, un valeur v
, le nœud fils du sous arbre à gauche (SAG) et le nœud fils du sous arbre à droite (SAD) :
Noeud | Valeur v | Noeud SAG | Noeud SAD |
---|---|---|---|
1 |
* |
2 |
3 |
2 |
+ |
4 |
5 |
3 |
- |
6 |
7 |
4 |
3 |
||
5 |
/ |
8 |
9 |
6 |
8 |
||
7 |
* |
10 |
11 |
8 |
4 |
||
9 |
2 |
||
10 |
2 |
||
11 |
3 |
-
Dessiner l’arbre correspondant.
Correction___*___ / \ + - / \ / \ 3 '/' 8 * / \ / \ 4 2 2 3 |
-
Quelle est la hauteur de cet arbre ?
-
Cet arbre est-il binaire ?
CorrectionOui cet arbre est binaire, chaque nœud à maximum deux enfants |
-
Sachant que cet arbre permet un calcul mathématique, quel est le résultat obtenu ?
Correction\( [(4/2) + 3] * [ 8 – ( 2*3) ] = 5 * 2 = 10 \) |
6. Codage en python
6.1. Codage de l’arbre
Il y a plusieurs façons de coder un arbre binaire, on peut par exemple utiliser des objets reliés à partir d’une classe appelée Noeud
.
class Noeud:
def __init__(self, g,v,d):
self.gauche= g
self.valeur = v
self.droite=d
Utilisation pour l’arbre ci-dessous :

arbre = Noeud( Noeud(None, 'B', Noeud(None, 'C', None)), 'A', Noeud(None, 'D', None))
6.2. Codage des mesures
Des fonctions extérieures à la classe peuvent calculer la taille et la hauteur de manière récursive :
def taille(arbre):
""" nombre de noeuds de l'arbre"""
if arbre is None:
return 0
return 1 + taille(arbre.gauche) + taille(arbre.droite)
def hauteur(arbre):
""" hauteur de l'arbre"""
if arbre is None:
return 0
return 1 + max(hauteur(arbre.gauche), hauteur(arbre.droite))
6.3. Parcours d’un arbre binaire
Pour le calcul de la hauteur et de la taille, le sens du parcours en premier par le sous-arbre de gauche ou celui de droite n’a pas d’importance.
Par contre quand il s’agit de parcourir l’arbre pour en extraire les valeurs, on peut définir plusieurs façons, appelées :
|
Selon la méthode choisie, l’algorithme du parcours sera différent.

Dans cet abre binaire, les différents parcours donnent :
-
Préfixe : 1 → 2 → 4 → 5 → 7 → 8 → 3 → 6 → 9
-
Postfixe : 4 → 7 → 8 → 5 → 2 → 9 → 6 → 3 → 1
-
Infixe : 4 → 2 → 7 → 5 → 8 → 1 → 3 → 9 → 6
-
En largeur : 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9
6.4. Codage d’un parcours infixe
def parcours_infixe(arbre):
if arbre is None:
return
parcours_infixe(arbre.gauche)
print(arbre.valeur)
parcours_infixe(arbre.droite)
6.5. Travail à faire : Description et parcours d’un arbre binaire
On vous donne l’arbre suivant:

Q1) Mesures
-
Quelle est la taille de cet arbre?
-
Quel est sa hauteur?
-
Combien cet arbre a-t-il de feuilles ?
-
Cet arbre est-il équilibré ?
Correction
|
Q2) On décide de coder cet arbre à partir d’objets de la classe Noeud
:
class Noeud:
def __init__(self, g,v,d):
self.gauche = g
self.valeur = v
self.droite = d
-
Coder cet arbre à partir d’objets de la classe
Noeud
.
Correction
|
Q3) Obtenir la hauteur et la taille
-
Ecrire les fonctions python
calcule_taille(arbre)
etmesure_hauteur(arbre)
qui permettent de déterminer la taille et la hauteur d’un arbre binaire. -
Vérifier que vous obtenez bien les mêmes valeurs qu’à la question 1.
Correction
|
Q4) Parcours en profondeur infixe :
Dans un parcours en profondeur infixe, on liste chaque nœud ayant un fils à gauche la seconde fois qu’on le voit et chaque nœud sans fils à gauche la première fois qu’on le voit, on commence par parcourir le sous-arbre de gauche. Les numéros sur les segments de l’arbre ci-dessous vous montre le sens parcours :
Pour l’arbre ci-dessous, on obtiendra l’ordre d’affichage: 4;2;7;5;8;1;3;9;6 ![]() |
-
On vous donne le programme python qui réalise ce parcours :
def parcours_infixe(arbre):
if arbre is None:
return
parcours_infixe(arbre.gauche)
print(arbre.valeur, end =";")
parcours_infixe(arbre.droite)
-
Tester le code du parcours infixe sur l’arbre de la figure 1 et comparer avec les valeurs sous la figure 2.
-
Que se passe-t-il après un
return
suite à un test évaluéTrue
deif arbre is None
?
Q5) Parcours en profondeur préfixe :
Dans ce cas de parcours en profondeur, on liste chaque sommet la première fois qu’on le rencontre dans le parcours. Cela donne l’affichage: 1;2;4;5;7;8;3;6;9 pour l’arbre étudié. |
ParcoursPréfixe(arbre_binaire):
si arbre_binaire est vide:
retourner
Afficher valeur
ParcoursPréfixe( sous arbre_binaire gauche)
ParcoursPréfixe( sous arbre_binaire droit)
-
Coder en python le pseudo code du parcours préfixe et tester le code.
Correction
|
Q6) Parcours en profondeur postfixe :
Dans ce cas de parcours en profondeur, on liste chaque sommet la dernière fois qu’on le rencontre. Cela donne l’affichage: 4;7;8;5;2;9;6;3;1 pour l’arbre étudié. |
ParcoursPostfixe(arbre_binaire):
si arbre_binaire est vide:
retourner
ParcoursPostfixe( sous arbre_binaire gauche)
ParcoursPostfixe( sous arbre_binaire droit)
Afficher valeur
-
Coder en python le pseudo code du parcours postfixe et tester le code.
Correction
|
Q7) Parcours en largeur :
Le parcours d’un arbre en largeur consiste à partir de la racine puis visiter le fils gauche puis le fils droit, puis le fils gauche du fils gauche, puis le fils droit du fils droit, puis leurs enfants, etc.. Cela donne l’affichage: 123456789 pour l’arbre étudié. |
* On utilise une File f passée en paramètre de la fonction de parcours.
* On passe également en paramètre de la fonction l’arbre à parcourir.
* On met l’arbre à parcourir dans la file f.
* Tant que la file n’est pas vide:
* On enlève un element de la file
* Si l'élément enlevé ne vaut pas `None`
* On affiche la valeur du nœud de l'élément
* On met dans la file son fils gauche
* On met dans la file son fils droit
# classe cellule permet de construire un élément de la File
class Cellule :
""" construit une cellule de liste chaînée """
def __init__( self, valeur, adresse_cellule_suivante) :
self.valeur=valeur
self.suite_liste = adresse_cellule_suivante
class File:
def __init__(self):
self.tete = None
self.queue = None
def est_vide(self):
return self.tete is None
def ajouter(self, valeur):
# on crée un objet cellule qui contient la valeur à ajouter
cell = Cellule( valeur, None)
# on teste si c’est le premier élément ajouté
if self.tete is None:
self.tete = cell
# s’il y a déjà une cellule, on rajoute en queue
else:
self.queue.suite_liste = cell
self.queue = cell
def retirer(self):
if self.est_vide():
raise IndexError (" la file est vide")
# on enlève le premier élement de la file
la_valeur_a_retirer = self.tete.valeur
# on fixe l’élément suivant en tête
self.tete = self.tete.suite_liste
# s’il n’y a plus d’élément en tête, la file est vide
if self.tete is None:
self.queue = None
# on renvoie la valeur retirée.
return la_valeur_a_retirer
def parcours_en_largeur(un_arbre, f):
f.ajouter(un_arbre)
while not f.est_vide():
arbre=f.retirer()
if arbre is not None:
print(arbre.valeur, end= ";")
f.ajouter(arbre.gauche)
f.ajouter(arbre.droite)
mon_arbre = Noeud( Noeud( Noeud( None,4, None) ,2, Noeud( Noeud( None,7, None) ,5, Noeud( None,8, None))), 1 , Noeud( None,3, Noeud( Noeud( None,9, None),6, None)) )
une_file = File()
parcours_en_largeur(mon_arbre, une_file)
-
Tester le code. Détailler les étapes d’exécution de la fonction
parcours_en_largeur(..)
ce qui vous permettra de comprendre le code de la fonction.
7. Arbres binaires de recherche (ABR)
Un arbre binaire de recherche appelé ABR est un arbre dont les nœuds contiennent des valeurs qui peuvent être comparées entre elles (des nombres, des caractères par exemples). Pour tout nœud de l’arbre :
|

7.1. Travail à faire : Reconnaitre un ABR

-
Peut-on classer cet arbre comme ABR ?
CorrectionNon ce n’est pas un ABR sinon le fils gauche du nœud 3 aurait une valeur inférieur à ce dernier, or elle est de 4. |
7.2. Recherche d’un élément
Un ABR permet une recherche optimisée, on abandonne le parcours de la sous branche qui ne correspond pas au critère de recherche.
Par exemple écrivons une fonction qui indique si un nombre appartient à l’arbre de recherche.
Un ABR permet une recherche optimisée, on abandonne le parcours de la sous branche qui ne correspond pas au critère de recherche.
fonction appartient(nbre, arbre):
// determine si nbre est dans arbre
si arbre est vide:
retourner Faux
si nbre < arbre.valeur:
retourner appartient(nbre, arbre.gauche)
si nbre > arbre.valeur:
retourner appartient(nbre, arbre.droite)
returner Vrai
7.3. Travail à faire : Recherche d’un élément
Soit l’abre binaire de recherche suivant :

Q1) Quelle propriété possède un ABR ?
CorrectionPour tout nœud de l’arbre :
|
Q2) Mesures
-
Quelle est la taille de cet arbre?
-
Quel est sa hauteur?
-
Combien cet arbre a-t-il de feuilles ?
Correction
|
Q3) Ecrire une fonction qui permette de déterminer si un nombre appartient ou pas à l’arbre.
Correction
|
Q4) Quelle est la classe de complexité d’une recherche dans un ABR ?
CorrectionLe coût de la recherche et de l’ajout dépend de la structure de l’ABR, la hauteur \( h \) de l’arbre a son influence et l’équilibre des nœuds de l’arbre impactent le temps de parcours. Un arbre très mal équilibré (de type peigne par exemple) aura un coût linéraire, c’est l’équivalent d’une liste, le temps de parcours est proportionnel au nombre \( N \) de noeuds. Si l’arbre est conçu équilibré ce qui demande des techniques hors programme de terminale, on obtient un coût logarithmique : \( h \leqslant C \log(N) \) avec \( C \) une constante. |
7.4. Ajout d’une valeur dans un ABR
On utilise aussi une fonction récursive, l’exemple suivant montre la construction obtenue.
def ajoute(nbre, arbre):
"""ajoute nbre dans l'arbre de recherche existant et renvoie un nouvel arbre"""
if arbre is None:
return Noeud( None, nbre, None)
if nbre < arbre.valeur:
return Noeud( ajoute(nbre, arbre.gauche), arbre.valeur, arbre.droite)
return Noeud( arbre.gauche, arbre.valeur, ajoute(nbre, arbre.droite))
# Utilisation
mon_arbre = Noeud( None, 1, None)
mon_arbre = ajoute( 3, mon_arbre)
mon_arbre = ajoute(2, mon_arbre)
mon_arbre = ajoute( 4, mon_arbre)
mon_arbre = ajoute( 3, mon_arbre)

