web-dev-qa-db-fra.com

Pourquoi std :: map est-il implémenté en tant qu'arbre rouge-noir?

Pourquoi est-ce std::map implémenté en tant que arbre rouge-noir ?

Il existe plusieurs équilibrés arbres de recherche binaires (BST). Quels ont été les compromis de conception dans le choix d'un arbre rouge-noir?

173
Denis Gorodetskiy

Les deux algorithmes les plus courants d’auto-équilibrage sont probablement arbres rouge-noir et arbres AVL . Pour équilibrer l’arbre après une insertion/mise à jour, les deux algorithmes utilisent la notion de rotation dans laquelle les nœuds de l’arbre sont pivotés pour effectuer le rééquilibrage.

Alors que dans les deux algorithmes, les opérations d'insertion/suppression sont O (log n), dans le cas de la rotation de rééquilibrage de l'arbre rouge-noir, l'opération O (1)O (log n) opération, ce qui rend l’arbre rouge-noir plus efficace dans cet aspect de la phase de rééquilibrage et constitue l’une des raisons possibles de son utilisation plus courante.

Les arbres rouge-noir sont utilisés dans la plupart des bibliothèques de collections, y compris les offres de Java et Microsoft .NET Framework.

112
Chris Taylor

Cela dépend vraiment de l'utilisation. AVL arbre a généralement plus de rotations de rééquilibrage. Ainsi, si votre application ne comporte pas trop d'opérations d'insertion et de suppression, mais pèse lourdement sur la recherche, l'arborescence AVL est probablement un bon choix.

std::map utilise l’arbre rouge-noir car il permet de faire un compromis raisonnable entre la vitesse d’insertion/suppression de nœud et la recherche.

43
webbertiger

Les arbres AVL ont une hauteur maximale de 1,44logn, alors que les arbres RB ont un maximum de 2logn. L'insertion d'un élément dans une AVL peut impliquer un rééquilibrage en un point de l'arborescence. Le rééquilibrage termine l'insertion. Après l'insertion d'une nouvelle feuille, la mise à jour des ancêtres de cette feuille doit être effectuée jusqu'à la racine, ou jusqu'à un point où les deux sous-arbres ont la même profondeur. La probabilité d'avoir à mettre à jour k nœuds est de 1/3 ^ k. Le rééquilibrage est O (1). La suppression d'un élément peut impliquer plus d'un rééquilibrage (jusqu'à la moitié de la profondeur de l'arbre).

Les arbres RB sont des arbres B d'ordre 4 représentés comme des arbres de recherche binaires. Un nœud à 4 nœuds dans le B-tree entraîne deux niveaux dans le BST équivalent. Dans le pire des cas, tous les nœuds de l’arbre sont 2 nœuds, avec une seule chaîne de 3 nœuds jusqu’à une feuille. Cette feuille sera à une distance de 2logn de la racine.

En descendant de la racine au point d'insertion, il faut changer 4 nœuds en 2 nœuds pour s'assurer que toute insertion ne sature pas une feuille. En revenant de l'insertion, tous ces nœuds doivent être analysés pour s'assurer qu'ils représentent correctement 4 nœuds. Cela peut aussi être fait en descendant dans l'arbre. Le coût global sera le même. Il n'y a pas de repas gratuit! La suppression d'un élément de l'arborescence est du même ordre.

Tous ces arbres exigent que les nœuds portent des informations sur la hauteur, le poids, la couleur, etc. Seuls les arbres Splay sont exempts de telles informations supplémentaires. Mais la plupart des gens ont peur des arbres Splay, à cause de la lourdeur de leur structure!

Enfin, les arbres peuvent également transporter des informations de poids dans les nœuds, permettant ainsi un équilibrage de poids. Différents schémas peuvent être appliqués. On doit rééquilibrer lorsqu'un sous-arbre contient plus de trois fois le nombre d'éléments de l'autre sous-arbre. Le rééquilibrage est à nouveau effectué par une rotation simple ou double. Cela signifie un pire cas de 2.4logn. On peut s'en tirer avec 2 fois au lieu de 3, un rapport bien meilleur, mais cela peut signifier de laisser un peu moins de 1% des sous-arbres déséquilibrés ici et là. Rusé!

Quel type d'arbre est le meilleur? AVL à coup sûr. Ils sont les plus simples à coder et ont leur plus mauvaise hauteur au plus près de logn. Pour un arbre de 1000000 éléments, un AVL sera au maximum de hauteur 29, un RB 40 et un poids équilibré 36 ou 50 en fonction du rapport.

Il y a beaucoup d'autres variables: caractère aléatoire, taux d'ajouts, de suppressions, de recherches, etc.

24
user847376

Les réponses précédentes ne traitent que des alternatives d'arborescence et le rouge noir ne reste probablement que pour des raisons historiques.

