web-dev-qa-db-fra.com

Table de hachage vs arbre binaire équilibré

Quels facteurs dois-je prendre en compte lorsque je dois choisir entre une table de hachage ou une arborescence binaire équilibrée pour implémenter un ensemble ou un tableau associatif?

48
peoro

On ne peut pas répondre à cette question, en général, je le crains.

Le problème est qu'il existe de nombreux types de tables de hachage et d'arbres binaires équilibrés, et leurs performances varient considérablement.

Donc, la réponse naïve est: cela dépend des fonctionnalités dont vous avez besoin. Utilisez une table de hachage si vous n'avez pas besoin de classement et sinon un arbre binaire équilibré.

Pour une réponse plus élaborée, considérons quelques alternatives.

Hash Table (voir l'entrée de Wikipedia pour quelques notions de base)

  • Toutes les tables de hachage n'utilisent pas une liste chaînée comme compartiment. Une alternative populaire consiste à utiliser un "meilleur" compartiment, par exemple un arbre binaire, ou une autre table de hachage (avec une autre fonction de hachage), ...
  • Certaines tables de hachage n'utilisent pas du tout les compartiments: voir Open Addressing (ils viennent avec d'autres problèmes, évidemment)
  • Il y a quelque chose appelé le hachage linéaire (c'est une qualité de détail d'implémentation), qui évite l'écueil "stop-the-world-and-rehash". Fondamentalement, pendant la phase de migration, vous insérez uniquement dans la "nouvelle" table et déplacez également une "ancienne" entrée dans la "nouvelle" table. Bien sûr, la phase de migration signifie double recherche etc ...

Arbre binaire

  • Le rééquilibrage est coûteux, vous pouvez envisager une Skip-List (également meilleure pour les accès multi-threads) ou un Splay Tree.
  • Un bon allocateur peut "regrouper" les nœuds ensemble en mémoire (meilleur comportement de mise en cache), même si cela ne résout pas le problème de recherche de pointeur.
  • B-Tree et ses variantes proposent également un "emballage"

N'oublions pas que O(1) est une complexité asymptotique. Pour quelques éléments, le coefficient est généralement plus important (en termes de performances). Ceci est particulièrement vrai si votre fonction de hachage est lente .. .

Enfin, pour les ensembles, vous pouvez également envisager des structures de données probabilistes, comme Filtres Bloom .

52
Matthieu M.

Les tables de hachage sont généralement meilleures s'il n'est pas nécessaire de conserver les données dans n'importe quel ordre. Les arbres binaires sont meilleurs si les données doivent être triées.

41
supercat

Un point digne sur une architecture moderne: une table de hachage aura généralement, si son facteur de charge est faible, moins de lectures de mémoire qu'un arbre binaire. Étant donné que l'accès à la mémoire a tendance à être plutôt coûteux par rapport à la gravure de cycles CPU, la table de hachage est souvent plus rapide.

Dans l'arbre binaire suivant, on suppose qu'il s'auto-équilibre, comme un arbre rouge noir, un arbre AVL ou comme un treap.

D'un autre côté, si vous devez tout ressasser dans la table de hachage lorsque vous décidez de l'étendre, cela peut être une opération coûteuse qui se produit (amortie). Les arbres binaires n'ont pas cette limitation.

Les arbres binaires sont plus faciles à implémenter dans des langages purement fonctionnels.

Les arbres binaires ont un ordre de tri naturel et une façon naturelle de parcourir l'arbre pour tous les éléments.

Lorsque le facteur de charge dans la table de hachage est faible, vous perdez peut-être beaucoup d'espace mémoire, mais avec deux pointeurs, les arbres binaires ont tendance à occuper plus d'espace.

Les tables de hachage sont presque O(1) (selon la façon dont vous gérez le facteur de charge) par rapport aux arbres Bin O (lg n).

Les arbres ont tendance à être "l'artiste moyen". Il n'y a rien qu'ils font particulièrement bien, mais alors ils ne font rien de particulièrement mauvais.

11
I GIVE CRAP ANSWERS

Un arbre de recherche binaire nécessite une relation d'ordre total entre les clés. Une table de hachage ne nécessite qu'une relation d'équivalence ou d'identité avec une fonction de hachage cohérente.

Si une relation d'ordre total est disponible, un tableau trié a des performances de recherche comparables à des arbres binaires, des performances d'insertion dans le pire des cas dans l'ordre des tables de hachage, et moins de complexité et d'utilisation de la mémoire que les deux.

La complexité d'insertion dans le pire des cas pour une table de hachage peut être laissée à O (1)/O (log K) (avec K le nombre d'éléments ayant le même hachage) s'il est acceptable d'augmenter la complexité de la recherche dans le pire cas à O(K) ou O (log K) si les éléments peuvent être triés.

