Arbres binaires¶
On va considérer le type suivant pour des arbres binaires en C :
struct noeud {
struct noeud *gauche;
struct noeud *droite;
int valeur;
};
typedef struct noeud arbre;
L'arbre vide est alors représenté par la valeur NULL, et un arbre est
manipulé par un pointeur arbre * vers sa racine, alloué sur le tas.
Pour construire des arbres de test, on pourra utiliser la fonction suivante :
arbre *noeud(arbre *g, int v, arbre *d)
{
arbre *a = malloc(sizeof(arbre));
a->gauche = g;
a->valeur = v;
a->droite = d;
return a;
}
Par exemple, l'arbre de racine 5, de fils gauche 1 (lui-même de fils droit 4) et de fils droit 9 (de fils 7 et 20) s'obtient avec :
arbre *a = noeud(noeud(NULL, 1, noeud(NULL, 4, NULL)), 5,
noeud(noeud(NULL, 7, NULL), 9, noeud(NULL, 20, NULL)));
Question 1
Écrire une fonction de prototype
int taille(arbre *a);
qui renvoie la taille d'un arbre binaire, c'est-à-dire son nombre de nœuds.
Corrigé
int taille(arbre *a)
{
if (a == NULL)
return 0;
return 1 + taille(a->gauche) + taille(a->droite);
}
Question 2
Écrire une fonction de prototype
int hauteur(arbre *a);
qui renvoie la hauteur d'un arbre binaire.
Corrigé
Avec la convention du cours, l'arbre vide est de hauteur \(-1\) (une feuille est donc de hauteur 0).
int max(int a, int b)
{
return a > b ? a : b;
}
int hauteur(arbre *a)
{
if (a == NULL)
return -1;
return 1 + max(hauteur(a->gauche), hauteur(a->droite));
}
Sur l'exemple, on obtient une taille de 6 et une hauteur de 2.
Question 3
Écrire une fonction de prototype
arbre *parfait(int n);
qui renvoie un arbre binaire parfait de hauteur n. On mettra des
valeurs 1 comme étiquettes partout pour simplifier.
Corrigé
Un arbre parfait de hauteur \(n \ge 0\) a pour sous-arbres deux arbres parfaits de hauteur \(n-1\) ; l'arbre parfait de hauteur \(-1\) est l'arbre vide.
arbre *parfait(int n)
{
if (n == -1)
return NULL;
return noeud(parfait(n - 1), 1, parfait(n - 1));
}
On vérifie par récurrence que parfait(n) a \(2^{n+1} - 1\) nœuds. Les deux
appels récursifs sont nécessaires : avec un seul appel dont on
réutiliserait le résultat pour les deux fils, les deux sous-arbres
seraient partagés, et la libération de l'arbre libérerait deux fois les
mêmes nœuds.
Question 4
Écrire une fonction qui libère toute la mémoire occupée par un arbre binaire.
Corrigé
void libere(arbre *a)
{
if (a != NULL)
{
libere(a->gauche);
libere(a->droite);
free(a); // en dernier : après free, on ne peut plus lire a->gauche
}
}
Parcours¶
Question 5
Écrire des fonctions réalisant un affichage des étiquettes des nœuds suivant les trois ordres : préfixe, postfixe et infixe.
Corrigé
Seule la position de l'affichage par rapport aux deux appels récursifs change.
void affiche_prefixe(arbre *a)
{
if (a != NULL)
{
printf("%d ", a->valeur);
affiche_prefixe(a->gauche);
affiche_prefixe(a->droite);
}
}
void affiche_infixe(arbre *a)
{
if (a != NULL)
{
affiche_infixe(a->gauche);
printf("%d ", a->valeur);
affiche_infixe(a->droite);
}
}
void affiche_postfixe(arbre *a)
{
if (a != NULL)
{
affiche_postfixe(a->gauche);
affiche_postfixe(a->droite);
printf("%d ", a->valeur);
}
}
Sur l'exemple, on obtient respectivement 5 1 4 9 7 20, 1 4 5 7 9 20
et 4 1 7 20 9 5.
Comme les nœuds sont mutables, on peut faire des modifications en place.
Question 6
Écrire une fonction qui incrémente toutes les étiquettes dans un arbre.
Corrigé
void incremente(arbre *a)
{
if (a != NULL)
{
a->valeur = a->valeur + 1;
incremente(a->gauche);
incremente(a->droite);
}
}
La fonction ne renvoie rien : elle modifie directement les nœuds
accessibles depuis le pointeur a.