Pourquoi pas une table de hachage?

Un type nécessite uniquement < comparaison à utiliser comme clé dans un arbre. Cependant, les tables de hachage exigent que chaque type de clé ait une fonction hash définie. Garder les exigences de type au minimum est très important pour la programmation générique.

Concevoir une bonne table de hachage nécessite une connaissance intime du contexte dans lequel il sera utilisé. Devrait-il utiliser un adressage ouvert ou un chaînage lié? Quels niveaux de charge doit-il accepter avant de redimensionner? Devrait-il utiliser un hachage coûteux qui évite les collisions ou qui soit grossier et rapide?

Étant donné que la STL ne peut prévoir quel est le meilleur choix pour votre application, la valeur par défaut doit être plus flexible. Les arbres "fonctionnent" et mesurent bien.

(C++ 11 a ajouté des tables de hachage avec unordered_map. Vous pouvez voir dans la documentation il faut définir des stratégies pour configurer plusieurs de ces options.)

Qu'en est-il des autres arbres?

Red Black Tree offre une recherche rapide et s'équilibre automatiquement, contrairement aux BST. Un autre utilisateur a souligné ses avantages par rapport à l'arborescence AVL à auto-équilibrage.

Alexander Stepanov (le créateur de STL) a déclaré qu'il utiliserait un arbre B * au lieu d'un arbre rouge-noir s'il écrivait std::map encore une fois, car il est plus convivial pour les caches de mémoire modernes.

L'un des plus grands changements depuis lors a été la croissance des caches. Les oublis de mémoire cache sont très coûteux, de sorte que la localité de référence est beaucoup plus importante maintenant. Les structures de données basées sur les nœuds, qui ont une faible localité de référence, ont beaucoup moins de sens. Si je concevais STL aujourd'hui, j'aurais un ensemble de conteneurs différent. Par exemple, un arbre B * en mémoire est un bien meilleur choix qu'un arbre rouge-noir pour l'implémentation d'un conteneur associatif. - Alexander Stepanov

Devriez-vous toujours utiliser un arbre noir rouge ou B *?

En d'autres occasions, Alex a déclaré que std::vector est presque toujours le meilleur conteneur de liste pour des raisons similaires. Il est rarement logique d'utiliser std::list ou std::deque même pour les situations qui nous ont été enseignées à l’école (telles que le retrait d’un élément du milieu de la liste). std::vector est si rapide qu'il bat ces structures pour tout sauf les grands N.

En appliquant ce raisonnement, si vous n’avez qu’un petit nombre d’éléments (des centaines?) Utilisant un std::vector et la recherche linéaire peut être plus efficace que l'implémentation arborescente de std::map. Selon la fréquence d’insertion, un std::vector combiné avec std::binary_search peut être le choix le plus rapide.

17
Justin Meiners

Mise à jour du 14/06/2017: webbertiger modifie sa réponse après mon commentaire. Je dois souligner que sa réponse est maintenant bien meilleure à mes yeux. Mais j'ai gardé ma réponse à titre d'information supplémentaire ...

En raison du fait que je pense que la première réponse est fausse (correction: plus les deux à la fois) et que la troisième a une fausse affirmation. Je sens que je devais clarifier les choses ...

Les 2 arbres les plus populaires sont AVL et Red Black (RB). La principale différence réside dans l'utilisation:

  • AVL: Mieux si le ratio de consultation (lecture) est supérieur à la manipulation (modification). L'empreinte mémoire est un peu inférieure à RB (en raison du bit requis pour la coloration).
  • RB: Mieux dans les cas généraux où il y a un équilibre entre consultation (lecture) et manipulation (modification) ou plus modification par rapport à la consultation. Une empreinte mémoire légèrement plus importante due au stockage du drapeau rouge-noir.

La principale différence vient de la coloration. Vous avez moins d’action de rééquilibrage dans l’arborescence RB que AVL car la coloration vous permet parfois d’omettre ou de raccourcir les actions de rééquilibrage qui ont un coût relativement élevé. En raison de la coloration, l’arborescence RB a également un plus grand nombre de nœuds, car elle peut accepter des nœuds rouges entre des nœuds noirs (ayant la possibilité d’environ 2x plus de niveaux), ce qui rend la recherche (lecture) un peu moins efficace ... mais parce que c’est un constant (2x), il reste dans O (log n).

Si vous considérez la performance frappée par une modification d'arbre (significative) par rapport à la consultation d'une arborescence (presque insignifiante), il devient naturel de préférer RB à AVL pour un cas général.

3
Eric Ouellet

C'est juste le choix de votre implémentation - elles pourraient être implémentées comme n'importe quel arbre équilibré. Les différents choix sont tous comparables avec des différences mineures. Par conséquent, tout est aussi bon que tout.

2
necromancer