Vous êtes sur la page 1sur 23

Chapitre 4

Implémentation des structures de


données arborescentes

4.1 Introduction

Tout programme utile à besoin de données pour fonctionner. Quand ces données ont une
structure particulière, il est judicieux d'en tenir compte an d'obtenir des programmes
concis et ecaces. Ainsi, si des données se présentent sous la forme d'une hiérarchie
d'éléments, ça serait pure perte d'information, (et peut être aussi de temps de calcul),
de les considérer comme une simple collection d'éléments sans aucun lien entre eux. Par
ailleurs, les structures de données présentées dans les chapitres précédents sont de simples
structures linéaires, à l'exception des tableaux multi-dimensionnels, qui n'en demeurent
pas moins linéaire au niveau de chaque dimension.
Dans le présent chapitre, nous étudions les structures de données arborescentes
, qui
interviennent de manière naturelle dans beaucoup de problèmes concrets. Les structures
arborescentes rompent avec la linéarité an de représenter, de manière adéquate, des
données organisées selon une hiérarchie, où il y a, par exemple, un chef, plusieurs sous-
chefs qui sont au même niveau de la hiérarchie, pour terminer avec beaucoup d'ouvriers
qui sont, également, au même niveau de la hiérarchie.
Par ailleurs, le langage C met à la disposition du programmeur les outils nécessaires
pour implémenter des structures de données arborescentes et ceci à l'aide des notions de
pointeur et destructure étudiées dans le Chapitre 3. Dans ce qui suit, nous étudions et
implémentons, en langage C, les arborescences binaires ordonnées puis les arbres binaires
de recherche (ABR), pour terminer avec les tas-min et les tas-max. Mais, tout d'abord,
commençons par rappeler quelques notions de la théorie des graphes qui sont nécessaires
pour la clarté de notre exposée.

4.2 Notations de la théorie des graphes

Nous commençons par rappeler la dénition d'un graphe simple, dont les structures arbo-
rescentes sont des cas particuliers.

1
2 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

Figure 4.1  Un graphe simple composé de six sommets et huit arêtes.

Dénition 1. Un graphe simple est un couple (S, A), où S est un ensemble ni
d'éléments appelés sommets et A ⊆ est un ensemble d'éléments appelés arêtes .
S
2
a

a. S

2 désigne l'ensemble de toutes les paires de S.

Les graphes sont très souvent représentés graphiquement d'une manière qui reète plus
leurs structures. Dans la Figure 4.1, on peut voir la représentation graphique d'un graphe
simple composé de six sommets et huit arêtes.

Une chaîne, entre deux sommets s et s0 d'un graphe simple (S, A) est une séquence de
sommets (s1 , . . . , sp ) telle que : s1 = s, sp = s0 et {si , si+1 } ∈ A, pour tout i : 1, . . . , p − 1.
La longueur d'une chaîne est le nombre d'arêtes formant la chaîne. Un cycleest une
chaîne dont les deux extrémités sont identiques. Un graphe simple est dit connexe s'il
existe une chaîne entre toute paire de sommets du graphe.

Dénition 2. Un arbre est un graphe simple connexe sans cycle.


Quand il s'agit d'arbre, on emploie souvent le terme n÷uds au lieu du terme sommet
et le terme branche au lieu du terme arête. Ainsi, on peut voir dans la Figure 4.2, un
arbre comportant dix n÷uds et neuf branches.
Dans un arbre, deux n÷uds distincts sont toujours liés par une chaîne unique ; c'est
d'ailleurs l'une des propriétés fondamentales de la structure d'arbre. Une autre parti-
cularité essentielle des arbres c'est que le nombre de n÷uds dépasse de 1 le nombre de
branches.
Un arbre enraciné
(ou arborescence
) est un arbre pour lequel on distingue un n÷ud
particulier comme étant la racine de l'arbre.

Dénition 3. Un arbre enraciné (ou


arborescence ) se dénit par un triplet
(N, B, r) , où (N, B) est un arbre et r∈N est la racine de l'arbre.
Soit (N, B, r) un arbre enraciné et soit x un n÷ud de N .
 Un n÷ud y quelconque de N sur l'unique chaîne allant de r à x est appelé ancêtre
de x.
 Si y est un ancêtre de x, alors x est un de y .descendant
 Le sous-arbre
enraciné en x est l'arbre enraciné en x composé des descendants de
x.
 Si x est un ancêtre de y et {x, y} ∈ B alors on dit que x est le parent
de y et y est
un ls
de x.
4.3. ARBORESCENCES BINAIRES ORDONNÉES 3

r
prof(b)=2

a e haut(r)=3

b c f i

sous-arbre enraciné en a
d g h

Figure 4.2  Un arbre enraciné. La racine est le n÷ud r. Les n÷uds internes sont sur
fond maron clair et les feuilles sont sur fond vert clair.

 Un n÷ud qui n'a pas de ls est une feuille


et un n÷ud qui n'est pas une feuille est
un n÷ud interne
.
 La profondeurd'un n÷ud x est égale à la longueur de la chaîne entre la racine r
et x.
 Le sous-ensemble de tous les n÷uds qui sont à la même profondeur forment un
niveau
.
 Un arbre enraciné est dit complet
si toutes ses feuilles sont à la même profondeur.
 La hauteur d'un n÷ud x est égale à la longueur de la plus longue chaîne qui relie
x à une feuille.
 La hauteur d'un arbre enraciné est égale à la hauteur de sa racine.
Quelques unes des notions exposés ci-dessus sont illustrés dans la Figure 4.2.

4.3 Arborescences binaires ordonnées


Dans ce qui suit, on s'intéresse à des arborescences particulières qui interviennent dans
de nombreux problèmes algorithmiques.

Dénition 4. Un arbre enraciné binaire ordonné est dénit récursivement comme


étant :
 un arbre enracinés qui ne contient aucun n÷ud , ou a

 un arbre enracinés qui est formé d'un n÷ud racine, d'un sous-arbre enraciné
binaire ordonné gauche et un sous-arbre enraciné binaire ordonné droit.
a. Il s'agit de l'arbre enraciné vide

Une particularité essentielle des arbres enracinés binaires ordonnés est que dans ces
arbres, on fait la distinction entre le ls gauche et le ls droit d'un même n÷ud, même si
l'un des deux est absent. En fait, le qualicatif ordonné
est dû à cette distinction. Ainsi, les
deux arbres enracinés binaires ordonnés de la Figure 4.3 sont considérés comme diérents,
4 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

