map vs. hash_map em C ++


117

Tenho uma pergunta com hash_mape mapem C ++. Eu entendo que mapestá em STL, mas hash_mapnão é um padrão. Qual a diferença entre os dois?

Respostas:


133

Eles são implementados de maneiras muito diferentes.

hash_map( unordered_mapem TR1 e Boost; use-os em vez disso) use uma tabela hash onde a chave é hash para um slot na tabela e o valor é armazenado em uma lista vinculada a essa chave.

map é implementado como uma árvore de pesquisa binária balanceada (geralmente uma árvore vermelha / preta).

Um unordered_mapdeve ter um desempenho ligeiramente melhor para acessar elementos conhecidos da coleção, mas um mapterá características adicionais úteis (por exemplo, ele é armazenado em ordem classificada, o que permite a travessia do início ao fim). unordered_mapserá mais rápido na inserção e exclusão do que a map.


7
Não concordo totalmente com você em relação ao desempenho. O desempenho é influenciado por uma série de parâmetros e eu repreenderia qualquer programador usando um unordered_map por apenas 10 entradas porque "é mais rápido". Preocupe-se primeiro com a interface / funcionalidade e depois com o desempenho.
Matthieu M.

24
Bem, sim, ajuda se você entender seu problema. Até certas ordens de magnitude, é provavelmente um desempenho de lavagem, mas é importante entender as características de desempenho de ambos os recipientes, pois eles se desviam de maneiras diferentes conforme os volumes de dados ficam maiores.
Joe

7
Curiosamente, acabei de trocar std :: map por boost :: unordered_map em um aplicativo no qual faço muitas pesquisas aleatórias, mas também itero sobre todas as chaves do mapa. Economizei muito tempo na pesquisa, mas recuperei por meio das iterações, então voltei para mapear e estou procurando outras maneiras de melhorar o desempenho do aplicativo.
Erik Garrison

4
@ErikGarrison Se você usar acesso aleatório e iteração muito mais do que inserir e excluir elementos, você pode ter seus objetos em uma árvore e em um hash_map (armazenando um ponteiro, ou melhor ainda, um shared_ptr, para os mesmos objetos em ambos caso você estivesse usando instâncias reais). Você terá então tempo O (1) de tempo de acesso por meio do hash_map e tempo de iteração O (n) por meio do mapa. Claro, você deve se lembrar de adicionar e remover os ponteiros de ambos sempre. Você poderia facilmente escrever uma classe de contêiner customizada que (provavelmente também a modele) que encapsularia esse comportamento para você.
sprite

2
@ErikGarrison Claro, se você tentar esse método, estará pagando com um pequeno espaço adicional. No entanto, como você estaria usando ponteiros, isso não deve ser muito. Se você realmente quiser, pode ir ao mar e escrever sua própria implementação de um AVL e usar o ponteiro do nó como seu tipo de dados no hash_map, isso lhe dará acesso em tempo O (1) a um nó na árvore a partir do qual você poderá iterar linearmente para onde for necessário. Claro que isso envolveria um pouco de codificação e não tenho certeza se valeria a pena, a menos que você precise iterar muito de e para locais de acesso aleatório.
sprite

35

hash_mapfoi uma extensão comum fornecida por muitas implementações de biblioteca. É exatamente por isso que foi renomeado para unordered_mapquando foi adicionado ao padrão C ++ como parte do TR1. map é geralmente implementado com uma árvore binária balanceada como uma árvore vermelha e preta (as implementações variam, é claro). hash_mape unordered_mapgeralmente são implementados com tabelas de hash. Portanto, a ordem não é mantida. unordered_mapinserir / excluir / consultar será O (1) (tempo constante), onde map será O (log n), onde n é o número de itens na estrutura de dados. Então unordered_mapé mais rápido, e se você não se preocupa com a ordem dos itens deve dar preferência map. Às vezes você quer manter a ordem (ordenada pela chave) e para isso mapseria a escolha.


9
Gostaria de salientar que o hashmap tem o pior caso de acesso de O (N) quando as colisões são prováveis ​​(hash fcn ruim, fator de carregamento muito alto, etc)
KitsuneYMG

