map vs hash_map en C ++

117

J'ai une question avec hash_mapet mapen C ++. Je comprends que mapc'est dans STL, mais ce hash_mapn'est pas une norme. Quelle est la différence entre les deux?

skydoor
la source

Réponses:

133

Ils sont mis en œuvre de manière très différente.

hash_map( unordered_mapdans TR1 et Boost; utilisez-les à la place) utilisez une table de hachage où la clé est hachée dans un emplacement de la table et la valeur est stockée dans une liste liée à cette clé.

map est implémenté comme un arbre de recherche binaire équilibré (généralement un arbre rouge / noir).

An unordered_mapdevrait donner des performances légèrement meilleures pour accéder aux éléments connus de la collection, mais a mapaura des caractéristiques supplémentaires utiles (par exemple, il est stocké dans un ordre trié, ce qui permet une traversée du début à la fin). unordered_mapsera plus rapide lors de l'insertion et de la suppression qu'un fichier map.

Joe
la source
7
Je ne suis pas entièrement d'accord avec vous concernant la performance. La performance est influencée par un certain nombre de paramètres et je gronderais n'importe quel programmeur utilisant un unordered_map pour seulement 10 entrées parce que "c'est plus rapide". Préoccupez-vous d'abord de l'interface / des fonctionnalités, des performances plus tard.
Matthieu M.
24
Eh bien, oui, cela aide si vous comprenez votre problème. Jusqu'à certains ordres de grandeur, il s'agit probablement d'une performance de lavage, mais il est important de comprendre les caractéristiques de performance des deux conteneurs car ils varient de différentes manières à mesure que les volumes de données augmentent.
Joe le
7
Fait intéressant, je viens d'échanger un std :: map avec un boost :: unordered_map dans une application dans laquelle je fais beaucoup de recherches aléatoires, mais aussi itérer sur toutes les clés de la carte. J'ai économisé beaucoup de temps lors de la recherche, mais je l'ai récupéré via les itérations.Je suis donc revenu à la carte et je cherche d'autres moyens d'améliorer les performances de l'application.
Erik Garrison le
4
@ErikGarrison Si vous utilisez beaucoup plus l'accès aléatoire et l'itération que vous n'insérez et supprimez des éléments, vous pouvez avoir vos objets à la fois dans un arbre et dans une table de hachage (en stockant un pointeur, ou mieux encore un shared_ptr, vers les mêmes objets dans les deux cas où vous utilisiez des instances réelles). Vous aurez alors le temps d'accès au temps O (1) à travers le hash_map et le temps d'itération O (n) à travers la carte. Bien sûr, vous devez vous rappeler d'ajouter et de supprimer les pointeurs des deux à chaque fois. Vous pouvez facilement écrire une classe de conteneur personnalisée qui (probablement la modèle également) qui encapsulerait ce comportement pour vous.
sprite
2
@ErikGarrison Bien sûr, si vous essayez cette méthode, vous paierez avec un petit espace supplémentaire. Cependant, puisque vous utiliseriez des pointeurs, cela ne devrait pas être trop. Si vous le souhaitez vraiment, vous pouvez aller trop loin et écrire votre propre implémentation d'un AVL et utiliser le pointeur de nœud comme type de données dans hash_map, cela vous donnera un accès temporel O (1) à un nœud de l'arborescence à partir duquel vous pourrez itérer linéairement là où vous en avez besoin. Bien sûr, cela impliquerait un peu de codage et je ne suis pas sûr que cela rapporterait à moins que vous n'ayez besoin d'itérer beaucoup depuis et vers des emplacements à accès aléatoire.
sprite
35

