Arbre bouc-émissaire

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

modifier

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

modifier

Insertion

modifier

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

modifier

On choisit une constante d'équilibrage, par exemple  . Le nœud (4.4) est à une profondeur 6. Comme  , 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  .

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.

  •   donc le nœud (4.2) n'est pas le bouc-émissaire.
  •   donc le nœud (4.5) n'est pas le bouc-émissaire.
  •   donc le nœud (4) n'est pas le bouc-émissaire.
  •   donc le nœud (6) est le bouc-émissaire.

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

Rééquilibrage

modifier

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

modifier

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

modifier

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

  

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 : .

Propriétés

modifier

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

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)  .

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 à  . C'est la détection de ces derniers qui enclenche le processus de rééquilibrage.

Opérations

modifier

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

modifier

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  .

Insertion

modifier

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  ). 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).

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.

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   comme suit:

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

où seule la taille du frère de   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( )) en temps.

Suppression

modifier

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é

modifier

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  .

É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)).

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  .

Étymologie

modifier

Le nom d'arbre bouc-émissaire est « basé sur la sagesse populaire selon laquelle, dès que quelque chose ne va pas, la première chose que les gens auront tendance à faire est de trouver quelqu'un à blâmer pour cela (le bouc-émissaire). Une fois ce blâme clairement établi, on peut laisser au bouc-émissaire le soin de résoudre le problème »[4].

Voir également

modifier

Notes et références

modifier
  1. Arne Andersson « Improving partial rebuilding by using simple balance criteria » () (DOI 10.1007/3-540-51542-9_33, CiteSeerx 10.1.1.138.4859, lire en ligne)
    Proc. Workshop on Algorithms and Data Structures
    « (ibid.) », Journal of Algorithms, Springer-Verlag,‎ , p. 393–402
  2. Igal Galperin et Ronald L. Rivest « Scapegoat trees » () (CiteSeerx 10.1.1.309.9376, lire en ligne)
    « (ibid.) », Proceedings of the fourth annual ACM-SIAM Symposium on Discrete algorithms, Philadelphia, Society for Industrial and Applied Mathematics,‎ , p. 165–174
  3. Igor Galperin, On Consulting a Set of Experts and Searching (lire en ligne), « Chapter 3 - Scapegoat Trees »
  4. Pat Morin, Open Data Structures (in pseudocode), 0.1G β (lire en ligne), « Chapter 8 - Scapegoat Trees »

Liens externes

modifier