Arbre bouc-émissaire

De testwiki
Aller à la navigation Aller à la recherche

Modèle:À déjargoniser Modèle:À désangliciser Un arbre bouc-émissaire, en informatique, plus précisément en algorithmique, est un arbre binaire de recherche auto-équilibrant. Ce type d'arbre a été inventé en 1989 par Arne Andersson[1], puis réinventé en 1993 par Igal Galperin et Ronald L. Rivest[2]. Contrairement à la plupart des autres arbres auto-équilibrants, l'arbre bouc-émissaire se restructure plus rarement. Ainsi, la structure de l'arbre se déséquilibre peu à peu, jusqu'au moment où l'algorithme désigne un nœud bouc-émissaire, désigné comme responsable du déséquilibre. On y effectue alors un rééquilibrage pour que l'arbre retrouve une structure satisfaisante.

Intérêt

Le principal intérêt de l'arbre bouc-émissaire concerne sa complexité spatiale. Il s'agit du premier arbre binaire de recherche dont les opérations sont en O(log(n)) en moyenne où n est le nombre de nœuds, et qui ne prend pas plus de mémoire qu'un arbre binaire de recherche. En effet, contrairement aux arbres bicolores et AVL qui stockent dans les nœuds des informations supplémentaires (telles que sa couleur ou sa hauteur), le bouc-émissaire ne garde en mémoire que l'étiquette du nœud et ses deux pointeurs vers ses enfants. Ainsi, cet arbre est plus économe en mémoire.

Exemple

Insertion

Considérons l'arbre suivant, où le nœud 4.4 vient tout juste d'être inséré. Cet arbre est un arbre binaire de recherche : chaque nœud est plus grand que tous les nœuds de son sous-arbre gauche et plus petit que les nœuds dans son sous-arbre droit. Si un arbre possède 11 nœuds, on dira qu'il est de taille 11.

Exemple d'un arbre devant être rééquilibré.

Recherche du nœud bouc-émissaire

On choisit une constante d'équilibrage, par exemple α=2/3. Le nœud (4.4) est à une profondeur 6. Comme 6>log3/2(11), notre arbre n'est donc pas 2/3-équilibré. Un rééquilibrage est nécessaire. Pour cela, nous allons trouver le nœud responsable du déséquilibre : le nœud bouc-émissaire. On définit la taille d'un nœud comme étant la taille du sous-arbre enraciné sur ce nœud. La condition de bouc-émissaire est taille(enfant(u))>αtaille(u).

Dans l'exemple précédent, on insère le nœud 4.4, et on remonte de parent en parent pour trouver le bouc-émissaire.

  • taille(4.4)=143=23taille(4.2) donc le nœud (4.2) n'est pas le bouc-émissaire.
  • taille(4.2)=22=23taille(4.5) donc le nœud (4.5) n'est pas le bouc-émissaire.
  • taille(4.5)=34=23taille(4) donc le nœud (4) n'est pas le bouc-émissaire.
  • taille(4)=6>143=23taille(6) donc le nœud (6) est le bouc-émissaire.

La section théorique explique plus en détail ce processus.

Rééquilibrage

Une fois que le bouc-émissaire a été trouvé, on rééquilibre tout son sous-arbre. Ici, le nœud (6) a été choisi, donc les 7 éléments de son sous-arbre sont placés comme dans un arbre binaire de recherche. On observe que ceci réduit sa profondeur : l'arbre entier est mieux équilibré.

Arbre bouc-émissaire rééquilibré

Théorie

Dans toute cette partie, un arbre sera la donnée d'un nœud noté "e" et de ses enfants gauche "g" et droite "d". La taille d'un nœud e, notée taille(e) est le nombre de nœuds qu'il y a dans l'arbre enraciné en ce nœud.

Définitions

Un arbre binaire de recherche est dit équilibré s'il y a autant de nœuds à droite de la racine qu'à gauche. On relâche cette notion en introduisant deux définitions paramétrées. Un nœud e est un nœud α-équilibré en poids si

taille(g)<αtaille(e);taille(d)<αtaille(e).

Un arbre α-équilibré en poids est alors un arbre n'ayant que des nœuds α-équilibrés en poids. De façon similaire, on dira qu'un arbre est α-équilibré en hauteur si :hauteur(arbre)log1α(taille(arbre)).

Propriétés

Propriété 1. Un arbre α-équilibré en poids est également α-équilibré en hauteur.

Modèle:Boîte déroulante/début Par définition, nous avons pour chaque nœud e, de fils gauche g et de fils droit d : taille(e)1αmax(taille(g),taille(d))

Ainsi, par récurrence sur la taille de l'arbre, on constate que :

  • Pour un arbre de taille 1, la propriété est vérifiée
  • Pour un arbre de taille n+1, nous avons alors: (n+1)1αmax(taille(d),taille(g))

Or, par hypothèse de récurrence, comme g et d sont de tailles strictement inférieure à n+1, taille(g)1αhauteur(g) et taille(d)1αhauteur(d)

