Lycée La Martinière Monplaisir
Option Informatiqe 2eannée Arbres B
Arbres B
Judicaël Courant 2018-W37-4
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ètretentier (avect ≥2), un arbre-B de degré minimaltest un arbre où chaque nœud autre que la racine contient au moins tclé, et au plus2t.
On appellera ce paramètreparamet 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 recherchen-aires.
Plus précisément, les arbres-B que nous considérons seront décrit par un type Caml(’k, ’v) btreereprésentant des arbres contenant des clés de type’kauxquelles 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 externexcontient un tableausxdenx valeurs, ainsi qu’un tableaukx denx clés, triées par ordre croissant. Pour touti∈[[0, nx[[, la valeursx[i]est la valeur associée à la clékx[i].
3. Chaque nœud internexcontient un tableausxdenxsous-arbres, ainsi qu’un tableaukxde nx−1clés, triées par ordre croissant, telles que pour touti∈[[0, nx[[,kx[i]est supérieur ou égal aux éléments desx[i]et inférieur ou égal à ceux desx[i+ 1]. On garantit de plus qu’il y a au moins une valeur associée à la clékx[i]danssx[i].
4. Pour tout nœudx,nx≤2t.
5. Pour tout nœudx,nx≥t, sauf peut-être pour la racine.
6. Toutes les feuilles ont même profondeur.
Judicaël Courant - 2018-W37-4 1/4 Document sous licence Art Libre (http://artlibre.org)
Lycée La Martinière Monplaisir
Option Informatiqe 2eannée Arbres B
2 Principe de fonctionnement
Le principe de la recherche dans un arbre-B est assez simple :
— Pour rechercher une clékdans une feuillex, on effectue une recherche séquentielle de cette clé dans le tableaukx. Si on la trouve à un indicei, alors la valeur cherchée estsx[i].
— Pour rechercher une clékdans un nœudx, on cherche le plus petititel quek≤kx[i]et on recherche la clékdans le sous-arbresx[i](on posei=nxsikest supérieur à toutes les clés).
L’ajout d’une association entre une cléket une valeurvfonctionne 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 endroitidans le tableaukxinsérer cette clé et on insère la clé enidans kxet enidanssx.
Pour pouvoir effectuer plus facilement les insertions, les tableaux dans les nœuds seront en fait tous de taille2t, et l’entiernxdonnant le nombre d’éléments du tableau utilisés (lesnxpremiers éléments du tableau) sera stocké dans le nœud. Ainsi, pour insérer une nouvelle clé à l’indicei, il suffira de décaler les éléments initialement contenus danskxà partir de l’indicei.
Le seul problème qui se pose est lorsque tous les éléments dekx(etsx) sont utilisés, c’est-à-dire lorsquenx= 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 , ’ v ) b t r e e = {
mutable k e y s : ’ k v e c t ; mutable s i z e : i n t ;
mutable v a l s : ( ’ k , ’ v ) i n t e x t ; }
( ∗ L e s 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 =
| V a l u e s of ’ v v e c t
| S o n s 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 ( ) = {
s i z e = 0 ; k e y s = [ | | ] ;
v a l s = V a l u e s [ | | ] ; }
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 ?
Judicaël Courant - 2018-W37-4 2/4 Document sous licence Art Libre (http://artlibre.org)
Lycée La Martinière Monplaisir
Option Informatiqe 2eannée Arbres B
2. Écrire une fonction
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 queinsert_vect n u i v décale les élémentsu[i], . . . , u[n−1]d’un cran vers la droite et remplaceu[i]parv.
3. Un nœud est plein si son champsizevaut2t. Écrire une fonction i s _ f u l l : ( ’ k , ’ v ) b t r e e −> b o o l
retournanttruesi son argument est plein.
4. Écrire une fonctionlookup : ’a vect -> int -> ’a -> inttelle que(lookup t n k) retourne le plus petit indicei < ntel quek≤t[i], ounsi un telin’existe pas. Lesnpremiers éléments detsont supposés triés par ordre croissant.
5. Écrire une fonctionfind : (’k, ’v) btree -> ’k -> ’vretournant la valeurvasso- ciée àkdans l’arbre donné en argument et levant l’exceptionNot_founds’il n’y en a pas.
6. Quelle est la complexité definddans 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 r e c a d d _ a u x a k v = match a . v a l s with
| V a l u e s 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 ∗ )
| S o n s s −>
l e t i = l o o k u p a . k e y s ( a . s i z e −1) k in a d d _ a u x 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 feuillesf1etf2. Il s’agit alors, dans le tableausxdu pèrexde la feuille, de remplacer la feuillefpar les deux nouvelles feuillesf1etf2. Cela pose évidemment problème si le nœud pèrexest lui-même plein, auquel cas, il convient de le séparer en deux nouveaux nœudsx1 etx2 et, dans le grand-père, de remplacerxparx1 etx2, à 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œudsn1etn2et il convient de créer un nouveau nœud racine ndont les fils sontn1etn2. Toutes les feuilles voient alors leur profondeur augmenter de1.
La mise en œuvre de cette méthode pose cependant un problème pratique : dans le code de add_auxpré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 deadd_auxdevient alors :
Judicaël Courant - 2018-W37-4 3/4 Document sous licence Art Libre (http://artlibre.org)
Lycée La Martinière Monplaisir
Option Informatiqe 2eannée Arbres B
( ∗ 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 r e c a d d _ a u x a k v = match a . v a l s with
| V a l u e s 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 ∗ )
| S o n s s −>
l e t i = l o o k u p 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 c e 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 jusqu ’ 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 c e 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 ) ∗ )
e l s e
a d d _ a u x 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 deadd.
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 laquellenx≥tennx ≥t−1).
5 Culture
Quel est l’intérêt d’avoir des arbresn-aires avecnélevé lorsqu’on manipule un gros volume de données ne tenant pas en mémoire vive ?
Judicaël Courant - 2018-W37-4 4/4 Document sous licence Art Libre (http://artlibre.org)