Je me demande quelles sont les applications particulières des arbres binaires. Pourriez-vous donner de vrais exemples?
Se quereller à propos de la performance des binaires-arbres n'a pas de sens - ils ne sont pas une structure de données, mais une famille de structures de données, toutes avec des caractéristiques de performance différentes. Bien qu'il soit vrai que des arbres binaires non équilibrés fonctionnent beaucoup moins bien que des arbres binaires auto-équilibrés pour la recherche, il existe de nombreux arbres binaires (tels que les essais binaires) pour lesquels "équilibrer" n'a pas de sens.
map
et set
objets dans de nombreuses bibliothèques de langages.La raison pour laquelle les arbres binaires sont utilisés plus souvent que les arbres n-aire pour la recherche est que les arbres n-aire sont plus complexes, mais n'offrent généralement aucun avantage en termes de vitesse.
Dans une arborescence binaire (équilibrée) avec m
nœuds, le passage d'un niveau à l'autre nécessite une comparaison et il existe des niveaux log_2(m)
, pour un total de comparaisons log_2(m)
.
En revanche, un arbre n-aire nécessitera des comparaisons de log_2(n)
(en utilisant une recherche binaire) pour passer au niveau suivant. Comme il existe ___ niveaux totaux log_n(m)
, la recherche nécessitera des comparaisons de log_2(n)*log_n(m)
= log_2(m)
. Ainsi, bien que les arbres n-aire soient plus complexes, ils ne fournissent aucun avantage en termes de comparaisons totales nécessaires.
(Cependant, les arbres à plusieurs niveaux sont toujours utiles dans des situations de niche. Les exemples qui viennent immédiatement à l'esprit sont quad-trees et d'autres arbres à partitionnement d'espace, où diviser l'espace en n'utilisant que deux nœuds par niveau rendrait la logique inutilement complexe; et arbres B utilisé dans de nombreuses bases de données, où le facteur limitant n'est pas le nombre de comparaisons effectuées à chaque niveau, mais le nombre de nœuds pouvant être utilisés. être chargé à partir du disque dur à la fois)
Quand la plupart des gens parlent d’arbres binaires, ils pensent le plus souvent aux arbres binaires recherche, je vais donc couvrir cela en premier.
Un arbre de recherche binaire non équilibré n’est en réalité utile que pour éduquer les étudiants sur les structures de données. En effet, à moins que les données n'arrivent dans un ordre relativement aléatoire, l'arbre peut facilement dégénérer en sa forme la plus défavorable, qui est une liste chaînée, car les simples arbres binaires sont non équilibrés.
Un bon exemple: j'ai déjà eu à réparer un logiciel qui chargeait ses données dans un arbre binaire à des fins de manipulation et de recherche. Il a écrit les données sous forme triée:
Alice
Bob
Chloe
David
Edwina
Frank
de sorte que, lors de la relecture, l’arborescence suivante a été trouvée:
Alice
/ \
= Bob
/ \
= Chloe
/ \
= David
/ \
= Edwina
/ \
= Frank
/ \
= =
qui est la forme dégénérée. Si vous recherchez Frank dans cet arbre, vous devrez rechercher les six nœuds avant de le trouver.
Les arbres binaires deviennent vraiment utiles pour rechercher lorsque vous les équilibrez. Cela implique la rotation des sous-arbres via leur nœud racine afin que la différence de hauteur entre deux sous-arbres soit inférieure ou égale à 1. Ajouter ces noms l'un après l'autre dans un arbre équilibré vous donnerait la séquence suivante:
1. Alice
/ \
= =
2. Alice
/ \
= Bob
/ \
= =
3. Bob
_/ \_
Alice Chloe
/ \ / \
= = = =
4. Bob
_/ \_
Alice Chloe
/ \ / \
= = = David
/ \
= =
5. Bob
____/ \____
Alice David
/ \ / \
= = Chloe Edwina
/ \ / \
= = = =
6. Chloe
___/ \___
Bob Edwina
/ \ / \
Alice = David Frank
/ \ / \ / \
= = = = = =
Vous pouvez réellement voir des sous-arbres entiers pivoter vers la gauche (aux étapes 3 et 6) au fur et à mesure que les entrées sont ajoutées, ce qui vous donne un arbre binaire équilibré dans lequel la recherche dans le pire des cas est O(log N)
plutôt que le O(N
) que donne la forme dégénérée. À aucun moment, le NULL le plus élevé (=
) ne diffère du plus bas de plus d'un niveau. Et, dans la dernière arborescence ci-dessus, vous pouvez trouver Frank en ne regardant que trois nœuds (Chloe
, Edwina
et, enfin, Frank
).
Bien sûr, ils peuvent devenir encore plus utiles lorsque vous les équilibrez multi-way arbres plutôt que binaires. Cela signifie que chaque nœud contient plus d'un élément (techniquement, ils contiennent N éléments et N + 1 pointeurs, un arbre binaire étant le cas particulier d'un arbre à plusieurs voies à sens unique avec 1 élément et 2 pointeurs).
Avec un arbre à trois voies, vous vous retrouvez avec:
Alice Bob Chloe
/ | | \
= = = David Edwina Frank
/ | | \
= = = =
Ceci est généralement utilisé dans la gestion des clés pour un index d'éléments. J'ai écrit un logiciel de base de données optimisé pour le matériel où un nœud a exactement la taille d'un bloc de disque (disons 512 octets) et vous mettez autant de clés que possible dans un seul nœud. Les pointeurs dans ce cas étaient en fait des numéros d’enregistrement dans un fichier d’accès direct d’enregistrement de longueur fixe, séparé du fichier d’index (afin que le numéro d’enregistrement X
puisse être trouvé en cherchant simplement à X * record_length
).
Par exemple, si les pointeurs ont 4 octets et que la taille de la clé est 10, le nombre de clés dans un nœud de 512 octets est de 36. C’est 36 clés (360 octets) et 37 pointeurs (148 octets) pour un total de 508 octets avec 4 octets gaspillés par nœud.
L'utilisation de clés à plusieurs voies introduit la complexité d'une recherche en deux phases (recherche à plusieurs voies pour trouver le bon noeud combinée à une petite recherche séquentielle (ou binaire linéaire) pour trouver la bonne clé dans le noeud), mais présente l'avantage faire moins d’entrées/sorties de disque fait plus que compenser cela.
Je ne vois aucune raison de faire cela pour une structure en mémoire, vous feriez mieux de vous en tenir à un arbre binaire équilibré et de garder votre code simple.
N'oubliez pas non plus que les avantages de O(log N)
par rapport à O(N)
n'apparaissent pas vraiment lorsque vos ensembles de données sont petits. Si vous utilisez un arbre multidirectionnel pour stocker les quinze personnes de votre carnet d'adresses, c'est sans doute excessif. Les avantages viennent lorsque vous stockez quelque chose comme toutes les commandes de vos cent mille clients au cours des dix dernières années.
Le but de la notation big-O est d'indiquer ce qui se passe lorsque la N
s'approche de l'infini. Certaines personnes peuvent ne pas être d'accord, mais vous pouvez même utiliser le tri à bulles si vous êtes certain que les ensembles de données resteront en dessous d'une certaine taille, à condition que rien d'autre ne soit facilement disponible :-)
En ce qui concerne les autres utilisations des arbres binaires, il en existe beaucoup, telles que:
Étant donné les nombreuses explications que j'ai générées pour les arbres de recherche, je suis réticent à entrer beaucoup de détails dans les autres, mais cela devrait être suffisant pour les rechercher, si vous le désirez.
L'organisation de code Morse est un arbre binaire.
Une arborescence binaire est une structure de données arborescente dans laquelle chaque nœud a au plus deux nœuds enfants, généralement distingués par "gauche" et "droite". Les nœuds avec enfants sont des nœuds parents et les nœuds enfants peuvent contenir des références à leurs parents. En dehors de l'arborescence, il existe souvent une référence au nœud "racine" (l'ancêtre de tous les nœuds), s'il existe. Vous pouvez atteindre n'importe quel nœud de la structure de données en commençant par le nœud racine et en suivant de manière répétée les références à l'enfant gauche ou droit. Dans un arbre binaire, un degré de chaque nœud est égal à deux au maximum.
Les arbres binaires sont utiles car, comme vous pouvez le voir sur l'image, si vous voulez trouver un nœud dans l'arbre, il vous suffit de regarder 6 fois au maximum. Si vous voulez rechercher le noeud 24, par exemple, vous commencerez à la racine.
Cette recherche est illustrée ci-dessous:
Vous pouvez voir que vous pouvez exclure la moitié des nœuds de l’arbre complet lors du premier passage. et la moitié du sous-arbre gauche sur le second. Cela rend les recherches très efficaces. Si cela était fait sur 4 milliards d'éléments , il vous suffirait d'effectuer une recherche maximum 32 fois. Par conséquent, plus l’arbre contient d’éléments, plus votre recherche peut être efficace.
Les suppressions peuvent devenir complexes. Si le nœud a 0 ou 1 enfant, il suffit simplement de déplacer certains pointeurs pour exclure celui à supprimer. Cependant, vous ne pouvez pas facilement supprimer un nœud avec 2 enfants. Nous prenons donc un raccourci. Disons que nous voulions supprimer le noeud 19.
Comme il n’est pas facile de déterminer où déplacer les pointeurs gauche et droit, nous en trouvons un pour le remplacer. Nous allons au sous-arbre de gauche et allons aussi loin à droite que possible. Cela nous donne la plus grande valeur suivante du nœud que nous voulons supprimer.
Maintenant, nous copions tout le contenu de 18, à l'exception des pointeurs gauche et droit, et supprimons le nœud d'origine.
Pour créer ces images, j'ai implémenté une arborescence AVL, une arborescence à auto-équilibrage, de sorte qu'à tout moment, l'arborescence ait au plus un niveau de différence entre les nœuds terminaux (nœuds sans enfants). Cela empêche l’arborescence de devenir asymétrique et maintient le maximum de O(log n)
temps de recherche, avec le coût d’un peu plus de temps nécessaire aux insertions et suppressions.
Voici un exemple montrant comment mon arbre AVL s’est gardé aussi compact et équilibré que possible.
Dans un tableau trié, les recherches prendraient toujours O(log(n))
, tout comme un arbre, mais l'insertion et le retrait aléatoires prendraient O(n) au lieu de O(log(n))
. Certains conteneurs STL utilisent ces caractéristiques de performance à leur avantage, de sorte que les temps d'insertion et de retrait prennent au maximum O(log n)
, ce qui est très rapide. Certains de ces conteneurs sont map
, multimap
, set
et multiset
.
Vous trouverez un exemple de code pour une arborescence AVL à l’adresse suivante: http://ideone.com/MheW8
L'application principale est arbres de recherche binaires . Il s’agit d’une structure de données dans laquelle la recherche, l’insertion et la suppression sont très rapides (à propos de log(n)
opérations)
Un exemple intéressant d'arbre binaire qui n'a pas été mentionné est celui d'une expression mathématique évaluée récursivement. C'est pratiquement inutile du point de vue pratique, mais c'est une façon intéressante de penser à de telles expressions.
Fondamentalement, chaque nœud de l’arbre a une valeur qui lui est inhérente ou qui est évaluée de manière récursive en opérant sur les valeurs de ses enfants.
Par exemple, l'expression (1+3)*2
peut être exprimée comme suit:
*
/ \
+ 2
/ \
1 3
Pour évaluer l'expression, nous demandons la valeur du parent. Ce nœud tire à son tour ses valeurs de ses enfants, d'un opérateur plus et d'un nœud contenant simplement "2". L'opérateur plus obtient à son tour ses valeurs des enfants avec les valeurs '1' et '3' et les ajoute, en renvoyant 4 au noeud de multiplication qui renvoie 8.
Cette utilisation d'un arbre binaire s'apparente en quelque sorte à la notation polonaise inversée, en ce sens que l'ordre dans lequel les opérations sont effectuées est identique. De plus, il convient de noter qu'il ne doit pas nécessairement s'agir d'un arbre binaire, mais simplement que les opérateurs les plus couramment utilisés sont binaires. Au niveau le plus élémentaire, l’arbre binaire n’est en fait qu’un langage de programmation purement fonctionnel et très simple.
L'une des applications les plus courantes consiste à stocker efficacement les données sous forme triée afin d'accéder et de rechercher rapidement les éléments stockés. Par exemple, std::map
ou std::set
dans la bibliothèque standard C++.
L'arborescence binaire en tant que structure de données est utile pour diverses implémentations d'analyseurs d'expression et de résolveurs d'expression.
Il peut également être utilisé pour résoudre certains problèmes de base de données, par exemple l'indexation.
En règle générale, l'arborescence binaire est un concept général de structure de données particulière basée sur l'arborescence. Différents types d'arborescences binaires peuvent être construits avec différentes propriétés.
Je ne pense pas qu'il soit utile d'utiliser des arbres binaires "purs". (sauf à des fins éducatives) Les arbres binaires équilibrés, tels que Arbres rouge-noir ou Arbres AVL sont beaucoup plus utiles, car ils garantissent O(logn) opérations. Les arbres binaires normaux peuvent finir par être une liste (ou presque) et ne sont pas vraiment utiles dans les applications utilisant beaucoup de données.
Les arbres équilibrés sont souvent utilisés pour implémenter des cartes ou des ensembles. Ils peuvent également être utilisés pour trier en O (nlogn), même s’il existe de meilleurs moyens de le faire.
Vous pouvez également utiliser pour rechercher/insérer/supprimer Tables de hachage , qui offrent généralement de meilleures performances que les arbres de recherche binaires (équilibrés ou non).
Une application où des arbres de recherche binaires (équilibrés) serait utile serait si la recherche/insertion/suppression et le tri étaient nécessaires. Le tri peut être en place (presque, en ignorant l'espace de pile requis pour la récursion), avec un arbre équilibré prêt à être construit. Ce serait toujours O(nlogn) mais avec un facteur constant plus petit et aucun espace supplémentaire requis (sauf pour le nouveau tableau, en supposant que les données doivent être placées dans un tableau). Les tables de hachage, en revanche, ne peuvent pas être triées (du moins pas directement).
Peut-être qu’ils sont également utiles dans certains algorithmes sophistiqués pour faire quelque chose, mais rien ne me vient à l’esprit. Si je trouve plus, je vais éditer mon post.
D'autres arbres comme f.e. B + trees sont largement utilisés dans les bases de données
En C++ STL et dans de nombreuses autres bibliothèques standard dans d'autres langages, comme Java et C #. Les arbres de recherche binaires sont utilisés pour implémenter set et map.
Une des applications les plus importantes des arbres binaires est équilibrée des arbres de recherche binaires comme:
Ces types d'arbres ont pour propriété de maintenir la différence de hauteur entre le sous-arbre gauche et le sous-arbre droit en effectuant des opérations telles que les rotations chaque fois qu'un nœud est inséré ou supprimé.
De ce fait, la hauteur totale de l’arbre reste de l’ordre du journal n et les opérations telles que la recherche, l’insertion et la suppression des nœuds sont exécutées dans le temps O (log n). La STL de C++ implémente également ces arbres sous la forme d'ensembles et de cartes.
Ils peuvent être utilisés comme un moyen rapide de trier les données. Insérez les données dans un arbre de recherche binaire en O (log (n)). Traversez ensuite l’arbre pour les trier.
la syntaxe de vos programmes, ou bien d’autres choses, telles que les langages naturels, peuvent être analysées à l’aide de l’arbre binaire (mais pas nécessairement).
Sur le matériel moderne, une arborescence binaire est presque toujours sous-optimale en raison d'un mauvais comportement du cache et de l'espace. Cela vaut également pour les variantes (semi) équilibrées. Si vous les trouvez, c'est que la performance ne compte pas (ou est dominée par la fonction de comparaison), ou plus probablement pour des raisons historiques ou d'ignorance.
Implémentations de Java.util.Set