Arbres - Exercices - 2

Auteur : Gilles Lassus

Exercice 1

Exercice 2 du sujet 2022 Nouvelle-Caledonie J2

Correction 1.

C'est un arbre binaire car chaque nƓud possùde au maximum deux fils.

Correction 2.a

V est un dictionnaire.

Correction 2.b
🐍 Script Python
V['J']
Correction 2.c
🐍 Script Python
1
2
3
4
5
def somme(W):
    s = 0
    for cle in W:
        s += W[cle]
    return s
Correction 2.d
🐍 Script Python
1
2
3
4
5
6
7
def VMax(W):
    val_max = 0
    for cle in W:
        if W[cle] > val_max:
            val_max = W[cle]
            cle_max = cle
    return cle_max
Correction 3.

Cet algorithme calcule le nombre total de nƓuds de l'arbre, donc la taille de l'arbre. C'est un algorithme rĂ©cursif qui va renvoyer, si on n'est pas positionnĂ© sur un arbre vide, la valeur 1 (correspond au nƓud racine sur lequel on est positionnĂ©), plus la taille des deux sous-arbres gauche et droits.

Correction 4.a

Le parcours est A-B-C-E-D-F-G-I-H-J

Correction 4.b

C'est un parcours préfixe.

Exercice 2

2020, sujet 0

Question 1

DĂ©terminer la taille et la hauteur de l’arbre binaire suivant : image

Question 2

On dĂ©cide de numĂ©roter en binaire les nƓuds d’un arbre binaire de la façon suivante :

  • la racine correspond Ă  1 ;
  • la numĂ©rotation pour un fils gauche s’obtient en ajoutant le chiffre 0 Ă  droite au numĂ©ro de son pĂšre ;
  • la numĂ©rotation pour un fils droit s’obtient en ajoutant le chiffre 1 Ă  droite au numĂ©ro de son pĂšre ;

Par exemple, dans l’arbre ci-dessous, on a utilisĂ© ce procĂ©dĂ© pour numĂ©roter les nƓuds A, B, C, E et F .

image

  1. Dans l’exemple prĂ©cĂ©dent, quel est le numĂ©ro en binaire associĂ© au nƓud G ?
  2. Quel est le nƓud dont le numĂ©ro en binaire vaut 13 en dĂ©cimal ?
  3. En notant \(h\) la hauteur de l’arbre, sur combien de bits seront numĂ©rotĂ©s les nƓuds les plus en bas ?
  4. Justifier que pour tout arbre de hauteur \(h\) et de taille \(n \geqslant 2\), on a : \(h\leqslant n \leqslant 2^h-1\)

Question 3
Un arbre binaire est dit complet si tous les niveaux de l’arbre sont remplis. image

On dĂ©cide de reprĂ©senter un arbre binaire complet par un tableau de taille n + 1, oĂč n est la taille de l’arbre, de la façon suivante :

  • La racine a pour indice 1 ;
  • Le fils gauche du nƓud d’indice i a pour indice \(2 \times i\) ;
  • Le fils droit du nƓud d’indice i a pour indice \(2 \times i + 1\) ;
  • On place la taille \(n\) de l’arbre dans la case d’indice 0.

RĂ©pondre aux questions suivantes :

  1. DĂ©terminer le tableau qui reprĂ©sente l’arbre binaire complet de l’exemple prĂ©cĂ©dent.
  2. On considùre le pùre du nƓud d’indice \(i\) avec \(i \geqslant 2\). Quel est son indice dans le tableau ?

Question 4

On se place dans le cas particulier d’un arbre binaire de recherche complet oĂč les nƓuds contiennent des entiers et pour lequel la valeur de chaque noeud est supĂ©rieure Ă  celles des noeuds de son fils gauche, et infĂ©rieure Ă  celles des noeuds de son fils droit.

Écrire une fonction recherche ayant pour paramĂštres un arbre arbre et un Ă©lĂ©ment element. Cette fonction renvoie True si element est dans l’arbre et False sinon. L’arbre sera reprĂ©sentĂ© par un tableau comme dans la question prĂ©cĂ©dente.

