Seules mes notes de cours sont autorisées.

publicité
Les arbres généraux
1. Introduction
Comme mentionné au chapitre précédent, un arbre est général si les nombre d’enfants est variable
pour les nœuds. Dans ce chapitre, nous allons, dans un premier temps, discuter des différentes
implantations de ces arbres, et, dans un deuxième temps, étudier une classe particulière d’arbres
généraux: les B-arbres.
légende :
-
Subtree rooted at V : sous-arbre enraciné à V
Children of V: enfants de V
Siblings of V: soeurs/frères de V
Root : racine
2. Différentes implantations
Avant de discuter des différentes implémentations d’un arbre général, regardons d’abord les
différentes opérations que nous pourrions effectuer dans un arbre. La première étant d’accéder à
la racine. Une autre est d’accéder aux enfants d’un nœud. Dans un arbre binaire, cette dernière
opération est possible en utilisant des fonctions qui donnent des accès explicites à l’enfant gauche
et enfant droit de ce nœud. Malheureusement, cette manière de procéder n’est pas possible dans
le cas d’un arbre général, pour la simple raison que le nombre d’enfants n’est pas connu à priori.
Une alternative est d’avoir une fonction qui prend en paramètre l’indice de l’enfant à visiter
combinée à une autre fonction qui retourne le nombre d’enfants d’un nœud donnée est capable de
supporter l’accès à un nœud donné ou traiter tous les nœuds. Cette manière de faire favorise
l’utilisation d’un tableau (pourquoi?). En pratique, une implémentation basée sur les pointeurs est
1
souvent préférée. Une autre alternative consiste à avoir accès à l’enfant le plus à gauche d’un
nœud, et une autre fonction qui a accès au frère/sœur de droite d’un nœud. Ce qui suit montre la
déclaration de classes pour les arbres généraux et leur nœuds basée sur cette approche.
// classe nœud pour arbre général
template <class Elem>
class GTNode {
public:
GTNode(const Elem&);
// Constructeur 1
GTNode(const Elem&, GTNode<Elem>*, GTNode<Elem>*, GTNode<Elem>*); //
Constructeur 2
~GTNode();
// Destructeur
Elem value();
// Retourne la valeur du noeud
// VRAI si le nœud est une feuille
bool isLeaf();
GTNode* parent();
// Retourne le parent
GTNode* leftmost_child(); // Retourne le premier enfant
GTNode* right_sibling(); // Retourne le frère de droite
void setValue(Elem&);
// Assigne une valeur au noeud
void insert_first(GTNode<Elem>* n); // insère comme premier enfant
void insert_next(GTNode<Elem>* n); // insère comme frère de droite
// Enlève le premier enfant
void remove_first();
void remove_next();
// Enlève le frère de droite
};
// Classe arbre general
class GenTree {
private
void printhelp(GTNode*); // print helper function
public:
GenTree(); // Constructeur
~GenTree(); // Destructeur
void clear(); // libère les noeuds
GTNode* root(); // Retourne la racine
void newroot(Elem, GTNode*, GTNode*); // fusionne deux arbres
};
2
Parcours d’un arbre général
template <class Elem>
void GenTree<Elem>::
printhelp(GTNode<Elem>* subroot) {
if (subroot->isLeaf()) cout << "Leaf: ";
else cout << "Internal: ";
cout << subroot->value() << "\n";
for (GTNode<Elem>* temp = subroot->leftmost_child(); temp != NULL;
temp = temp-> right_sibling())
printhelp(temp);
}
Réponse : R A C D E B F
2.1. Implantation par le parent : Chaque nœud contient une valeur et un pointeur à son parent.
Les pointeurs parents sont représentés par la position du parent dans le tableau. Cette
représentation est souvent utilisée quand on veut savoir si deux objets sont dans le même arbre et
aussi fusionner deux arbres en un seul arbre.
3
?
bool Gentree::differ(int a, int b) {
// les noeuds a et b sont-ils dans es arbres différents?
GTNode* root1 = FIND(&array[a]); // trouve la racine de a
GTNode* root2 = FIND(&array[b]); // trouve la racine de b
return root1 != root2;
// compare les raciness
}
class Gentree { // General tree: UNION/FIND
private:
GTNode* array; // Node array
int size; // Size of node array
GTNode* FIND(GTNode*) const; // trouver la racine
public:
Gentree(int); // Constructeur
~Gentree(); // Destructor
void UNION(int, int); // fusionner
bool differ(int, int); // TRUE si pas dans le même arbre
};
Gentree::Gentree(int sz) { // Constructeur
size = sz;
array = new GTNode[sz]; // Créer le noeud tableau
}
Gentree::~Gentree() { // Destructeur
delete [] array; // libérer le noeud tableau
}
bool Gentree::differ(int a, int b) {
// les noeuds a et b sont-ils dans des arbres différents ?
GTNode* root1 = FIND(&array[a]); // touver la racine de
GTNode* root2 = FIND(&array[b]); // trouver la racine de b
return root1 != root2; // Comparer les raciness
}
4
void Gentree::UNION(int a, int b) { // fusionner les sousarbres
GTNode* root1 = FIND(&array[a]); // trouver la racine de a
GTNode* root2 = FIND(&array[b]); // trouver la racine de b
if (root1 != root2) root2->par = root1; // fusionner
}
GTNode* Gentree::FIND(GTNode* curr) const {
while (curr->parent() != NULL) curr = curr->par;
return curr; // At root
}
2.2. Implantation par liste des enfants : La liste des nœuds est stockée dans un tableau. Chaque
élément de ce tableau contient, la valeur du nœud, un pointeur vers son parent et un autre
pointeur vers la liste de ses enfants de gauche vers la droite.
Remarque: la combinaison d’arbre avec cette implantation est plutôt difficile à réaliser si chaque
arbre est stocké dans un nœud tableau. Si les nœuds des arbres sont stockées dans un seul nœud
tableau, alors ajouter l’arbre T comme un sous arbre du nœud R est fait simplement en joutant la
racine de T à la liste des enfants de R
2.3. Implantation par enfant gauche – frère droit
L’inconvénient de l’implantation ci-dessus est qu’il est difficile d’accéder au nœud droit d’un
frère. La figure ci-dessous montre une implantation améliorée. Chaque élément du tableau stocke
la valeur du nœud et des pointeurs à son parent, l’enfant le plus à gauche et le frère droit . Donc,
chaque opération de ci-dessus peut être implantée en lisant la valeur directement du nœud.
5
Si deux arbres sont stockés dans le même nœud tableau, alors ajouter un arbre comme un sous
arbre de l’ordre exige la manipulation de pointeurs comme suit.
Union de deux arbres
2.4. Implantation par des pointeurs (1)
Les implantations qu’on vient de voir ci-dessous utilisent des tableaux pour stocker l’ensemble
mble des nœuds de l’arbre. À l’inverse d’un arbre binaire, le nombre d’enfants que possède un
noeud es variable. Si l’on désire procéder comme on a fait pour les arbres binaires, on est obligé
de mettre une limite sur le nombre d’enfants que peut avoir un noeud donné et allouer des
pointeurs pour exactement ce nombre d’enfants.
6
Deux inconvénients majeurs à cette implantation :
1. cette limite peut être telle que certains arbre ne pourront pas être implantée
2. une perte énorme d’espace étant donné que beaucoup de nœuds n’auront pas autant
d’enfants; ce qui va mettre ces pointeurs à NULL.
2.5. Implantation par des pointeurs (2)
Une autre alternative, plus flexible, est de stoker une liste chaînée des pointeurs des enfants d’un
nœud, comme illustré par la figure ci-dessous. Cette implantation et essentiellement similaire à
celle de la liste des enfants vue précédemment, à la différence que les nœuds sont allouées d’une
manière dynamique plutôt que de les stocker dans un tableau.
2.6. Implantation par des pointeurs (3)
L’implantation, par tableau, de ‘enfant gauche – frère droit de ci-dessus’ peut être facilement
adaptée à une implantation dynamique. En gros, on substitue un arbre binaire à un arbre général.
Chaque nœud de cet arbre va pointer vers l’enfant le plus gauche et le frère de droite. Comme on
peut le constater, à l’aide de cette implantation, nous traitons un arbre général comme un arbre
binaire. La conversion se fait comme ci-dessous. Plus que cela, nous pouvons même adapter cette
implantation à un ensemble d’arbres, puisque chaque racine peut être interprétée comme étant le
7
frère droit de la racine d’un autre arbre. Cette implantation est de loin la plus utilisée pour les
arbres généraux.
2.7. Conversion d’un arbre général en un arbre binaire
Remarquez que l’implantation enfant gauche - frère droit essentiellement stocke un arbre binaire.
Pour convertir d’une manière méthodique un arbre général en un arbre binaire, on procède
comme suit.
Pour chaque nœud de l’arbre
1. Laisser l’enfant gauche comme tel
2. Supprimer les autres enfants de ce nœud et les chaîner à l’enfant gauche
Exemple : un ensemble d’arbres généraux convertis en un arbre binaire
template <class Elem>
class GTNode {
private:
Elem element;
// Parent
GTNode<Elem>*parent;
GTNode<Elem>* leftchild; // Premier fils - fils gauche
GTNode<Elem>* rightsib; // Frère de droite
public:
GTNode(const Elem&);
// Constructeur
GTNode(const Elem&, GTNode<Elem>*, GTNode<Elem>*, GTNode<Elem>*);
~GTNode();
// Destructeur
Elem value();
// Retourne la valeur du noeud
bool isLeaf();
// VRAI si le nœud est une feuille
GTNode* parent();
// Retourne le parent
GTNode* leftmost_child(); // Retourne le premier enfant
GTNode* right_sibling(); // Retourne le frère de droite
void setValue(Elem&);
// Assigne une valeur au noeud
8
void insert_first(GTNode<Elem>* n); // insère comme premier enfant
void insert_next(GTNode<Elem>* n); // insère comme frère de droite
void remove_first();
// Enlève le premier enfant
void remove_next();
// Enlève le frère de droite
};
template <class Elem>
GTNode<Elem>::GTNode(const Elem& value) {
parent = leftchild = rightsib = NULL;
element = value;
}
template <class Elem>
GTNode<Elem>::GTNode(const Elem& value, GTNode<Elem>* par,
GTNode<Elem>* leftc, GTNode<Elem>* rights) {
element = value;
parent = par; leftchild = leftc; rightsib = rights;
}
template <class Elem>
GTNode<Elem>::~GTNode() { }
template <class Elem>
Elem GTNode<Elem>::value() { return element; }
template <class Elem>
bool GTNode<Elem>::isLeaf() { return leftchild == NULL; }
template <class Elem>
GTNode<Elem>* GTNode<Elem>::parent() { return rent; }
template <class Elem>
GTNode<Elem>* GTNode<Elem>::leftmost_child() { return leftchild; }
template <class Elem>
GTNode<Elem>* GTNode<Elem>::right_sibling() { return rightsib; }
template <class Elem>
void GTNode<Elem>::setValue(Elem& value) { element = value; }
template <class Elem>
void GTNode<Elem>::insert_first(GTNode<Elem>* n) {
n->rightsib = leftchild;
n->parent = this;
leftchild = n;
}
9
template <class Elem>
void GTNode<Elem>::insert_next(GTNode<Elem>* n) {
n->rightsib = rightsib;
n->parent = parent;
rightsib = n;
}
template <class Elem>
void GTNode<Elem>::remove_first() {
if (leftchild == NULL) return;
GTNode<Elem>* temp = leftchild;
leftchild = leftchild->rightsib;
delete temp; // pas bon – perd son sous arbre!
}
template <class Elem>
void GTNode<Elem>::remove_next() {
if (rightsib == NULL) return;
GTNode<Elem>* temp = rightsib;
rightsib = rightsib->rightsib;
delete temp; // pas bon -- Perd son sous-arbre!
}
2.8. Implantation séquentielle
On considère dans cette section une approche complètement différente pour implanter des arbres.
Le but est de stocker une série de valeurs des nœuds avec un minimum d’informations nécessaire
à la reconstruction de l’arbre. Cette approche est connue sous le nom de implantation séquentielle
des arbres. Elle a l’avantage de sauvegarder de l,espace car aucun pointeur n’est sauvegardé.
Toutefois, son désavantage est de ne pouvoir accéder un nœud qu’après avoir traité tous les nœud
qui apparaissent avant ce nœud dans la liste. Autrement dit, l’avantage que nous avons eu sur
toutes les implantations discutées dans le chapitres précédent est perdue, à savoir un accès en
O(log n).
Cette implantation est idéale pour archiver des fichiers dans un disque pour un usage ultérieur car
elle sauvegarde de l’espace, et l’arbre peut être reconstruit par la suite.
La méthode consiste à énumérer les nœuds dans l’ordre qu’ils apparaissent lors d’un recherche en
pré fixe.
10
Exemple: Pour les arbres binaires, on utilise un symbole / pour indiquer un lien nul:
AB/D//CEG///FH//I//
AB/D//CEG///FH//I//
Pour reconstruire l’arbre, on commence par mettre le nœud A comme racine. L’enfant gauche de
A est B. L’enfant gauche de B est NULL. Donc le nœud D doit être l’enfant droit de B. Le nœud
D a deux enfant NULL,. Donc le nœud C l’enfant droit de A, … etc.
Pour montrer la difficulté d’utiliser cette implantation, considérons la recherche de l’enfant droit
de la racine. Nous devons traiter avant tous les noeuds du sous-arbre gauche de cette racine.
Cette implantation doit reconnaître quand un nœud est une feuille ou interne. Une manière de
procéder est de lister explicitement un nœud : feuille ou interne.
A’B’/DC’E’G/F’HI
Cette approche nécessite un bit additionnel pour indiquer si le nœud est une feuille ou interne.
Cependant, stoker n bits est beaucoup que de stocker n valeurs NULLS
Implémenter les arbres généraux à l’aide de l’implémentation séquentielle nécessite plus
d’informations à inclure dans un nœud : feuille ou interne et nombre d’enfants. Dans l’exemple
suivant, le symbole parenthèse ) indique la fin de la liste d’enfant d’un noeud. Toutes les feuilles
11
sont suivies d’un ). Une feuille qui est aussi le dernier enfant pour ses parents va indiquer ceci par
deux ou plus symboles )
RAC)D)E))BF)))
12
3. Les B-Arbres
Parmi les les arbres génaux, on en distingue une classe particulière qui est celle des B-arbres.
3.1. Introduction
Les algorithmes de recherche, sur les très grandes collections de données, sont très différents de
tout les algorithmes précédents. Tous les algorithmes précédents étaient dédiés à une utilisation
en mémoire primaire.
La recherche dans de gros fichier sur disque ne peut donc pas utiliser les algorithmes précédents,
pourtant très rapides. Étrangement, les algorithmes utilisés pour ce genre de tâches, peuvent
retrouver un élément de données sur un disque d'une capacité allant jusqu'a 3 milliards de mots en
seulement 2 ou 3 accès disques.
La mémoire secondaire est considérée comme beaucoup plus lente que la mémoire principale et
comme étant paginée; c'est a dire découpée en pages pouvant contenir chacune un certain nombre
d'éléments. La page est l’unité de transfert entre la mémoire centrale et le disque. Un seul accès
au disque prend environ autant de temps que 200 000 instructions. La structure de données que
nous introduisons ci-dessus est utilisée dans le but de réduire le nombre d’accès au disque.
Un B-arbre d'ordre m est un arbre de recherche formé de noeuds qui peuvent chacun contenir
jusqu'a 2m éléments. Chaque noeud est dans une page différente du disque, et la complexité des
algorithmes de recherche, adjonction et suppression d'un élément parmi n, est comptée en nombre
d'accès à la mémoire secondaire est, comme on le verra plus loin, en O(logm n).
Définition: Un B-arbre d'ordre m est un arbre ayant les propriétés suivantes
•
•
•
Chaque nœud a un maximum de 2m et un minimum de m éléments, excepté la racine qui
peut contenir un minimum de un élément.
Un noeud est soit une feuille (sans enfant) ou un noeud interne avec k éléments et k+1
enfants ( m ≤ k ≤ 2m )
If X 1 , X 2 ,..., X k sont ces noeuds et P0 , P1 ,..., Pk les enfants, alors :
- P0 contient les éléments ≤ X 1
- P1 contient les éléments > X 1 et ≤ X 2
- …………………
- Pi contient les éléments > X i −1 et ≤ X i
……..
- Pk contient les éléments > X k
13
• Toutes les feuilles sont au même niveau.
Le sous arbre le plus à gauche contient toutes les valeurs inférieures à v1, le 2ème sous-arbre
contient celles comprises entre v1 et v2, et ainsi de suite.
3.2 Opérations de base sur un B-arbre
3.2.1) Recherche d’un élément
La recherche dans un B-arbre est une généralisation de la recherche dans un arbre binaire de
recherche Noter que l’on doit lire le nœud cherché en mémoire secondaire, puis faire une
recherche (qui peut être dichotomique, pourquoi?) dans le nœud lui-même pour accéder à
l’enregistrement recherché.
14
Pseudo-code :
1. ki < x < ki+1 pour 0< i <m : x est compris entre les clés ki et ki+1,
auquel cas on continue la recherche sur l’enfant pi
2. km < x : x est plus grande que toutes les clés du nœud,
auquel cas on continue la recherche sur l,enfant pm
3. x < k1 : x est plus petite que toutes les clés du noeud,
auquel cas on continue la recherche sur l’enfant indiquée par p0.
3.2.2 Insertion et suppression :
Les ajouts et suppressions d'éléments entraînent des opérations d'éclatement et de
regroupement des sommets. Avant toute chose, il est nécessaire de retrouver (à l’aide
de la fonction de recherche) la place où il faut insérer l’élément où l’élément en question.
a. Insertion
L’insertion dans un B-arbre est plus complexe que l’insertion dans un ABR. Elle utilise
l’opération auxiliaire de découpage d’un nœud complet, que nous allons d’abord décrire.
On rappelle qu’un nœud complet contient 2t clés. Après insertion, ce nombre devient impair. Le
nœud possède donc une clé médiane. Le découpage consiste à éclater le nœud complet en deux
nœuds, autour de cette clé médiane. La clé médiane (avec son enregistrement) remonte dans le
nœud père, qui ne doit pas être complet avant le découpage. Chacun des deux nouveaux nœuds
contient donc exactement t clés, le minimum requis. Si le nœud complet découpé était la racine
(et n’a donc pas de père) une nouvelle racine va être créée pour recueillir la clé médiane, et la
hauteur va augmenter de 1. C’est ainsi que croît le B-arbre: par la racine. La figure 11.2 illustre
l’opération de découpage d’un nœud complet différent de la racine. La figure 11.3 illustre le cas
du découpage de la racine. La figure 11.4 montre une succession d’insertions dans le même Barbre. On montre que l’insertion dans un B-arbre nécessite O(h) = O(log n) accès à des nœuds.
Remarque: certains auteurs définissent les bornes de chaque nœud, pour un arbre de degré t,
comme étant t-1 et 2t-1 (comme c’est le cas ci-dessous)
15
16
Algorithme d’insertion
1. chercher le nœud où le nouvel élément doit être inséré
2. insérer l'élément dans son noeud
3. si débordement
partager le bloc en deux blocs à moitié pleins
si (le noeud n'est pas la racine)
insérer de la même manière la valeur médiane dans le noeud de niveau supérieur où elle
servira de critère de séparation (cette insertion peut elle-même créer une séparation
dans les noeuds supérieurs)
sinon créer un nouveau noeud racine pour y mettre cette valeur médiane.
17
b. Suppression d’un élément
C’est une opération relativement complexe, que nous ne détaillerons pas ici. On peut deviner
cependant que la plupart du temps elle nécessite uniquement la lecture des nœuds d’un chemin
jusqu’à une feuille, parce que la plupart des clés se trouvent dans les feuilles. Si tel n’est pas le
cas, on recherche l’élément le plus grand à la gauche de cet élément (où l’élément le plus petit à
la droite de cet élément) qui ne peut se trouver que dans une feuille. On remplace l’élément à
supprimer par cet élément qui lui doit être supprimé de ce nœud terminal Dans ce qui suit, nous
allons donc supposer, sans perdre de généralité, que le nœud à supprimer est dans une feuille.
La suppression dans un B-arbre s’effectue également en O(h) = O(log n) accès à des nœuds. Pour
retrouver la clé proprement, une recherche s’impose dans le nœud lui-même.
18
Algorithme de suppression
1. chercher le noeud s où se trouve l'élément à supprimer
2. enlever l'élément du noeud
3. si le noeud contient moins que m (l’ordre de cet arbre) éléments
si (nb. élt d'un noeud adjacent t + nb élt de s < m)
- fusionner s, t et l'élément du noeud supérieur e qui les séparait en un noeud u
- enlever e du noeud supérieur
- remplacer les deux références à s et t par une seule référence à u
- si (le noeud supérieur contient moins que m éléments)
appliquer récursivement le même traitement, sauf si c'est la racine
19
sinon
- effectuer une répartition égale des éléments entre s et un noeud adjacent t,
- mettre à jour l'élément séparateur dans le noeud supérieur.
3.3. Complexité
Comme nous venons de le voir, le coût des opérations d’insertion, suppression et recherche est
proportionnel à la hauteur de l’arbre. Dans ce qui suit, nous montrons que la hauteur d’un B-arbre
est en O (log m n) . En effet, nous savons que le nombre de clés, n, dans l’arbre vérifie la relation
suivante:
n ≥ minimum #nœuds du B-arbre de hauteur h et de degré minimum m
Le minimum #noeuds signifie que la racine à une seule clé (deux enfants) et les autres
nœuds en ont m (minimum) clés chacun (comme illustré sur la figure ci-dessous).
20
niveau 1
1 clé
m clés
m clés
niveau 2
m+1
branches
m clés
Dans ce cas, donc, nous avons une seul nœud d’une clé au niveau 1, 2 nœuds de m clés au 2ème
niveau, 2(m + 1) nœuds de m valeurs chacun au niveau 3 (c’est-à-dire 2m(m + 1) valeurs), ….,
2(m + 1) h − 2 nœuds de m clés chacun au niveau h (c’est-à-dire 2m(m + 1) h − 2 Par conséquent, le
nombre minimum de nœuds dans l’arbre est la somme des clés sur chaque niveau:
h
n ≥ 1 + m∑ 2(m + 1) i − 2
i =2
h−2
n ≥ 1 + m∑ 2(m + 1) i
i =1
 (m + 1) h −1 − 1 
 (série géométrique de raison (m+1));
n ≥ 1 + 2m
m


21
en mettant le 1 du coté de n, en divisant par 2 et le log des deux cotés de cette inégalité, on
obtient :
 n +1
log
 ≥ (h − 1) log(m + 1)
 2 
 n + 1
log m +1 
 ≥ (h − 1)
 2 
 n + 1
h ≤ log m+1 
 +1
 2 
h = O (log m n)
Il est facile de déduire la complexité des opérations de recherche, d’insertion et de suppression.
Dans tous les cas, ces complexités sont proportionnelles à la hauteur de l’arbre. Une fois, le nœud
trouvé, il y a lieu de parcourir la liste des valeur existant dans ce nœud. Comme les valeur dans
chaque nœud sont triées, on pourra appliquer l’algorithme de dichotomie pour retrouver cette
valeur en O (log m) (ou en O(m) si on applique la recherche séquentielle). Comme le coût de
l’opération de recherche est dominé par la recherche du nœud (page) représentant les accès au
disques, on dira que les complexité de ces algorithme sont en O (log m n) .
Pour l’opération d’insertion, dans le pire cas, après avoir trouvé l’endroit où il faut insérer
l’élément, on remontera l’arbre, d’éclatement en éclatement de nœud jusqu’à créer
éventuellement une nouvelle racine. Ce parcours n’excédera pas la hauteur de l’arbre + 1.
Autrement dit, la complexité de l’insertion est en O (log m n) .
Pour la suppression, dans le pire cas, après avoir trouvé l’emplacement de l’élément à supprimer,
on remontera l’arbre, de regroupement en regroupement de nœuds jusqu’à éventuellement la
racine. Dans tous les cas, ce parcours n’excédera pas la hauteur de l’arbre. Autrement dit, la
complexité de la suppression est en O (log m n) .
3.4. Variantes du B-arbre
1. B+-arbre: Dan un a B+ arbres, les données sont enregistrées dans les feuilles. Les nœuds
internes contiennent juste des clés qui sont utilisées pour diriger la recherche vers la
feuille considérée. Par ailleurs, les données sont chacune contenues dans une feuille et
sont chaînées entre-elles de telle manière que ces dernières peuvent être toutes traversées.
La suppression se trouve ainsi facilitée car elle se fait sur les feuilles.
2. B*-arbre: Similaire aux B-arbres, seulement les noeuds sont occupés au moins au 2/3.
3. Compact B-arbre: Similaire aux B-arbres, seulement les nœuds sont occupés à au
moins 90%.
4. Arbres 2-3 : Ce sont des B-arbres particuliers de degrés 1. Autrement dit, chaque nœud
contient 1 à 2 clés et 2 ou 3 enfants.
22
5. Arbre 2-3-4 : Pour éviter le cas de la dégénérescence de l'arbre, nous allons augmenter
les directions de recherche en autorisant les noeuds à devenir des 4 noeuds; c'est à dire un
noeud possédant au maximum 4 noeuds enfants et donc 3 clés.
Les clés à l'intérieur d'un noeud sont toujours classées selon le principe des arbres binaires
de recherche. Les noeuds enfants se soumettent à cette propriété aussi
.
23
Téléchargement