Donc finalement: (n+1)1αmax(1αhauteur(g),1αhauteur(d))

(n+1)1α(1αhauteur(e)1)

D'où finalement: (n+1)1αhauteur(e)

Le principe de récurrence conclut. Modèle:Boîte déroulante/fin

Ainsi, par contraposée, un arbre bouc-émissaire étant le plus petit arbre non α-équilibré en poids, il ne peut être un arbre α-équilibré en hauteur (c'est d'ailleurs un premier critère discriminatoire dans la recherche de notre bouc-émissaire). Cependant, on dispose de la propriété suivante.

Propriété 2. Un arbre bouc-émissaire est presque α-équilibré en hauteur : hauteur(arbre) log1α(taille(arbre))+1.

Modèle:Boîte déroulante/début La démonstration repose sur le fait que notre arbre bouc émissaire est celui de plus petite hauteur violant la propriété α-équilibré en poids.

De fait, en notant h la hauteur de cet arbre, et en supposant - sans perdre de généralité - que son fils de plus grande taille est le droit, on a :

h-1 log1α(taille(d))

(En effet, dans le cas contraire, on aurait le fils droit qui briserait également la propriété α-équilibré en poids tout en étant de hauteur strictement inférieure à celle de notre bouc-émissaire: absurde !)

D'où finalement, par croissance du logarithme (taille(arbre) > taille(d)):

h log1α(taille(arbre)) + 1 Modèle:Boîte déroulante/fin

Ceci constitue donc un premier critère discriminatoire dans la recherche de notre arbre bouc-émissaire.

De plus, cette propriété montre que la hauteur d'un arbre bouc-émissaire est majorée, à l'instar des arbres rouge-noir. La principale différence provient du moment où s'effectue le rééquilibrage: l'arbre bouc-émissaire le fait à la première rencontre avec un nœud non α-équilibré en poids.

