目录
- 哈希表源代码
- 哈希表模板参数的控制
- 哈希表区分
- set与map的不同模板参数
- 哈希节点定义的模板参数修改
- 提供仿函数,获取T类型数据当中的键值
- unordered_map的仿函数
- unordered_set的仿函数
- 哈希表的模板参数增加
- string类型无法取模问题
- 哈希表的模板参数增加
- 哈希表默认成员函数实现
- 构造函数
- 拷贝构造函数
- 赋值运算符重载函数
- 析构函数
- 哈希表正向迭代器的实现
- 构造函数
- operator*
- operator->
- operator!= && operator==
- operator++
- 哈希表结构的其他实现方式
- unordered_set的模拟实现
- unordered_map的模拟实现
- 封装完成后的完整代码如下
- 哈希表的代码
- 正向迭代器的代码
- unordered_set && unordered_map
哈希表源代码
template<class K, class V>struct HashNode{pair<K, V> _kv;HashNode<K, V>* _next;HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:HashTable(){_tables.resize(10, nullptr);}~HashTable(){// 依次把每个桶释放for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}bool Insert(const pair<K, V>& kv){Hash hs;size_t hashi = hs(kv.first) % _tables.size();// 负载因子==1扩容if (_n == _tables.size()){/*HashTable<K, V> newHT;newHT._tables.resize(_tables.size() * 2);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while(cur){newHT.Insert(cur->_kv);cur = cur->_next;}}_tables.swap(newHT._tables);*/vector<Node*> newtables(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;// 旧表中节点,挪动新表重新映射的位置size_t hashi = hs(cur->_kv.first) % newtables.size();// 头插到新表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}// 头插Node* newnode = new Node(kv);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}Node* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){Hash hs;size_t hashi = hs(key) % _tables .size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private://vector<list<pair<K, V>>> _tables; // 指针数组//struct Bucket//{// list<pair<K, V>> _lt;// map<K, V> _m;// size_t _bucketsize; // >8 map <=8 list//};//vector<Bucket> _tables;vector<Node*> _tables; // 指针数组size_t _n = 0; // 表中存储数据个数};//测试:
void TestHT1(){HashTable<int, int> ht;int a[] = { 11,21,4,14,24,15,9,19,29,39 };for (auto e : a){ht.Insert({ e,e });}ht.Insert({ -6, 6 });for (auto e : a){ht.Erase(e);}}void TestHT2(){HashTable<string, string> ht;ht.Insert({ "sort", "排序" });ht.Insert({ "left", "左边" });}
哈希表模板参数的控制
需要明确的是,unordered_set是K模型的容器,而unordered_map是KV模型的容器。
要想只用一份哈希表代码同时封装出K模型和KV模型的容器,我们必定要对哈希表的模板参数进行控制。
哈希表区分
首先为了与原哈希表的模板参数进行区分,这里将哈希表的第二个模板参数的名字改为T。
template<class K, class T>
class HashTable
set与map的不同模板参数
其次,如果上层使用的是unordered_set容器,那么传入哈希表的模板参数就是key和key。
template<class K>
class unordered_set
{
public://...
private:HashTable<K, K> _ht; //传入底层哈希表的是K和K
};
但如果上层使用的是unordered_map容器,那么传入哈希表的模板参数就是key以及key和value构成的键值对。
template<class K, class V>
class unordered_map
{
public://...
private:HashTable<K, pair<K, V>> _ht; //传入底层哈希表的是K以及K和V构成的键值对
};
也就是说,哈希表中的模板参数T的类型到底是什么,完全却决于上层所使用容器的种类
而哈希结点的模板参数也应该由原来的K、V变为T:
上层容器是unordered_set时,传入的T是键值,哈希结点中存储的就是键值。
上层容器是unordered_map时,传入的T是键值对,哈希结点中存储的就是键值对。
哈希节点定义的模板参数修改
更改模板参数后,哈希结点的定义如下:
//哈希结点的定义
template<class T>
struct HashNode
{T _data;HashNode<T>* _next;//构造函数HashNode(const T& data):_data(data), _next(nullptr){}
};
提供仿函数,获取T类型数据当中的键值
在哈希映射过程中,我们需要获得元素的键值,然后通过哈希函数计算出对应的哈希地址进行映射。
现在由于我们在哈希结点当中存储的数据类型是T,这个T可能就是一个键值,也可能是一个键值对,对于底层的哈希表来说,它并不知道哈希结点当中存储的数据究竟是什么类型,因此需要由上层容器提供一个仿函数,用于获取T类型数据当中的键值。
unordered_map的仿函数
因此,unordered_map容器需要向底层哈希表提供一个仿函数,该仿函数返回键值对当中的键值。
template<class K, class V>
class unordered_map
{//仿函数struct MapKeyOfT{const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值key{return kv.first;}};
public://...
private:HashTable<K, pair<K, V>, MapKeyOfT> _ht;
};
unordered_set的仿函数
而虽然unordered_set容器传入哈希表的T就是键值,但是底层哈希表并不知道上层容器的种类,底层哈希表在获取键值时会统一通过传入的仿函数进行获取,因此unordered_set容器也需要向底层哈希表提供一个仿函数。
template<class K>
class unordered_set
{//仿函数struct SetKeyOfT{const K& operator()(const K& key) //返回键值key{return key;}};
public://...
private:HashTable<K, K, SetKeyOfT> _ht;
};
哈希表的模板参数增加
因此,底层哈希表的模板参数现在需要增加一个,用于接收上层容器提供的仿函数。
template<class K, class T, class KeyOfT>
class HashTable
string类型无法取模问题
字符串并不是整型,也就意味着字符串不能直接用于计算哈希地址,我们需要通过某种方法将字符串转换成整型后,才能代入哈希函数计算哈希地址。
哈希表的模板参数增加
现在我们需要在哈希表的模板参数中再增加一个仿函数,用于将键值key转换成对应的整型。
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
用字符串作为键值key是比较常见的,因此我们可以针对string类型写一个类模板的特化
仿函数与特化版本代码如下:
template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 31;hash += e;}return hash;}
};
哈希表默认成员函数实现
构造函数
哈希表中有两个成员变量,当我们实例化一个对象时:
_table会自动调用vector的默认构造函数进行初始化。
_n会根据我们所给的缺省值被设置为0。
vector<Node*> _tables; // 指针数组
size_t _n = 0; // 表中存储数据个数
因此我们不需要编写构造函数,使用默认生成的构造函数就足够了,但是由于我们后面需要编写拷贝构造函数,编写了拷贝构造函数后,默认的构造函数就不会生成了,此时我们需要使用default关键字显示指定生成默认构造函数。
//构造函数
HashTable() = default; //显示指定生成默认构造函数
或者
HashTable()
{_tables.resize(10, nullptr);
}
拷贝构造函数
哈希表在拷贝时需要进行深拷贝,否则拷贝出来的哈希表和原哈希表中存储的都是同一批结点。
哈希表的拷贝构造函数实现逻辑如下:
- 将哈希表的大小调整为ht._tables的大小。
- 将ht._table每个桶当中的结点一个个拷贝到自己的哈希表中。
- 更改哈希表当中的有效数据个数。
//拷贝构造函数
HashTable(const HashTable& ht)
{//1、将哈希表的大小调整为ht._table的大小_tables.resize(ht._tables.size());//2、将ht._table每个桶当中的结点一个个拷贝到自己的哈希表中(深拷贝)for (size_t i = 0; i < ht._tables.size(); i++){if (ht._tables[i]) //桶不为空{Node* cur = ht._tables[i];while (cur) //将该桶的结点取完为止{Node* copy = new Node(cur->_data); //创建拷贝结点//将拷贝结点头插到当前桶copy->_next = _tables[i];_tables[i] = copy;cur = cur->_next; //取下一个待拷贝结点}}}//3、更改哈希表当中的有效数据个数_n = ht._n;
}
赋值运算符重载函数
实现赋值运算符重载函数时,可以通过参数间接调用拷贝构造函数,之后将拷贝构造出来的哈希表和当前哈希表的两个成员变量分别进行交换即可,当赋值运算符重载函数调用结束后,拷贝构造出来的哈希表会因为出了作用域而被自动析构,此时原哈希表之前的数据也就顺势被释放了。
//赋值运算符重载函数
HashTable& operator=(HashTable ht)
{//交换哈希表中两个成员变量的数据_tables.swap(ht._table);swap(_n, ht._n);return *this; //支持连续赋值
}
析构函数
因为哈希表当中存储的结点都是new出来的,因此在哈希表被析构时必须进行结点的释放。在析构哈希表时我们只需要依次取出非空的哈希桶,遍历哈希桶当中的结点并进行释放即可。
//析构函数
~HashTable()
{//将哈希表当中的结点一个个释放for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]) //桶不为空{Node* cur = _tables[i];while (cur) //将该桶的结点取完为止{Node* next = cur->_next; //记录下一个结点delete cur; //释放结点cur = next;}_tables[i] = nullptr; //将该哈希桶置空}}
}
哈希表正向迭代器的实现
哈希表的正向迭代器实际上就是对哈希结点指针进行了封装,但是由于在实现++运算符重载时,可能需要在哈希表中去寻找下一个非空哈希桶,因此每一个正向迭代器中都应该存储哈希表的地址。
template<class K, class T, class KeyOfT, class Hash>
struct HTIterator
{typedef HashNode<T> Node;//哈希结点的类型typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;//正向迭代器的类型Node* _node;//结点指针const HashTable<K, T, KeyOfT, Hash>* _pht;//哈希表的地址
}
构造函数
因此在构造正向迭代器时,我们不仅需要对应哈希结点的指针,还需要该哈希结点所在哈希表的地址。
HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht):_node(node),_pht(pht)
{}
operator*
当对正向迭代器进行解引用操作时,我们直接返回对应结点数据的有引用即可。
T& operator*()
{return _node->_data; //返回哈希结点中数据的引用
}
operator->
当对正向迭代器进行->操作时,我们直接返回对应结点数据的地址即可。
T* operator->()
{return &_node->_data; //返回哈希结点中数据的地址
}
后续需要封装const迭代器,需要在给一个class Ref,class Ptr,class T是给HashTable用的
因此上述两个操作的返回值类型需要修改
Ref operator*()
{return _node->_data;
}Ptr operator->()
{return &_node->_data;
}
模板需要更新
template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
operator!= && operator==
我们需要比较两个迭代器是否相等时,只需要判断这两个迭代器所封装的结点是否是同一个即可。
bool operator!=(const Self& s) const
{return _node != s._node; //判断两个结点的地址是否不同
}bool operator==(const Self& s) const
{return _node == s._node; //判断两个结点的地址是否相同
}
operator++
++运算符重载函数的实现逻辑并不是很难,我们只需要知道如何找到当前结点的下一个结点即可。
- 若当前结点不是当前哈希桶中的最后一个结点,则++后走到当前哈希桶的下一个结点。
- 若当前结点是当前哈希桶的最后一个结点,则++后走到下一个非空哈希桶的第一个结点。
Self& operator++()
{if (_node->_next){// 当前桶还有节点_node = _node->_next;}else{// 当前桶走完了,找下一个不为空的桶KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();++hashi;while (hashi < _pht->_tables.size()){if (_pht->_tables[hashi]){break;}++hashi;}if (hashi == _pht->_tables.size()){_node = nullptr; // end()}else{_node = _pht->_tables[hashi];}}return *this;
}
注意: 哈希表的迭代器类型是单向迭代器,没有反向迭代器,即没有实现–运算符的重载,若是想让哈希表支持双向遍历,可以考虑将哈希桶中存储的单链表结构换为双链表结构。
哈希表结构的其他实现方式
在其他地方可能将插入哈希表的哈希结点统一链接到同一个单链表上,此时实现哈希表的正向迭代器时就更简单了,实现++运算符重载时,想要找到下一个结点就直接通过当前结点就可以找到。
正向迭代器实现后,我们需要在哈希表的实现当中进行如下操作:
- 进行正向迭代器类型的typedef,需要注意的是,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在public区域进行typedef。
- 由于正向迭代器中++运算符重载函数在寻找下一个结点时,会访问哈希表中的成员变量_table,而_table成员变量是哈希表的私有成员,因此我们需要将正向迭代器类声明为哈希表类的友元。
- 将哈希表中查找函数返回的结点指针,改为返回由结点指针和哈希表地址构成的正向迭代器。
- 将哈希表中插入函数的返回值类型,改为由正向迭代器类型和布尔类型所构成的键值对。
然后我们就可以在哈希表中实现迭代器相关的成员函数了:
- begin函数: 返回哈希表中第一个非空哈希桶中的第一个结点的正向迭代器。
- end函数: 返回空指针的正向迭代器。
//哈希表的实现
template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
class HashTable
{//将正向迭代器类声明为哈希表类的友元template<class K, class T, class KeyOfT, class HashFunc>friend struct __HTIterator;typedef HashNode<T> Node; //哈希结点类型
public:typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator; //正向迭代器的类型iterator begin(){size_t i = 0;while (i < _table.size()) //找到第一个非空哈希桶{if (_table[i]) //该哈希桶非空{return iterator(_table[i], this); //返回该哈希桶中的第一个结点的正向迭代器}i++;}return end(); //哈希桶中无数据,返回end()}iterator end(){return iterator(nullptr, this); //返回nullptr的正向迭代器}
private:vector<Node*> _table; //哈希表size_t _n = 0; //哈希表中的有效元素个数
};
unordered_set的模拟实现
实现unordered_set的各个接口时,就只需要调用底层哈希表对应的接口就行了。
template<class K, class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator 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 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, const K, SetKeyOfT, Hash> _ht;};
unordered_map的模拟实现
template<class K, class V, class Hash = HashFunc<K>>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator 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<K, V>& kv){return _ht.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}iterator Find(const K& key){return _ht.Find(key);}bool Erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};
封装完成后的完整代码如下
哈希表的代码
template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 31;hash += e;}return hash;}
};template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}};template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>class HashTable{// 友元声明template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash = HashFunc<K>>friend struct HTIterator;typedef HashNode<T> Node;public:typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;typedef HTIterator<K, T, const T*, const T&, KeyOfT, Hash> ConstIterator;Iterator Begin(){if (_n == 0)return End();for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();}Iterator End(){return Iterator(nullptr, this);}ConstIterator Begin() const{if (_n == 0)return End();for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return ConstIterator(cur, this);}}return End();}ConstIterator End() const{return ConstIterator(nullptr, this);}HashTable(){_tables.resize(10, nullptr);}~HashTable(){// 依次把每个桶释放for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}pair<Iterator, bool> Insert(const T& data){KeyOfT kot;Iterator it = Find(kot(data));if (it != End())return make_pair(it, false);Hash hs;size_t hashi = hs(kot(data)) % _tables.size();// 负载因子==1扩容if (_n == _tables.size()){/*HashTable<K, V> newHT;newHT._tables.resize(_tables.size() * 2);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while(cur){newHT.Insert(cur->_kv);cur = cur->_next;}}_tables.swap(newHT._tables);*/vector<Node*> newtables(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;// 旧表中节点,挪动新表重新映射的位置size_t hashi = hs(kot(cur->_data)) % newtables.size();// 头插到新表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}// 头插Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(Iterator(newnode, this), true);}Iterator Find(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return Iterator(cur, this);}cur = cur->_next;}return End();}bool Erase(const K& key){KeyOfT kot;Hash hs;size_t hashi = hs(key) % _tables .size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private://vector<list<pair<K, V>>> _tables; // 指针数组//struct Bucket//{// list<pair<K, V>> _lt;// map<K, V> _m;// size_t _bucketsize; // >8 map <=8 list//};//vector<Bucket> _tables;vector<Node*> _tables; // 指针数组size_t _n = 0; // 表中存储数据个数};
正向迭代器的代码
// 前置声明template<class K, class T, class KeyOfT, class Hash>class HashTable;template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>struct HTIterator{typedef HashNode<T> Node;typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;Node* _node;const HashTable<K, T, KeyOfT, Hash>* _pht;HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht):_node(node),_pht(pht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){if (_node->_next){// 当前桶还有节点_node = _node->_next;}else{// 当前桶走完了,找下一个不为空的桶KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();++hashi;while (hashi < _pht->_tables.size()){if (_pht->_tables[hashi]){break;}++hashi;}if (hashi == _pht->_tables.size()){_node = nullptr; // end()}else{_node = _pht->_tables[hashi];}}return *this;}};
unordered_set && unordered_map
与上面模拟实现相同