r r

a e a e

b c f i b c i f

d g h d g h

Figure 4.3  Deux arbres enracinés binaires distincts.


même si on fait abstraction des étiquettes des n÷ud, car le ls gauche du n÷ud e a deux
ls dans l'arbre de gauche, alors qu'il n'en a aucun dans l'arbre de droite. Ces deux mêmes
arbres seront considérés comme identiques si on les regardait en tant qu'arbres enracinés
binaire uniquement (non ordonné), en faisant abstraction, bien sûr, des étiquettes des
n÷uds.
Exercice 1.
 Combien de n÷uds, au maximum, peut-on avoir dans un arbre enraciné bi-
naire de hauteur h ?
 Combien de feuilles, au maximum, peut-on avoir dans un arbre enraciné
binaire de hauteur h ?
 Combien de n÷uds, au minimum, peut-on avoir dans un arbre enraciné bi-
naire de hauteur h ?
 Citez un cas où le nombre de n÷uds dans un arbre enraciné binaire est
déterminé par la hauteur de l'arbre?
Exercice 2. Représentez graphiquement tous les arbres enracinés binaires ordonnés
de taille 1,2,3 et 4.

4.3.1 Implémentation des arbres enracinés

La technique de chaînage utilisée pour implémenter les listes chaînées, (voir Chapitre 3),
peut être enrichie pour stocker toute collection homogène et structurée d'éléments ; et
c'est le cas des arbres enracinés. Dans ce qui suit, nous nous limitons à l'implémentation
des arbres enracinés binaires ordonnés et ceci à l'aide de structures de données utilisant
la technique de chaînage.
Le point de départ dans l'implémentation d'un arbre enraciné binaire ordonné est
l'implémentation de la composante élémentaire de tout arbre, à savoir, le n÷ud. Pour ce
faire, on utilise une structure comportant quatre champs : un champs pour mémoriser
l'information portée par le n÷ud et trois autres champs qui vont respectivement pointer
sur le parent, le ls gauche et le ls droit du n÷ud (voir le Programme 2). Les branches de
l'arbre enraciné binaire ordonné sont implicitement représentées par les chaînages entre
les n÷ud parents et les n÷ud ls. Pour le n÷ud racine de l'arbre, qui n'a pas de parent,
4.3. ARBORESCENCES BINAIRES ORDONNÉES 5

.. noeud->elt = elt;
.
#dene alloc(t) (t*) malloc(sizeof(t)) noeud->parent = parent;
typedef struct sNoeud noeud->fg = fg;
{ noeud->fd = fd;
tElt elt; return noeud;
struct sNoeud *parent; }
struct sNoeud *fg; int main()
struct sNoeud *fd; {
..
} tNoeud; .
tNoeud *noeud;
tNoeud *InitNoeud(tNoeud *noeud, noeud = alloc(tNoeud);
tElt elt, InitNoeud(noeud, elt, NULL, NULL,
tNoeud *parent, NULL);
tNoeud *fg, ..
.
tNoeud *fd) }
{
Programme 1: Dénition du type générique tNoeud, déclaration d'une variable
pointeur sur tNoeud et création puis initialisation d'un nouveau n÷ud.

le champ parent aura la constante NULL comme valeur. Pareillement, si l'un des ls d'un
n÷ud n'existe pas alors le pointeur correspondant aura la constante NULL comme valeur
et si l'arbre est vide alors il sera représenté par le pointeur NULL.

4.3.2 Parcours d'arbres enracinés

Pour ce qui est de parcourir tous les n÷uds d'un arbre enraciné binaire ordonné, (on parle
aussi d'explorer l'arbre en sa totalité), on distingue deux parcours classiques :
 le parcours en profondeur d'abord et
 le parcours en largueur d'abord .
Le parcours en profondeur d'abord admet, à sont tour, trois variantes qui diérent par
l'ordre de traitement du n÷ud par rapport au traitement des sous-arbres enracinés en ses
ls. On distingue alors :
 Le parcours préxé, où on traite le n÷ud courant d'abord, le sous-arbre gauche,
puis le sous-arbre droit.
 Le parcours post-xé, où on traite le sous-arbre gauche, le sous-arbre droit puis le
n÷ud courant.
 Le parcours inxe, où l'on traite le sous-arbre gauche, le n÷ud courant puis le
sous-arbre droit.
Algorithmiquement, la manière la plus concise et la plus élégante permettant d'eec-
tuer un parcours en profondeur d'abord, avec ses trois variantes, est de recourir à des
procédures récursives, qui s'appelle elle même à deux reprises an de traiter le sous-arbre
gauche et le sous-arbre droit de chaque n÷ud. Les trois premières procédures du Pro-
gramme 2 sont dédiées aux trois variantes du parcours en profondeur d'abord.

Le parcours en largeur d'abord permet de parcourir les n÷uds d'un arbre enraciné
6 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

if (root)
void Prexe(tNoeud *root) {
{ Inxe(root->fg);
if (root) Traiter(root->elt);
{ Inxe(root->fd)
Traiter(root->elt); }
Préxe(root->fg); }
Préxe(root->fd) void LargeurDabord(tNoeud *root)
} {
} tFile F;
void Postxe(tNoeud *root) InitFile(F);
{ Emler(F,root);
if (root) while (!FileVide(F))
{ {
Postxe(root->fg); root = Deler(F);
Postxe(root->fd); if (root)
Traiter(root->elt) {
} Traiter(root->elt);
} Emler(F,root->fg);
void Inxe(tNoeud *root) Enler(F,root->fd);
{ }
}
}
Programme 2: Les trois variantes du parcours en profondeur d'abord d'un arbre
binaire ordonné, plus le parcours en largeur d'abord d'un arbre enraciné binaire
ordonné. Ce parcours a été rendu possible grâce à l'utilisation d'une le.
4.3. ARBORESCENCES BINAIRES ORDONNÉES 7

1 1

2 6 2 3

3 4 7 10 4 5 6 7

5 8 9 8 9 10

Figure 4.4  Ordre d'exploration des n÷uds d'une arborescence par un parcours en
profondeur d'abord (gauche) vs
par un parcours en largueur d'abord (droite).

