L'idée des arbres binaires est simple : chaque nœud a au maximum deux enfants, un à gauche et un à droite, ce qui permet d'organiser les données de manière structurée pour faciliter certaines opérations comme la recherche ou le tri.
A copier dans le cahier.
Un arbre binaire est un arbre dont l'arité est égale à deux.
Une fois ceci fixé, nous pouvons ainsi parler d'enfant gauche et d'enfant droit.
Dans l'exemple ci-dessus :
A faire dans le cahier.
Dans cet exercice un arbre composé uniquement d'un seul noeud a une hauteur de 1.
On considère cet arbre binaire :
Dans un arbre binaire, à partir d'un noeud, on peut considérer son sous-arbre gauche et son sous-arbre droit.
A copier dans le cahier.
Un arbre est dit localement complet si chaque noeud possède soit 0 soit deux enfants.
Cet arbre est localement complet.
A copier dans le cahier.
Un arbre est dit complet s'il est localement complet et que ses noeuds ne comportant pas d'enfant sont au même niveau.
Cet arbre est complet.
A copier dans le cahier.
Un arbre est dit dégénéré s'il ne possède qu'une seule feuille.
On peut alors l'associer à une structure linéaire.
Cet arbre est dégénéré.
A faire dans le cahier.
Représenter l'arbre correspondant au tableau ci-dessous :
Noeud | Valeur | Enfant gauche | Enfant droit |
---|---|---|---|
1 | René | 2 | 3 |
2 | Lilou | 4 | \(\emptyset\) |
4 | Léa | \(\emptyset\) | \(\emptyset\) |
3 | Julie | \(\emptyset\) | 6 |
6 | Chloé | 7 | 8 |
7 | Titou | \(\emptyset\) | \(\emptyset\) |
8 | Irène | \(\emptyset\) | \(\emptyset\) |
A faire dans le cahier.
En prenant exemple du tableau de l'exercice précédent, faire un tableau correspondant à l'arbre ci-dessous :
A copier dans le cahier.
On peut stocker un arbre binaire dans Python à l'aide de liste de liste :
[racine,[sous_arbre_gauche],[sous_arbre_droit]]
Insérer dans Python l'arbre suivant à l'aide de liste de liste.
Une feuille aura ce format : [valeur,None,None]
Ecrire une fonction ajout_feuille_gauche(liste_arbre,valeur)
qui prend en paramètre un arbre sous forme de liste et une valeur.
Elle fonctionnera de manière récursive ainsi :
A copier dans le cahier.
Ces trois algorithmes sont des algorithmes récursifs :
Taille d'un arbre :
Fonction taille(arbre) :
Si arbre vide :
retourne 0
Sinon :
retourne 1 +taille(sous-arbre gauche)+ taille(sous-arbre droit)
Hauteur d'un arbre (cas où un arbre composé seulement de sa racine est de hauteur 1):
Fonction hauteur(arbre) :
Si arbre vide :
retourne 0
Sinon :
retourne 1 +max(hauteur(sous-arbre gauche),hauteur(sous-arbre droit))
Nombre de feuilles d'un arbre :
Fonction nombre_feuilles(arbre) :
Si arbre vide :
retourne 0
Sinon si arbre n'est composé que d'une racine :
retourne 1
Sinon :
retourne nombre_feuilles(sous-arbre gauche)+ nombre_feuilles(sous-arbre droit)
Ecrire en Python les fonctions suivantes
taille
prenant paramètre une listehauteur
prenant paramètre une liste (un arbre composé que d'un seul noeud à une hauteur égale à
1).nombre_feuille
prenant paramètre une listeA copier dans le cahier.
Parcourir l'arbre en profondeur signifie que l'on explore chaque sous-arbre complètement avant de passer à un autre sous-arbre.
On distingue alors trois cas :
A faire dans le cahier.
On considère l'arbre suivant:
Sur une feuille :
A copier dans le cahier.
Ces trois algorithmes sont des algorithmes récursifs :
Parcours préfixe :
Fonction parcours_prefixe(arbre) :
Si arbre vide :
On ne fait rien
Sinon :
On évalue la racine
On lance parcours_prefixe(sous-arbre gauche)
On lance parcours_prefixe(sous-arbre droit)
Parcours infixe :
Fonction parcours_infixe(arbre) :
Si arbre vide :
On ne fait rien
Sinon :
On lance parcours_infixe(sous-arbre gauche)
On évalue la racine
On lance parcours_infixe(sous-arbre droit)
Parcours postfixe :
Fonction parcours_postfixe(arbre) :
Si arbre vide :
On ne fait rien
Sinon :
On lance parcours_postfixe(sous-arbre gauche)
On lance parcours_postfixe(sous-arbre droit)
On évalue la racine
lecture_prefixe
qui prend comme paramètre une liste représentant un
arbre et qui
affiche à l'aide de print les valeurs rencontrées en parcourant dans l'ordre préfixe.lecture_infixe
qui prend comme paramètre une liste représentant un arbre
et qui
affiche à l'aide de print les valeurs rencontrées en parcourant dans l'ordre infixe.lecture_postfixe
qui prend comme paramètre une liste représentant un
arbre et qui
affiche à l'aide de print les valeurs rencontrées en parcourant dans l'ordre postfixe.On peut implémenter un arbre dans Python avec une classe de différentes façons.
Afin de faciliter les différentes corrections, nous utiliserons la même qui est celle-ci:
class ArbreBinaire:
def __init__(self,valeur):
self.valeur=valeur
self.gauche=None
self.droit=None
def ajout_gauche(self,valeur):
self.gauche=ArbreBinaire(valeur)
def ajout_droit(self,valeur):
self.droit=ArbreBinaire(valeur)
def suppr_gauche(self):
self.gauche=None
def suppr_droit(self):
self.droit=None
Insérer dans Python l'arbre suivant à l'aide de la classe vu précedemment.
En utilisant la classe ci-dessus, écrire en Python les fonctions suivantes: (on pourra mettre les précédentes fonctions écrites en commentaire pour ne pas avoir d'ambiguïté)
taille
prenant en paramètre un objet ArbreBinaire
hauteur
prenant en paramètre un objet ArbreBinaire
nombre_feuille
prenant en paramètres un objet ArbreBinaire
lecture_prefixe
qui prend comme paramètre un objet ArbreBinaire
et qui affiche à l'aide de
print les valeurs rencontrées en parcourant l'arbre dans l'ordre préfixe.lecture_infixe
qui prend comme paramètre un objet ArbreBinaire
et qui affiche à l'aide de
print les valeurs rencontrées en parcourant l'arbre dans l'ordre infixe.lecture_postfixe
qui prend comme paramètre un objet ArbreBinaire
et qui affiche à l'aide de
print les valeurs rencontrées en parcourant l'arbre dans l'ordre postfixe.Nous avons vu que nous pouvons parcourir un arbre en profondeur. Nous pouvons aussi le parcourir en largeur d'abord.
Il suffit d'évaluer les valeurs de chaque noeud niveau par niveau.
On considère l'arbre suivant:
Sur une feuille, écrire les valeurs des noeuds rencontrés par un parcours en largeur.
A copier dans le cahier.
fonction parcours_largeur(arbre):
si arbre vide:
On ne fait rien
sinon :
on place l'arbre dans une file.
tant que la file n'est pas vide:
on sort un element de la file
on affiche la valeur de la racine
on place le sous-arbre gauche dans la file s'il n'est pas vide
on place le sous-arbre droit dans la file s'il n'est pas vide
Le but est de programmer en Python la lecture en largeur d'abord comme ci-dessus
ArbreBinaire
et File
.Un arbre permet de stocker un calcul tout en précisant les priorités opératoires à effectuer.
On considère l'arbre suivant:
Il permet de stocker le calcul suivant : \( 3 \times 2 + \frac{60}{4+6}\)
A faire dans le cahier
On considère l'arbre de l'exemple précédent.
Certains langages de programmation ou même de calculatrice utilisent de telles notations pour calculer.
Par exemple le langage scheme utilise une notation en ligne d'un parcours préfixe d'un arbre contenant une expression mathématique.
On peut y trouver de telles notations : print(+ (- 3 2) 5)
La calculatrice HP utilise une notation postfixe
https://fr.wikipedia.org/wiki/HP-25Programmer une fonction Python evaluer
qui prend comme paramètre un objet ArbreBinaire
contenant une
expression mathématique et qui l'évalue.
class ArbreBinaire:
def __init__(self,valeur):
self.valeur=valeur
self.gauche=None
self.droit=None
def ajout_gauche(self,valeur):
self.gauche=Arbre_binaire(valeur)
def ajout_droit(self,valeur):
self.droit=Arbre_binaire(valeur)
def suppr_gauche(self):
self.gauche=None
def suppr_droit(self):
self.droit=None
arbre=ArbreBinaire("+")
arbre.ajout_gauche("*")
arbre.gauche.ajout_gauche(3)
arbre.gauche.ajout_droit(2)
arbre.ajout_droit("/")
arbre.droit.ajout_gauche(60)
arbre.droit.ajout_droit("+")
arbre.droit.droit.ajout_gauche(4)
arbre.droit.droit.ajout_droit(6)
def evaluer(arbre):
......
Une expression arithmétique ne comportant que les quatre opérations \( + \), \(-\), \( \times \) , \( \div \) peut être représentée sous forme d’arbre binaire. Les nœuds internes sont des opérateurs et les feuilles sont des nombres. Dans un tel arbre, la disposition des nœuds joue le rôle des parenthèses que nous connaissons bien.
En parcourant en profondeur infixe l’arbre binaire ci-dessus, on retrouve l’expression notée habituellement :
$$ ( 3 \times (8+7) )-(2+1) $$La classe Noeud
ci-après permet d’implémenter une
structure d’arbre binaire.
Compléter la fonction récursive expression_infixe
qui
prend en paramètre un objet de la classe Noeud
et qui
renvoie l’expression arithmétique représentée par l’arbre
binaire passé en paramètre, sous forme d’une chaîne de
caractères contenant des parenthèses.
e = Noeud(Noeud(Noeud(None, 3, None),
'*', Noeud(Noeud(None, 8, None), '+', Noeud(None, 7, None))),
'-', Noeud(Noeud(None, 2, None), '+', Noeud(None, 1, None)))
class Noeud:
'''
classe implementant un noeud d'arbre binaire
'''
def __init__(self, g, v, d):
'''
un objet Noeud possede 3 attributs :
- gauche : le sous-arbre gauche,
- valeur : la valeur de l'etiquette,
- droit : le sous-arbre droit.
'''
self.gauche = g
self.valeur = v
self.droit = d
def __str__(self):
'''
renvoie la representation du noeud en chaine de caracteres
'''
return str(self.valeur)
def est_une_feuille(self):
'''
renvoie True si et seulement si le noeud est une feuille
'''
return self.gauche is None and self.droit is None
def expression_infixe(e):
s = ...
if e.gauche is not None:
s = '(' + s + expression_infixe(...)
s = s + ...
if ... is not None:
s = s + ... + ...
return s
Résultat attendu avec l'arbre ci-dessus :
>>> e = Noeud(Noeud(Noeud(None, 3, None),
'*', Noeud(Noeud(None, 8, None), '+', Noeud(None, 7, None))),
'-', Noeud(Noeud(None, 2, None), '+', Noeud(None, 1, None)))
>>> expression_infixe(e)
'((3*(8+7))-(2+1))'
Un arbre binaire de caractères est stocké sous la forme d’un dictionnaire où les clés sont les caractères des nœuds de l’arbre et les valeurs, pour chaque clé, la liste des caractères des fils gauche et droit du nœud.
Par exemple, l’arbre
est stocké dans
a = {'F':['B','G'], 'B':['A','D'], 'A':['',''], 'D':['C','E'],'C':['',''], 'E':['',''], 'G':['','I'], 'I':['','H'], 'H':['','']}
Écrire une fonction récursive taille
prenant en paramètres un arbre binaire arbre
sous la forme d’un dictionnaire et un caractère lettre
qui est la valeur du sommet de
l’arbre, et qui renvoie la taille de l’arbre, à savoir le nombre total de nœud.
On observe que, par exemple, arbre[lettre][0]
, respectivement
arbre[lettre][1]
, permet d’atteindre la clé du sous-arbre gauche, respectivement
droit, de l’arbre arbre
de sommet lettre
.
Exemple :
>>> taille(a, 'F')
9
On considère les classes ci-dessous :
class Noeud:
def __init__(self, valeur):
self.valeur = valeur
self.gauche = None
self.droit = None
class Arbre_Binaire:
def __init__(self):
self.racine = None
On considère l'arbre suivant :
Il est par exemple stocké dans Python à l'aide du code ci-dessous :
# Creation des noeuds
noeud_f = Noeud("F")
noeud_b = Noeud("B")
noeud_c = Noeud("C")
noeud_d = Noeud("D")
noeud_e = Noeud("E")
# Creation de l'arbre et assignation des sous-arbres
arbre = Arbre_Binaire()
arbre.racine = noeud_f
# Assignation des enfants selon le graphe
noeud_f.gauche = noeud_b
noeud_f.droit = noeud_c
noeud_b.droit = noeud_d
noeud_d.gauche = noeud_e
valeur_de_la_racine(arbre: ArbreBinaire)
et qui renvoie un string associé à la valeur de la racine.valeur_enfant_droit(arbre: ArbreBinaire)
et qui renvoie un string associé à la valeur de l'enfant directement à droite de la racine.taille(arbre: ArbreBinaire)
et qui renvoie un integer associé à la taille de l'arbre.hauteur(arbre: ArbreBinaire)
et qui renvoie un integer associé à la hauteur de l'arbre. Ici, un arbre composé uniquement d'une racine aura pour hauteur 0 et l'arbre donné en exemple aura une hauteur de 3.liste_prefixe(arbre: ArbreBinaire)
et qui renverra une liste des valeurs des noeuds de l'arbre parcouru en profondeur dans l'ordre préfixe. Par exemple, l'arbre donné en exemple renverra ["F","B","D","E","C"]
.Il est moins usuel mais nous pouvons programmer un parcours préfixé en itératif à l'aide de l'algorithme suivant :
fonction parcours_prefixe_iteratif(arbre):
créer une pile p
insérer l'arbre dans la pile
tant que la pile p n'est pas vide
depiler un element de la pile que l'on nommera element
traiter la racine de cet element en l'affichant par exemple
si le sous-arbre droit n'est pas vide :
on empile dans p le sous-arbre droit
si le sous arbre gauche n'est pas vide :
on empile dans p le sous-arbre gauche
Coder ceci en python à l'aide des classes ArbreBinaire
et Pile
. On pourra utiliser un arbre des exercices précédents.