Qual é a melhor maneira de usar um HashMap em C ++?


174

Eu sei que o STL tem uma API HashMap, mas não consigo encontrar nenhuma documentação boa e completa com bons exemplos a respeito.

Quaisquer bons exemplos serão apreciados.


Você está perguntando sobre o C ++ 1x hash_map ou sobre o std :: map?
Philant

2
Eu quero algo como um java.util.HashMap em C ++ e a maneira padronizada de fazê-lo, se houver um. Caso contrário, a melhor biblioteca não padrão. O que os desenvolvedores de C ++ geralmente usam quando precisam de um HashMap?
User855 26/08/10

Respostas:


238

A biblioteca padrão inclui os contêineres de mapa ( std::mape std::unordered_map) ordenados e não ordenados . Em um mapa ordenado, os elementos são classificados pela chave, inserção e acesso em O (log n) . Geralmente a biblioteca padrão usa internamente árvores negras vermelhas para mapas ordenados. Mas este é apenas um detalhe de implementação. Em um mapa não ordenado, insira e o acesso está em O (1). É apenas outro nome para uma hashtable.

Um exemplo com (pedido) 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;
}

Resultado:

23
Chave: olá Valor: 23

Se você precisar fazer um pedido em seu contêiner e estiver bem com o tempo de execução O (log n), basta usar std::map.

Caso contrário, se você realmente precisa de um hash-table (O (1) insert / acesso), confira std::unordered_map, que tem um similar à std::mapAPI (por exemplo, no exemplo acima, você só tem que procurar e substituir mapcom unordered_map).

O unordered_mapcontêiner foi introduzido com a revisão padrão do C ++ 11 . Portanto, dependendo do seu compilador, você precisa habilitar os recursos do C ++ 11 (por exemplo, ao usar o GCC 4.8, você deve adicionar -std=c++11ao CXXFLAGS).

Mesmo antes do lançamento do C ++ 11, o GCC era compatível unordered_map- no espaço para nome std::tr1. Assim, para os compiladores antigos do GCC, você pode tentar usá-lo assim:

#include <tr1/unordered_map>

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

Também faz parte do impulso, ou seja, você pode usar o cabeçalho de impulso correspondente para melhor portabilidade.


1
Embora a biblioteca padrão não tenha um contêiner baseado em tabela de hash, quase todas as implementações incluem hash_mapdo SGI STL de uma forma ou de outra.
James McNellis

@JamesMcNellis que é aconselhada unordered_map ou hash_map para implementação HashMap
Shameel Mohamed

2
@ ShameelMohamed, 2017, ou seja, 6 anos após o C ++ 11, deve ser difícil encontrar um STL que não forneça unordered_map. Portanto, não há razão para considerar o não-padrão hash_map.
maxschlepzig

30

A hash_mapé uma versão mais antiga e não padronizada do que, para fins de padronização, é chamado de unordered_map(originalmente em TR1 e incluído no padrão desde C ++ 11). Como o nome indica, é diferente de std::mapser desordenado - se, por exemplo, você percorre um mapa de begin()para end(), obtém os itens em ordem pela chave 1 , mas se percorre um unordered_mapde begin()para end(), obtém itens em um ordem mais ou menos arbitrária.

A unordered_map é normalmente esperado para ter complexidade constante. Ou seja, uma inserção, pesquisa etc. geralmente leva essencialmente um tempo fixo, independentemente de quantos itens estão na tabela. Um std::maptem uma complexidade logarítmica no número de itens que estão sendo armazenados - o que significa que o tempo para inserir ou recuperar um item aumenta, mas muito lentamente , à medida que o mapa aumenta. Por exemplo, se leva 1 microssegundo para pesquisar um dos 1 milhão de itens, você pode esperar cerca de 2 microssegundos para pesquisar um dos 2 milhões de itens, 3 microssegundos para um dos 4 milhões de itens, 4 microssegundos para um dos 8 milhões itens etc.

Do ponto de vista prático, essa não é a história toda. Por natureza, uma tabela de hash simples tem um tamanho fixo. A adaptação aos requisitos de tamanho variável para um contêiner de uso geral não é trivial. Como resultado, as operações que (potencialmente) aumentam a tabela (por exemplo, inserção) são potencialmente relativamente lentas (ou seja, a maioria é razoavelmente rápida, mas periodicamente uma será muito mais lenta). As pesquisas, que não podem alterar o tamanho da tabela, geralmente são muito mais rápidas. Como resultado, a maioria das tabelas baseadas em hash costuma ter o melhor desempenho quando você faz muitas pesquisas em comparação ao número de inserções. Para situações em que você insere muitos dados, repita a tabela uma vez para recuperar os resultados (por exemplo, contando o número de palavras exclusivas em um arquivo).std::map será tão rápido e possivelmente até mais rápido (mas, novamente, a complexidade computacional é diferente, de modo que também pode depender do número de palavras exclusivas no arquivo).


