web-dev-qa-db-fra.com

Quand choisir l'arbre RB, l'arbre B ou l'arbre AVL?

En tant que programmeur, quand devrais-je envisager d'utiliser un arbre RB, un arbre B ou un arbre AVL? Quels sont les points clés à prendre en compte avant de décider du choix?

Quelqu'un peut-il expliquer avec un scénario pour chaque structure arborescente pourquoi elle est choisie par rapport aux autres en référence aux points clés?

85
Palladin

Prenez ceci avec une pincée de sel:

B-tree lorsque vous gérez plus de milliers d'éléments et que vous les paginez à partir d'un disque ou d'un support de stockage lent.

Arborescence RB lorsque vous effectuez des insertions, suppressions et récupérations assez fréquentes sur l'arborescence.

Arborescence AVL lorsque vos insertions et suppressions sont peu fréquentes par rapport à vos récupérations.

108
blwy10

Je pense que les arbres B + sont une bonne structure de données de conteneur ordonnée à usage général, même dans la mémoire principale. Même lorsque la mémoire virtuelle n'est pas un problème, la compatibilité avec le cache l'est souvent et les arborescences B + sont particulièrement bonnes pour l'accès séquentiel - les mêmes performances asymptotiques qu'une liste chaînée, mais avec une compatibilité avec le cache proche d'un simple tableau. Tout cela et O (log n) rechercher, insérer et supprimer.

Les arbres B + ont cependant des problèmes - tels que les éléments se déplaçant à l'intérieur des nœuds lorsque vous effectuez des insertions/suppressions, invalidant les pointeurs vers ces éléments. J'ai une bibliothèque de conteneurs qui fait la "maintenance du curseur" - les curseurs se fixent au nœud feuille qu'ils référencent actuellement dans une liste liée, afin qu'ils puissent être corrigés ou invalidés automatiquement. Puisqu'il y a rarement plus d'un ou deux curseurs, cela fonctionne bien - mais c'est tout de même un peu plus de travail.

Une autre chose est que l'arbre B + est essentiellement juste cela. Je suppose que vous pouvez supprimer ou recréer les nœuds non-feuilles selon que vous en avez besoin ou non, mais avec les nœuds d'arbre binaire, vous obtenez beaucoup plus de flexibilité. Un arbre binaire peut être converti en une liste liée et inversement sans copier les nœuds - il vous suffit de changer les pointeurs puis de vous souvenir que vous le traitez maintenant comme une structure de données différente. Entre autres choses, cela signifie que vous obtenez assez facilement O(n) fusion d'arbres - convertissez les deux arbres en listes, fusionnez-les, puis reconvertissez-les en arbre.

Encore une autre chose est l'allocation et la libération de mémoire. Dans un arbre binaire, cela peut être séparé des algorithmes - l'utilisateur peut créer un nœud puis appeler l'algorithme d'insertion, et les suppressions peuvent extraire des nœuds (les détacher de l'arbre, mais ne pas libérer la mémoire). Dans un arbre B ou un arbre B +, cela ne fonctionne évidemment pas - les données vivront dans un nœud à plusieurs éléments. Écrire des méthodes d'insertion qui "planifient" l'opération sans modifier les nœuds jusqu'à ce qu'ils sachent combien de nouveaux nœuds sont nécessaires et qu'ils puissent être alloués est un défi.

Rouge noir vs AVL? Je ne suis pas sûr que cela fasse une grande différence. Ma propre bibliothèque possède une classe "outil" basée sur des politiques pour manipuler les nœuds, avec des méthodes pour les listes à double liaison, les arbres binaires simples, les arbres splay, les arbres rouge-noir et les tracés, y compris diverses conversions. Certaines de ces méthodes n'ont été mises en œuvre que parce que je m'ennuyais à un moment ou à un autre. Je ne suis même pas sûr d'avoir testé les méthodes de treap. La raison pour laquelle j'ai choisi des arbres rouge-noir plutôt que AVL est parce que je comprends personnellement mieux les algorithmes - ce qui ne veut pas dire qu'ils sont plus simples, c'est juste un coup d'histoire que je connais mieux.

Une dernière chose - je n'ai développé à l'origine mes conteneurs d'arbres B + qu'à titre d'expérience. C'est une de ces expériences qui n'a jamais vraiment pris fin, mais ce n'est pas quelque chose que j'encouragerais les autres à répéter. Si tout ce dont vous avez besoin est un conteneur commandé, la meilleure réponse est d'utiliser celui que votre bibliothèque existante fournit - par exemple std :: map etc en C++. Ma bibliothèque a évolué au fil des ans, il a fallu un certain temps pour la rendre stable, et j'ai récemment découvert qu'elle était techniquement non portable (dépend d'un peu de comportement non défini WRT offsetof).

19
Steve314

En mémoire, B-Tree a l'avantage lorsque le nombre d'éléments est supérieur à 32000 ... Regardez speedtest.pdf de stx-btree .

4
stan5

Lorsque vous choisissez des structures de données, vous échangez des facteurs tels que

  • vitesse de récupération v vitesse de mise à jour
  • dans quelle mesure la structure résiste aux pires opérations, par exemple l'insertion d'enregistrements qui arrivent dans un ordre trié
  • espace perdu

Je commencerais par lire les articles Wikipedia référencés par Robert Harvey.

De manière pragmatique, lorsque vous travaillez dans des langages tels que Java, le programmeur moyen a tendance à utiliser les classes de collecte fournies. Si dans une activité d'optimisation des performances, on découvre que les performances de collecte sont problématiques, alors on peut chercher des implémentations alternatives. C'est rarement la première chose qu'un développement dirigé par une entreprise doit considérer. Il est extrêmement rare que l'on ait besoin d'implémenter de telles structures de données à la main, il y a généralement des bibliothèques qui peuvent être utilisées.

0
djna