Dans la suite, nous considérons α ]1/2,1[fixé. Nous appellerons "nœud profond" tout nœud de profondeur supérieure à log1α(taille(arbre)). C'est la détection de ces derniers qui enclenche le processus de rééquilibrage.

Opérations

Dans cette section, nous détaillons les opérations de recherche, d'insertion et de suppression dans un arbre bouc-émissaire[3]. On note n = taille(A), où A désigne l'arbre bouc-émissaire sur lequel on effectue les opérations. La valeur de n est stockée. Au cours de l'insertion et la suppression, on met à jour la valeur de n.

Recherche

L'algorithme de recherche est le même que pour un arbre binaire de recherche classique. Ainsi, la complexité dans le pire des cas est en O(log(n)).

Insertion

Le principe de l'insertion repose sur la mémorisation de la profondeur à laquelle le nouveau nœud est inséré. On commence par insérer aux feuilles l'élément x. On regarde récursivement si l'élément doit être ajouté à gauche ou à droite du nœud et on incrémente à chaque étape la valeur de la profondeur d d'insertion. On vérifie si l'arbre ainsi construit est α-équilibré en taille en testant que d log1α(n)). Si ce n'est pas le cas, un rééquilibrage est nécessaire.

On cherche alors le sous-arbre bouc émissaire dont la racine va subir le rééquilibrage. Celle-ci est un ancêtre du nœud inséré et n'est pas α-équilibré en poids (il y aura toujours un tel nœud).

Modèle:Boîte déroulante/début En effet, par l'absurde, si x est un nœud profond n'ayant que des parents α-équilibrés en poids, on a pour son parent y :

taille(x) α*taille(y)

Et donc, par récurrence sur le chemin entre x et la racine, on a :

taille(x) αprofondeur(x)*taille(arbre)

Nécessairement, la profondeur de x est au plus de log(1α)(taille(arbre)) (car αlog1α(n)*n=1, donc une plus grande profondeur induirait que x est de taille nulle), ce qui est contradictoire avec le fait que x soit un nœud profond. Modèle:Boîte déroulante/fin

Ce nœud peut être trouvé en remontant de parent en parent à partir du nœud inséré. Le rééquilibrage d'un tel sous-arbre assure la propriété d'α-équilibré en taille dans tout l'arbre.

Modèle:Boîte déroulante/début Si un arbre binaire contient un nœud profond x0, alors l'ancêtre de x0 le plus profond xi qui n'est pas α-équilibré en hauteur n'est pas non plus α-équilibré en poids.

En effet, un tel nœud xi vérifie :

hauteur(xi)=i>log(1α)(taille(xi))

hauteur(xi1)=i1log(1α)(taille(xi1))

Ainsi, en soustrayant les deux inégalités :

1 > log1α(taille(xi))log1α(taille(xi1))=log1α(taille(xi)/taille(xi1)

D'où, en élevant à la puissance 1α :

1α>taille(xi)/taille(xi1)

taille(xi1)>alpha*taille(xi)

D'où le résultat. Modèle:Boîte déroulante/fin

L'intérêt de remonter ainsi dans l'arbre pour trouver le nœud bouc-émissaire ne vérifiant pas les conditions

  • taille(enfant gauche) < α*taille(nœud)
  • taille(enfant droite) < α*taille(nœud)

est que nous disposons déjà des tailles d'un des enfants et du nœud.

Il y a dès lors moins de calcul à effectuer. On sait que le cas de base (la hauteur du nœud inséré qui est une feuille) est égal à 1. Il suffit alors de remonter l'arbre comme décrit précédemment, en calculant la taille de chaque nouveau nœud xi+1 comme suit:

taille(xi+1) = taille(xi) + taille(frère de xi) + 1

où seule la taille du frère de xi reste à déterminer.

Une fois le nœud bouc-émissaire localisé, on rééquilibre l'arbre dont il est racine (en le remplaçant par un autre arbre contenant les mêmes noeuds mais parfaitement α-balancé). Une manière de faire est de parcourir les noeuds de l'arbre en les triant par valeur, puis choisir récursivement la valeur médiane comme racine du nouvel arbre. Ceci s'effectue en O(taille(xi)) en temps.

Suppression

Contrairement à la plupart des autres types d'arbre, la suppression est plus simple que l'insertion. Pour ce faire, il faut garder en mémoire une valeur en plus de la structure d'arbre, que nous appellerons MaxNoeudsComptés. Il s'agit du plus grand nombre de noeuds que l'arbre a pu compter depuis le dernier rééquilibrage complet de l'arbre.

Ainsi, lorsqu'il y a un rééquilibrage, MaxNoeudsComptés est réinitialisé à la taille de l'arbre. À chaque insertion, MaxNoeudsComptés = max(MaxNoeudsComptés,n+1).

Ensuite, pour supprimer le nœud, on fait comme dans n'importe quel arbre binaire, puis on regarde si:

n α*MaxNoeudsComptés

Si c'est le cas, alors on rééquilibre entièrement l'arbre, en réinitialisant MaxNoeudsComptés.

Complexité

En ignorant les opérations de rééquilibrage et de recherche d'un arbre bouc-émissaire, les complexités de l'insertion, suppression et recherche sont proportionnelles à la hauteur de l'arbre. Elles sont en O(log(n)).

Étudions la part de complexité qui n'est pas encore prise en compte.

Lemme. lors de l'insertion d'un élément, le coût pour trouver le nœud bouc-émissaire w et le reconstruire est O(taille(w)).

Modèle:Boîte déroulante/début Le coût de reconstruction du sous arbre de racine w est O(taille(w)). Lorsque l'on cherche le nœud bouc-émissaire, on remonte de père en père depuis le nœud ajouté x0 et on appelle successivement taille(xi) jusqu'à avoir xi=w. Comme w est le premier nœud de cette séquence qui ne soit pas alpha-équilibré en poids, on a pour tout i compris entre 0 et k-2 taille(xi) < α*taille(xi+1).

Ainsi le coût total des appels à taille(xi) est:

O(i=0ktaille(xki)=O(taille(xk)+i=0k1taille(xki1) =O(taille(xk)+i=0k1αi*taille(xk)=O(taille(xk)(1+i=0k1αi)) =O(taille(xk))=O(taille(w))

Modèle:Boîte déroulante/fin

Théorème. En partant d'un arbre vide, une séquence de m opération d'insertion ou suppression a une complexité amortie de O(nlog(n)).

Modèle:Boîte déroulante/début On va prouver cette complexité amortie avec un système de crédit. On image que chaque nœud va conserver des crédits qui correspondent à un certain temps constant c. Ce crédit de temps pourra être utilisés pour les opérations de reconstruction et de recherche de bouc émissaire.

Lors d'une insertion ou d'une suppression, on ajoute un crédit à chaque nœud parcouru sur le chemin emprunté et un crédit supplémentaire est mis "de côté" à chaque suppression. On "paye" ainsi O(mlog(m)). Il reste à vérifier que ce crédit est suffisant pour compenser les opérations non prises en compte (à savoir les rééquilibrages).

Lorsqu'on doit reconstruire le bouc émissaire s=(w,g,d) où w racine, g sous arbre de gauche et d sous arbre de droite. On a (sans perte de généralité) taille(g)>alpha*taille(s) . Or taille(s)=1+taille(g)+taille(d).

On a donc taille(g)>α*taille(d)1α . Enfin (2α1)taille(s)(taille(g)taille(s)). La dernière fois qu'un sous arbre contenant s a été reconstruit, on avais taille(g)taille(s)1. On a donc fait entre-temps (2α1)taille(s)1 opérations affectant cet arbre et on a donc autant de crédit pour payer O(taille(w)) et reconstruire s.

Si on reconstruit l'arbre lors du suppression, on a n α*MaxNoeudsComptés. On a alors mis de côté MaxNoeudsComptés-n (α1)n crédits qui permettent de payer la reconstruction en O(n). Modèle:Boîte déroulante/fin

Étymologie

Le nom d'arbre bouc-émissaire est Modèle:Citation[4].

Voir également

Notes et références

Modèle:Références

Liens externes

Modèle:Palette

Modèle:Portail