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.
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.
Respostas:
A biblioteca padrão inclui os contêineres de mapa ( std::map
e 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::map
API (por exemplo, no exemplo acima, você só tem que procurar e substituir map
com unordered_map
).
O unordered_map
contê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++11
ao 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.
hash_map
do SGI STL de uma forma ou de outra.
unordered_map
. Portanto, não há razão para considerar o não-padrão hash_map
.
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::map
ser 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_map
de 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::map
tem 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.
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).
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.)
void*
. Para iniciantes, não há motivo para quebrar o unordered_map
có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.
Evidência que std::unordered_map
usa 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:
Aqui está uma prévia do gráfico de características de desempenho descrito nessa resposta:
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);
}
};
}
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:
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/
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
...
}
};
}
std::map
ou std::unordered_map
exatamente como faria normalmente e usar my_type
como 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;
}