我知道STL有一个HashMap API,但我找不到任何关于此的好例子的完整文档。
任何好的例子都将受到赞赏。
答案 0 :(得分:182)
标准库包括有序和无序映射(std::map
和std::unordered_map
)容器。在有序映射中,元素按键排序,插入和访问在O(log n)中。通常,标准库在内部使用red black trees作为有序映射。但这只是一个实现细节。在无序映射中,插入和访问在O(1)中。它只是哈希表的另一个名称。
(有序)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;
}
输出:
23 Key: hello Value: 23
如果您需要在容器中订购,并且可以使用O(log n)运行时,那么只需使用std::map
。
否则,如果您确实需要哈希表(O(1)插入/访问),请查看std::unordered_map
,它与std::map
API类似(例如,在上面的示例中,您只需必须使用map
搜索并替换unordered_map
。
unordered_map
容器随C++11 standard修订版一起引入。因此,根据您的编译器,您必须启用C ++ 11功能(例如,在使用GCC 4.8时,您必须将-std=c++11
添加到CXXFLAGS)。
甚至在C ++ 11版本GCC支持unordered_map
之前 - 在命名空间std::tr1
中。因此,对于旧的GCC编译器,您可以尝试使用它:
#include <tr1/unordered_map>
std::tr1::unordered_map<std::string, int> m;
它也是提升的一部分,即您可以使用相应的boost-header来提高便携性。
答案 1 :(得分:26)
hash_map
是标准化目的的较旧的非标准化版本,称为unordered_map
(最初在TR1中,并且自C ++ 11以来包含在标准中)。顾名思义,它与std::map
的不同之处主要在于无序 - 例如,如果您遍历从begin()
到end()
的地图,则按键顺序获取项目< sup> 1 ,但如果您从unordered_map
到begin()
遍历end()
,则会以或多或少的顺序获取项目。
unordered_map
通常预计会有复杂性。也就是说,插入,查找等通常基本上花费固定的时间量,而不管表中有多少项。 std::map
具有与存储项目数量成对数的复杂性 - 这意味着插入或检索项目的时间会增长,但随着地图变大,慢慢地。例如,如果查找100万个项目中的一个需要1微秒,那么您可以预期查找200万个项目中的一个需要大约2微秒,400万个项目中的一个项目需要3微秒,800万个项目中的一个项目需要4微秒物品等。
从实际的角度来看,这并不是真正的整个故事。本质上,简单的哈希表具有固定的大小。使其适应通用容器的可变大小要求有点不重要。结果,(可能)增长表(例如,插入)的操作可能相对较慢(即,大多数相当快,但周期性地会慢得多)。查找不能改变表的大小,通常要快得多。因此,与插入次数相比,当您执行大量查找时,大多数基于散列的表往往处于最佳状态。对于插入大量数据的情况,然后遍历表一次以检索结果(例如,计算文件中唯一单词的数量),std::map
可能同样快,很可能甚至更快(但同样,计算复杂性也不同,因此也可能取决于文件中唯一字的数量)。
1 在创建地图时,第三个模板参数定义了顺序,默认情况下为std::less<T>
。
功能
答案 2 :(得分:21)
这是一个更完整,更灵活的示例,没有必要包含生成编译错误的内容:
#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");
}
对于密钥仍然没有特别有用,除非它们被预定义为指针,因为匹配的值不会发生! (但是,因为我通常使用字符串作为键,所以用&#34; string&#34;代替&#34; const void *&#34;在键的声明中应该解决这个问题。)
答案 3 :(得分:1)
std::unordered_map
在GCC stdlibc ++ 6.4中使用哈希映射的证据
在https://stackoverflow.com/a/3578247/895245提到了这一点,但在下面的回答中:What data structure is inside std::map in C++?我已经通过以下方式为GCC stdlibc ++ 6.4实现提供了进一步的证据:
以下是该答案中描述的性能特征图的预览:
如何使用unordered_map
这个答案指出:C++ unordered_map using a custom class type as the key
摘录:平等:
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);
}
};
哈希函数:
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);
}
};
}
答案 4 :(得分:0)
对于那些试图弄清楚如何在仍然使用标准模板的同时对自己的类进行哈希处理的人,有一个简单的解决方案:
在您的类中,需要定义一个相等运算符重载==
。如果您不知道该怎么做,GeeksforGeeks会提供出色的教程https://www.geeksforgeeks.org/operator-overloading-c/
在标准名称空间下,声明一个名为hash的模板结构,其类名为类型(请参见下文)。我发现了一个很棒的博客文章,其中还展示了使用XOR和位移位计算哈希的示例,但这超出了本问题的范围,但其中还包括有关如何完成使用哈希函数的详细说明https://prateekvjoshi.com/2014/06/05/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
或std::unordered_map
,就像平常一样并使用my_type
作为密钥,标准库将自动使用您之前(在第2步中)定义的哈希函数来哈希密钥。#include <unordered_map>
int main() {
std::unordered_map<my_type, other_type> my_map;
}