suivant un ordre qui dière de celui suivi lors d'un parcours en profondeur d'abord (voir
Figure 4.4). Il s'agit de parcourir l'arbre niveau par niveau, de la racine jusqu'aux feuilles.
Les n÷uds d'un même niveau sont explorés de gauche à droite. Ainsi, les n÷uds qui se
trouvent à une même profondeur sont explorés de manière successive. Par conséquent,
tous les n÷uds se trouvant à une profondeur p doivent avoir été traités avant que le
premier n÷ud se trouvant à la profondeur p + 1 ne le soit. Un tel parcours suppose que
l'on dispose d'un moyen pour se souvenir de l'ensemble des n÷uds qui appartiennent à un
même niveau et qui ne sont pas encore traités. Pour ce faire, le plus adéquat est d'utiliser
une le, comme c'est le cas dans le Programme 2. La structure de le est appropriée pour
le parcours en largeur d'abord comme il a été décrit ci-dessus, car les règles d'accès à une
le assurent que les n÷uds qui entrent dans la le en premier seront traités en premier ; et
ceci est compatible avec une exploration par niveau où les n÷uds de chaque niveau sont
explorés de gauche à droite.

Exercice 3. On voudrait insérer un élément dans un arbre enraciné binaire ordonné


dont la hauteur maximale ne doit pas dépasser une valeur hmax connue.
 Dans quel cas, une telle insertion n'est pas possible?
 Proposez une fonction qui réalise une telle insertion à un endroit quelconque
de l'arbre si la condition sur la hauteur de l'arbre est respectée. La fonction
retournera un pointeur sur la racine de l'arbre.
 Une diculté supplémentaire vient s'ajouter à la condition concernant la
hauteur de l'arbre décrite ci-dessus. On voudrait que le nouvel élément soit
impérativement inséré dans l'arbre en tant que ls gauche, à moins que
l'arbre soit vide. Dans ce dernier cas, le nouvel élément sera inséré à la
racine de l'arbre. Concevez une nouvelle fonction qui réalise cette tâche.
8 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

Exercice 4. Les arbres de Fibonacci sont des arbres enracinés binaires ordonnés
qu'on peut dénir comme suit : L'arbre se réduisant à un seul n÷ud est l'arbre de
Fibonacci A . L'arbre composé d'une racine et de deux ls est l'arbre de Fibonacci
A . L'arbre de Fibonacci A , pour h ≥ 2, est l'arbre enraciné binaire ordonné dont
0

le sous-arbre gauche est A et le sous-arbre droit est A .


1 h

 Écrivez une fonction booléenne C qui permet de tester si un arbre enraciné


h−1 h−2

binaire ordonné est un arbre de Fibonacci ou pas.


 Proposez une fonction C qui reçoit, comme paramètre, un pointeur sur la
racine d'un arbre enraciné binaire ordonné et eectue les suppressions né-
cessaires pour transformer l'arbre reçu en arbre de Fibonacci.

4.4 Arbres binaires de recherche (ABR)


Un arbre binaire de recherche ou ABR est un arbre enraciné binaire ordonné. Un tel
arbre peut être utilisé comme une structure de donnée permettant de mémoriser un en-
semble d'éléments homogènes. Les ABR sont particulièrement appropriés pour le stockage
d'ensembles d'éléments totalement ordonnés dans lesquelles on a fréquemment besoin de
chercher un élément donné.
Tout comme les autres types d'arbres, les éléments stockés dans un ABR, le sont au
niveau des n÷ud de l'ABR. De plus, ces éléments doivent être deux-à-deux comparables,
c'est-à-dire, qu'ils doivent vérier une relation d'ordre total connue. Il peut s'agir de
stocker un ensemble d'entiers ou, plus généralement, un ensemble de clés
.
Dans un ABR, les éléments doivent être stocké de manière à satisfaire la propriété
fondamentale des ABR, qui est la suivante :

Propriété 1. Soit x un n÷ud quelconque d'un ABR.


 Tous les n÷uds du sous-arbre gauche de x portent des éléments inférieurs ou
égaux à l'élément stocké dans x.
 Tous les n÷uds du sous-arbre droit de x portent des éléments supérieurs à
l'élément stocké dans x.
La Figure 4.5 montre deux ABRs qui stockent les même dix entiers. Généralement, on
peut avoir plusieurs ABR diérents qui stockent le même ensemble d'éléments. Cependant,
certain ABR sont plus intéressants, du point de vue algorithmique, que d'autres. La
qualité d'un ABR peut être mesurée par sa hauteur. Plus cette hauteur est proche de
log n, où n est le nombre d'éléments stockés dans l'ABR, plus ce dernier est de bonne
qualité. Il s'ensuit que la structure optimale d'un ABR est celle d'un arbre enraciné binaire
ordonné quasi-complet. Dans un tel arbre, à chaque profondeur p, on trouve 2p n÷uds,
sauf éventuellement, à la profondeur maximale, dont les n÷uds doivent être entassés vers
la gauche.

4.4.1 Implémentation et parcours d'un ABR

Les ABRs peuvent être implémentés de la même façon que les arbres enracinés binaires
ordonnés quelconques, c'est-à-dire, en utilisant la technique de chaînage. On peut donc
4.4. ARBRES BINAIRES DE RECHERCHE (ABR) 9

5 4

2 9 2 8

1 4 7 10 1 3 6 9

3 6 8 5 7 10

Figure 4.5  Deux ABRs distincts qui stockent les entiers de 1 à 10.

utiliser le type tNoeud déni dans le Programme 1 pour implémenter les n÷uds et les
branches d'un ABR.
Pour le parcours total d'un ABR, il est aussi possible d'utiliser les procédures détaillées
dans le Programme 2. Toutefois, il y a parmi ces diérents parcours, un qui est particuliè-
rement adapté au parcours d'un ABR. Il s'agit du parcours en profondeur d'abord dans sa
variante inxe (voir la procédure Inxe du Programme 2). Ce parcours permet de traiter
les éléments stockés dans un ABR suivant l'ordre sous-jacent à leur stockage. Ce parcours
permet, par exemple, d'acher les éléments stockés dans un ABR dans l'ordre croissant.

En dehors du parcours total, on peut avoir a chercher un élément particulier dans un