Les invariants pour les arbres et les tables de hachage sont chers à restaurer si les clés changent, mais moins de O (n log N) pour les tableaux triés.

Ce sont des facteurs à prendre en compte pour décider de l'implémentation à utiliser:

  1. Disponibilité d'une relation de commande totale.
  2. Disponibilité d'une bonne fonction de hachage pour la relation d'équivalence.
  3. Connaissance a priori du nombre d'éléments.
  4. Connaissance du taux d'insertions, de suppressions et de recherches.
  5. Complexité relative des fonctions de comparaison et de hachage.
7
Apalala

Les tables de hachage sont des recherches plus rapides:

  • Vous avez besoin d'une clé qui génère une distribution uniforme (sinon vous manquerez beaucoup et devrez compter sur autre chose que du hachage; comme une recherche linéaire).
  • Les hachages peuvent utiliser beaucoup d'espace vide. Vous pouvez réserver 256 entrées mais n'en avez besoin que de 8 (jusqu'à présent).

Arbres binaires:

  • Déterministe. O (log n) je pense ...
  • Pas besoin d'espace supplémentaire comme les tables de hachage
  • Doit être conservé trié. Ajouter un élément au milieu signifie déplacer le reste.
6
whitey04

Si vous n'avez besoin d'accéder qu'à des éléments uniques, les tables de hachage sont meilleures. Si vous avez besoin d'une gamme d'éléments, vous n'avez tout simplement pas d'autre option que les arbres binaires.

3
biziclop

Pour ajouter aux autres bonnes réponses ci-dessus, je dirais:

Utilisez une table de hachage si la quantité de données ne changera pas (par exemple, le stockage des constantes); mais, si la quantité de données change, utilisez un arbre. Cela est dû au fait que, dans une table de hachage, une fois le facteur de charge atteint, la table de hachage doit être redimensionnée. L'opération de redimensionnement peut être très lente.

3
David Weiser

Un point que je ne pense pas avoir été abordé est que les arbres sont bien meilleurs pour les structures de données persistantes. Autrement dit, des structures immuables. Une table de hachage standard (c'est-à-dire qui utilise un seul tableau de listes liées) ne peut pas être modifiée sans modifier la table entière. Une situation dans laquelle cela est pertinent est si deux fonctions simultanées ont toutes deux une copie d'une table de hachage, et l'une d'elles modifie la table (si la table est modifiable, cette modification sera également visible pour l'autre). Une autre situation serait quelque chose comme ceci:

def bar(table):
    # some intern stuck this line of code in
    table["hello"] = "world"
    return table["the answer"]

def foo(x, y, table):
    z = bar(table)
    if "hello" in table:
        raise Exception("failed catastrophically!")
    return x + y + z

important_result = foo(1, 2, {
    "the answer": 5,
    "this table": "doesn't contain hello", 
    "so it should": "be ok"
})
# catastrophic failure occurs

Avec une table mutable, nous ne pouvons pas garantir que la table qu'un appel de fonction reçoit restera cette table tout au long de son exécution, car d'autres appels de fonction pourraient la modifier.

Ainsi, la mutabilité n'est parfois pas une chose agréable. Maintenant, un moyen de contourner ce problème serait de garder la table immuable et de demander aux mises à jour de renvoyer une table nouvelle sans modifier l'ancienne. Mais avec une table de hachage, cela serait souvent une opération O (n) coûteuse, car tout le tableau sous-jacent devrait être copié. En revanche, avec un arbre équilibré, un nouvel arbre peut être généré avec seulement O (log n) nœuds à créer (le reste de l'arbre étant identique).

Cela signifie qu'un arbre efficace peut être très pratique lorsque des cartes immuables sont souhaitées.

2
limp_chimp

Si vous avez de nombreuses instances d'ensembles légèrement différentes, vous souhaiterez probablement qu'elles partagent la structure. C'est facile avec les arbres (s'ils sont immuables ou copiés sur écriture). Je ne sais pas dans quelle mesure vous pouvez le faire avec des tables de hachage; c'est au moins moins évident.

1
Darius Bacon

D'après mon expérience, les hastables sont toujours plus rapides car les arbres souffrent trop d'effets de cache.

Pour voir des données réelles, vous pouvez consulter la page de référence de ma bibliothèque TommyDS http://tommyds.sourceforge.net/

Ici, vous pouvez comparer les performances des bibliothèques de hachage, d'arborescence et de trie les plus courantes disponibles.

1
amadvance

Un point à noter concerne l'item de traversée, minimum et maximum. Les tables de hachage ne prennent en charge aucun type de parcours ordonné, ni d'accès aux éléments minimum ou maximum. Si ces capacités sont importantes, l'arborescence binaire est un meilleur choix.

0