自 C++11 开始,STL 引入了基于 hash table 的 unordered_set、unordered_map 等容器,正如其名它们是无序容器。一定数量(据说有测试数据是10000000)元素时无序容器的性能要比对应的有序容器优。
一、容器数据结构
unordered_set、unordered_map 等容器的 Hash Table(哈希表/散列表)结构通常是:桶(bucket) + 线性表,bucket 一般用 vector 实现,线性表通常采用链表。当插入元素时通过哈希函数计算其哈希值,以哈希值作为 vector 的下标索引,取得元素要插入的链表头,把元素插入进去。也就是说通过 链地址法(Separate Chaining) 解决哈希冲突。
二、哈希函数
哈希函数是这些容器的核心,也是保证它们高性能的关键。C++11 为基本类型:int、float、double、char、string 提供了哈希函数。 但是要在这些容器中添加自定义类型的元素就必须提供特定的哈希函数。
无自定义类的哈希函数
无论是 clang++、还是 g++ 都无法正确如下代码,原因就是:缺失计算 Person 哈希值的函数
#include <string>
#include <unordered_set>class Person {
public:Person(const std::string &name, int age) : _name(name), _age(age) {}private:std::string _name;int _age;
};int main(int argc, char *argv[]) {std::unordered_set<Person> persons;
}
为自定义类添加哈希函数
查看 unordered_set 的声明、 g++ 的 /usr/include/c++/9/bits/functional_hash.h 可知:
- unordered_set 的模板参数 Hash 默认设置为 std::hash<Key>,std::hash 是一个类模板;
- 通过特化 std::hash,支持了诸如 int、double 基本类型的哈希函数;
- 每个特化版本中重载了函数调用运算符
template<class Key,class Hash = std::hash<Key>,class KeyEqual = std::equal_to<Key>,class Allocator = std::allocator<Key>
> class unordered_set;/// Explicit specialization for int.
_Cxx_hashtable_define_trivial_hash(int)/// Explicit specialization for bool.
_Cxx_hashtable_define_trivial_hash(bool)/// Explicit specialization for char.
_Cxx_hashtable_define_trivial_hash(char)#define _Cxx_hashtable_define_trivial_hash(_Tp) \template<> \struct hash<_Tp> : public __hash_base<size_t, _Tp> \{ \size_t \operator()(_Tp __val) const noexcept \{ return static_cast<size_t>(__val); } \};
综述为 Person 类定义函数对象类实现哈希函数功能
#include <string>
#include <unordered_set>struct Person {Person(const std::string &name, int age) : _name(name), _age(age) {}std::string _name;int _age;
};struct PersonHash
{size_t operator()(const Person &person) const noexcept{return static_cast<size_t>(person._age + person._name.length());}
};int main(int argc, char *argv[]) {std::unordered_set<Person, PersonHash> persons;persons.insert({"yaoyuan", 30});}
为自定义类添加哈希函数
未完...