C++哈希(包含unordered_set和unordered_map的封装)
目录
1、unordered系列关联式容器
1.1unordered_map
1.2unordered_set
2、底层结构
2.1哈希概念
2.2哈希冲突
2.3哈希函数
2.4解决哈希冲突
2.4.1闭散列
2.4.2开散列
2.4.3开散列和闭散列的比较
2.5实现
2.5.1闭散列
2.5.1闭散列
3、unordered系列的封装(哈希表使用链地址法)
HashTable.h
MyUnordered_Set.h
MyUnordered_Map.h
1、unordered系列关联式容器
1.1unordered_map

- unordered_map是存储<key,value>键值对的关联式容器,其允许通过keys快速索引到与其对应的value
- 在unordered_map中,键值通常用于唯一的标识元素,而映射值是一个对象,其内容与此键关联,键和映射值的类型可能不同
- 在内部,unordered_map没有对<key,value>按照任何特定的顺序排序,为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中
- unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低
- unordered_map实现了直接访问操作符,它允许使用key作为参数直接访问value
- 它的迭代器至少是前向迭代器
1.2unordered_set

2、底层结构
unordered系列的关联式容器效率高的原因是其底层使用了哈希结构
2.1哈希概念
由于在顺序结构和平衡树中,元素的关键码和它存储的位置没有任何关系,因此在查找一个元素时,需要与关键码进行多次比较
而理想的搜索方式则是:可以不经过任何比较,直接找到需要搜索的元素。
我们构造一个存储结构,通过某一函数将元素的关键码和它的存储位置建立起一个一一映射的关系,那么在查找时可以很快的通过该函数找到相对应的元素
当我们向这个结构中:
- 插入元素:根据待插入元素的关键码,以该函数计算出该元素存储的位置并按照该位置进行存储
- 搜索元素:对需要搜索的元素的关键码进行同样的计算,并按照求出的存储位置进行搜索,最后将搜索的值与关键码进行比较,如果相等则搜索成功
上述方式就是哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(或称为散列表)
使用上述方法进行搜索时,不需要进行多次关键码的比较,可以直接求出存储位置进行查找,搜索速度比较快
2.2哈希冲突
哈希冲突就是指不同的关键字通过相同的哈希函数计算出相同的地址,这种情况被称为哈希冲突或哈希碰撞
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”
2.3哈希函数
引起哈希冲突的一个原因可能是:哈希函数设计不合理
哈希函数设计原则:
- 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
- 哈希函数计算出来的地址能均匀分布在整个空间中
- 哈希函数应该是比较简单的
常见的哈希函数有直接定址法、除留余数法、平方取中法、折叠法、随机数法和数学分析法,其中直接定址法和除留余数法是常用的
注:哈希函数设计的越精妙,产生哈希冲突的可能性越小,但是无法避免哈希冲突的产生
2.4解决哈希冲突
哈希冲突有两种常见的解决方式:闭散列和开散列
2.4.1闭散列
闭散列也叫做开放定址法,当发生哈希冲突时,如果哈希表没有被填满,则说明在哈希表中必然还有空位置,那么这就可以把发生冲突的那个元素插入到冲突位置的“下一个”空位置中
“下一个”位置可以通过线性探测和二次探测进行查找
- 线性探测:从发生冲突的位置开始依次向后查找,直到找到下一个位置结束。它的缺点是一旦出现哈希冲突可能会导致所有的冲突连在一起,容易造成数据的堆积,这会导致部分元素的位置被占,使得其存储位置后移,这会导致查找的效率变低
- 二次探测:当发生冲突时,“下一个”位置并不是简单的顺序向后查找,而是按照二次函数间隔探测下一个位置,这主要用来解决数据堆积的问题

