Qual è il modo migliore per utilizzare una HashMap in C ++?


174

So che STL ha un'API HashMap, ma non riesco a trovare alcuna documentazione valida e completa con buoni esempi al riguardo.

Eventuali buoni esempi saranno apprezzati.


Stai chiedendo informazioni sulla hash_map C ++ 1x o sulla std :: map?
filante,

2
Voglio qualcosa come java.util.HashMap in C ++ e il modo standardizzato di farlo se ce n'è uno. Else la migliore libreria non standard. Cosa usano comunemente gli sviluppatori C ++ quando hanno bisogno di una HashMap?
user855

Risposte:


238

La libreria standard include i contenitori ( std::mape std::unordered_map) della mappa ordinati e non ordinati . In una mappa ordinata gli elementi sono ordinati per chiave, inserire e l'accesso è in O (log n) . Di solito la libreria standard utilizza internamente alberi neri rossi per le mappe ordinate. Ma questo è solo un dettaglio di implementazione. In una mappa non ordinata inserire e accedere è in O (1). È solo un altro nome per un hashtable.

Un esempio con (ordinato) std::map:

#include <map>
#include <iostream>
#include <cassert>

int main(int argc, char **argv)
{
  std::map<std::string, int> m;
  m["hello"] = 23;
  // check if key is present
  if (m.find("world") != m.end())
    std::cout << "map contains key world!\n";
  // retrieve
  std::cout << m["hello"] << '\n';
  std::map<std::string, int>::iterator i = m.find("hello");
  assert(i != m.end());
  std::cout << "Key: " << i->first << " Value: " << i->second << '\n';
  return 0;
}

Produzione:

23
Chiave: ciao Valore: 23

Se hai bisogno di ordinare nel tuo contenitore e stai bene con il runtime O (log n), usa semplicemente std::map.

Altrimenti, se hai davvero bisogno di una tabella hash (O (1) insert / access), dai un'occhiata std::unordered_map, che ha std::mapun'API simile (ad esempio nell'esempio sopra devi solo cercare e sostituire mapcon unordered_map).

Il unordered_mapcontenitore è stato introdotto con la revisione standard C ++ 11 . Pertanto, a seconda del compilatore, è necessario abilitare le funzionalità C ++ 11 (ad es. Quando si utilizza GCC 4.8 è necessario aggiungere -std=c++11a CXXFLAGS).

Anche prima della versione C ++ 11 supportata da GCC unordered_map- nello spazio dei nomi std::tr1. Quindi, per i vecchi compilatori GCC puoi provare ad usarlo in questo modo:

#include <tr1/unordered_map>

std::tr1::unordered_map<std::string, int> m;

Fa anche parte del boost, ovvero è possibile utilizzare l' intestazione boost corrispondente per una migliore portabilità.


1
Mentre la libreria standard non ha un contenitore basato su tabella hash, quasi tutte le implementazioni includono hash_mapSL STL in un modo o nell'altro.
James McNellis,

@JamesMcNellis che è consigliato unordered_map o hash_map per l'implementazione di
HashMap

2
@ShameelMohamed, 2017, ovvero 6 anni dopo C ++ 11, dovrebbe essere difficile trovare un STL che non fornisce unordered_map. Pertanto, non vi è motivo di considerare il non standard hash_map.
maxschlepzig,

30

A hash_mapè una versione precedente e non standardizzata di ciò che ai fini della standardizzazione è chiamata an unordered_map(originariamente in TR1 e inclusa nello standard dal C ++ 11). Come suggerisce il nome, è diverso dall'essere std::mapprincipalmente non ordinato: se, ad esempio, si scorre attraverso una mappa da begin()a end(), si ottengono gli elementi in ordine con il tasto 1 , ma se si scorre attraverso un unordered_mapda begin()a end(), si ottengono elementi in un ordine più o meno arbitrario.

unordered_mapNormalmente si prevede che An abbia una complessità costante. Cioè, un inserimento, una ricerca, ecc., Richiede in genere essenzialmente un periodo di tempo fisso, indipendentemente dal numero di elementi presenti nella tabella. Una std::mapcomplessità che è logaritmica sul numero di elementi archiviati, il che significa che il tempo per inserire o recuperare un oggetto aumenta, ma abbastanza lentamente , man mano che la mappa aumenta. Ad esempio, se ci vuole 1 microsecondo per cercare uno dei 1 milione di articoli, allora ci si può aspettare che ci vorranno circa 2 microsecondi per cercare uno dei 2 milioni di articoli, 3 microsecondi per uno dei 4 milioni di articoli, 4 microsecondi per uno degli 8 milioni articoli, ecc.

Da un punto di vista pratico, questa non è proprio l'intera storia. Per natura, una semplice tabella hash ha una dimensione fissa. Adattarlo ai requisiti di dimensioni variabili per un contenitore per scopi generici è in qualche modo non banale. Di conseguenza, le operazioni che (potenzialmente) fanno crescere la tabella (ad esempio, l'inserimento) sono potenzialmente relativamente lente (cioè la maggior parte sono abbastanza veloci, ma periodicamente una sarà molto più lenta). Le ricerche, che non possono modificare le dimensioni della tabella, sono generalmente molto più veloci. Di conseguenza, la maggior parte delle tabelle basate sull'hash tendono ad essere al meglio quando si eseguono molte ricerche rispetto al numero di inserimenti. Per le situazioni in cui si inseriscono molti dati, scorrere una volta nella tabella per recuperare i risultati (ad esempio, contando il numero di parole univoche in un file) è probabile che unstd::map sarà altrettanto veloce e probabilmente anche più veloce (ma, ancora una volta, la complessità computazionale è diversa, quindi può dipendere anche dal numero di parole uniche nel file).