hash_mapétait une extension courante fournie par de nombreuses implémentations de bibliothèques. C'est exactement pourquoi il a été renommé unordered_maplorsqu'il a été ajouté au standard C ++ dans le cadre de TR1. map est généralement implémentée avec un arbre binaire équilibré comme un arbre rouge-noir (les implémentations varient bien sûr). hash_mapet unordered_mapsont généralement implémentés avec des tables de hachage. Ainsi l'ordre n'est pas maintenu. unordered_mapinsert / delete / query sera O (1) (temps constant) où la carte sera O (log n) où n est le nombre d'éléments dans la structure de données. C'est donc unordered_mapplus rapide, et si vous ne vous souciez pas de l'ordre des articles, il faut préférer map. Parfois, vous voulez maintenir l'ordre (ordonné par la clé) et pour cela mapserait le choix.

Janglin
la source
9
Je tiens à souligner que hashmap a le pire des cas d'accès de O (N) lorsque des collisions sont probables (mauvais hachage fcn, facteur de chargement trop élevé, etc.)
KitsuneYMG
Un bon hashmap a un coût attendu de O (1), ce n'est pas garanti. Les mauvais hashmaps peuvent avoir un coût attendu qui n'est pas O (1).
Plus clair
14

Certaines des principales différences résident dans les exigences de complexité.

  • A mapnécessite du O(log(N))temps pour les opérations d'insertion et de recherche, car il est implémenté sous la forme d'une structure de données d' arbre rouge-noir .

  • An unordered_maprequiert un temps «moyen» de O(1)pour les insertions et les découvertes, mais il est autorisé à avoir un temps dans le pire des cas de O(N). En effet, il est implémenté à l'aide de la structure de données de table de hachage .

Donc, généralement, unordered_map sera plus rapide, mais en fonction des touches et de la fonction de hachage que vous stockez, cela peut devenir bien pire.

R Samuel Klatchko
la source
4

La spécification C ++ ne dit pas exactement quel algorithme vous devez utiliser pour les conteneurs STL. Elle impose cependant certaines contraintes sur leurs performances, ce qui exclut l'utilisation de tables de hachage pourmap et d'autres conteneurs associatifs. (Ils sont le plus souvent implémentés avec des arbres rouges / noirs.) Ces contraintes nécessitent de meilleures performances dans le pire des cas pour ces conteneurs que les tables de hachage peuvent offrir.

Cependant, beaucoup de gens veulent vraiment des tables de hachage, c'est pourquoi les conteneurs associatifs STL basés sur le hachage sont une extension courante depuis des années. Par conséquent, ils ont ajouté unordered_mapet ainsi de suite aux versions ultérieures de la norme C ++.

Warren Young
la source
Il a en fait été ajouté dans TR1 (std :: tr1 :: unordered_map), pas dans C ++ 0x
Terry Mahaffey
Je pensais que la raison en mapest généralement qu'un arbre équilibré était dû à l'utilisation operator<()comme moyen de déterminer l'emplacement.
KitsuneYMG
@kts: Est-ce que les implémentations STL utilisent réellement un arbre B?
bk1e
Techniquement, tous les arbres de recherche binaire sont des arbres binaires (un arbre 1-2). Cela étant dit, je ne connais aucune STL qui utilise autre chose que du rouge / noir
KitsuneYMG
@ bk1e Les B-arbres "appropriés" sont exceptionnellement utiles dans les bases de données, où vous voulez des nœuds d'arbres "gros" qui s'alignent bien avec les pages du disque. OTOH, ce n'est pas si utile dans le modèle de mémoire "plat" utilisé dans les programmes "normaux" - toutes les implémentations STL que je connais utilisent des arbres rouge-noir.
Branko Dimitrijevic
3

mapest implémenté à partir de balanced binary search tree(généralement a rb_tree), puisque tous les membres balanced binary search treesont triés, ainsi que la carte;

hash_mapest implémenté à partir de hashtable.Comme tous hashtableles membres de hash_map(unordered_map)ne sont pas triés, les membres de ne sont pas triés.