2.4.2开散列
开散列法又被称为链地址法(开链法),对关键码用哈希函数计算出存储地址,然后将地址相同的 放到一个集合中,每个集合被称为一个桶,每个桶中的元素用一个链表链接起来,然后将各个链表的头结点存储到哈希表中
2.4.3开散列和闭散列的比较
应用链地址法在处理数据溢出时,需要创建节点增加链接指针,看似增加了储存开销,但是由于开放地址法为了保证数据的插入,它必须保持大量的空闲空间以确保搜索效率,而表项所占的空间要比指针大得多,因此链地址法更为节省空间
2.5实现
2.5.1闭散列
enum Status{Exist,Delete,Empty};template<class K, class V>struct HashDataType{pair<K, V> _kv;Status _st = Empty; //当前位置存储数据的状态};template<class K, class V>class HashTable{public:HashTable(){_table.resize(10);}bool insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}if (_n * 10 / _table.size() >= 7) //当(有效数据/顺序表大小>0.7)时,扩容{size_t newsize = 2 * _table.size();HashTable<K, V> newHash;newHash._table.resize(newsize);for (size_t i = 0; i < _table.size(); i++){if (_table[i]._st == Exist){newHash.insert(_table[i]._kv);}}_table.swap(newHash._table);}size_t tablei = kv.first % _table.size(); //通过哈希函数计算元素的存储位置while (_table[tablei]._st != Empty) //如果当前位置有数据或删除过数据需要向后查询直到找到空位置进行插入{tablei++;tablei %= _table.size();}_table[tablei]._kv = kv;_table[tablei]._st = Exist;_n++;return true;}HashDataType<const K, V>* Find(const K& key){size_t tablei = key % _table.size();while (_table[tablei]._st != Empty){if (_table[tablei]._st == Exist && _table[tablei]._kv.first == key) //删除是伪删除,并没有真正的删除里面的数据,因此需要从存在的数据中查找{return (HashDataType<const K, V>*)& _table[tablei];}tablei++;tablei %= _table.size();}return nullptr;}bool Erase(const K& key){HashDataType<const K, V>* ret = Find(key);if (ret){ret->_st = Delete; //直接将需要删除元素的数据状态改为Delete即可表达为删除,这是因为如果真正删除掉,在查找某个元素时如果查找的那个元素位于该删除元素的后面可能会导致找不到查找的那个元素,这是因为查找时,找到空就会结束--_n;return true;}return false;}private:vector<HashDataType<K, V>> _table;size_t _n = 0; //存储的有效数据个数};
2.5.1闭散列
template<class K>struct HashFunc //仿函数,当pair<K,V>中的K是整数类型时{size_t operator()(const K& key){return (size_t)key;}};template<>struct HashFunc<string> //仿函数的模版特化,当pair<K,V>中的K是string类型时{size_t operator()(const string& s){size_t ret = 0;for (auto e : s){ret *= 31; //可以降低不同元素产生相同关键码的概率ret += e;}return ret;}};template<class K, class V>struct HashNode{pair<K, V> _kv;HashNode* _next;HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr){}};template<class K, class V, class Hash = HashFunc<K>> //根据对应的K值走相应的仿函数class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_table.resize(10);}bool insert(const pair<K, V>& kv){if (find(kv.first)){return false;}Hash hs;if (_n == _table.size()) //当有效数据==顺序表大小时,扩容{vector<Node*> newtable;newtable.resize(_table.size() * 2);for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i]; //为了避免创建节点时间的浪费,直接将旧表中的节点插入到新表中,旧表置空while (cur){size_t hashi = hs(cur->_kv.first) % newtable.size();cur->_next = newtable[hashi];newtable[hashi] = cur;cur = cur->_next;}_table[i] = nullptr;}_table.swap(newtable);}size_t hashi = hs(kv.first) % _table.size();Node* cur = new Node(kv);cur->_next = _table[hashi];_table[hashi] = cur;++_n;return true;}bool find(const K& key){Hash hs;size_t hashi = hs(key) % _table.size();Node* cur = _table[hashi];while (cur){if (cur->_kv.first == key){return true;}cur = cur->_next;}return false;}bool erase(const K& key){Hash hs;size_t hashi = hs(key) % _table.size();Node* cur = _table[hashi];while (cur){Node* prev = nullptr;Node* next = cur->_next;if (cur->_kv.first == key){if (cur = _table[hashi]){_table[hashi] = next;}else{prev->_next = next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _table;size_t _n;};
3、unordered系列的封装(哈希表使用链地址法)
HashTable.h
template<class K>
struct HashData
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashData<string>
{size_t operator()(const string& s){size_t ret = 0;for (auto e : s){ret *= 31;ret += e;}return ret;}
}; template<class T>
struct HashNode
{T _data;HashNode<T>* _next = nullptr;HashNode(const T& data):_data(data){ }
};template<class K, class T, class Hash, class KeyOfT>
class HashTable;template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>
struct __HashIterator
{typedef HashNode<T> Node;typedef __HashIterator<K, T, Ref, Ptr, Hash, KeyOfT> Self;typedef __HashIterator<K, T, T&, T*, Hash, KeyOfT> iterator;size_t _hashi;Node* _node;const HashTable<K, T, Hash, KeyOfT>* _newtable;__HashIterator(Node* node, const HashTable<K, T, Hash, KeyOfT>* table, size_t hashi):_node(node), _newtable(table), _hashi(hashi){}__HashIterator(const iterator& it){_node = it._node;_hashi = it._hashi;_newtable = it._newtable;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_next){_node = _node->_next;}else{++_hashi;while (_hashi < _newtable->_table.size()){if (_newtable->_table[_hashi]){_node = _newtable->_table[_hashi];break;}++_hashi;}if (_hashi == _newtable->_table.size()){_node = nullptr;}}return *this;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};template<class K, class T, class Hash, class KeyOfT>
class HashTable
{typedef HashNode<T> Node;template<class K, class T, class Ref, class Ptr, class Hash, class KeyOfT>friend struct __HashIterator;public:typedef __HashIterator<K, T, T&, T*, Hash, KeyOfT> iterator;typedef __HashIterator<K, T, const T&, const T*, Hash, KeyOfT> const_iterator;iterator begin(){for (size_t i = 0; i < _table.size(); i++){if (_table[i]){return iterator(_table[i], this, i);}}return end();}iterator end(){return iterator(nullptr, this, -1);}const_iterator begin()const{for (size_t i = 0; i < _table.size(); i++){if (_table[i]){return const_iterator(_table[i], this, i);}}return end();}const_iterator end()const{return const_iterator(nullptr, this, -1);}HashTable(){_table.resize(10);}pair<iterator, bool> insert(const T& data){KeyOfT kot;Hash hs;iterator it = find(kot(data));if (it != end()){return make_pair(it, false);}if (_n == _table.size()){vector<Node*> newtable;newtable.resize(2 * _table.size(), nullptr);for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hashi = hs(kot(data)) % newtable.size();cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newtable);}size_t hashi = hs(kot(data)) % _table.size();Node* cur = new Node(data);cur->_next = _table[hashi];_table[hashi] = cur;++_n;return make_pair(iterator(cur, this, hashi), true);}iterator find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this, hashi);}cur = cur->_next;}return end();}bool erase(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _table.size();Node* cur = _table[hashi];while (cur){Node* prev = nullptr;if (kot(cur->_data) == key){if (cur == _table[hashi]){_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete[] cur;return true;}prev = cur;cur = cur->_next;}--_n;return false;}
private:vector<Node*> _table;size_t _n = 0;
};
MyUnordered_Set.h
namespace Set
{template<class K, class Hash = HashData<K>>class unordered_set{struct SetkeyOfT{const K& operator()(const K& key){return key;}};typedef typename hash_bucket::HashTable<K, K, Hash, SetkeyOfT>::const_iterator iterator;typedef typename hash_bucket::HashTable<K, K, Hash, SetkeyOfT>::const_iterator const_iterator;public:const_iterator begin()const{return _ht.begin();}const_iterator end()const{return _ht.end();}pair<iterator, bool> insert(const K& key){return _ht.insert(key);}iterator find(const K& key){return _ht.find(key);}bool erase(const K& key){return _ht.erase(key);}private:hash_bucket::HashTable<K, K, Hash, SetkeyOfT> _ht;};
}
MyUnordered_Map.h
namespace Map
{template<class K, class V, class Hash = HashData<K>>class unordered_map{struct SetkeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, Hash, SetkeyOfT>::iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, Hash, SetkeyOfT>::const_iterator const_iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}const_iterator begin()const{return _ht.begin();}const_iterator end()const{return _ht.end();}pair<iterator, bool> insert(const pair<const K, V> kv){return _ht.insert(kv);}bool erase(const K& key){return _ht.erase(key);}iterator find(const K& key){return _ht.find(key);}private:hash_bucket::HashTable<K, pair<const K, V>, Hash, SetkeyOfT> _ht;};
}
