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