Académique Documents
Professionnel Documents
Culture Documents
binaire
I. Constructeur par défaut d’arbre binaire
Lorsqu’une variable de type arbre binaire est déclarée dans un programme, par exemple de la
manière suivante
ArbreBinaire A;
le pointeur racine de A n’est pas initialisé et pointe donc n’importe où en mémoire. Ceci est
dangereux et doit absolument être évité. Nous souhaitons faire en sorte que lors de la déclaration
d’une variable de type ArbreBinaire son pointeur racine soit automatiquement initialisé à un
pointeur nul, c'est-à-dire que l’arbre soit initialisé à un arbre vide.
Il faut pour cela écrire ce qu’on appelle un constructeur d’arbre binaire. C’est une fonction qui sera
appelée automatiquement dès qu’un arbre binaire sera créé. Rappelons que le nom d’un
constructeur est simplement le nom de la classe.
ArbreBinaire::ArbreBinaire()
{
racine = nullptr;
}
On a plutôt pour habitude d’initialiser les champs d’un objet (comme ici le champ racine) dans ce
qu’on appelle la liste d’initialisation du constructeur. Cette liste se met directement à la suite de
l’entête du constructeur de la manière suivante :
ArbreBinaire::ArbreBinaire() : racine{nullptr}
{}
Il faut également penser à ajouter l’entête de ce constructeur dans la partie publique de la classe
ArbreBinaire (voir la section VI).
Noeud N{10};
Il faut pour cela écrire un constructeur de nœud qui prend un entier en paramètre. Ce constructeur
s’écrit de la manière suivante :
Comme pour toute autre fonction, on peut écrire différents constructeurs de nœuds à condition que
leurs listes de paramètres soient différentes. Si on n’écrit pas de constructeur de nœud par défaut
(c’est-à-dire un constructeur sans paramètre), il n’est plus possible de créer un nœud sans préciser sa
valeur. Par exemple, la déclaration
Noeud N;
L’entête du constructeur de nœud doit également être ajouté dans la classe Noeud. Nous choisissons
de placer cet entête dans la partie privée de la classe Noeud. On peut en effet considérer que
l’utilisateur de la classe ArbreBinaire n’a pas de raison de créer lui-même des nœuds. Pour lui, l’arbre
binaire est une structure de données dans laquelle il veut insérer des valeurs, en l’occurrence des
entiers. Il conviendra donc de lui fournir une fonction qui permet d’insérer un entier dans un arbre.
On souhaite pouvoir faire la même chose avec des arbres binaires, de la manière suivante :
ArbreBinaire A;
… // initialisation de A
ArbreBinaire B=A; // équivalent à ArbreBinaire B{A};
La construction d’un arbre à partir d’un arbre qui existe déjà se fait grâce au constructeur par
recopie d’arbre binaire. Si nous n’écrivons pas nous même ce constructeur par recopie, il est généré
automatiquement et effectue une simple copie champ par champ. Or, un objet ArbreBinaire ne
contient qu’un seul champ, le champ racine. Le champ racine de A est donc recopié dans le champ
racine de B, ce qui a pour effet de faire pointer le pointeur racine de B au même endroit que là où
pointe le pointeur racine de A (voir la figure qui suit).
Décomposition
Recopier un arbre A dans un arbre B revient à recopier la racine de A dans la racine de B, à recopier le
fils gauche de A dans le fils gauche de B, et à recopier le fils droit de A dans le fils droit de B.
Cas de base
Si A est vide, B doit aussi être vide.
Comme la fonction qui implémente cette définition va devoir être appelée récursivement sur des
sous-arbres, elle va devoir avoir en paramètre un pointeur rA sur la racine de A et un pointeur rB sur
la racine de B.
Recopier la racine de A dans la racine de B signifie qu’il faut créer en mémoire un nouveau nœud qui
contiendra la même information que la racine de A. Une manière de créer ce nœud consiste à
déclarer une variable de type Noeud de la manière suivante :
Noeud N{rA->info};
new Noeud{rA->info}
Remarquez qu’un nœud créé de cette manière n’a pas de nom. En fait l’opérateur new renvoie
l’adresse où le nœud a été créé en mémoire. Cette adresse doit être récupérée dans un pointeur qui
pointera alors sur le nœud créé.
D’où la fonction récursive qui recopie le sous arbre de racine rA dans le sous arbre de racine rB.
Attention
Le pointeur rB est modifié par la fonction afin qu’il pointe sur le nouveau nœud créé. Il doit donc être
passé par référence à la fonction (le & dans l’entête de la fonction).
Lorsque le nœud est créé par l’instruction new Noeud{rA->info}, le constructeur de nœud que
nous avons écrit dans la section précédente est automatiquement appelé. Or, ce constructeur
initialise les pointeurs fg et fd du nœud à des pointeurs nuls. Lorsque la fonction copie est appelée
récursivement, les pointeurs rB->fg et rB->fd qui lui sont passés en paramètre sont donc des
pointeurs nuls. L’instruction rB = nullptr est alors inutile, à condition de supposer que le
pointeur rB initial est aussi un pointeur nul.
void f()
{
ArbreBinaire A;
… // création des noeuds de l’arbre A
… // manipulation de l’arbre
}
La variable A est une variable locale de la fonction f. Lorsque la fonction s’arrête cette variable est
automatiquement supprimée de la mémoire. En revanche, comme les nœuds de l’arbre ont été
alloués dynamiquement, ils ne sont pas supprimés. Pour que la suppression des nœuds de l’arbre A
soit faite automatiquement au moment où la variable A est supprimée de la mémoire, il faut écrire
un destructeur d’arbre binaire. C’est une fonction qui est appelée automatiquement dès qu’un objet
de type ArbreBinaire disparait de la mémoire.
Décomposition
Détruire les nœuds d’un arbre binaire revient à détruire les nœuds de ses deux sous arbres puis à
détruire sa racine.
Cas de base
Si l’arbre est vide, il n’y a rien à détruire.
L’instruction delete r détruit ce qui est pointé par r. Dans notre cas, elle détruit le nœud pointé
par r.
ArbreBinaire::~ArbreBinaire()
{
detruit(racine);
}
ArbreBinaire A,B;
… // création des noeuds des arbres A et B
… // manipulation des arbres
B=A;
Il faut pour cela écrire un opérateur = pour les arbres binaires. On dit qu’on surcharge l’opérateur
d’affectation (en effet, l’opérateur = est déjà défini sur d’autres types de variables tels que les
entiers). La différence fondamentale avec le constructeur par recopie vu dans la section III est que
l’arbre B existe déjà au moment où on écrit B=A. L’arbre B contient donc éventuellement déjà des
nœuds. Ces nœuds doivent être détruits avant de recopier A dans B.
Pour recopier un arbre A dans un arbre B il suffit de créer un arbre auxiliaire Aux par recopie à partir
de l’arbre A, puis d’échanger les pointeurs racine de Aux et de B.
Comme Aux est une variable locale de la fonction, Aux est détruit lorsque la fonction s’arrête.
Comme à ce moment-là l’arbre Aux contient les nœuds qui étaient auparavant dans l’arbre B, ces
nœuds sont détruits par le destructeur d’arbre qui est appelé automatiquement sur Aux.
La fonction qui permet de surcharger l’opérateur d’affectation doit s’appeler operator=. Dans son
entête apparaissent trois arbres. L’arbre auquel la méthode est appliquée (celui qui est avant les ::)
correspond à l’arbre qui sera à gauche du signe =. L’arbre qui est dans la parenthèse correspond à
private:
// Données privées de la classe
Noeud *racine; // un pointeur sur la racine