corrigé

Q1 La taille est 9, la hauteur est 4.
Q2 1. G est associé à 1010.
Q2 2. 13 s'Ă©crit 1101 en binaire, c'est donc le nƓud I.
Q2 3. Les nƓuds les plus en bas sont notĂ©s sur \(h\) bits.
Q2 4. L'arbre de hauteur \(h\) de taille minimale est l'arbre filiforme, qui est de taille \(h\).
L'arbre de hauteur \(h\) de taille maximale est l'arbre complet, qui est de taille \(2^h-1\). Si \(n\) est la taille d'un arbre quelconque de taille \(h\), on a donc bien

\(h \leqslant n \leqslant 2^h-1\).

Q3 1. Tableau : [15, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O] .
Q3 2. Le pùre du nƓud d'indice i a pour indice i//2.

Q4 :

🐍 Script Python
def recherche(arbre, element):
    i = 1
    while i < len(arbre):
        if arbre[i] == element:
            return True
        if element < arbre[i]:
            i = 2*i # on se place sur le fils gauche
        else:
            i = 2*i +  1 # on se place sur le fils droit
    return False

Exercice 3

2021, MĂ©tropole sujet 1

Dans cet exercice, les arbres binaires de recherche ne peuvent pas comporter plusieurs fois la mĂȘme clĂ©. De plus, un arbre binaire de recherche limitĂ© Ă  un nƓud a une hauteur de 1. On considĂšre l’arbre binaire de recherche reprĂ©sentĂ© ci-dessous (figure 1), oĂč val reprĂ©sente un entier :

image

1.a Donner le nombre de feuilles de cet arbre et préciser leur valeur (étiquette).

1.b Donner le sous arbre-gauche du nƓud 23.

1.c Donner la hauteur et la taille de l’arbre.

1.d Donner les valeurs entiĂšres possibles de val pour cet arbre binaire de recherche.

On suppose, pour la suite de cet exercice, que val est Ă©gal Ă  16.

2. On rappelle qu’un parcours infixe depuis un nƓud consiste, dans l’ordre, à faire un parcours infixe sur le sous arbre-gauche, afficher le nƓud puis faire un parcours infixe sur le sous-arbre droit.
Dans le cas d’un parcours suffixe, on fait un parcours suffixe sur le sous-arbre gauche puis un parcours suffixe sur le sous-arbre droit, avant d’afficher le nƓud.

a. Donner les valeurs d’affichage des nƓuds dans le cas du parcours infixe de l’arbre.
b. Donner les valeurs d’affichage des nƓuds dans le cas du parcours suffixe de l’arbre.

3. On considÚre la classe Noeud définie de la façon suivante en Python :

image

a. ReprĂ©senter l’arbre construit suite Ă  l’exĂ©cution de l’instruction suivante :

🐍 Script Python
racine = Noeud(18)
racine.insere_tout([12, 13, 15, 16, 19, 21, 32, 23])
b. Écrire les deux instructions permettant de construire l’arbre de la figure 1. On rappelle que le nombre val est Ă©gal Ă  16.

c. On considĂšre l’arbre tel qu’il est prĂ©sentĂ© sur la figure 1. DĂ©terminer l’ordre d’exĂ©cution des blocs (repĂ©rĂ©s de 1 Ă  3) suite Ă  l’application de la mĂ©thode insere(19) au nƓud racine de cet arbre.

4. Écrire une mĂ©thode recherche(self, v) qui prend en argument un entier v et renvoie la valeur True si cet entier est une Ă©tiquette de l’arbre, False sinon.

corrigé

1.a. Il y a 4 feuilles, d'Ă©tiquette 12, val, 21 et 32.
1.b. Le sous-arbre gauche du nƓud 23 est 19-21.
1.c. La hauteur de l'arbre est 4. Sa taille est 9.
1.d. Les valeurs possibles de val sont 16 et 17.

