Arbres de données

publicité
Lycée Faidherbe, Lille
MP1
Cours d’informatique
2013–2014
Arbres de données
I
Homogènes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduction 2
Définition 2
comme graphes 4
II
2
Arbres
5
Implémentation 5
Binaires
Définition 6
brements 7
Parcours 10
IV
Implémentation 3
Hétérogènes . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduction 5
III
Exemples 3
................................
6
Implémentation 6
Fonctions élémentaires 7
DénomEncombrement 8
Arbres parfaits 8
Arbre équilibré 9
Exercices
...............................
12
Exemple 12
Longueur de cheminement 12
Arbres de Fibonacci 12
Symétrisation 12
Hiérarchie 13
Reconstruction d’un arbre 13
Représentation binaire 13
Parcours en largeur 13
Profondeur minimale 14
Tas 14
Arbres
page 1
I Homogènes
I.1 Introduction
Une structure est arborescente quand, à chaque élément, on associe d’autres éléments de manière hierarchique :
• fils
• descendants
• sous–ensembles
• ...
Le modèle, qui a donné le nom, est celui d’un arbre où les branches se divisent en
d’autres branches.
On suppose que le nombre d’éléments est fini et qu’il n’existe pas de cycle . Il existe
alors au moins un élément qui n’est le descendant d’aucun autre.
On appellera arbre une structure dans laquelle il n’y a qu’un seul tel élément : la
racine. Comme il n’existe pas de cycle chaque élément est associé à la racine par
un nombre fini de dépendances.
Contrairement à la vision de la nature les arbres sont représentés à l’envers.
I.2 Définition
Un arbre non vide est un ensemble fini d’objets : les nœuds liés par une relation n
est le fils de m ou m est le père de n telle que
page 2
Arbres
• il existe un unique élément sans père : la racine
• tout élément, sauf la racine, a un père unique
• tout élément est descendant de la racine.
Un nœud sans fils est une feuille de l’arbre ou nœud externe ; les autres sont les
nœuds internes.
L’ensemble des nœuds qui sont les descendants d’un nœud n0 forme un arbre dont
n0 est la racine: c’est le sous–arbre de racine n0 .
Un arbre peut donc être défini récursivement comme
• soit l’arbre vide
• soit un ensemble d’arbres fils d’une racine.
L’ensemble des fils n’est pas, dans ce cas général, ordonné.
I.3 Exemples
•
•
Arbre généalogique
Plan du cours d’informatique
Informatique en MP
binaires
langages
complexité
arbres
ABR
rationnels
automates
AFD
AFND
I.4 Implémentation
Pour définir le type “arbre” on va introduire une définition récursive : un arbre est
un nœud associé à une liste d’arbres.
type ’f arbre = Noeud of ’f * (’f
Arbres
arbre list);;
page 3
let a = Noeud (4,[]);;
let b = Noeud (5,[]);;
let c = Noeud (2,[]);;
let d = Noeud (3, [a;b;c]);;
val d : int arbre = Noeud (3, [Noeud (4, []); Noeud (5, []); Noeud (2, [])])
I.5 Arbres comme graphes
On peut aussi considérer les arbres comme des ensembles de sommets (les noeuds)
liés par des arêtes avec une hierarchie : ce sont des notions de graphe.
• Il y a un sommet particulier : la racine.
• Il y a toujours un chemin entre deux sommets.
• Ce chemin est unique si on exclut les arêtes parcourures dans les deux sens.
Un arbre est un graphe (non orienté) connexe et acyclique avec
un sommet privilégié (s’il est non vide).
•
•
Connexe signifie qu’il y a toujours un chemin entre deux sommets.
Acyclique signifie qu’il n’y a pas de circuit c’est–à–dire de chemin non trivial
entre un sommet et lui–même.
•
•
Si on se donne un tel graphe la racine est le sommet privilégié.
Pour tout sommet il a un chemin unique entre la racine et ce sommet (sinon il y
aurait un cycle).
Le père d’un sommet distinct de la racine est l’avant dernier sommet dans ce
chemin.
Les fils d’un sommet s sont les sommets extrémités d’une arête d’origine s à
l’exclusion du père : on n’obtient jamais ni la racine ni un nœud qui aurait un
autre père en raison de l’acyclicité.
•
•
page 4
Arbres
II Hétérogènes
II.1 Introduction
On peut aussi imaginer un arbre comme la description d’une formule.
Dans ce cas il y a deux types d’objets
• des objets initiaux ou atomiques : dans l’arbre ils seront représentés aux extrémités, ce sont les feuilles
• des assemblages effectués par des opérations sur des objets déjà construits, ce
sont les nœuds de l’arbre.
Exemple : représentation parun arbre de 5 × (2 − e3 ) + (2 × sin(π)
+
×
×
−
5
2
2
sin
exp
π
3
II.2 Implémentation
On voit en fait qu’il y a plusieurs types de nœuds qui diffèrent selon leur arité, le
nombre de variables qu’ils modifient.
En général il n’y a que des opérateurs binaires : ∪, ou, ×, . . . ou unaires : complémentaire, non, ln, ...
type (’f,’n) arbre2 = Feuille of ’f
|Noeud1 of (’n * (’f,’n) arbre2)
|Noeud2 of ((’f,’n) arbre2 * ’n *(’f,’n) arbre2);;
Arbres
page 5
III Binaires
III.1 Définition
Un arbre binaire est un arbre dans lequel chaque nœud admet au plus 2 fils avec,
de plus, un ordre dans ceux–ci.
Il peut être défini de manière récursive :
un arbre binaire est
• soit l’arbre vide
• soit un arbre formé d’une racine et de deux fils, nommés fils droit et fils gauche,
qui sont eux–même des arbres binaires.
Remarque
Un arbre avec deux nœuds est défini de manière unique en tant qu’arbre général
p
f
mais admet deux structures d’arbre binaire :
p
f
p
f
III.2 Implémentation
type ’n arbre_bin =
Vide
|Noeud of (’n arbre_bin*’n *’n arbre_bin);;
On construit un arbre à partir de ses composantes en invoquant simplement le
constructeur.
Par exemple l’arbre d’entiers à un seul nœud, de valeur 5, est défini par
let a = Noeud(Vide,5,Vide);;
Un tel nœud est un nœud terminal.
page 6
Arbres
III.3 Fonctions élémentaires
Le traitement d’un arbre se fera, comme sa définition, récursivement ; on traite le
cas de l’arbre vide puis on séparera un arbre, en général en filtrant :
let filsG = function
|Vide -> raise(Failure "Arbre vide")
|Noeud (g,_,_) -> g;;
let filsD = function
|Vide -> raise(Failure "Arbre vide")
|Noeud (_,_,d) -> d;;
let racine = function
|Vide -> raise(Failure "Arbre vide")
|Noeud (_,n,_) -> n;;
III.4 Dénombrements
La taille d’un arbre est le nombre de nœuds :
let rec taille = function
|Vide -> 0
|Noeud (g,_,d) -> 1 + taille g + taille d;;
La profondeur d’un nœud est le nombre total de relations de parenté entre la racine
et lui.
• La racine est à une profondeur de 0, ses fils ont une profondeur de 1.
• La profondeur des fils d’un nœud est égale à la profondeur du nœud augmentée
de 1.
La hauteur d’un arbre est la profondeur de nœud maximale. Elle est atteinte pour
une nœud terminal.
Par convention la hauteur de l’arbre vide est -1.
let rec hauteur = function
|Vide -> -1
|Noeud (g,_,d) -> 1 + max (hauteur g)
Arbres
(hauteur d);;
page 7
III.5 Encombrement
Cas minimal
Quand chaque nœud non terminal n’a qu’un seul fils l’arbre est en fait une liste.
Si la taille de l’arbre est n il y a un nœud unique à chaque profondeur de 0 à n − 1
donc la hauteur est n − 1.
Pour tout autre arbre de hauteur n − 1 il y a au moins un nœud de plus donc la taille
est minorée pas la hauteur augmentée de 1.
Cas maximal
Chaque nœud d’un arbre binaire admet au plus deux fils donc, si ni est le nombre de
nœuds de profondeur i, on a ni+1 6 2ni .
De plus n0 = 1 donc ni 6 2i .
Si la hauteur est h la taille de l’arbre est donc majorée par 1 + 2 + · · · + 2h = 2h+1 − 1.
La taille n et la hauteur h d’un arbre binaire vérifient
h + 1 6 n 6 2h+1 − 1
On a alors log2 (n + 1) 6 h + 1 d’où h + 1 > dlog2 (n + 1)e.
Si 2 p 6 n 6 2 p+1 − 1 on a p < log2 (n + 1) 6 p + 1 donc dlog2 (n + 1)e = p + 1 puis
h > p.
Or l’entier p tel que 2 p 6 n 6 2 p+1 − 1 est p = blog2 (n)c d’où
La taille n et la hauteur d’un arbre binaire vérifient
blog2 (n)c 6 h 6 n − 1
III.6 Arbres parfaits
Un arbre binaire est parfait s’il est vide ou si, pour tout nœud, les hauteurs des
sous–arbres droit et gauche sont égales.
page 8
Arbres
•
•
Les arbres parfaits sont les arbres tels que
n = 2h+1 − 1 où h est la hauteur et n la taille.
Les arbres parfaits sont les arbres pour lesquels tous les nœuds terminaux sont à
la même profondeur.
La condition précédente sélectionne des arbres très compacts : ils contiennent le
maximum de nœuds pour une hauteur donnée.
On peut généraliser en considérant les arbres qui ont une hauteur minimale pour
une taille donnée ; c’est le cas, entre autres, des arbres dont les niveaux sont remplis
au maximum sauf le dernier.
Mais cette définition n’est pas très souple.
III.7 Arbre équilibré
Un arbre binaire est équilibré s’il est vide ou si, pour tout nœud, les hauteurs des
sous–arbres droit et gauche diffèrent de 1 au plus.
Pour construire un arbre équilibré de hauteur h il suffit d’assembler des arbres équilibrés de hauteur h − 1 tous les deux ou l’un de hauteur h − 1 et l’autre de hauteur
h − 2.
En particulier s’il existe un arbre équilibré de hauteur h − 1 et de taille n et un arbre
équilibré de hauteur h − 2 et de taille m alors il existe arbre équilibré de hauteur h et
de taille n + m + 1.
La hauteur h d’un arbre équilibré de taille n vérifie h = O ln(n)
Si on note fk le nombre minimal de nœuds dans un arbre équilibré de hauteur k on a
f0 = 1, f1 = 2 et, pour k > 2, fk = fk−1 + fk−2 + 1.
fk + 1 est alors égal au nombre de Fibonacci Fk+2 où (Fn ) définie par F0 = F1 = 1
et Fn+2 = Fn+1 + Fn .
Ainsi n > Fh+2 − 1 donc ln(n) > ln(Fh+2 − 1) ∼ Ah.
Arbres
page 9
III.8 Parcours
Parcourir un arbre (binaire) c’est donner une énumération de ses éléments. Cela
revient à transformer la structure bidimensionnelle de l’arbre en une liste linéaire.
Il y a plusieurs types de parcours selon que l’on privilégie la hierarchie de la hauteur (parcours en largeur) ou celle provenant de l’ordre des fils, dans ce dernier cas
on différenciera encore selon la position de la racine.
Pour parcourir (ou traiter) un arbre binaire en ordre on doit parcourir le fils gauche
avant le fils droit à chaque hauteur. Il reste à traiter la racine.
Il y a trois possibilités.
• Ordre préfixe : on traite la racine puis les fils. C’est l’écriture des composées de
fonctions de deux variables.
• Ordre infixe : on traite le fils gauche puis la racine puis le fils droit. C’est l’écriture des expressions arithmétiques.
• Ordre postfixe (ou suffixe) : on traite les fils puis la racine.
let infixe a =
let rec infx = function
|Vide -> print_string "-"
|Noeud (Vide,n,Vide) -> print_int n
|Noeud (g,n,d) -> print_string "(";
infx g;
print_string ",";
print_int n;
print_string ",";
infx d;
print_string ")" in
infx a; print_newline;;
let prefixe a =
let rec prefx = function
|Vide -> print_string "-"
|Noeud (Vide,n,Vide) -> print_int n
|Noeud (g,n,d) -> print_int n;
print_string "(";
prefx g;
print_string ",";
prefx d;
print_string ")" in
prefx a; print_newline;;
page 10
Arbres
let suffixe a =
let rec sufx = function
|Vide -> print_string "-"
|Noeud (Vide,n,Vide) -> print_int n
|Noeud (g,n,d) -> print_string "(";
sufx g;
print_string ",";
sufx d;
print_string ")";
print_int n in
sufx a; print_newline;;
Exemple
11
1
15
4
8
5
7
3
13
12
# infixe aa;; ((4, 15, (5, 8, 3)), 11, (7, 1, (12, 13, −)))
# prefixe aa;; 11(15(4, 8(5, 3)), 1(7, 13(12, −)))
# suffixe aa;; ((4, (5, 3)8)15, (7, (12, −)13)1)11
Arbres
page 11
IV Exercices
IV.1 Exemple
L’arbre A est
12
8
11
5
6
3
2
7
4
IV.2 Longueur de cheminement
La longueur de cheminement d’un arbre est la somme des hauteurs de tous les
nœuds.
Déterminer une fonction récursive qui calcule la longueur de cheminement d’un
arbre.
Prouver que les arbres de taille n qui minimisent la longueur de cheminement sont
ceux qui ont 2k nœuds de profondeur k pour tout k < blog2 (n + 1)c.
En déduire que, si LC est la longueur de cheminement d’un arbre de taille n, on a
n
X
n(n − 1)
blog2 (k)c 6 LC 6
.
2
k=1
IV.3 Arbres de Fibonacci
Écrire une fonction qui, pour tout n, renvoie un arbre équilibré de hauteur n et de
taille Fn − 1 où Fn est défini par
F0 = 1, F1 = 2, Fn+2 = Fn+1 + Fn
IV.4 Symétrisation
Écrire une fonction qui transforme un arbre en son symétrique.
Par exemple A devient
page 12
Arbres
12
8
7
11
2
6
4
5
3
IV.5 Hiérarchie
Prouver que le nœud a et un descendant du nœud b dans un arbre si et seulement si
nœud b est avant nœud a dans le parcours préfixe et nœud a est avant nœud b dans
le parcours postfixe.
IV.6 Reconstruction d’un arbre
Montrer que si on connait le parcours préfixe (ou postfixe) et le parcours infixe d’un
arbre alors on peut reconstituer l’arbre.
IV.7 Représentation binaire
On peut représenter la structure d’un arbre par la liste des entiers associés à chaque
nœud par
• la racine de l’arbre est associée à 1
• le fils gauche (resp. droit) d’un nœud associé à n, s’il existe, est associé à 2n
(resp. à 2n + 1).
Prouver que si on écrit les nombres en base 2 le parcours infixe d’un arbre consiste
à énumérer les nombre dans l’ordre lexicographique.
IV.8 Parcours en largeur
Caml définit le module Queue qui impémente la structure de file d’attente (FIFO)
d’une suite d’objets.
On peut le charger par open Queue;;.
Il contient les définitions suivantes
• le type ‘a t, type des files d’attentes
• la fonction is_empty : ’a t -> bool qui teste si une file est vide
• la fonction create : unit -> ’a t qui crée une file vide
• la fonction push : ’a -> ’a t qui ajoute un élément à une file
• la fonction pop : ’a t -> ’a qui retire et retourne le premier élément d’une
file.
Arbres
page 13
À l’aide de cette structure écrire une procédure qui parcourt un arbre en largeur.
A devra donner la suite (12,11,8,5,6,2,7,3,4).
IV.9 Profondeur minimale
Déterminer la profondeur minimale d’une feuille dans un arbre équilibré de hauteur
n.
IV.10 Tas
Un tas est un arbre d’entiers tel que la valeur de chaque nœud est strictement supérieure à celle de tous ses descendants.
Par exemple A est un tel arbre.
À toute suite d’entiers distincts, x, on associe le tas a par :
• si x est la suite vide (de longueur 0), alors a=Vide.
• si x = (x0 , . . . , xn−1 ) alors a est l’arbre dont la racine est le nœud de valeur
xk = max{xi ; 0 6 1 < n}, dont le sous–arbre gauche (resp. droit) est l’arbre
associé à la suite
x0 = (x0 , . . . , xk−1 ) (`resp. x00 = (xk+1 , . . . , xn−1 )`.
a) Dessiner l’arbre associé à la suite x = (3, 8, 1, 5, 9, 4, 7).
b) Écrire la fonction de conversion vecteur_de_tas qui reçoit un tas a et renvoie la suite x qui lui est associée.
Par exemple A donnerait (5, 11, 3, 6, 4, 12, 2, 8, 7).
On utilisera un parcours adéquat de l’arbre.
c) Écrire la fonction ajoute qui reçoit un entier t et le tas a, associé à la suite
(x0 , . . . , xn−1 ) et renvoie l’arbre associé à la suite (x0 , . . . , xn−1 , t) d’éléments
distincts.
d) Écrire la fonction tas_de_vecteur qui reçoit une suite x et renvoie le tas associé cette suite. (En partant de l’arbre vide, on ajoute successivement les xi .)
e) Écrire la fonction fusion qui reçoit les deux arbres a et b associés aux suites
x = (x0 , . . . , xn−1 ) et y = (y0 , . . . , ym−1 ) et renvoie l’arbre associé à la suite
concaténée
z = (x0 , . . . , xn−1 , y0 , . . . , ym−1 ) d’éléments distincts.
f) Écrire la fonction supprime qui reçoit un élément t et un arbre a associé à une
suite x = (x0 , . . . , xn−1 ) et qui renvoie a si t ne figure pas dans x ou renvoie
l’arbre associé à la suite
x = (x0, . . . , xk−1 , xk+1 , . . . , xn−1 ) si t = x[k].
page 14
Arbres
Téléchargement