Arbres binaires


Partie 1: description et parcours d'un arbre


On vous donne l'arbre suivant:


figure1


Question 1.1 


Quelle est la taille de cet arbre? Quel est sa hauteur h si la racine est de profondeur 1?

Citer le nombre de feuilles de cet arbre.

Cet arbre est-il équilibré?

Quelle est la profondeur du noeud 5?

Compte tenu de la hauteur h de l'arbre, quel est le nombre maximal N de noeuds qu'aurait pu avoir cette arbre? Exprimer la relation entre h et N


Question 1.2

On décide de coder cet arbre à partir d'objets nœuds décrits dans la classe suivante:


class Nœud:

    def __init__(self, g,v,d):

        self.gauche = g

        self.valeur = v

        self.droite = d


Coder l'arbre de la figure 1 à partir d'objets nœuds.



Question 1.3


Ecrire les fonctions python  calcule_taille(arbre)  et  mesure_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.1




A)  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:  la valeur 4 (sans fils à gauche) est affichée la première fois qu'elle est rencontrée soit à l'étape 2 du parcours, la valeur 2 qui a un fils à gauche sera affichée,  la seconde fois qu'on le parcourt, au retour de l'étape 2 soit à l'étape 3. Pour l'arbre ci-dessous, on obtiendra l'ordre d'affichage: 427581396


figure 2



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)


Question 1.4 


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 de  if arbre is None ?  




B)  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: 124578369 pour l'arbre étudié.



pseudo code:


ParcoursPréfixe(arbre_binaire):

    si arbre_binaire est vide:

        retourner

Afficher valeur

ParcoursPréfixe( sous arbre_binaire gauche)

ParcoursPréfixe( sous arbre_binaire droit)


Question 1.5


Coder en python le pseudo code du parcours préfixe et tester le code.




C)  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: 478529631 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


Question 1.6


Coder en python le pseudo code du parcours postfixe et tester le code.



C)  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


Algorithme python:


# 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, cellule_suivante) :

        self.valeur=valeur

        self.suite_liste = cellule_suivante


# classe File permet de construire une file pour accueillir les nœuds de l'arbre

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



# la fonction de parcours en largeur

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)



# le programme principal

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)

     

Question 1.7


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.


Partie 2:  implémentation d'un arbre sous forme d'un dictionnaire

Il est également possible de coder un arbre avec la structure dictionnaire étudiée en première. Voici une fonction qui renvoie un dictionnaire qui représente le noeud d'un arbre:


def noeud(nom, fg = None, fd = None) :

    return {'racine': nom, 'fg' : fg, 'fd': fd}


# création d'un noeud qui contient la valeur 2

k = noeud(2)   # k = { 'racine':2, 'fg' : None, 'fd': None }


# visite du noeud:

print( k['racine'])   # affichera 2


# Ajout d'un nœud parent m au nœud k

m = noeud(3, k, None)   # m = { 'racine':3, 'fg' : { 'racine':2, 'fg' : None, 'fd': None }, 'fd': None }



Question 2.1


Compléter l'exemple ci-dessus pour obtenir l'arbre ci-dessous:



Question 2.2

Tester votre code en mesurant la taille de votre arbre avec le code ci-dessous:


def taille(arbre):

        """ nombre de noeuds de l'arbre"""

        if arbre is None:

            return 0

        else:

            return 1 + taille(arbre['fg']) + taille(arbre['fd'])




Question 2.3


En vous inspirant de la mesure de la taille ci-dessus et de l'implémentation objet de la mesure de la hauteur vu en cours: écrire le code qui permet de mesurer la hauteur d'un arbre conçu avec un dictionnaire.




Partie 3:  arbre de recherche ( ABR)






Question 3.1


Quelle propriété possède un ABR que l'on ne retrouve pas sur l'arbre binaire étudié en partie 1?



Question 3.2 


Quelle est la taille de cet arbre? Quel est sa hauteur?

Citer le nombre de feuilles de cet arbre.



Question 3.3


Coder cet arbre en utilisant un dictionnaire  pour chaque noeud de l'arbre.


Question 3.4:  appartenance à un arbre


Écrire une fonction qui permette de déterminer si un nombre appartient ou pas à l'arbre.



Question 3.5:  ajout d'une valeur dans un ABR


Ecrire une fonction qui permette d'ajouter un nombre à l'arbre en respectant la propriété d'un ABR. Tester ce code en ajoutant la valeur 5, redessiner l'arbre avec le nœud qui porte la valeur 5.


Projet


Voir la partie projet des activités ( encadré ci-dessous) :


Créé avec HelpNDoc Personal Edition: Produire des livres Kindle gratuitement