Il semble que partout où je regarde, les structures de données sont mises en œuvre en utilisant des arbres rouges-noirs ( std::set
en C ++, SortedDictionary
en C #, etc.)
Après avoir couvert (a, b), les arbres rouge-noir et AVL dans mon cours d’algorithme, voici ce que j’ai dégagé (aussi en interrogeant des professeurs, en parcourant quelques livres et en recherchant un peu sur Google):
- La profondeur moyenne des arbres AVL est inférieure à celle des arbres rouge-noir. Par conséquent, la recherche d’une valeur dans l’arbre AVL est toujours plus rapide.
- Les arbres rouge-noir apportent moins de changements structurels pour s'équilibrer que les arbres AVL, ce qui pourrait les rendre potentiellement plus rapides pour l'insertion / suppression. Je dis potentiellement, car cela dépendrait du coût de la modification structurelle de l’arbre, car cela dépendra beaucoup du temps d’exécution et de l’implémentation (pourrait aussi être complètement différent dans un langage fonctionnel lorsque l’arbre est immuable?)
Il existe de nombreux points de repère en ligne qui comparent les arbres AVL et les arbres rouge-noir, mais ce qui m'a frappé est que, selon mon professeur, vous feriez généralement l'une des deux choses suivantes:
- Soit vous ne vous souciez pas vraiment de la performance. Dans ce cas, la différence de 10 à 20% entre AVL et Rouge-Noir dans la plupart des cas n’aura aucune importance.
- Ou bien vous vous souciez vraiment de la performance, dans laquelle vous abandonneriez à la fois les arbres AVL et rouge-noir, et utilisiez les arbres B, qui peuvent être modifiés pour fonctionner beaucoup mieux (ou (a, b)-arbres, je ' Je vais tous les mettre dans le même panier.)
La raison en est qu'un B-tree stocke les données de manière plus compacte dans la mémoire (un nœud contient plusieurs valeurs), le nombre d'erreurs dans le cache sera beaucoup moins important. Vous pouvez également modifier l'implémentation en fonction du cas d'utilisation et rendre l'ordre de l'arborescence B dépend de la taille du cache de la CPU, etc.
Le problème est que je ne trouve pratiquement aucune source capable d'analyser l'utilisation réelle de différentes implémentations d'arbres de recherche sur du matériel réel et moderne. J'ai parcouru de nombreux ouvrages sur les algorithmes et rien trouvé qui puisse comparer différentes variantes d'arborescence, à part montrer que l'une a une profondeur moyenne inférieure à l'autre (ce qui ne dit pas vraiment comment l'arbre se comportera dans de vrais programmes.)
Cela dit, y a-t-il une raison particulière pour laquelle les arbres rouge-noir sont utilisés partout, alors que sur la base de ce qui est dit ci-dessus, les arbres B devraient les surpasser? (en tant que seul point de référence que j'ai pu trouver, montre également les spectacles http://lh3lh3.users.sourceforge.net/udb.shtml , mais il pourrait s'agir simplement d'une mise en œuvre spécifique). Ou est-ce la raison pour laquelle tout le monde utilise des arbres rouge-noir parce qu'ils sont plutôt faciles à mettre en œuvre, ou pour le dire autrement, difficiles à mettre en œuvre de manière médiocre?
Aussi, comment cela change-t-il lorsque l'on passe au royaume des langages fonctionnels? Il semble que Clojure et Scala utilisent des essais mappés sur des tableaux de hachage , où Clojure utilise un facteur de branchement de 32.
la source
Réponses:
Pour citer la réponse à la question « Traversées de la racine dans des arbres AVL et des arbres noirs et rouges »
Ainsi, une insertion d'arborescence RedBlack peut être implémentée sans récursion. Sur certains processeurs, la récursivité est très coûteuse si vous surchargez le cache des appels de fonctions (par exemple, SPARC en raison de l'utilisation de la fenêtre Enregistrer ).
(J'ai vu un logiciel fonctionner 10 fois plus vite sur le Sparc en supprimant un appel de fonction, ce qui rendait un chemin de code souvent appelé trop profond pour la fenêtre de registre. Comme vous ne savez pas à quelle profondeur la fenêtre de registre sera active le système de votre client, et vous ne savez pas à quelle distance de la pile d’appels vous vous trouvez dans le "chemin du code chaud", le fait de ne pas utiliser la récursivité est plus prévisible.)
De plus, ne pas risquer de tomber à court de pile est un avantage.
la source
J'ai également fait des recherches sur ce sujet récemment, alors voici mes conclusions, mais n'oubliez pas que je ne suis pas un expert en structures de données!
Dans certains cas, vous ne pouvez pas utiliser d'arbres B du tout.
Un cas important est celui
std::map
de C ++ STL. La norme exige queinsert
ne pas invalider les itérateurs existantshttp://en.cppreference.com/w/cpp/container/map/insert
Cela exclut B-tree en tant qu'implémentation car l'insertion déplacerait les éléments existants.
Un autre cas d'utilisation similaire est celui des infrastructures de données intrusives. C'est-à-dire qu'au lieu de stocker vos données dans le nœud de l'arborescence, vous stockez les pointeurs vers les enfants / parents dans votre structure:
Vous ne pouvez tout simplement pas créer une intrusion dans le B-tree, car ce n'est pas une structure de données avec un pointeur uniquement.
Des arbres rouge-noirs intrusifs sont utilisés, par exemple, dans jemalloc pour gérer des blocs de mémoire libres. C'est également une structure de données populaire dans le noyau Linux.
Je pense également que l'implémentation "récursive en un seul passage" n'est pas la raison de la popularité de l'arbre noir rouge en tant que structure de données modifiable .
Tout d’abord, la profondeur de la pile n’a aucune importance ici, car (étant donné height), vous manqueriez de mémoire principale avant de manquer d’espace de pile. Jemalloc est satisfait de la préallocation de la profondeur de cas la plus défavorable sur la pile.logn
Il existe un certain nombre de types d'implémentation d'arborescence rouge-noir. Robert Sedgewick a laissé des arbres noirs et rouges penchés ( attention, il existe d’autres variantes nommées également "penchées à gauche", mais qui utilisent un algorithme différent). Cette variante permet certes d’effectuer des rotations en descendant dans l’arbre, mais elle manque de la propriété importante de nombre de corrections amorties, ce qui la ralentit ( telle que mesurée par l’auteur de jemalloc ). Ou, comme opendatastrutures metO(1)
La variante décrite dans opendatastructures utilise des pointeurs parents, une passe descendante récursive pour l’insertion et une passe en boucle itérative pour les corrections. Les appels récursifs sont dans une position de queue et les compilateurs l'optimisent en boucle (j'ai vérifié cela dans Rust).
En d'autres termes, vous pouvez obtenir une implémentation en boucle de mémoire constante d'un arbre de recherche mutable sans magie rouge-noir si vous utilisez des pointeurs parents. Cela fonctionne aussi pour les arbres B. Vous avez besoin de magie pour la variante immuable récursive en un seul passage, et elle corrigera de toute façon la correction .O(1)
la source
Eh bien, ce n'est pas une réponse qui fait autorité, mais chaque fois que je dois coder un arbre de recherche binaire équilibré, c'est un arbre rouge-noir. Il y a plusieurs raisons à cela:
1) Le coût moyen d'insertion est constant pour les arbres rouge-noir (si vous n'avez pas à chercher), alors que c'est logarithmique pour les arbres AVL. En outre, cela implique tout au plus une restructuration compliquée. C’est toujours O (log N) dans le pire des cas, mais ce n’est que de simples changements de couleur.
2) Ils n’exigent que 1 bit d’information supplémentaire par nœud, et vous pouvez souvent trouver le moyen de l’obtenir gratuitement.
3) Je n'ai pas à le faire très souvent, alors chaque fois que je le fais, je dois trouver le moyen de le faire à nouveau. Les règles simples et la correspondance avec 2-4 arbres le rendent facile à chaque fois , même si le code s'avère compliqué à chaque fois . J'espère toujours qu'un jour le code deviendra simple.
4) La manière dont l'arborescence rouge-noire divise le nœud d'arbre 2-4 correspondant et insère la clé centrale dans le nœud parent 2-4 par simple recoloration est extrêmement élégante. J'aime juste le faire.
la source
Les arbres rouge-noir ou AVL ont un avantage sur les arbres B et autres lorsque la clé est longue ou pour une autre raison que le déplacement d'une clé coûte cher.
J'ai créé ma propre alternative à
std::set
un projet majeur, pour un certain nombre de raisons de performance. J'ai choisi AVL au lieu de rouge-noir pour des raisons de performances (mais cette petite amélioration des performances n'était pas une justification pour lancer la mienne au lieu de std :: set). La "clé" étant compliquée et difficile à déplacer était un facteur important. Les arbres (a, b) sont-ils toujours utiles si vous avez besoin d’un autre niveau d’indirection devant les touches? Les arbres AVL et rouge-noir peuvent être restructurés sans déplacer les clés, ils ont donc cet avantage lorsque le déplacement des clés est coûteux.la source