Um bom hashmap tem um custo esperado de O (1), não é garantido que o seja. Hashmaps ruins podem ter um custo esperado diferente de O (1).
Clearer

14

Algumas das principais diferenças estão nos requisitos de complexidade.

  • A maprequer O(log(N))tempo para inserir e localizar operações, pois é implementado como uma estrutura de dados Red-Black Tree .

  • Um unordered_maprequer um tempo 'médio' de O(1)para inserções e descobertas, mas pode ter um tempo de pior caso de O(N). Isso ocorre porque é implementado usando a estrutura de dados Hash Table .

Então, normalmente, unordered_mapserá mais rápido, mas dependendo das chaves e da função hash que você armazena, pode ficar muito pior.


4

A especificação C ++ não diz exatamente qual algoritmo você deve usar para os contêineres STL. No entanto, ele impõe certas restrições ao desempenho deles, o que exclui o uso de tabelas hash para mape outros contêineres associativos. (Eles são mais comumente implementados com árvores vermelhas / pretas.) Essas restrições requerem melhor desempenho de pior caso para esses contêineres do que as tabelas de hash podem fornecer.

Muitas pessoas realmente querem tabelas de hash, portanto, os contêineres associativos STL baseados em hash têm sido uma extensão comum por anos. Consequentemente, eles adicionaram unordered_mape tal a versões posteriores do padrão C ++.


Na verdade, foi adicionado em TR1 (std :: tr1 :: unordered_map), não em C ++ 0x
Terry Mahaffey

Eu pensei que o motivo mapé geralmente uma árvore balanceada devido ao uso operator<()como meio de determinar a localização.
KitsuneYMG

@kts: Alguma implementação STL realmente usa uma árvore B?
bk1e

Tecnicamente, todas as árvores binárias de pesquisa são árvores b (uma árvore 1-2). Dito isso, não conheço nenhum STL que use algo diferente de vermelho / preto
KitsuneYMG

@ bk1e Árvores B "adequadas" são excepcionalmente úteis em bancos de dados, onde você quer nós de árvore "gordos" que se alinham bem com as páginas do disco. OTOH, isso não é tão útil no modelo de memória "plana" usado em programas "normais" - todas as implementações de STL que conheço usam árvores vermelho-pretas.
Branko Dimitrijevic

3

mapé implementado a partir de balanced binary search tree(geralmente a rb_tree), uma vez que todo o membro em balanced binary search treeé classificado, assim como o mapa;

hash_mapé implementado a partir de. hashtableVisto que todo o membro em hashtablenão está classificado, os membros em hash_map(unordered_map)não são classificados.

hash_mapnão é uma biblioteca padrão c ++, mas agora é renomeada para unordered_map(você pode pensar nela renomeada) e se torna a biblioteca padrão c ++ desde c ++ 11 veja esta questão Diferença entre hash_map e unordered_map? para mais detalhes.

Abaixo, apresentarei algumas interfaces básicas do código-fonte de como o mapa de dois tipos é implementado.

mapa:

O código abaixo é apenas para mostrar que, map é apenas um envoltório de um balanced binary search tree, quase toda a sua função é apenas invocar a balanced binary search treefunção.

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_map é implementado de hashtable cuja estrutura é mais ou menos assim:

insira a descrição da imagem aqui

No código abaixo, darei a parte principal do hashtable e, em seguida,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);

};

Como map'sapenas um membro érb_tree , o hash_map'súnico membro é hashtable. É o código principal abaixo:

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);
        };

};

A imagem abaixo mostra quando um hash_map tem 53 buckets, e insere alguns valores, sua estrutura interna.

insira a descrição da imagem aqui

A imagem abaixo mostra algumas diferenças entre map e hash_map (unordered_map), a imagem vem de Como escolher entre map e unordered_map? :

insira a descrição da imagem aqui


1

Eu não sei o que dá, mas, hash_map leva mais de 20 segundos para limpar () 150K chaves inteiras não assinadas e valores flutuantes. Estou apenas executando e lendo o código de outra pessoa.

É assim que inclui hash_map.

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

Eu li isso aqui https://bytes.com/topic/c/answers/570079-perfomance-clear-vs-swap

dizendo que clear () é a ordem de O (N). Isso pra mim é muito estranho, mas é assim que é.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.