arbre. L'organisation d'un ABR est particulièrement adaptée pour eectuer une telle re-
cherche, (c'est pour cette raison qu'on les appelle arbre binaire de recherche
). La recherche
d'un élément dans un ABR peut être eectuée par une fonction qui prend comme para-
mètre un pointeur sur la racine de l'ABR et l'élément recherché et retourne un pointeur
sur le premier n÷ud, (selon le parcours en profondeur d'abord), qui contient l'élément
recherché. Si l'élément recherché ne se trouve pas dans l'ABR, la fonction retournera le
pointeur NULL. Il s'agit d'eectuer un parcours partiel en profondeur d'abord qui s'arrête
dès que l'élément est trouvé. A chaque n÷ud de l'ABR, un seul sous-arbre, (le gauche ou le
droit), est exploré selon la comparaison de l'élément recherché avec l'élément stocké dans
le n÷ud courant. Le programme de la Figure 4.6 montre deux versions de la recherche
d'un élément dans un ABR, une version récursive et une version itérative.

On peut aussi avoir besoin d'eectuer un parcours partiel dans un ABR an de dé-
terminer le plus petit ou le plus grand élément qui se trouve dans l'ABR. Il sut alors
de parcourir la chaîne la plus à gauche de l'ABR pour trouver le plus petit élément au
bout de cette chaîne. Pour le plus grand élément, c'est plutôt la chaîne la plus à droite
de l'ABR qu'il faut parcourir (voir le code de la Figure 4.7).

Proposition 2.
 La recherche d'un élément donné dans un ABR se fait en O(h), où h désigne
la hauteur de l'ABR.
 L'accès à l'élément minimal et maximal dans un ABR se fond en O(h), où
h désigne la hauteur de l'ABR.
10 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

1 tNoeud ∗ ChercheABR ( t N o e u d ∗ r o o t , tElt elt )

if return
2 {

3 ( ! root || elt == root −> e l t ) root ;

if
4

−> e l t )
return
5 ( elt < root

−>f g
else
6 ChercheABR ( r o o t , elt ) ;

return
7

8 ChercheABR ( r o o t −>f d , elt ) ;

9 }

10

11 t N o e u d ∗ C h e r c h e A B R i t e r ( tNoeud ∗ r o o t , tElt elt )

while
12 {

−> e l t )
if
13 ( root && elt != root

14 ( elt < root −> e l t )


r o o t −>f g ;

else
15 root =

16

−>f d ;
return
17 root = root

18 root ;

19 }

Figure 4.6  Fonctions de recherche d'un élément dans un ABR.

1 tNoeud ∗MinABR ( t N o e u d ∗ r o o t )
2 {

3 tNoeud ∗ pred = NULL ;

while
4

5 ( root )

6 {

7 pred = root ;

8 root = root −>f g ;

return
9 }

10 pred ;

11 }

12

13 t N o e u d ∗MaxABR( t N o e u d ∗ r o o t )
14 {

15 tNoeud ∗ pred = NULL ;

while
16

17 ( root )

18 {

19 pred = root ;

20 root = root −>f d ;

return
21 }

22 pred ;

23 }

Figure 4.7  Fonctions C pour retrouver l'adresse de l'élément minimal et l'adresse de


l'élément maximal dans un ABR.
4.4. ARBRES BINAIRES DE RECHERCHE (ABR) 11

Éléments de preuve : La complexité des fonctions ChercheABRiter(.,.), MinABR(.)


et MaxABR(.), détaillées dans les Figures 4.6 et 4.7, dépend du nombre de passage dans
les boucles tant-que qui s'y trouvent. Il est clair que ces boucles peuvent se répéter un
nombre de fois qui est borné par la hauteur de l'ABR, d'où la complexité O(h).

Exercice 5. Écrivez une fonction C qui reçoit, comme paramètre, un pointeur sur
la racine d'un ABR et retourne la hauteur de ce dernier.
Exercice 6.
 Écrivez une fonction C qui reçoit, comme paramètre, un pointeur sur la ra-
cine d'un ABR et un entier k et retourne un pointeur sur le n÷ud qui contient
le kième élément, selon l'ordre croissant des éléments, de l'ABR.
 On s'intéresse, à présent, à localiser le n÷ud médian d'un ABR, c'est à dire
celui qui contient l'élément qui, dans la liste des éléments en ordre croissant,
se trouve à la position (n + 1) div 2, où n est le nombre d'éléments de l'ABR.
Proposez une fonction C qui permet de localiser le n÷ud médian à partir d'un
pointeur sur la racine d'un ABR.

4.4.2 Insertion et suppression dans un ABR

L'avantage des structures dynamiques, tels que les ABRs, c'est qu'il est possible de de-
mander de l'espace mémoire supplémentaire ou d'en libérer pendant l'exécution du pro-
gramme. Cette dynamique est le résultat d'ajouts et de suppressions d'éléments à/de la
collection d'éléments. Pour les ABR, c'est deux opérations doivent être exécutées de sorte
à conserver la principale propriété des ABR (voir la Propriété 1).
La fonction InsereABR(.,.) détaillée dans la Figure 4.8 prend comme paramètre
un pointeur sur la racine d'un ABR et un nouvel élément de type générique tElt et
retourne un pointeur sur la racine, éventuellement changée, de l'ABR. InsereABR(.,.)
permet d'insérer le nouvel élément dans une position de sorte à conserver la propriété
fondamentale des ABR (voir la Propriété 1). Pour introduire le moins de changement
possible sur la structure de l'ABR, le nouvel élément sera stocké dans une nouvelle feuille
de l'ABR.
An de localiser la position où le nouvel élément doit être inséré, la fonction InsereABR
procède comme suit : A l'aide de deux pointeurs pred et cour, on parcours un chemin de
l'arbre, qui part de la racine, à la recherche d'un n÷ud qui a zéro ou un ls. Au niveau
de chaque n÷ud traversé sur le chemin, seul un de ses ls sera exploré. Le choix entre
les deux ls est décidé en comparant le nouvel élément à insérer, et l'élément stocké dans
le n÷ud courant. Si le nouvel élément n'est pas plus grand que l'élément stocké dans le
n÷ud courant alors le parcours bifurque vers le ls gauche, sinon le parcours bifurque
vers le ls droit. Le rôle du pointeur pred est de sauvegarder l'adresse du n÷ud que la
fonction InsereABR(.,.) atteindra au bout du chemin. Le nouvel élément sera inséré
dans un nouveau n÷ud nouv, qui sera le ls du n÷ud se trouvant au bout du chemin
exploré (pred). Si le nouvel élément n'est pas plus grand que l'élément stocké dans pred
alors nouv sera un ls gauche sinon il sera un ls droit. Remarquez que, dans tous les cas,
le nouveau n÷ud sera un n÷ud feuille.
12 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

1 tNoeud ∗ InsereABR ( tNoeud ∗ r o o t , tElt elt )

2 {

3 tNoeud ∗ pred , ∗ cour ;


4

5 pred = NULL ;

6 cour = root ;

while
7

8 ( cour )

9 {

if
10 pred = cour ;

11 ( elt <= cour −> e l t )


c o u r −>f g ;

else
12 cour =

13

14 cour = cour −>f d ;


15 }

16

17 cour = I n i t N o e u d ( a l l o c ( t N o e u d ) , e l t , p r e d , NULL, NULL) ;

18

if
19

20 ( pred == NULL) // L 'ABR d e départ été vide

else
21 root = cour ;

if
22

23 ( elt <= pred −> e l t )


−>f g
else
24 pred = cour ;

25

26 pred −>f d = cour ;

return
27

28 root ;

29 }

Figure 4.8  Fonctions C permettant d'insérer un nouvel élément dans un ABR.

La position prise par nouv dans l'ABR lui garantit le fait que, pour tout ancêtre anc
de nouv, ce dernier descendra du ls gauche de anc si l'élément qu'il stocke, (le nouvel
élément), n'est pas plus grand que l'élément stocké dans anc. Sinon nouv descendra du
ls droit de anc. C'est cette propriété qui garantit que l'arbre gardera sa propriété d'ABR
après insertion.

Exercice 7. Soit σ = 3807241965 une permutation des entiers de 0 à 9.


 Construite l'ABR qui résulte de l'insertion des éléments de σ en partant de
la gauche.
 Même question, mais en partant de la droite, cette fois.
 Que peut-on déduire?
Nous arrivons, à présent, à la tâche délicate qui consiste à supprimer un élément d'un
ABR. An de réaliser cette suppression, il faut, tout d'abord localiser le n÷ud qui contient
l'élément à supprimer. Pour ce faire, la fonction SupprimeABR(.,.), qui est détaillée dans
4.4. ARBRES BINAIRES DE RECHERCHE (ABR) 13

5 5

2 9 2 9

1 4 7 10 1 3 7

3 6 8 6 8

Figure 4.9  ABR avant et après la suppression des éléments 4 et 10.

la Figure 4.11, fait appel à la fonction ChercheABR(.,.), (voir Figure 4.6). Cet appel
renvoi l'adresse du n÷ud, adr, contenant l'élément à supprimer. On distingue alors trois
cas, qui sont les suivants, (voir aussi l'illustration dans les Figures 4.9 et 4.10) :
 Si le n÷ud adr n'a pas d'enfant alors il sut de faire pointer sur NULL, le champs
(fg ou fd) du parent d'adr qui pointe sur ce dernier.
 Si le n÷ud adr n'a qu'un seul ls alors ce ls prendra la place de son parent. Il
sut, alors, de faire pointer le champ du parent d'adr, qui pointe sur ce dernier,
sur l'unique ls d'adr.
 Enn, si le n÷ud adr a deux ls alors ce n÷ud sera désormais occupé par le
prédécesseur immédiat, selon l'ordre sous-jacent à l'ABR, de l'élément à supprimer.
Ceci veut dire que le n÷ud adr ne sera pas physiquement supprimé de l'arbre,
mais c'est son contenu qui va changer. Notez que le prédécesseur immédiat de
l'élément à supprimer est le plus grand élément du sous-arbre ayant pour racine
le ls gauche du n÷ud adr. Le n÷ud contenant cet élément peut être localisé à
l'aide de la fonciton MaxABR(.). Une fois localisé, ce n÷ud doit être physiquement
supprimé, car l'élément qu'il contient va être sauvegardé dans le n÷ud adr. Une
autre propriété du n÷ud qui contient le prédécesseur immédiat c'est qu'il a, au plus,
un ls et donc sa suppression physique sera plus facile que celle du n÷ud adr, qui
lui à deux ls. On procède alors à la localisation du prédécesseur immédiat par
un appel à la fonction MaxABR(.) à partir du ls gauche de adr (voir Ligne 10
du programme de la Figure 4.11). Enn, puisque le n÷ud qui sera physiquement
supprimé a, au plus, un ls, on se retrouve, désormais, dans un des deux cas
précédant. Ces deux cas sont traités par les lignes de code de 15 à 24 de la fonction
SupprimeABR(.,.), (voir Figure 4.11).

4.4.3 Rotation d'un ABR

Il existe une autre opération très utile qu'on peut appliquer sur un ABR ; il s'agit de
larotation d'ABR. Une rotation consiste à restructurer un ABR en faisant remonter
certains n÷uds et à en faire descendre d'autres. Dans la plus part du temps, le but d'une
telle man÷uvre est d'équilibrer l'ABR en diminuant sa hauteur. Ceci peut avoir pour
eet l'accélération de la recherche d'élément dans cet ABR. En eet, quand un sous-
arbre d'un ABR subit une rotation, la hauteur de l'un de ses deux sous-arbres augmente
14 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

5 6

2 9 2 9

1 4 7 10 1 4 7 10

3 6 8 3 8

Figure 4.10  ABR avant et après la suppression de l'élément 5.

1 tNoeud ∗ SupprimeABR ( t N o e u d ∗ r o o t , tElt elt )

2 {

3 tNoeud ∗ adr , ∗ ptr , ∗fls ;

4 adr = ChercheABR ( r o o t , e l t ) ;

if
5

6 ( adr )

if
7 {

8 ( adr −>f g && a d r −>f d ) // le cas où adr à deux fils

9 {

10 ptr = MaxABR( a d r −>f g ) ;


11 adr −> e l t = ptr −> e l t ; // sauvegarde du successeur immédiat

12 adr = ptr ;

13 }

14 // On revient au cas où adr a, au plus , un fils

15 fls = adr −>f g ? a d r −>f g : a d r −>f d ;


−>p a r e n t ;
if
16 ptr = adr

17 ( fls ) f l s −>p a r e n t = p t r ;

if
18

if
19 ( ptr )

20 ( ptr −>f g == a d r )
−>f g = f l s ;
else
21 ptr

22

−>f d
else
23 ptr = fls ;

24

25 root = fls ;

26

27 f r e e ( adr ) ;

28 }

return
29

30 root ;

31 }

Figure 4.11  Fonctions C permettant de supprimer un élément d'un ABR.


4.5. LES TAS 15

alors que la hauteur de l'autre diminue. Ainsi, en choisissant de diminuer la hauteur


du sous-arbre qui à la plus grande hauteur au détriment de son sous-arbre frère, on
rééquilibre la hauteur des deux sous-arbres. En appliquant ce même principe au niveau
de chaque sous-arbre mal équilibré, on rééquilibre l'ABR dans sa totalité. En conséquence
de ceci, la hauteur de l'ABR va s'approcher de sa valeur optimale qui est log n, où n est le
nombre d'éléments stockés dans l'ABR. Cependant, l'opération de rotation doit conserver
la propriété fondamentale des ABR (voir Propriété 1). Donc, des précautions particulières
doivent être prises lors de l'application d'une rotation.
En fait, on distingue deux types de rotation : la rotation droite et la rotation gauche.
En appliquant une rotation droite sur un n÷ud r, c'est le ls gauche de r, qu'on désignera
par g , qui prend la place de r. Le n÷ud r, avec son sous-arbre droit, deviendra le ls droit
de g . L'ancien ls droit de g , avec tout son sous-arbre, deviendra le ls gauche de r La
fonction RotationDroite(.), détaillée dans la Figure 4.13 eectue ces changements.
En appliquant une rotation gauche sur un n÷ud r, c'est le ls droit de r, qu'on
désignera par d, qui prend la place de r. Le n÷ud r, avec son sous-arbre gauche, deviendra
le ls gauche de d. L'ancien ls gauche de d, avec tout son sous-arbre, deviendra le ls
droit de r La fonction RotationGauche(.), détaillée dans la Figure 4.13 eectue ces
changements.
Il est clair, d'après le code des deux fonctions de rotation, que les deux rotations se
font en temps constant.

Exercice 8. Proposez une fonction C qui reçoit, comme paramètre, un pointeur sur
la racine d'un ABR et procède à l'équilibrage de ce dernier. Précisons qu'équilibrer
un ABR veut dire le restructurer de façon à minimiser sa hauteur.

4.5 Les tas


Un tas est une structure de donnée permettant d'implémenter un ensemble d'éléments
homogènes dont la taille peut évoluer pendant l'exécution du programme. Il s'agit donc
d'une structure de donnée dynamique . Les tas sont particulièrement appropriés pour le
stockage d'ensembles totalement ordonnés et dont on a souvent besoin d'accéder au plus
petit ou au plus grand éléments.
Un tas est un arbre enraciné binaire ordonné quasi-complet, c'est-à-dire que, les ni-
veaux de l'arbre doivent être complets, sauf éventuellement le dernier niveau, qui ne
contient que des feuilles de l'arbre. Si ce dernier niveau n'est pas complet alors ses n÷uds
doivent être entassés vers la gauche, c'est-à-dire, que les n÷uds manquants se trouvent
sur la droite du dernier niveau (voir Figure 4.14). De plus, le stockage des éléments dans
le tas obéi à des règles bien précises selon que le tas est un tas-min ou un tas-max . Ceci
suppose, toutefois, que l'ensemble des éléments que l'on peut stockés dans un tas (min ou
max) doit vérier une relation d'ordre totale qui permet de comparer les éléments entre
eux. Ainsi, s'il s'agit d'un tas-min, alors on a la propriété suivante :

Propriété 3. Dans un tas-min, un élément stocké dans un n÷ud ne peut être plus
grand que les éléments stockés dans ses n÷ud ls.
Si c'est un tas-max, on doit avoir l'inverse. Par conséquent, dans un tas-min, le plus
16 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

1 tNoeud ∗ R o t a t i o n D r o i t e ( tNoeud ∗ r )

if
2 {

3 (r)

4 {

∗g −>f g ;
if
5 tNoeud = r

6 (g)

7 {

−>f g = g−>f d ;
if
8 r

9 ( g−>f d ) g−>f d −>p a r e n t = r ;

10 g−>f d = r ;

11 r −>p a r e n t = g ;

12 r = g;

13 }

return
14 }

15 r ;

16 }

17

18 t N o e u d ∗ R o t a t i o n G a u c h e ( tNoeud ∗ r )

if
19 {

20 (r)

21 {

∗d −>f d ;
if
22 tNoeud = r

23 (d)

24 {

−>f d = d−>f g ;
if
25 r

26 ( d−>f g ) d−>f g −>p a r e n t = r ;

27 d−>f g = r ;

28 r −>p a r e n t = d ;

29 r = d;

30 }

return
31 }

32 r ;

33 }

Figure 4.12  Fonctions C permettant d'eectuer une rotation droite ou gauche sur un
n÷ud d'un ABR.
4.5. LES TAS 17

1 tNoeud ∗ E q u i l i b r e r A B R ( tNoeud ∗ r o o t )

if return
2 {

3 ( ! root ) NULL ;

4 root −>f g = EquilibrerABR ( root −>f g ) ;


−>f d −>f d ) ;
while
5 root = EquilibrerABR ( root

6 ( ProfondeurABR ( r o o t −>f g ) < ProfondeurABR ( r o o t −>f d ) )

while
7 root = RotationGauche ( r o o t ) ;

8 ( ProfondeurABR ( r o o t −>f d ) < ProfondeurABR ( r o o t −>f g ) )

return
9 root = RotationDroite ( root ) ;

10 root ;

11 }

int
12

13 t N o e u d ∗ S e l e c t ( tNoeud ∗ r o o t , ∗k )

if return
14 {

15 ( ! root ) NULL ;

∗ −>f g
if return
16 tNoeud ptr = S e l e c t ( root , k) ;

if −− ∗ return
17 ( ptr ) ptr ;

return
18 ( ( k ) ==0) root ;

19 −
S e l e c t ( root >f d , k ) ;

20 }

Figure 4.13  Fonctions C permettant l'équilibrage et la sélection du kème élément d'un


ABR.

petit élément est porté par la racine du tas, alors que le plus grand élément va se trouvé
dans une des feuilles (voir Figure 4.14).

Une remarque importante concernant la position relative des n÷uds frères dans un
tas, c'est que les frères ne sont pas forcement ordonnés entre eux. On peut voir ceci dans
le tas-min de la Figure 4.14, où pour les ls du n÷ud 1, c'est le ls gauche qui est le plus
petit, alors que pour les ls du n÷ud 2, c'est l'inverse.

2 3

4 5 7 6

8 9 10

Figure 4.14  Un tas-min stockant des nombres entiers.


18 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

4.5.1 Implémentation et parcours d'un tas

Un tas, (min ou max), peut être implémenté à l'aide d'un simple tableau uni-dimensionnel.
Ceci est possible car, il y a moyen de stocker les éléments du tas dans le tableau de telle
sorte qu'à partir de l'indice de la case qui contient un n÷ud donné, on peut aisément
retrouver les indices des cases qui contiennent le parent et les deux ls (s'ils existent).
Ce stockage astucieux vérie la règle suivante : si un n÷ud du tas est stocké dans la case
i du tableau alors sont parent, (s'il existe), se trouvera dans la case d'indice i div 2,
alors que ses ls gauche et droit, (s'ils existent), se trouveront dans les cases d'indices
respectifs 2*i et 2*i+1. Il sut alors de stocker la racine du tas dans la case 1, pour
retrouver les indices des cases de tous les n÷uds du tas. On pourra alors convenir que
l'indice 0 désignera un parent inexistant. Le bout de code de la Figure 4.15 contient la
dénition d'un type générique tTas. Il s'agit d'une structure composée de deux champs :
un champ taille servant à mémoriser la taille du tas et un champ elts, qui est un
tableau uni-dimensionnel de tElt réservé au stockage des éléments du tas. Le bout de
code de la Figure 4.15 contient aussi le code des fonctions qui permettent le passage d'un
n÷ud à ces parents.

Comme toute arborescence, un tas peut être exploré en profondeur d'abord ou en


largeur d'abord. Rappelons que les procédures d'exploration sont détaillées dans le Pro-
gramme 2. Toutefois, les fonctions d'accès aux composantes du n÷ud doivent être rem-
placées par ceux montrées dans la Figure 4.15.

Proposition 4. L'accès à l'élément minimum (resp. maximum) dans un tas-min


(resp. tas-max), peut être eectué en temps constant (O(1)).

4.5.2 Insertion et suppression dans un tas

Un tas est une structure dynamique qui peut augmenter et diminuer de taille pendant
l'exécution du programme. Il est donc possible d'insérer de nouveaux éléments dans un
tas. Toutefois, cette insertion doit conserver les propriétés du tas. Après insertion, on
doit donc avoir un arbre enraciné binaire ordonné quasi-complet dont les feuilles doivent
être entassées vers la gauche. De plus, un n÷ud doit toujours porter un élément inférieur
ou égal à ceux portés par ses ls pour les tas-min et l'inverse pour les tas-max. Pour
conserver ces propriétés, on procède comme suit, lors de l'insertion dans un tas-min. Le
nouvel élément est stocké dans le premier emplacement libre, en partant de la gauche,
du dernier niveau, qui correspond à la case d'indice taille-du-tas plus 1 (voir procédure
Entasser de la Figure 4.16). Ensuite, le nouvel élément est comparé avec son parent, au
niveau de la procédure EntasserAux(.,.). Si le parent est plus grand que le ls, ce qui
n'est pas conforme avec l'ordre des tas-min, alors le parent et le ls échangent de cases. A
son tour, le nouveau parent est comparé avec son parent et un échange aura lieu si l'ordre
des tas-min n'est pas respecté, et ainsi de suite. Dans le cas extrême, le nouvel élément
va se trouver dans la racine du tas, c'est-à-dire, dans la case d'indice 1. Cette éventuelle
remontée du nouvel élément vers la racine du tas est assurée par la boucle while qui ce
trouve à la Ligne 6 du code de la Figure 4.16.
4.5. LES TAS 19

#include
#include
1 < s t d i o . h>

#define int
2 < l i m i t s . h>

#define
3 tElt

#define
4 MAX_TAS 512

5 INVALID_ELT INT_MAX

typedef struct
6

int
8 {

9 taille ;

10 tElt e l t s [MAX_TAS+ 1 ] ;

11 } tTas ;

void
12

13 I n i t T a s ( tTas ∗ tas )
14 {

15 ( ∗ tas ) . taille = 0;

16 }

int int
17

18 Parent ( i )

return
19 {

20 i /2;

21 }

int int
22

23 Fg ( i , tTas tas )

if ∗
24 {

return
25 (2 i > tas . t a i l l e )

else
26 0;

return
27

28 2 ∗i ;

29 }

int int
30

31 Fd ( i , tTas tas )

if ∗
32 {

return
33 (2 i +1 > tas . t a i l l e )

else
34 0;

return
35

36 2 ∗ i +1;
37 }

Figure 4.15  Bout de code C permettant l'implémentation d'un tas-min.


20 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

1 void EntasserAux ( int i , tTas ∗ tas )

int
2 {

3 p = Parent ( i ) ;

4 tElt tmp ;

while
5

6 (p > 0 && ( ∗ tas ) . elts [ i ] < ( ∗ tas ) . elts [p])

7 {

8 tmp = ( ∗ tas ) . elts [ i ];

9 (∗ tas ) . elts [ i ] = ( ∗ tas ) . elts [p ];

10 (∗ tas ) . elts [p] = tmp ;

11 i = p;

12 p = Parent ( i ) ;

13 }

14 }

void
15

16 Entasser ( tElt elt , tTas ∗ tas )

if ∗
17 {

18 (( tas ) . t a i l l e == MAX_TAS)

else
19 p r i n t f ( " Erreur tas p l e i n \n" ) ;

20

21 {

22 ( ∗ tas ) . e l t s [++( ∗ tas ) . t a i l l e ] = elt ;

23 EntasserAux ( ( ∗ tas ) . t a i l l e , tas ) ;


24 }

25 }

Figure 4.16  Fonctions C permettant d'entasser un élément dans un tas-min.


4.5. LES TAS 21

On passe à présent à l'extraction de l'élément minimal d'un tas-min. L'accès à l'élément


minimal dans un tas-min est facile puisqu'on sait que cet élément se trouve dans la case
d'indice 1. Toutefois, la suppression de cet élément du tas-min nécessite une réorganisation
de ce dernier. En eet, la case d'indice 1, qui est désormais disponible, doit contenir
le nouvel élément minimal du tas-min résultant de l'extraction. L'idée est de déplacer
momentanément, dans la case d'indice 1, le dernier élément du tas, (celui qui se trouve
dans la case dont l'indice est donné par la taille du tas initial). Cette première étape de
l'extraction est réalisée par la fonction Extraire(.), qui est détaillée dans Figure 4.17.
L'élément déplacé, qu'on désignera par x, est ensuite poussé vers le fond du tas jusqu'à ce
qu'il se trouve dans une position qui respecte la propriété fondamentale des tas-min (voir
Propriété 4). Ainsi, x est enfoncé plus profondément dans le tas jusqu'à ce qu'il atteint
une position où il n'est pas plus grand que ses ls. Pour ce faire, x est permuté avec le
plus petit de ses ls tant que ce dernier est plus petit que x. Cette tâche est réalisée par
la procédure ExtraireAux(.,.), qui est détaillée dans la Figure 4.17.

Proposition 5. L'insertion et l'extraction d'un élément dans un tas se fond en


O(log n), où n désigne le nombre d'éléments dans le tas.

Éléments de preuve : La complexité des routines d'entassement et d'extraction dépend


de la hauteur du tas. Or, un tas est un arbre binaire quasi-complet ; il s'ensuit que sa
hauteur est de l'ordre de O(log n).
La complexité de l'entassement peut être déduite de la boucle while
qui se trouve dans
la procédure EntasserAux. Cette boucle peut se répéter un nombre de fois qui est borné
par la hauteur du tas. De même pour la complexité de l'extraction, qui peut être déduite
de la longueur de la séquence d'appel récursif à la procédure ExtraireAux. En eet, la
longueur de la plus longue séquence d'appel est bornée par la hauteur du tas.

Exercice 9. Donnez un code C implémentant les opérations d'entassement et d'ex-


traction dans un tas-max.

Exercice 10. Proposez une procédure C qui reçoit un tableau d'entiers et produit
un tas-max avec les éléments de ce tableau. Indication : an de ne pas utiliser de
l'espace mémoire supplémentaire, on supposera que les élément du tableau de départ
se trouvent dans le tableau implémentant le tas.

Exercice 11. Étant un tas-min implémenté à l'aide d'un tableau, un entier k et


un réel x, proposez une fonction C qui détermine si le kième plus petit élément du
tas est supérieur ou égal au réel x ou pas. La fonction proposée doit opérer en O(k),
dans le pire des cas, indépendamment de la taille du tas.
22 CHAPITRE 4. STRUCTURES DE DONNÉES ARBORESCENTES

void int
1

2 ExtraireAux ( root , tTas ∗ tas )

int
3 {

4 min=r o o t , f g=Fg ( r o o t , ∗ tas ) , f d=Fd ( r o o t , ∗ tas ) ;

if
5

6 ( fg && ( ∗ tas ) . e l t s [ fg ] < ( ∗ tas ) . e l t s [ min ] )

7 min = fg ;

if
8

9 ( fd && ( ∗ tas ) . e l t s [ fd ] < ( ∗ tas ) . e l t s [ min ] )

10 min = fd ;

if
11

12 ( min != root )

13 {

14 tElt tmp = ( ∗ tas ) . e l t s [ root ] ;

15 (∗ tas ) . e l t s [ root ] = ( ∗ tas ) . e l t s [ min ] ;

16 (∗ tas ) . e l t s [ min ] = tmp ;

17 E x t r a i r e A u x ( min , t a s ) ;

18 }

19 }

20

21 tElt E x t r a i r e ( tTas ∗ tas )

if
22 {

23 (( ∗ tas ) . taille == 0)

24 {

return
25 p r i n t f ( " Erreur tas v i d e \n" ) ;

26 INVALID_ELT ;

else
27 }

28

29 {

30 tElt tmp = ( ∗ tas ) . elts [1];

31 ( ∗ tas ) . elts [1] = ( ∗ tas ) . elts [( ∗ tas ) . taille −− ];

return
32 ExtraireAux ( 1 , t a s ) ;

33 tmp ;

34 }

Figure 4.17  Fonctions C permettant d'extraire l'élément minimal d'un tas-min.


4.5. LES TAS 23

Exercice 12. (Tri par tas) Le tri par tas tire prot de la propriété des tas pour
eectuer un tri en O(n log n) . La démarche de ce tri est la suivant :
 On commence par construire un tas-max à partir du tableau qu'on veut trier
dans le sens croisant.
 On procède, ensuite, à l'échange de l'élément maximal du tas-max, qui se
trouve à la racine de ce dernier, avec le dernier élément du tas, (l'élément
qui occupe la dernière case du tableau). Cette opération garantie qu'un élé-
ment maximal du tableau à trier à pris sa place nale. En contre partie,
cette permutation peut faire perdre, au tas-max, sa propriété de tas-max, car
l'élément qui occupe désormais la racine du tas, à la suite de la permutation,
n'est pas forcement un élément maximal du tas.
 On décrémente alors la taille du tas, pour que l'élément maximal, qui occupe
la dernière case du tableau, ne soit plus déplacé. On restaure, ensuite, la
propriété du tas-max en entassant l'élément déplacé à la racine du tas. On
itère le même processus avec l'élément qui occupe l'avant dernière case du
tableau, et ainsi de suite, jusqu'à l'élément qui occupe la deuxième case du
tableau.
Proposez une procédure C qui implémente la démarche du tri par tas. Indication :
an de ne pas utiliser de l'espace mémoire supplémentaire, on supposera que le
tableau à trier se trouve au départ dans le tableau implémentant le tas.
Exercice 13. On se propose d'ajouter à la structure de tas-min un tableau Booléen
supplémentaire qui permettra d'imposer la condition que tout ls gauche est inférieur
ou égal à son frère (le ls droit).
 Redénissez les fonctions FG() et FD() pour imposer la condition décrire
ci-dessus sans avoir à restructurer le tas.
 Étant donné un tas-min, proposez une procédure qui initialise le tableau boo-
léen en tenant compte du contenu du tas.

Vous aimerez peut-être aussi