1 Onde a ordem é definida pelo terceiro parâmetro do modelo quando você cria o mapa, std::less<T>por padrão.


1
Sei que estou chegando 9 anos após a resposta ter sido publicada, mas ... você tem um link para um documento que mencione o fato de que um mapa não ordenado pode diminuir de tamanho? Normalmente, as coleções std apenas crescem. Além disso, se você inserir muitos dados, mas souber com antecedência mais ou menos quantas chaves você inserirá, poderá especificar o tamanho do mapa na criação, o que basicamente anula o custo de redimensionamento (porque não haverá nenhum) .
Zonko

@Zonko: Desculpe, eu não percebi isso quando solicitado. Tanto quanto eu sei, um unordered_map não diminui, exceto em resposta à chamada rehash. Ao ligar rehash, você especifica um tamanho para a tabela. Esse tamanho será usado, a menos que isso exceda o fator de carga máximo especificado para a tabela (nesse caso, o tamanho será aumentado automaticamente para manter o fator de carga dentro dos limites).
Jerry Coffin

22

Aqui está um exemplo mais completo e flexível que não omite as inclusões necessárias para gerar erros de compilação:

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

Ainda não é particularmente útil para chaves, a menos que sejam predefinidas como ponteiros, porque um valor correspondente não serve! (No entanto, como normalmente uso cadeias de caracteres para chaves, substituir "string" por "const void *" na declaração da chave deve resolver esse problema.)


4
Devo dizer que este exemplo é uma prática muito ruim em C ++. Você está usando um idioma fortemente tipado e o destrói usando void*. Para iniciantes, não há motivo para quebrar o unordered_mapcódigo, pois faz parte do padrão e reduz a manutenção do código. Em seguida, se insistir em envolvê-lo, use templates. É exatamente para isso que eles servem.
Guyarad 16/05/19

Fortemente digitado? Você provavelmente quer dizer digitado estaticamente. O fato de ele poder passar de const char ptr para void silenciosamente torna o C ++ estaticamente, mas não fortemente, digitado. Existem tipos, mas o compilador não diz nada a menos que você ative algum sinalizador obscuro que provavelmente não existe.
Sahsahae 04/12/19

6

Evidência que std::unordered_mapusa um mapa de hash no GCC stdlibc ++ 6.4

Isso foi mencionado em: https://stackoverflow.com/a/3578247/895245, mas na resposta a seguir: Que estrutura de dados está dentro de std :: map em C ++? Forneci evidências adicionais para a implementação do GCC stdlibc ++ 6.4 por:

  • Etapa GDB de depuração na classe
  • análise de características de desempenho

Aqui está uma prévia do gráfico de características de desempenho descrito nessa resposta:

insira a descrição da imagem aqui

Como usar uma função de classe e hash personalizada com unordered_map

Esta resposta explica: C ++ unordered_map usando um tipo de classe personalizado como a chave

Trecho: igualdade:

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

Função 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

Para aqueles que tentam descobrir como fazer o hash de nossas próprias classes enquanto ainda usam o modelo padrão, existe uma solução simples:

  1. Na sua classe, você precisa definir uma sobrecarga de operador de igualdade ==. Se você não sabe como fazer isso, o GeeksforGeeks possui um ótimo tutorial https://www.geeksforgeeks.org/operator-overloading-c/

  2. No espaço para nome padrão, declare uma estrutura de modelo chamada hash com o nome da classe como o tipo (veja abaixo). Encontrei um ótimo post no blog que também mostra um exemplo de cálculo de hashes usando XOR e deslocamento de bits, mas isso está fora do escopo desta pergunta, mas também inclui instruções detalhadas sobre como realizar o uso de funções hash também https://prateekvjoshi.com/ 06/06/2014 / using-hash-function-in-c-for-user-defined-classes /

namespace std {

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

}
  1. Portanto, para implementar uma hashtable usando sua nova função hash, você apenas precisa criar uma std::mapou std::unordered_mapexatamente como faria normalmente e usar my_typecomo chave, a biblioteca padrão usará automaticamente a função hash que você definiu anteriormente (na etapa 2) para hash suas chaves.
#include <unordered_map>

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