TP sur les arbres B

Transcription

TP sur les arbres B
Lycée La Martinière Monplaisir
Option Informatique MPSI
Année 2016-2017
Option Informatique 2e année
TP 1: Arbres B
Judicaël Courant
29 août 2016
1 Introduction
Les arbres B (B trees) sont une structure d’arbres équilibrés bien adaptés au stockage de
gros volumes de données sur disque ou en mémoire. Étant donné un paramètre t entier (avec
t ≥ 2), un arbre-B de degré minimal t est un arbre où chaque nœud autre que la racine contient
au moins t clé, et au plus 2t.
On appellera ce paramètre param et on le fixera à 3 :
l e t param = 3 ; ;
En pratique, il est fixé à de grandes valeurs, de l’ordre de plusieurs centaines ou milliers.
Ces arbres sont appelés arbres-B en hommage à Bayer et McCreight, les premiers chercheurs
ayant étudiés des arbres de recherche n-aires.
Plus précisément, les arbres-B que nous considérons seront décrit par un type Caml (’k,
’v) btree représentant des arbres contenant des clés de type ’k auxquelles sont associées des
valeurs de type ’v.
On respectera les contraintes suivantes :
1. Chaque nœud d’un arbre-B est soit un nœud interne, soit un nœud externe. Les valeurs
à trouver ne sont pas stockées dans les nœuds internes mais seulement dans les feuilles
de l’arbre.
2. Chaque nœud externe x contient un tableau sx de nx valeurs, ainsi qu’un tableau kx
de nx clés, triées par ordre croissant. Pour tout i ∈ [[0, nx [[, la valeur sx [i] est la valeur
associée à la clé kx [i].
3. Chaque nœud interne x contient un tableau sx de nx sous-arbres, ainsi qu’un tableau
kx de nx − 1 clés, triées par ordre croissant, telles que pour tout i ∈ [[0, nx [[, kx [i] est
supérieur ou égal aux éléments de sx [i] et inférieur ou égal à ceux de sx [i+1]. On garantit
de plus qu’il y a au moins une valeur associée à la clé kx [i] dans sx [i].
4. Pour tout nœud x, nx ≤ 2t.
5. Pour tout nœud x, nx ≥ t, sauf peut-être pour la racine.
6. Toutes les feuilles ont même profondeur.
2 Principe de fonctionnement
Le principe de la recherche dans un arbre-B est assez simple :
Judicaël Courant- 29 août 2016
1/4
Document sous licence Creative Commons
cba
Lycée La Martinière Monplaisir
Option Informatique MPSI
Année 2016-2017
Option Informatique 2e année
— Pour rechercher une clé k dans une feuille x, on effectue une recherche séquentielle de
cette clé dans le tableau kx . Si on la trouve à un indice i, alors la valeur cherchée est
sx [i].
— Pour rechercher une clé k dans une feuille x, on cherche le plus petit i tel que k ≤ kx [i] et
on recherche la clé k dans le sous-arbre sx [i] (on pose i = nx si k est supérieur à toutes
les clés).
L’ajout d’une association entre une clé k et une valeur v fonctionne de la façon suivante :
— On recherche de la même façon la feuille dans laquelle on va insérer cette clé et cette
feuille. On recherche à quel endroit i dans le tableau kx insérer cette clé et on insère la
clé en i dans kx et en i dans sx .
Pour pouvoir effectuer plus facilement les insertions, les tableaux dans les nœuds seront en
fait tous de taille 2t, et l’entier nx donnant le nombre d’éléments du tableau utilisés (les nx
premiers éléments du tableau) sera stocké dans le nœud. Ainsi, pour insérer une nouvelle clé à
l’indice i, il suffira de décaler les éléments initialement contenus dans kx à partir de l’indice i.
Le seul problème qui se pose est lorsque tous les éléments de kx (et sx ) sont utilisés, c’està-dire lorsque nx = 2t. On verra que l’on peut remédier à cette situation, dès la descente dans
l’arbre, en partageant en deux les nœuds trop gros.
On décrira les arbres-B par le type Caml suivant :
type ( ’ k ,
mutable
mutable
mutable
}
’v) btree = {
keys : ’ k vect ;
size : int ;
vals : ( ’k , ’v) intext ;
(∗ Les i n f o s a s s o c i é e s aux c l é s : s i z e v a l e u r s ou s i z e sous−a r b r e s . ∗)
and ( ’ k , ’ v ) i n t e x t =
| Values of ’ v v e c t
| Sons of ( ’ k , ’ v ) b t r e e v e c t
;;
(∗ empty : u n i t −> ( ’ k , ’ v ) b t r e e
c ’ e s t l e s e u l a r b r e s u r l e q u e l on a u t o r i s e r a l e s champs k e y s e t
v a l à c o n t e n i r d e s t a b l e a u x de t a i l l e d i f f é r e n t e de 2∗ param ∗)
l e t empty ( ) = {
size = 0;
keys = [ | | ] ;
vals = [ | | ] ;
}
3 Travail demandé
1. Donnez un majorant et un minorant de la hauteur d’un arbre-B en fonction du nombre
d’éléments qu’il contient. Quel est l’intérêt d’un arbre-B par rapport à des arbres binaires
de recherche ? Par rapport à des arbres de recherche équilibrés ?
2. Écrire une fonction
Judicaël Courant- 29 août 2016
2/4
Document sous licence Creative Commons
cba
Lycée La Martinière Monplaisir
Option Informatique MPSI
Année 2016-2017
Option Informatique 2e année
i n s e r t _ v e c t : i n t −> ’ a v e c t −> i n t −> ’ a −> u n i t
telle que insert_vect n u i v décale les éléments u[i], . . . , u[n − 1] d’un cran vers la
droite et remplace u[i] par v.
3. Un nœud est plein si son champ size vaut 2t. Écrire une fonction
i s _ f u l l : ( ’ k , ’ v ) b t r e e −> b o o l
retournant true si son argument est plein.
4. Écrire une fonction lookup : ’a vect -> int -> int -> int telle que (lookup t n k)
retourne le plus petit indice i < n tel que k ≤ t[i], ou n si un tel i n’existe pas. Les n
premiers éléments de t sont supposés triés par ordre croissant.
5. Écrire une fonction find : (’k, ’v) btree -> ’k -> ’v retournant la valeur v associée à k dans l’arbre donné en argument et levant l’exception Not_found s’il n’y en a
pas.
6. Quelle est la complexité de find dans le cas le pire ?
7. Pour insérer un élément dans un arbre-B, on peut penser écrire la fonction suivante :
(∗ add_aux : ( ’ k , ’ v ) b t r e e −> ’ k −> ’ v −> u n i t ∗)
l e t rec add_aux a k v =
match a . v a l s with
| Values t −>
(∗ i n s é r e r l a v a l e u r dans l e t a b l e a u t
e t l a c l é dans l e t a b l e a u a . k e y s ∗)
| Sons s −>
l e t i = lookup a . k e y s ( a . s i z e −1) k in
add_aux s . ( i ) k v
;;
Ce code est très simple et fonctionne bien... tant qu’aucune feuille n’est pleine ! Si une
feuille f est pleine, on ne peut plus rien y insérer. On la partage alors en deux pour
obtenir deux nouvelles feuilles f1 et f2 . Il s’agit alors, dans le tableau sx du père x de
la feuille, de remplacer la feuille f par les deux nouvelles feuilles f1 et f2 . Cela pose
évidemment problème si le nœud père x est lui-même plein, auquel cas, il convient de le
séparer en deux nouveaux nœuds x1 et x2 et, dans le grand-père, de remplacer x par x1
et x2 , à condition que le grand-père ne soit pas plein, sinon on effectue encore un partage
et on remonte récursivement.
Notez que cette méthode ne change rien à la hauteur de l’arbre sauf dans un cas : celui
où les partages remontent jusqu’à la racine et où celle-ci est elle-même pleine. La racine
est alors partagée en deux nouveaux nœuds n1 et n2 et il convient de créer un nouveau
nœud racine n dont les fils sont n1 et n2 . Toutes les feuilles voient alors leur profondeur
augmenter de 1.
La mise en œuvre de cette méthode pose cependant un problème pratique : dans le code de
add_aux présenté, on descend dans l’arbre sans espoir de pouvoir remonter aux parents
en cas de partage. On peut imaginer garder trace des parents mais c’est relativement
pénible à faire.
Une solution plus maligne est de partager un nœud dès qu’on constate qu’il est plein,
sans attendre d’être obligé de le faire. Le code de add_aux devient alors :
Judicaël Courant- 29 août 2016
3/4
Document sous licence Creative Commons
cba
Lycée La Martinière Monplaisir
Option Informatique MPSI
Année 2016-2017
Option Informatique 2e année
(∗ a j o u t e l a l i a i s o n ( k , v ) à l ’ a r b r e −B a .
E f f e c t u e l e s p a r t a g e s d e s noeuds p l e i n s r e n c o n t r é s l o r s de
son p a s s a g e pour ê t r e s û r d ’ ê t r e en mesure d ’ e f f e c t u e r
l ’ i n s e r t i o n demand é e .
Pr é c o n d i t i o n : l e noeud r a c i n e de a n ’ e s t pas p l e i n .
∗)
l e t rec add_aux a k v =
match a . v a l s with
| Values t −>
(∗ i n s é r e r l a v a l e u r dans l e t a b l e a u t
e t l a c l é dans l e t a b l e a u a . k e y s ∗)
| Sons s −>
l e t i = lookup a . k e y s ( a . s i z e −1) k in
i f i s _ f u l l s . ( i ) then
(∗ peut−ê t r e que l ’ i n s e r t i o n dans ce f i l s ne va pas
né c e s s i t e r d e s p a r t a g e s remontant j u s q u ’ i c i , mais
pour é v i t e r t o u t p r o b l ème , on p a r t a g e ce noeud
pr é v e n t i v e m e n t , on r e m p l a c e s . ( i ) par l e s deux
noeuds r é s u l t a n t du p a r t a g e e t on i n s è r e dans
l ’ un d e s deux ( l e bon ) ∗)
else
add_aux s . ( i ) k v
;;
Ce code fonctionne très bien, à condition que le nœud racine lui-même ne soit pas plein.
Sinon, que se passe t-il ? Complétez le code de add.
8. Pour résoudre ce dernier problème, on écrit une fonction d’ajout
add : ( ’ k , ’ v ) −> ’ k −> ’ v −> u n i t
qui gère le cas où la racine est pleine ainsi que le cas où l’arbre est vide.
4 Si vous vous ennuyez
Étudiez les problèmes liés à la suppression d’un couple (k, v) dans un arbre-B et programmez
une fonction de suppression (on pourra relaxer la contrainte selon laquelle nx ≥ t en nx ≥ t−1).
5 Culture
Quel est l’intérêt d’avoir des arbres n-aires avec n élevé lorsqu’on manipule un gros volume
de données ne tenant pas en mémoire vive ?
Judicaël Courant- 29 août 2016
4/4
Document sous licence Creative Commons
cba