1 Dove l'ordine è definito dal terzo parametro del modello quando si crea la mappa, std::less<T>per impostazione predefinita.


1
Mi rendo conto che sto arrivando 9 anni dopo che la risposta è stata pubblicata, ma ... hai un link a un documento che menziona il fatto che una mappa non ordinata può ridursi di dimensioni? Di solito, le raccolte standard crescono solo. Inoltre, se inserisci molti dati ma sai in anticipo più o meno quante chiavi inserirai, puoi specificare la dimensione della mappa al momento della creazione, che sostanzialmente annulla il costo di ridimensionamento (perché non ce ne saranno) .
Zonko,

@Zonko: Scusa, non me ne sono accorto quando mi è stato chiesto. Per quanto ne so, un unordered_map non si restringe, tranne che in risposta alla chiamata rehash. Quando si chiama rehash, si specifica una dimensione per la tabella. Tale dimensione verrà utilizzata a meno che ciò non superi il fattore di carico massimo specificato per la tabella (in tal caso, la dimensione verrà aumentata automaticamente per mantenere il fattore di carico entro i limiti).
Jerry Coffin il

22

Ecco un esempio più completo e flessibile che non omette le inclusioni necessarie per generare errori di compilazione:

#include <iostream>
#include <unordered_map>

class Hashtable {
    std::unordered_map<const void *, const void *> htmap;

public:
    void put(const void *key, const void *value) {
            htmap[key] = value;
    }

    const void *get(const void *key) {
            return htmap[key];
    }

};

int main() {
    Hashtable ht;
    ht.put("Bob", "Dylan");
    int one = 1;
    ht.put("one", &one);
    std::cout << (char *)ht.get("Bob") << "; " << *(int *)ht.get("one");
}

Ancora non particolarmente utile per le chiavi, a meno che non siano predefinite come puntatori, perché un valore corrispondente non lo farà! (Tuttavia, poiché normalmente utilizzo stringhe per chiavi, la sostituzione di "stringa" con "const void *" nella dichiarazione della chiave dovrebbe risolvere questo problema.)


4
Devo dire che questo esempio è una brutta pratica in C ++. Stai usando un linguaggio fortemente tipizzato e lo stai distruggendo usando void*. Per i principianti, non c'è motivo di concludere unordered_mappoiché fa parte dello standard e riduce la manutenibilità del codice. Quindi, se insisti per avvolgerlo, usa templates. Questo è esattamente quello per cui sono.
guyarad,

Fortemente tipizzato? Probabilmente intendi digitare staticamente. Il fatto che possa passare da const char ptr a void silenziosamente rende C ++ tipicamente, ma non fortemente, tipizzato. Ci sono tipi, ma il compilatore non dirà nulla se non abiliti qualche flag oscuro che molto probabilmente non esiste.
Sahsahae,

6

Prova che std::unordered_maputilizza una mappa hash in GCC stdlibc ++ 6.4

Questo è stato menzionato su: https://stackoverflow.com/a/3578247/895245 ma nella seguente risposta: Qual è la struttura dei dati all'interno di std :: map in C ++? Ne ho fornito ulteriori prove per l'implementazione di GCC stdlibc ++ 6.4:

  • Debug del passaggio GDB nella classe
  • analisi delle caratteristiche prestazionali

Ecco un'anteprima del grafico delle caratteristiche prestazionali descritto in quella risposta:

inserisci qui la descrizione dell'immagine

Come utilizzare una classe personalizzata e una funzione hash con unordered_map

Questa risposta lo inchioda: C ++ unordered_map usando un tipo di classe personalizzato come chiave

Estratto: uguaglianza:

struct Key
{
  std::string first;
  std::string second;
  int         third;

  bool operator==(const Key &other) const
  { return (first == other.first
            && second == other.second
            && third == other.third);
  }
};

Funzione hash:

namespace std {

  template <>
  struct hash<Key>
  {
    std::size_t operator()(const Key& k) const
    {
      using std::size_t;
      using std::hash;
      using std::string;

      // Compute individual hash values for first,
      // second and third and combine them using XOR
      // and bit shifting:

      return ((hash<string>()(k.first)
               ^ (hash<string>()(k.second) << 1)) >> 1)
               ^ (hash<int>()(k.third) << 1);
    }
  };

}

0

Per quelli di noi che cercano di capire come eseguire l'hashing delle proprie classi mentre si utilizza ancora il modello standard, esiste una soluzione semplice:

  1. Nella tua classe devi definire un sovraccarico dell'operatore di uguaglianza ==. Se non sai come fare, GeeksforGeeks ha un ottimo tutorial https://www.geeksforgeeks.org/operator-overloading-c/

  2. Sotto lo spazio dei nomi standard, dichiara un tipo di struttura chiamato hash con il tuo nome classe come tipo (vedi sotto). Ho trovato un ottimo post sul blog che mostra anche un esempio di calcolo degli hash utilizzando XOR e bitshifting, ma non rientra nell'ambito di questa domanda, ma include anche istruzioni dettagliate su come eseguire le funzioni hash e https://prateekvjoshi.com/ 2014/06/05 / using-hash-funzione-in-c-per-user-classi definite /

namespace std {

  template<>
  struct hash<my_type> {
    size_t operator()(const my_type& k) {
      // Do your hash function here
      ...
    }
  };

}
  1. Quindi, per implementare una tabella hash usando la tua nuova funzione hash, devi solo creare una std::mapo std::unordered_mapproprio come faresti normalmente e usarla my_typecome chiave, la libreria standard utilizzerà automaticamente la funzione hash definita in precedenza (nel passaggio 2) per eseguire l'hash le tue chiavi.
#include <unordered_map>

int main() {
  std::unordered_map<my_type, other_type> my_map;
}
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.