map vs. hash_map in C ++


117

Ho una domanda con hash_mape mapin C ++. Capisco che mapsia in STL, ma hash_mapnon è uno standard. Qual è la differenza tra i due?

Risposte:


133

Sono implementati in modi molto diversi.

hash_map( unordered_mapin TR1 e Boost; usa invece quelli) usa una tabella hash in cui la chiave è sottoposta ad hashing in uno slot nella tabella e il valore è memorizzato in un elenco legato a quella chiave.

map è implementato come un albero di ricerca binario bilanciato (di solito un albero rosso / nero).

An unordered_mapdovrebbe fornire prestazioni leggermente migliori per accedere agli elementi noti della raccolta, ma mapavrà caratteristiche utili aggiuntive (ad esempio, è memorizzato in ordine ordinato, che consente l'attraversamento dall'inizio alla fine). unordered_mapsarà più veloce durante l'inserimento e l'eliminazione di un file map.


7
Non sono completamente d'accordo con te per quanto riguarda la performance. Le prestazioni sono influenzate da una serie di parametri e rimprovererei qualsiasi programmatore che utilizza un unordered_map per appena 10 voci perché "è più veloce". Preoccupati prima dell'interfaccia / funzionalità, poi delle prestazioni.
Matthieu M.

24
Bene, sì, aiuta se capisci il tuo problema. Fino a certi ordini di grandezza è probabilmente un lavaggio dal punto di vista delle prestazioni, ma è importante comprendere le caratteristiche delle prestazioni di entrambi i contenitori poiché deviano in modi diversi man mano che i volumi di dati aumentano.
Joe,

7
È interessante notare che ho appena scambiato una std :: map con un boost :: unordered_map in un'applicazione in cui eseguo molte ricerche casuali, ma anche iterando su tutte le chiavi nella mappa. Ho risparmiato molto tempo nella ricerca, ma l'ho recuperato tramite le iterazioni, quindi sono tornato alla mappa e sto cercando altri modi per migliorare le prestazioni dell'applicazione.
Erik Garrison

4
@ErikGarrison Se usi l'accesso casuale e l'iterazione molto più che inserire ed eliminare elementi, potresti avere i tuoi oggetti sia in un albero che in una hash_map (memorizzando un puntatore, o meglio ancora un shared_ptr, agli stessi oggetti in entrambi in caso stavi utilizzando istanze reali). Avrai quindi O (1) tempo di accesso tramite hash_map e O (n) tempo di iterazione attraverso la mappa. Ovviamente, devi ricordarti di aggiungere e rimuovere i puntatori da entrambi ogni volta. Potresti facilmente scrivere una classe contenitore personalizzata che (probabilmente anche la modella) che incapsulerebbe questo comportamento per te.
sprite

2
@ErikGarrison Ovviamente, se provi questo metodo, pagheresti con uno spazio aggiuntivo minore. Tuttavia, poiché useresti i puntatori, non dovrebbe essere troppo. Se vuoi davvero, puoi esagerare e scrivere la tua implementazione di un AVL e usare il puntatore del nodo come tipo di dati nella hash_map, questo ti darà O (1) tempo di accesso a un nodo nell'albero da cui sarai in grado di iterare in modo lineare ovunque ti serva. Ovviamente ciò comporterebbe un bel po 'di codifica e non sono sicuro che sarebbe ripagato a meno che non sia necessario iterare molto da e verso posizioni di accesso casuale.
sprite

35

hash_mapera un'estensione comune fornita da molte implementazioni di librerie. Questo è esattamente il motivo per cui è stato rinominato unordered_mapquando è stato aggiunto allo standard C ++ come parte di TR1. map è generalmente implementato con un albero binario bilanciato come un albero rosso-nero (le implementazioni variano ovviamente). hash_mape unordered_mapsono generalmente implementati con tabelle hash. Pertanto l'ordine non viene mantenuto. unordered_mapinsert / delete / query sarà O (1) (tempo costante) dove map sarà O (log n) dove n è il numero di elementi nella struttura dati. Quindi unordered_mapè più veloce e se non ti interessa l'ordine degli articoli dovrebbe essere preferito map. A volte vuoi mantenere l'ordine (ordinato dalla chiave) e per questo mapsarebbe la scelta.


9
Vorrei sottolineare che hashmap ha un accesso nel caso peggiore di O (N) quando sono probabili collisioni (hash fcn errato, fattore di caricamento troppo alto, ecc.)
KitsuneYMG