hash_mapn'est pas une bibliothèque standard C ++, mais maintenant elle a été renommée unordered_map(vous pouvez penser qu'elle a été renommée) et devient une bibliothèque standard C ++ depuis C ++ 11 voir cette question Différence entre hash_map et unordered_map?pour plus de détails.

Ci-dessous, je vais donner une interface de base à partir du code source de la façon dont la carte à deux types est implémentée.

carte:

Le code ci-dessous est juste pour montrer que la carte est juste un wrapper d'une fonction balanced binary search tree, presque toute sa fonction est simplement d'invoquer la balanced binary search treefonction.

template <typename Key, typename Value, class Compare = std::less<Key>>
class map{
    // used for rb_tree to sort
    typedef Key    key_type;

    // rb_tree node value
    typedef std::pair<key_type, value_type> value_type;

    typedef Compare key_compare;

    // as to map, Key is used for sort, Value used for store value
    typedef rb_tree<key_type, value_type, key_compare> rep_type;

    // the only member value of map (it's  rb_tree)
    rep_type t;
};

// one construct function
template<typename InputIterator>
map(InputIterator first, InputIterator last):t(Compare()){
        // use rb_tree to insert value(just insert unique value)
        t.insert_unique(first, last);
}

// insert function, just use tb_tree insert_unique function
//and only insert unique value
//rb_tree insertion time is : log(n)+rebalance
// so map's  insertion time is also : log(n)+rebalance 
typedef typename rep_type::const_iterator iterator;
std::pair<iterator, bool> insert(const value_type& v){
    return t.insert_unique(v);
};

hash_map:

hash_mapest implémenté à partir de hashtablelaquelle la structure est un peu comme ceci:

entrez la description de l'image ici

Dans le code ci-dessous, je vais donner la partie principale de hashtable, puis je donne hash_map.

// used for node list
template<typename T>
struct __hashtable_node{
    T val;
    __hashtable_node* next;
};

template<typename Key, typename Value, typename HashFun>
class hashtable{
    public:
        typedef size_t   size_type;
        typedef HashFun  hasher;
        typedef Value    value_type;
        typedef Key      key_type;
    public:
        typedef __hashtable_node<value_type> node;

        // member data is buckets array(node* array)
        std::vector<node*> buckets;
        size_type num_elements;

        public:
            // insert only unique value
            std::pair<iterator, bool> insert_unique(const value_type& obj);

};

Comme map'sseul membre est rb_tree, le hash_map'sseul membre est hashtable. C'est le code principal comme ci-dessous:

template<typename Key, typename Value, class HashFun = std::hash<Key>>
class hash_map{
    private:
        typedef hashtable<Key, Value, HashFun> ht;

        // member data is hash_table
        ht rep;

    public:
        // 100 buckets by default
        // it may not be 100(in this just for simplify)
        hash_map():rep(100){};

        // like the above map's insert function just invoke rb_tree unique function
        // hash_map, insert function just invoke hashtable's unique insert function
        std::pair<iterator, bool> insert(const Value& v){
                return t.insert_unique(v);
        };

};

L'image ci-dessous montre quand un hash_map a 53 buckets et insère des valeurs, c'est sa structure interne.

entrez la description de l'image ici

L'image ci-dessous montre une certaine différence entre map et hash_map (unordered_map), l'image vient de Comment choisir entre map et unordered_map? :

entrez la description de l'image ici

Jayhello
la source
1

Je ne sais pas ce que cela donne, mais hash_map prend plus de 20 secondes pour effacer () 150K clés entières non signées et valeurs flottantes. Je suis juste en train d'exécuter et de lire le code de quelqu'un d'autre.

C'est ainsi qu'il inclut hash_map.

#include "StdAfx.h"
#include <hash_map>

J'ai lu ceci ici https://bytes.com/topic/c/answers/570079-perfomance-clear-vs-swap

dire que clear () est l'ordre de O (N). Cela pour moi, c'est très étrange, mais c'est comme ça.

BoBoDev
la source