哈希表封装实现unordered_set unordered_map
本文用哈希表来实现unordered_set及unordered_map 这里实现的逻辑和大框架和之前的红黑树封装实现map和set非常类似
和之前的set及map一样 源码中的命名风格是比较乱的 这里就统一 节点存储的类型为T key对象的类型为K value对象的类型为V
这里直接把上篇哈希的一些东西拿过来然后简单修改下
HashTable的前三个模版参数和红黑树封装实现set map完全是一样的
第二个模版参数T是存储的类型
unordered_set传的是K 里面存的就是K类型的对象key
unordered_map传的pair<K,V> 里面存的就是pair类型的对象
第一个模版参数是因为find erase的功能需要直接用到key 统一额外传过去一个key
第三个参数是可以把unordered_map里面存储的pair类型转换为key 为了统一兼容unordered_set会把key转换为key
第四 第五个模版参数就是上一篇哈希表的内容了 hash是可以把不支持转换为整形的类型如string自定义类型Date等通过一些方式来转换为整形 第五个模版参数是为了支持自定义类型对象==的比较
构造函数初始化空间大小 及插入的扩容还用下面质数表的方式
上篇实现的哈希表是以里面存pair类型的对象实现的
这里为了统一兼容把insert find erase里面用Rekey类型的对象把之前kv.first直接给变为 tokey(data) 这样不管data是unordered_set的K类型的key还是unordered_map的pair<K,V>的对象都会同样转换为key
bool insert(T& data)
{Hash toint;Rekey tokey;if (find(tokey(data))) //新插入元素已经存在 则插入插入失败return false;if (_n / _table.size() == 1) // 负载因子为1 扩容{HashTable<K, T, Rekey, Hash, HashSame> newhas;newhas._table.resize(__stl_next_prime(_table.size() + 1));//扩容后size改变了 此时需要把原来的数据重新映射到新的vector类型的对象中for (auto& x : _table) //每一个x是一个节点指针 直接复用insert{Node* head = x;while (x){ //把旧表每一个值在新表映射的位置算出来 然后直接把节点移动到新表正确的位置Node* next = x->_next;size_t hashi2 = toint(x->_kv.first) % newhas._table.size();x->_next = newhas._table[hashi2];newhas._table[hashi2] = x;x = next;}}_table.swap(newhas._table);}size_t hashi = toint(tokey(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;_n++;return true;
}Node* find(K key){Hash toint;Rekey tokey;HashSame ifsa;size_t hashi = toint(key) % _table.size();Node* cur = _table[hashi];while (cur){if (ifsa(tokey(cur->_data), key)){return cur;}elsecur = cur->_next;}return nullptr;
}bool erase(K key){Hash toint;Rekey tokey;HashSame ifsa;size_t hashi = toint(key) % _table.size();Node* cur = _table[hashi];Node* pre = nullptr;while (cur){if (ifsa(tokey(cur->_data), key)){if (_table[hashi] == cur) // 头节点{_table[hashi] = cur->_next;}else //中间节点{pre->_next = cur->_next;}delete cur;return true;}else{pre = cur;cur = cur->_next;}}return false;}
insert erase find先不管了 先来实现一下迭代器
这里迭代器的实现和之前的list map set的实现一样 为了实现普通迭代器和const类型迭代器 在原有模版参数的基础上再加两个模版参数Ref Ptr分别作为重载*和重载->的返回值
这样这两个模版参数传的是普通的 T& T*这个迭代器就是普通迭代器
传的是const T& const T*这个迭代器就是const类型的迭代器
先实现* -> != ==的重载
然后实现一下++的重载
先来分析一下 ++应该是要到下一个节点的位置 如果当前哈希桶的下一个位置还有值的话即当前节点的next指针不为空 下一个节点就应该到next指针指向的位置 如果下一个位置为空即next指针为空了说明当前的哈希桶已经走完了 此时++应该是到下一个不为空的哈希桶的头节点
所以这里要分两种情况
①当前节点的next指针为空 此时++就是到next指针所指向的位置
②当前节点的next指针为空 要找下一个哈希桶不为空的头节点
对于第一种情况++很容易实现 但是第二种需要找下一个不为空的哈希表 但是这里我们迭代器里面只有一个当前位置的指针 不能往回走所以无法满足我们的需求
所以在迭代器类中我们还需要一个哈希表对象的指针 然后就按照我们分析的实现
struct Hashtable_iterator
{typedef HashNode<T> Node;typedef Hashtable_iterator<K,T,Ref,Ptr,Rekey,Hash,HashSame> Self;typedef HashTable<K, T, Rekey, Hash, HashSame> Ht;Ht* _ht;Node* _node;Hashtable_iterator(Node* node, const Ht* ht):_node(node), _ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& node){return _node != node;}bool operator==(const Self& node){return _node == node;}Rekey tokey;Hash toint;Self& operator++(){size_t hashi;if (_node->_next) //当前桶下一个位置还有值{_node = _node->next;}else //当前桶为空 找新的不为空的桶{hashi=toint(tokey(_node->_data)) % _ht->_table.size(); //当前节点所在桶的位置++hashi;while (hashi < _ht->_table.size()){if (_ht->_table[hashi]) //找到新的不为空的哈希桶了{_node = _ht->_table[hashi];}elsehashi++;}//到了最后还没有找到_node = nullptr;}return *this;}};
然后在HashTable里面重命名一下两个不同的迭代器
此时还有两个问题
①在迭代器里面需要用到HashTable里面的_table 但它是私有的
②HashTable里面要用迭代器的东西 迭代器里面也要用到HashTable的东西 但是他们只会向上找 所以在上面的那一个就会找不到另一个
对于第一种解决方式就是在HashTable做迭代器的友元声明
对于第二个解决方式是把在上面的那一个前面加上另一个的声明 (我这里是迭代器在上面 所以需要在迭代器的前面加上HashTable前置的声明)
这里要注意不管是友元的声明 还是前置声明 模版里面的缺省值都不写
然后再unorderen_set unordered_map里面再重命名一层 这里需要注意需要在前面加typename来告诉编译器我们要取的是类模版里面的类型
然后在HashTable里面实现一下Begin和End
Begin要返回第一个不为空的桶的位置 End就直接返回空的位置 this就是HashTable对象的地址 迭代器用到的是两个参数 直接用两个参数用隐式类型转换的方式来构造迭代器类型
在unordered_set unordered_map里面直接复用HashTable的begin和end就可以了
接下来测试一下写的
在下面测试中才发现有一些细节写错了 通过报错信息及调试修改之后就可以正常运行了
迭代器打印的方式 范围for打印的方式及const类型对象的打印都可以了( 第三个打印调用的print函数 里面的形参是const类型 里面的范围for会调用const类型的迭代器)
map的使用也可以了
和之前map set一样这里的key应该是不支持修改的 所以给K前面都加上const
然后实现一下[]的重载
在这之前先把insert find一些地方改一下 find返回值变为迭代器类型 insert的返回值变为pair<Iterator,bool> 然后相应return的地方都改一下
在unordered_map里面实现一下[]重载 同样和之前实现map的方式一模一样
[]就可以正常使用了
用哈希表来实现unorder_set unordered_map基本上就完成了
这里把类型转换为key的和支持==的模版参数是写在哈希表里面的 但是平时使用的时候应该是往unorder_set unordered_map里面传
所以这里再改一下 把哈希表里面的缺省值给去掉 然后给unorder_set unordered_map里面的模版参数加上这些模版参数然后给上缺省值
现在哈希表里面的模版参数都必须要传了 unorder_set unordered_map往里面传的后两个就是之前的HashTable的缺省值
这样unorder_set unordered_map的使用就和库中的是一样的了
完整代码
Hash.h
# include <string>
# include<vector>
# include <iostream>
using namespace std;
namespace xx
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};template<class K>class intoint{public:size_t operator()(K x){return (size_t)x;}};template<>class intoint<string>{public:int operator()(string s){int add = 0;for (char x : s){add *= 131;add += x;}return add;}};template <class K>class ifsame{public:bool operator()(K key1, K key2){return key1 == key2;}};inline unsigned long __stl_next_prime(unsigned long n){// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] = {53, 97, 193, 389, 769,1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433,1572869, 3145739, 6291469, 12582917, 25165843,50331653, 100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}template<class K, class T, class Rekey, class Hash, class HashSame>class HashTable;template<class K, class T,class Ref,class Ptr, class Rekey, class Hash = intoint<K>, class HashSame = ifsame<K>>struct Hashtable_iterator{typedef HashNode<T> Node;typedef Hashtable_iterator<K,T,Ref,Ptr,Rekey,Hash,HashSame> Self;typedef HashTable<K, T, Rekey, Hash, HashSame> Ht;const Ht* _ht;Node* _node;Hashtable_iterator(Node* node, const Ht* ht):_node(node), _ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& it){return _node !=it._node ;}bool operator==(const Self& it){return _node ==it._node ;}Rekey tokey;Hash toint;Self& operator++(){size_t hashi;if (_node->_next) //当前桶下一个位置还有值{_node = _node->_next;}else //当前桶为空 找新的不为空的桶{hashi=toint(tokey(_node->_data)) % _ht->_table.size(); //当前节点所在桶的位置++hashi;while (hashi < _ht->_table.size()){if (_ht->_table[hashi]) //找到新的不为空的哈希桶了{_node = _ht->_table[hashi];break;}elsehashi++;}if (hashi == _ht->_table.size()){//到了最后还没有找到_node = nullptr;}}return *this;}};template<class K, class T, class Rekey, class Hash , class HashSame>class HashTable{public:template<class K, class T, class Ref, class Ptr, class Rekey, class Hash, class HashSame>friend struct Hashtable_iterator;typedef Hashtable_iterator<K, T, T&, T*, Rekey, Hash, HashSame> Iterator;typedef Hashtable_iterator<K, T, const T&, const T*, Rekey, Hash, HashSame> const_Iterator;typedef HashNode<T> Node;HashTable():_table(__stl_next_prime(0)), _n(0){}Iterator Begin(){if (_n == 0)return End();for (auto x : _table){if (x)return { x,this };}return { nullptr,this };}const_Iterator Begin() const{if (_n == 0)return End();for (auto x : _table){if (x)return { x,this };}return { nullptr,this };}Iterator End(){return { nullptr,this };}const_Iterator End() const{return { nullptr,this };}pair<Iterator,bool> insert(T& data){Hash toint;Rekey tokey;Iterator ii = find(tokey(data));if (ii!=End()) //新插入元素已经存在 则插入插入失败return { ii, false };if (_n / _table.size() == 1) // 负载因子为1 扩容{HashTable<K, T, Rekey, Hash, HashSame> newhas;newhas._table.resize(__stl_next_prime(_table.size() + 1));//扩容后size改变了 此时需要把原来的数据重新映射到新的vector类型的对象中for (auto& x : _table) //每一个x是一个节点指针 直接复用insert{Node* head = x;while (x){ //把旧表每一个值在新表映射的位置算出来 然后直接把节点移动到新表正确的位置Node* next = x->_next;size_t hashi2 = toint(tokey(x->_data)) % newhas._table.size();x->_next = newhas._table[hashi2];newhas._table[hashi2] = x;x = next;}}_table.swap(newhas._table);}size_t hashi = toint(tokey(data)) % _table.size();Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;_n++;return { {newnode,this},true };}Iterator find(K key){Hash toint;Rekey tokey;HashSame ifsa;size_t hashi = toint(key) % _table.size();Node* cur = _table[hashi];while (cur){if (ifsa(tokey(cur->_data), key)){return {cur,this};}elsecur = cur->_next;}return {nullptr,this};}bool erase(K key){Hash toint;Rekey tokey;HashSame ifsa;size_t hashi = toint(key) % _table.size();Node* cur = _table[hashi];Node* pre = nullptr;while (cur){if (ifsa(tokey(cur->_data), key)){if (_table[hashi] == cur) // 头节点{_table[hashi] = cur->_next;}else //中间节点{pre->_next = cur->_next;}delete cur;return true;}else{pre = cur;cur = cur->_next;}}return false;}private:vector<Node*> _table; // 指针数组size_t _n;};
}
unordered_set.h
# include "Hash.h"namespace xx
{template<class K, class Hash = intoint<K>, class HashSame = ifsame<K>>class unordered_set{struct unsetRekey{K operator()(K key){return key;}};public:typename typedef HashTable<K,const K, unsetRekey, intoint<K>, ifsame<K>>::Iterator iterator;typename typedef HashTable<K,const K, unsetRekey, intoint<K>, ifsame<K>>::const_Iterator const_iterator;iterator begin(){return _ht.Begin();}const_iterator begin() const{return _ht.Begin();}iterator end(){return _ht.End();}const_iterator end() const{return _ht.End();}pair<iterator,bool> insert(K& key){return _ht.insert(key);}iterator find(K key){return _ht.find(key);}bool erase(K key){return _ht.erase(key);}private:HashTable<K,const K, unsetRekey, intoint<K>, ifsame<K>> _ht;};void print(const unordered_set<int>& s){for (auto e :s ){cout << e << " ";}cout << endl;}
}
unordered_map.h
# include "Hash.h"namespace xx
{template<class K,class V, class Hash = intoint<K>, class HashSame = ifsame<K>>class unordered_map{ struct unmapRekey{K operator()(pair<K, V> kv){return kv.first;}};public:typename typedef HashTable<K, pair<const K, V>, unmapRekey, intoint<K>, ifsame<K>>::Iterator iterator;typename typedef HashTable<K, pair<const K, V>, unmapRekey, intoint<K>, ifsame<K>>::const_Iterator const_iterator;iterator Begin(){return _ht.begin();}const_iterator Begin() const{return _ht.begin();}iterator end(){return _ht.End();}const_iterator end() const{return _ht.End();}pair<iterator,bool> insert(pair<K, V>& kv){return _ht.insert(kv);}iterator find(K key){return _ht.find(key);}bool erase(K key){return _ht.erase(key);}V& operator[](K key){pair<iterator,bool>is= insert({key,V()});is.first->second;}private:HashTable<K, pair<const K, V>, unmapRekey, intoint<K>, ifsame<K>> _ht;};
}