Una buona hashmap ha un costo previsto di O (1), non è garantito che sia così. Le hashmap errate potrebbero avere un costo previsto che non è O (1).
Più sereno il

14

Alcune delle differenze principali risiedono nei requisiti di complessità.

  • A maprichiede O(log(N))tempo per le operazioni di inserimento e ricerca, poiché è implementato come una struttura dati ad albero rosso-nero .

  • An unordered_maprichiede un tempo "medio" di O(1)per inserti e ritrovamenti, ma può avere un tempo nel caso peggiore di O(N). Questo perché è implementato utilizzando la struttura dati della tabella hash .

Quindi, di solito, unordered_mapsarà più veloce, ma a seconda dei tasti e della funzione hash che memorizzi, può diventare molto peggio.


4

La specifica C ++ non dice esattamente quale algoritmo devi usare per i contenitori STL. Tuttavia, pone alcuni vincoli alle loro prestazioni, il che esclude l'uso di tabelle hash mape altri contenitori associativi. (Sono più comunemente implementati con alberi rosso / nero.) Questi vincoli richiedono prestazioni nel caso peggiore per questi contenitori migliori di quelle che le tabelle hash possono fornire.

Molte persone vogliono davvero le tabelle hash, tuttavia, i contenitori associativi STL basati su hash sono stati un'estensione comune da anni. Di conseguenza, hanno aggiunto unordered_mape tale alle versioni successive dello standard C ++.


In realtà è stato aggiunto in TR1 (std :: tr1 :: unordered_map), non C ++ 0x
Terry Mahaffey

Ho pensato che il motivo mapè generalmente un btree equilibrato era dovuto all'uso operator<()come mezzo per determinare la posizione.
KitsuneYMG

@kts: Ci sono implementazioni STL che usano effettivamente un B-tree?
bk1e

Tecnicamente tutti gli alberi di ricerca binari sono b-alberi (un albero 1-2). Detto questo, non conosco alcun STL che utilizzi qualcosa di diverso dal rosso / nero
KitsuneYMG

@ bk1e Gli alberi B "corretti" sono eccezionalmente utili nei database, dove si vogliono nodi dell'albero "grassi" che si allineano bene con le pagine del disco. OTOH, questo non è così utile nel modello di memoria "piatto" usato nei programmi "normali" - tutte le implementazioni STL che conosco usano alberi rosso-neri.
Branko Dimitrijevic

3

mapè implementato da balanced binary search tree(di solito a rb_tree), poiché tutti i membri in balanced binary search treesono ordinati così è map;

hash_mapè implementato da hashtable. Poiché tutti i membri in hashtablenon sono ordinati, i membri in hash_map(unordered_map)non vengono ordinati.

hash_mapnon è una libreria standard c ++, ma ora è rinominata unordered_map(puoi pensare che sia stata rinominata) e diventa libreria standard c ++ poiché c ++ 11 vedi questa domanda Differenza tra hash_map e unordered_map? per maggiori dettagli.

Di seguito fornirò alcune interfacce principali dal codice sorgente su come è implementata la mappa a due tipi.

carta geografica:

Il codice seguente è solo per mostrare che, map è solo un wrapper di una balanced binary search tree, quasi tutta la sua funzione è solo invocare la balanced binary search treefunzione.

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è implementato dalla hashtablecui struttura è un po 'come questa:

inserisci qui la descrizione dell'immagine

Nel codice seguente, darò la parte principale di hashtable, e poi darà 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);

};

Come map'ssolo il membro è rb_tree, l' hash_map'sunico membro è hashtable. È il codice principale come di seguito:

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'immagine sotto mostra quando una hash_map ha 53 bucket e inserisce alcuni valori, la sua struttura interna.

inserisci qui la descrizione dell'immagine

L'immagine sotto mostra alcune differenze tra map e hash_map (unordered_map), l'immagine proviene da Come scegliere tra map e unordered_map? :

inserisci qui la descrizione dell'immagine


1

Non so cosa dia, ma hash_map impiega più di 20 secondi per clear () 150K chiavi intere senza segno e valori float. Sto solo correndo e leggendo il codice di qualcun altro.

Ecco come include hash_map.

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

Ho letto questo qui https://bytes.com/topic/c/answers/570079-perfomance-clear-vs-swap

dicendo che clear () è l'ordine di O (N). Questo per me è molto strano, ma è così.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.