Nous avons vu de façon générale, les arbres dans la partie précédente. Nous allons restreindre un peu notre champ d'action en nous consacrant maintenant uniquement aux arbres binaires.
Les arbres binaires sont des arbres de degré 2, donc :
Comme on peut le remarquer dans la définition ci-dessus, la structure des arbres ( et en particulier des arbres binaires ) est récursive !
Et oui un arbre binaire, c'est :
Avant de continuer notre réflexion sur les arbres binaires, nous allons tenter de comprendre pourquoi cette structure hiérarchique est intéressante.
L'arbre ci-dessous est un arbre binaire un peu particulier car il est complet, c'est à dire que les nœuds de cet arbre ont tous exactement un fils gauche et un fils droit, sauf les feuilles bien entendu...
Vous avez déjà implémenté ou mis en œuvre de nombreux types abstraits de données linéaires : piles, files, listes...
L'arbre binaire n'est pas un type linéaire, mais il est tout à fait possible de l'implémenter en python.
Dans certains cas il est intéressant d'implémenter un arbre dans une liste Python. Cependant dans cette situation, il n'est pas forcément facile de trouver les fils d'un nœud,
et on perd l'intérêt de la structure récursive.
Nous choisirons une implémentation en POO qui ressemble à celle utilisée pour implémenter les listes chaînées :
class Arbre:
def __init__(self, valeur = None, filsgauche = None, filsdroit = None):
self.valeur = valeur
self.filsgauche = filsgauche
self.filsdroit = filsdroit
On notera bien que, dans cette classe :
Arbre
, enfants de la racine ou du nœud-racine du sous-arbreself.valeur = None
self.filsdroit = None
Afin de faciliter votre travail, une méthode d'affichage des arbres binaires a déjà été implémentée. Vous n'aurez donc, pour utiliser cette nouvelle classe, qu'a télécharger le fichier classe_arbre.py à l'enregistrer et à l'importer en début de script.
Vous pouvez utiliser ce module aussi bien avec Pyzo qu'en ligne dans les éditeurs.
Charger un fichier
) pour qu'il soit ensuite disponible pour
les scripts. Importer également le module de dessin Matplotlib ( bouton Importer un module
)Pour comprendre le fonctionnement de la classe Arbre
, construisez et affichez l'arbre hetre ci-contre.
L'affichage se fait grâce à la méthode dessine()
de la classe.
Nous avons vu dans la première partie qu'il était possible de coder une expression arithmétique dans un arbre binaire. En effet l'arithmétique correspond à la combinaison d'un opérateur (+, -, x, /) et de deux opérandes (entiers, réels...).
Par exemple l'arbre arithmétique ci-contre représente l'expression (x+1)(3y+2).
Construisez cet arbre grâce à la classe Arbre
.
L'intérêt de construire un tel arbre à partir d'une expression arithmétique est multiple :
str
et que si l'on connaît les valeur de x et de y il n'est pas simple de calculer sa valeur).Vous trouverez ci-dessous l'algorithme (récursif) qui permet de calculer la valeur d'une expression arithmétique contenue dans un arbre binaire :
méthode calcule(arbre arithmétique)
entrée : arbre binaire arithmétique
sortie : valeur de l'expression arithmétique
Si l'arbre est une feuille alors :
renvoyer valeur de la feuille
Sinon si l'étiquette du nœud est '+' alors:
renvoyer calcule(filsgauche) + calcule(filsdroit)
Sinon si l'étiquette du nœud est '-' alors:
renvoyer calcule(filsgauche) - calcule(filsdroit)
Sinon si l'étiquette du nœud est '*' alors:
renvoyer calcule(filsgauche) * calcule(filsdroit)
Sinon si l'étiquette du nœud est '/' alors:
renvoyer calcule(filsgauche) / calcule(filsdroit)
Vous devez coder une méthode calcule(self)
qui permet d'évaluer la valeur d'une expression arithmétique présentée sous forme d'arbre binaire arithmétique.
Vous testerez cette méthode sur l'arbre codé précédemment, en donnant au préalable des valeurs aux variables x et y ( attention alors lors de la construction de l'arbre ! ).
Mais vous ne pouvez pas "compléter" la classe Arbre
présente dans le fichier classe_arbre.py
que vous avez importé...
Vous allez en fait créer une sous classe Arbrearith
qui héritera de toutes les propriétés de la classe Arbre
.
Concrètement (l'héritage n'est pas au programme de la TNSI, mais ce n'est pas compliqué...), vous allez créer une nouvelle classe à partir de la classe Arbre
, qui disposera de ses méthodes mais
qui leur rajoutera ses propres fonctionnalités :
class Arbrearith(Arbre):
''' La classe Arbrearith hérite des méthodes de Arbre '''
def calcule(self): # mais elle implémente aussi ses propres fonctionnalités
...
Le constructeur d'Arebrearith
est celui de la classe Arbre
, et la méthode dessine
peut également être utilisée.
Même si le morse est un langage binaire un peu démodé, il peut être intéressant de se pencher sur la possibilité d'un décodage rapide de tel message pour voir l’intérêt des arbres binaires.
Nous baserons notre travail sur le dictionnaire de morse ci-dessous :
dicomorse = { 'A':'.-', 'B':'-...','C':'-.-.', 'D':'-..', 'E':'.','F':'..-.', 'G':'--.', 'H':'....','I':'..','J':'.---','K':'-.-','L':'.-..', 'M':'--', 'N':'-.','O':'---', 'P':'.--.', 'Q':'--.-','R':'.-.', 'S':'...','T':'-','U':'..-', 'V':'...-', 'W':'.--','X':'-..-', 'Y':'-.--', 'Z':'--..','1':'.----', '2':'..---', '3':'...--','4':'....-', '5':'.....', '6':'-....','7':'--...', '8':'---..', '9':'----.','0':'-----', ', ':'--..--', '.':'.-.-.-','?':'..--..', '/':'-..-.', '-':'-....-','(':'-.--.', ')':'-.--.-',' ':'.......'}
La première chose à faire est de transformer ce dictionnaire en arbre binaire.
La règle de construction pour une lettre sera la suivante :
On doit ainsi obtenir l'arbre suivant :
Vous allez maintenant devoir compléter la fonction creer_arbre_morse
ci-dessous, qui permet de créer cet arbre binaire.
Pour vous faciliter cette tache de création, vous pourrez utiliser les deux méthodes de la classe Arbre
:
insertion_gauche(valeur): # permet d'insérer un nœud-fils à gauche du nœud courant, en précisant son étiquette
insertion_droite(valeur): # idem à droite
Vous vérifierez que votre fonction est valide en affichant l'arbre ainsi construit.
Une fois cet arbre construit, nous allons l'utiliser pour décoder un message en morse.
Pour décoder un message en morse, il est normalement nécessaire de parcourir tout le dictionnaire de morse pour trouver le caractère qui correspond au code reçu.
Il est donc nécessaire, au pire, de faire 43 comparaisons ( = le nombre de caractères présents dans notre dictionnaire de morse ).
Pour trouver un caractère dans l'arbre que vous venez de construire, il suffit au contraire de suivre le "bon" chemin. Dans le pire des cas on doit faire 6 comparaison !
On voit ainsi le gain qu'il y a si on cherche ensuite "souvent" à décoder un code morse.
Mais bien sur ici le morse est une excuse pour voir plus loin 😏...
Imaginons une structure de données basée sur le même principe que l'arbre morse.
Si l'on fait une recherche sans l'arbre dans n caractères, la complexité sera en O(n).
Quelle sera la complexité de la recherche dans l'arbre en fonction de n ?
Maintenant que vous maîtrisez le concept, il ne vous reste plus qu'à l'utiliser pour décoder un message en morse.
La docstring de la fonction à compléter est présentée ci-dessous, avec les annotations de type.
A vous de la coder complètement.
Utiliser ensuite votre fonction :
".--. .- ... ... .. --- -. -. .- -. - ....... .-.. .- ....... -. ... .. "
Vous pourrez utiliser la méthode de chaîne str.split(' ')
, qui renvoie la liste Python des "mots" d'une chaîne en la découpant selon le caractère ' '
( espace ).