Arbres N-aires

publicité
Université Paris-Saclay
Master Bioinformatique – M1 BIBS 2015-2016
Algorithmique et complexité
Projet noté : Arbres N -aires
Jusqu’à maintenant vous avez étudié les arbres binaires. Il est néanmoins possible de représenter
des arbres dont les nœuds peuvent avoir plus que deux fils chacun (et plus qu’une seule clé). Il
existe pour cela des structures de données spécialisées comme, par exemple, les B -arbres (B-trees),
les arbres bicolores (red-black trees) ou encore les tries.
Pour ce projet nous allons nous contenter de définir et d’implémenter une structure très simple
que l’on appelle arbre N -aire.
1
Définition
On appelle arbre N -aire un arbre dont les nœuds peuvent chacun avoir entre 0 et N fils. De
plus, on impose l’invariant suivant : à tout moment, tous les nœuds qui sont des frères sont triés
dans l’ordre lexicographique.
La Figure 1 montre un exemple
d’arbre N -aire. La racine de l’arbre
est étiquetée par le symbole /, et elle
a deux fils, respectivement étiquetés
etc et home. Le nœud etc n’a pas
de fils. En revanche, le nœud home
a trois fils, respectivement étiquetés
bill, linus et steve.
On dit que home est le nœud parent de bill, linus et steve.
Les nœuds étiquetés etc et home
sont dits frères, puisqu’ils sont les
nœuds fils d’un même nœud parent.
Pareil pour bill, linus et steve.
En revanche, les nœuds étiquetés Figure 1: Un exemple d’arbre N -aire. Ici, N vaut au
windows.txt et Documents ne sont moins 3.
pas des frères (car leurs nœuds parents sont différents).
2
Structures de données
Créez un fichier narbre.h et placez-y les structures suivantes :
• struct Noeud – structure composée de deux champs id et n fils (de type entier) représentant
respectivement l’identifiant et le nombre de fils du nœud courant ; de deux champs fils et
frere (de type pointeur de Noeud) représentant respectivement des liens vers les fils et les
frères du nœud courant ; et d’un champ etiquette (de type pointeur de char) qui retient
l’étiquette associée au nœud. (Référez-vous aussi à la Figure 2.)
• struct NArbre – structure composée d’un champ racine (de type pointeur de Noeud) pour
la racine de l’arbre, et de deux champs nb noeuds et N (de type entier) représentant respectivement le nombre de nœuds présents dans l’arbre et le nombre maximal de fils par
nœud.
1
Figure 2: Structure d’un nœud.
Remarques : Chaque nœud d’un arbre N-aire doit avoir un identifiant unique. Par contre, il
peut y avoir plusieurs nœuds étiquetés par la même chaı̂ne de caractères (le champ etiquette du
type Noeud). Le champ de l’arbre retenant le nombre de nœuds ( nb noeuds) permet d’attribuer à
chaque nœud nouvellement créé un identifiant unique.
Conventions : L’identifiant du nœud racine vaut 0. Si un nœud donné n’a pas de fils (frères),
alors son champ fils ( frere) vaut NULL.
Ces structures permettront de représenter un arbre N -aire d’une façon exploitable de point de
vue informatique. La représentation correspondant à l’arbre N -aire de la Figure 1 est illustrée
dans la Figure 3.
Figure 3: Représentation équivalente du N -arbre dans la Figure 1. Les nœuds de cet arbre
suivent la représentation proposée dans la Figure 2. Le symbole ∅ signifie NULL.
3
API
Créez ensuite les fonctions ci-dessous dans un fichier narbre.c avec les prototypes associés
dans le fichier narbre.h. Il s’agit d’une API (Application Programming Interface), ce qui veut dire
que (i) vous devez implémenter ces fonctions telles qu’elles sont spécifiées, mais (ii) vous avez bien
sûr droit à définir toutes les fonctions supplémentaires que vous jugez nécessaires pour assurer le
fonctionnement correct de l’API.
3.1
Fonctions demandées
1. NArbre *nouvel arbre(int N, char *etiquette) – Crée (avec allocation mémoire) un
nouvel arbre N -aire. N représente le nombre maximal de fils par nœud, et etiquette est
l’étiquette de la racine de l’arbre. La fonction renvoie un pointeur vers cet arbre.
2. Noeud *nouveau noeud(char *etiquette) – Crée (avec allocation mémoire) un nouveau
nœud étiqueté par la chaı̂ne de caractères pointée par etiquette. Renvoie un pointeur vers
ce nœud. Vous autoriserez une longueur de la chaı̂ne de caractères inférieure ou égale à 16. Si
la chaı̂ne pointée par etiquette en contient plus, vous garderez uniquement les 16 premiers
caractères. Vous aurez besoin d’allouer de la mémoire séparément pour le champ etiquette
du nœud.
Indication : Vous pouvez utiliser les fonctions strlen et strncpy définies dans string.h.
Rappelez-vous qu’une chaı̂ne de caractères est un tableau de char dont le dernier élément est
le caractère nul '\0'.
2
3. void free arbre(NArbre *A) – Libère la mémoire allouée à l’arbre (ainsi que pour tous ses
nœuds et chaı̂nes de caractères allouées dynamiquement).
4. void affiche arbre(NArbre *A) – Affiche l’arbre reçu en argument.
D’abord, un message est affiché, pour dire quel est le nombre total de nœuds de l’arbre, et
pour afficher la valeur de N.
Ensuite, pour chaque nœud n, il faut afficher l’identifiant du nœud entre parenthèses, suivi
d’un espace, suivi de la chaı̂ne de caractères représentée par n->etiquette.
L’affichage commence par la racine de l’arbre. Cette ligne est située le plus à gauche possible.
Tous les nœuds frères apparaissent au même niveau (colonne à l’écran). Les fils d’un nœud
reçoivent chacun une indentation de 5 espaces par rapport au niveau de leur nœud parent.
Voici le résultat de affiche arbre sur l’arbre N -aire de la Figure 3 (ici N vaut 3) :
L’arbre a 11 noeud(s), N = 3.
( 0) /
( 2) etc
( 1) home
( 7) bill
( 8) windows.txt
( 5) linus
( 6) Documents
(10) penguins.c
( 3) steve
( 4) Documents
( 9) apples.h
5. int ajout noeud(NArbre *A, char *etiquette, int id parent) – Ajoute un nouveau
nœud comme fils du nœud dont l’identifiant est id parent. Le nouveau nœud est étiqueté
par la chaı̂ne de caractères etiquette. L’ajout d’un nouveau nœud à la liste de fils du nœud
parent (dont l’identifiant est id parent) doit se faire dans l’ordre lexicographique.
Indication : Vous pouvez utiliser la fonction strcmp définie dans string.h.
La fonction renvoie l’identifiant du nœud nouvellement créé, ou -1 si l’ajout n’est pas possible.
Ceci peut arriver : (i) si l’identifiant du parent n’est pas valide (s’il est inférieur à zéro), (ii)
si l’identifiant du parent n’est pas trouvé, (iii) si le parent est déjà rempli, ou (iv) si le parent
possède déjà un fils ayant la même étiquette etiquette. Si l’ajout d’un nouveau nœud
échoue, un message doit être affiché sur le flux standard d’erreur pour en expliquer la cause.
3
3.2
Exemple d’utilisation de l’API
Voici comment utiliser l’API que vous venez d’implémenter pour créer et afficher l’arbre N -aire
de la Figure 3 :
NArbre *A;
int home, bill, linus, steve, dlinus, dsteve;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
A = nouvel_arbre(3, "/");
home = ajout_noeud(A, "home", 0);
ajout_noeud(A, "etc", 0);
steve = ajout_noeud(A, "steve", home);
dsteve = ajout_noeud(A, "Documents", steve);
linus = ajout_noeud(A, "linus", home);
dlinus = ajout_noeud(A, "Documents", linus);
bill = ajout_noeud(A, "bill", home);
ajout_noeud(A, "windows.txt", bill);
ajout_noeud(A, "apples.h", dsteve);
ajout_noeud(A, "penguins.c", dlinus);
affiche_arbre(A);
free_arbre(A);
Votre code sera évalué dans le cadre d’un client de test du même style (mais qui teste en plus
les cas particuliers mentionnés) ; il est donc important que vous suivez cette API à la lettre.
4
Évaluation
Vous m’enverrez par mail vos fichiers narbre.c et narbre.h à [email protected]
avant dimanche le 8 mai 2016 à minuit. Un projet rendu en retard ne sera pas corrigé. Le travail
demandé est un travail individuel.
Votre code sera évalué selon plusieurs critères, notamment : le respect de la spécification de
l’API, la justesse du code et des résultats. Une attention particulière sera apportée aux avertissements de compilation (options -Wall et -ansi pour gcc), ainsi qu’aux fuites de mémoire et aux
erreurs détectées par valgrind. (Référez-vous aux TPs n° 2 et 3.)
4
Téléchargement