2.a. Parcours infixe : 12-13-15-16-18-19-21-23-32
2.b. Parcours suffixe : 12-13-16-15-21-19-32-23-18

3.a. image

3.b.

🐍 Script Python
racine = Noeud(18)
racine.insere([15, 13, 12, 16, 23, 32, 19, 21])
(d'autres solutions sont possibles)

3.c. Bloc 3 - Bloc 2 - Bloc 1

4.

🐍 Script Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Noeud():
    def __init__(self, v):
        self.ag = None
        self.ad = None
        self.v = v

    def insere(self, v):
        n = self
        est_insere = False
        while not est_insere:
            if v == n.v:
                est_insere = True
            elif v < n.v:
                if n.ag != None:
                    n = n.ag
                else:
                    n.ag = Noeud(v)
                    est_insere = True
            else:
                if n.ad != None:
                    n = n.ad
                else:
                    n.ad = Noeud(v)
                    est_insere = True

    def insere_tout(self, vals):
        for v in vals:
            self.insere(v)

    def recherche(self, v):
        arbre = self
        while not arbre is None:
            if arbre.v == v:
                return True
            if v < arbre.v:
                arbre = arbre.ag
            else:
                arbre = arbre.ad
        return False


racine = Noeud(18)
racine.insere_tout([12, 13, 15, 14, 19, 21, 32, 23])
print(racine.recherche(149))
print(racine.recherche(12))
Exercice 4

2021, MĂ©tropole Candidats Libres 2

On rappelle qu’un arbre binaire est composĂ© de nƓuds, chacun des nƓuds possĂ©dant Ă©ventuellement un sous-arbre gauche et Ă©ventuellement un sous-arbre droit. Un nƓud sans sous-arbre est appelĂ© feuille. La taille d’un arbre est le nombre de nƓuds qu’il contient ; sa hauteur est le nombre de nƓuds du plus long chemin qui joint le nƓud racine Ă  l’une des feuilles. Ainsi la hauteur d’un arbre rĂ©duit Ă  un nƓud, c’est-Ă -dire la racine, est 1.

Dans un arbre binaire de recherche, chaque nƓud contient une clĂ©, ici un nombre entier, qui est :

  • strictement supĂ©rieure Ă  toutes les clĂ©s des nƓuds du sous-arbre gauche ;
  • strictement infĂ©rieure Ă  toutes les clĂ©s des nƓuds du sous-arbre droit.

Un arbre binaire de recherche est dit « bien construit » s’il n’existe pas d’arbre de hauteur infĂ©rieure qui pourrait contenir tous ses nƓuds.

On considùre l’arbre binaire de recherche ci-dessous.

image

1.a. Quelle est la taille de l’arbre ci-dessus ?

1.b. Quelle est la hauteur de l’arbre ci-dessus ?

corrigé

1.a. La taille de l'arbre est 7.
1.b. La hauteur de l'arbre est 4.

2. Cet arbre binaire de recherche n’est pas « bien construit ». Proposer un arbre binaire de recherche contenant les mĂȘmes clĂ©s et dont la hauteur est plus petite que celle de l’arbre initial.

corrigé

2. image

3. Les classes Noeud et Arbre ci-dessous permettent de mettre en Ɠuvre en Python la structure d’arbre binaire de recherche. La mĂ©thode insere permet d’insĂ©rer rĂ©cursivement une nouvelle clĂ©.

🐍 Script Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Noeud :

    def __init__(self, cle):
        self.cle = cle
        self.gauche = None
        self.droit = None

    def insere(self, cle):
        if cle < self.cle :
            if self.gauche == None :
                self.gauche = Noeud(cle)
            else :
                self.gauche.insere(cle)
        elif cle > self.cle :
            if self.droit == None :
                self.droit = Noeud(cle)
            else :
                self.droit.insere(cle)

class Arbre :

    def __init__(self, cle):
        self.racine = Noeud(cle)

    def insere(self, cle):
        self.racine.insere(cle)

Donner la reprĂ©sentation de l’arbre codĂ© par les instructions ci-dessous.

🐍 Script Python
a = Arbre(10)
a.insere(20)
a.insere(15)
a.insere(12)
a.insere(8)
a.insere(4)
a.insere(5)
corrigé

3. image

4. Pour calculer la hauteur d’un arbre non vide, on a Ă©crit la mĂ©thode ci-dessous dans la classe Noeud.

🐍 Script Python
def hauteur(self):
    if self.gauche == None and self.droit == None:
        return 1
    if self.gauche == None:
        return 1 + self.droit.hauteur()
    elif self.droit == None:
        return 1 + self.gauche.hauteur()
    else:
        hg = self.gauche.hauteur()
        hd = self.droit.hauteur()
        if hg > hd:
            return hg + 1
        else:
            return hd + 1
Écrire la mĂ©thode hauteur de la classe Arbre qui renvoie la hauteur de l’arbre.

corrigé

4.

🐍 Script Python
1
2
def hauteur(self):
    return self.racine.hauteur()

5. Écrire les mĂ©thodes taille des classes Noeud et Arbre permettant de calculer la taille d’un arbre.

corrigé

5. MĂ©thode taille de la classe Noeud :

🐍 Script Python
1
2
3
4
5
6
7
8
9
def taille(self):
    if self.gauche is None and self.droit is None:
        return 1
    elif self.gauche is None:
        return 1 + self.droit.taille()
    elif self.droit is None:
        return 1 + self.gauche.taille()
    else:
        return 1 + self.gauche.taille() + self.droit.taille()
MĂ©thode taille de la classe Arbre :
🐍 Script Python
1
2
def taille(self):
    return self.racine.taille()

6. On souhaite Ă©crire une mĂ©thode bien_construit de la classe Arbre qui renvoie la valeur True si l’arbre est « bien construit » et False sinon.

On rappelle que la taille maximale d’un arbre binaire de recherche de hauteur \(ℎ\) est \(2^h - 1\).

6.a Quelle est la taille minimale, notĂ©e min d’un arbre binaire de recherche « bien construit » de hauteur \(ℎ\) ?

corrigé

6.a. La configuration minimale d'un arbre bien construit de hauteur \(h\) peut ĂȘtre :

image

La taille minimale min est donc Ă©gale Ă  \(2^{h-1}\).

6.b Écrire la mĂ©thode bien_construit demandĂ©e.

corrigé

6.b. Intuitivement, un arbre est mal construit si sa hauteur est trop grande par rapport à sa taille (trop étiré).

Donc un arbre est mal construit si sa taille est trop petite par rapport Ă  sa hauteur.

Donc un arbre de taille \(t\) et de hauteur \(h\) est mal construit si \(t < 2^{h-1}\), puisqu'on a démontré que \(2^{h-1}\) était la taille minimale.

Pour tester si un arbre est bien construit, on va donc juste vérifier que \(t \geqslant 2^{h-1}\) :

🐍 Script Python
1
2
3
def bien_construit(self):
    h = self.taille()
    return self.taille() >= 2**(h-1)
Exercice 5

2021, Polynésie

Cet exercice traite principalement du thÚme « algorithmique, langages et programmation » et en particulier les arbres binaires de recherche. La premiÚre partie aborde les arbres en mode débranché via l'application d'un algorithme sur un exemple. La suivante porte sur la programmation orientée objet. La derniÚre partie fait le lien avec les algorithmes de tri.

Partie A : Étude d'un exemple

Considérons l'arbre binaire de recherche ci-dessous :

image

Q1. Indiquer quelle valeur a le nƓud racine et quels sont les fils de ce nƓud.

corrigé

Le nƓud racine est 5 et ses fils sont 2 et 7.

Q2. Indiquer quels sont les nƓuds de la branche qui se termine par la feuille qui a pour valeur 3.

corrigé

La branche qui se termine par la feuille 3 a pour nƓuds 5, 2 et 3.

Q3. Dessiner l’arbre obtenu aprùs l’ajout de la valeur 6.

corrigé

image

Partie B : Implémentation en Python

Voici un extrait d’une implĂ©mentation en Python d'une classe modĂ©lisant un arbre binaire de recherche.

🐍 Script Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ABR:
    """ImplĂ©mentation d’un arbre binaire de recherche (ABR)"""
    def __init__(self, valeur=None):
        self.valeur = valeur
        self.fg = None
        self.fd = None

    def estVide(self):
        return self.valeur == None

    def insererElement(self, e):
        if self.estVide():
            self.valeur = e
        else:
            if e < self.valeur:
                if self.fg:
                    self.fg.insererElement(e)
                else:
                    self.fg = ABR(e)
            if e > self.valeur:
                if self.fd:
                    self.fd.insererElement(e)
                else:
                    self.fd = ABR(e)

Q1. Expliquer le rĂŽle de la fonction __init__.

corrigé

La fonction __init__ est appelée «méthode constructeur», c'est elle qui crée l'objet et le dote de tous les attributs nécessaires.

Q2. Dans cette implĂ©mentation, expliquer ce qui se passe si on ajoute un Ă©lĂ©ment dĂ©jĂ  prĂ©sent dans l’arbre.

corrigé

Si on ajoute un élément déjà présent dans l'arbre, la valeur e sera égale à self.valeur (éventuellement aprÚs quelques appels récursifs). Or ce cas d'égalité n'est pas prévu par les tests : il ne se passera donc RIEN. Ceci est le comportement souhaité puisqu'on ne veut pas avoir deux valeurs identiques dans notre ABR, ainsi qu'il est rappelé au début de l'énoncé.

Q3. Recopier et complĂ©ter les pointillĂ©s ci-dessous permettant de crĂ©er l’arbre de la partie A.

🐍 Script Python
arbre = ABR(.......... )
arbre.insererElement(2)
arbre.insererElement(.......... )
arbre.insererElement(7)
arbre.insererElement(.......... )

corrigé
🐍 Script Python
arbre = ABR(5)
arbre.insererElement(2)
arbre.insererElement(3)
arbre.insererElement(7)
arbre.insererElement(8)

Partie C : Tri par arbre binaire de recherche

On souhaite trier un ensemble de valeurs entiĂšres distinctes grĂące Ă  un arbre binaire de recherche. Pour cela, on ajoute un Ă  un les Ă©lĂ©ments de l’ensemble dans un arbre initialement vide. Il ne reste plus qu’à parcourir l’arbre afin de lire et de stocker dans un tableau rĂ©sultat les valeurs dans l’ordre croissant.

Q1. Donner le nom du parcours qui permet de visiter les valeurs d’un arbre binaire de recherche dans l’ordre croissant.

corrigé

Le parcours qui permet de visiter les valeurs d'un ABR dans l'ordre croissant est le parcours infixe.

Q2. Comparer la complexité de cette méthode de tri avec celle du tri par insertion ou du tri par sélection.

corrigé

question difficile Pour créer l'ABR, il faut d'abord insérer chacune des valeurs. La fonction insertion reposant sur une division par 2 à chaque étape de la taille de l'espace de recherche, on peut dire qu'elle a une complexité logarithmique. Mais cette opération est à effectuer autant de fois qu'il y a d'éléments à insérer : il faut donc multiplier la complexité logarithmique par n, ce qui fera donc une complexité en \(n \log(n)\). L'algorithme de parcours infixe est lui aussi linéraire, ce qui ne change pas la complexité totale. Cette complexité est meilleure que le tris par insertion ou sélection, qui sont de complexité quadratique.

Exercice 6

2021, Centres Étrangers, sujet 1

Un arbre binaire est soit vide, soit un nƓud qui a une valeur et au plus deux fils (le sous-arbre gauche et le sous-arbre droit).

image

  • X est un nƓud, sa valeur est X.valeur
  • G1 est le fils gauche de X, notĂ© X.fils_gauche
  • D1 est le fils droit de X, notĂ© X.fils_droit

Un arbre binaire de recherche est ordonné de la maniÚre suivante :

Pour chaque nƓud X,

  • les valeurs de tous les nƓuds du sous-arbre gauche sont strictement infĂ©rieures Ă  la valeur du nƓud X
  • les valeurs de tous les nƓuds du sous-arbre droit sont supĂ©rieures ou Ă©gales Ă  la valeur du nƓud X.

Ainsi, par exemple, toutes les valeurs des nƓuds G1, G2 et G3 sont strictement infĂ©rieures Ă  la valeur du nƓud X et toutes les valeurs des nƓuds D1, D2 et D3 sont supĂ©rieures ou Ă©gales Ă  la valeur du nƓud X.

Voici un exemple d'arbre binaire de recherche dans lequel on a stocké dans cet ordre les valeurs : [26, 3, 42, 15, 29, 19, 13, 1, 32, 37, 30]

L'Ă©tiquette d'un nƓud indique la valeur du nƓud suivie du nom du nƓud. Les nƓuds ont Ă©tĂ© nommĂ©s dans l'ordre de leur insertion dans l'arbre ci-dessous.

'29, noeud04' signifie que le nƓud nommĂ© noeud04 possĂšde la valeur 29.

image

Q1. On insĂšre la valeur 25 dans l'arbre, dans un nouveau nƓud nommĂ© nƓud11.

Recopier l'arbre binaire de recherche étudié et placer la valeur 25 sur cet arbre en coloriant en rouge le chemin parcouru.

PrĂ©ciser sous quel nƓud la valeur 25 sera insĂ©rĂ©e et si elle est insĂ©rĂ©e en fils gauche ou en fils droit, et expliquer toutes les Ă©tapes de la dĂ©cision.

Correction

image

25 Ă©tant plus petit que 26, on part dans son sous-arbre gauche.
25 Ă©tant plus grand que 3, on part dans son sous-arbre droit.
25 Ă©tant plus grand que 15, on part dans son sous-arbre droit.
25 Ă©tant plus grand que 19, on insĂšre 25 en tant que fils droit de 19.

Q2. PrĂ©ciser toutes les valeurs entiĂšres que l’on peut stocker dans le nƓud fils gauche du nƓud04 (vide pour l'instant), en respectant les rĂšgles sur les arbres binaires de recherche.

Correction

Les valeurs acceptables doivent ĂȘtre strictement infĂ©rieures Ă  29, et supĂ©rieures ou Ă©gales Ă  26. Ces valeurs sont donc : 26, 27 et 28.

Q3. Voici un algorithme récursif permettant de parcourir et d'afficher les valeurs de l'arbre :

🐍 Script Python
Parcours(A)  # A est un arbre binaire de recherche
    Afficher(A.valeur)
    Parcours(A.fils_gauche)
    Parcours(A.fils_droit)

Q3.a. Écrire la liste de toutes les valeurs dans l'ordre oĂč elles seront affichĂ©es.

Correction

Les valeurs seront affichées dans l'ordre suivant : 26-3-1-15-13-19-25-42-29-32-30-37

Q3.b. Choisir le type de parcours d'arbres binaires de recherche réalisé parmi les propositions suivantes : Préfixe, Suffixe ou Infixe.

Correction

On reconnait un parcours préfixe.

Q4. En vous inspirant de l’algorithme prĂ©cĂ©dent, Ă©crire un algorithme Parcours2 permettant de parcourir et d'afficher les valeurs de l'arbre A dans l'ordre croissant.

Correction

Pour afficher les valeurs d'un ABR dans un ordre croissant, il faut utiliser un parcours infixe. Un algorithme rĂ©cursif de parcours infixe peut ĂȘtre celui-ci:

🐍 Script Python
Parcours2(A)  # A est un arbre binaire de recherche
    Parcours2(A.fils_gauche)
    Afficher(A.valeur)
    Parcours2(A.fils_droit)