c++:封装哈希表实现unordered_map与unordered_set
1.前期准备与框架搭建
第一步:将哈希表(链地址法)的头文件加进项目,然后创建两个头文件Unordered_Map和Unorderd_Set。
第二步:unordered_set框架与unordered_map框架搭建
unordered_set:
#pragma once #include"hashtable.h" namespace myself { template<class K, class V> class unordered_map { public: private: HashTable<K, pair<K, V>> _ht; }; }
unordered_map:
#pragma once #include"hashtable.h" namespace myself { template<class K> class unordered_set { public: private: HashTable<K, K> _ht; }; }
这里我们传给哈希表的参数不再是key和value,而是单独的k和容器存储的数据类型(set中是key,map中是pair<K,V>),这样可以兼容set和map进哈希表
1.1支持insert
(1)解决find和erase中需要key查找,而哈希表中不确定存储数据类型是key还是key_value的问题
解法:使用仿函数KeyOfT,仿函数作用是返回容器的key值引用
首先我们要在unordered_set中写一个SetOfT,返回值类型为K,不过需要注意的是返回的是const类型的,否则会导致权限放大
struct SetOfT { const K& operator()(const K& key) { return key; } };
然后是unordered_map
struct MapOfT { const K& operator()(const pair<K,V>& data) { return data.first; } };
然后在哈希表中加一个模板参数KeyOfT,用于接收unordered_set和unordered_map的仿函数
(2)将key类型转换为可以进行取模运算的类型
解法:使用仿函数Func,对于特殊类型如string就进行类模板特化
template<class K> struct HashFunc { size_t operator()(const K& key) { return (size_t)key; } };
使用struct是因为这个仿函数是需要哈希表可以直接调用的,所以用struct就可以默认开放给类外面
(3)更改哈希表中的insert
bool Insert(const T& data) { KeyOfT kot; Func fc; //不允许冗余 if (Find(kot(data))) return false; //负载因子为1就扩容 if (_n == _tables.size()) { vector<Node*> newtable (2 * _tables.size()); for (size_t i = 0; i < _tables.size(); i++) { Node* cur = _tables[i]; while (cur) { //将旧的表中节点挪到新表中 Node* next = cur->_next; size_t hash0 = fc(kot(cur->_data)) % newtable.size(); //头插 cur->_next = newtable[hash0]; newtable[hash0] = cur; //更新cur cur = next; } _tables[i] = nullptr; } newtable.swap(_tables); } //头插 size_t hash0 = fc(kot(data)) % _tables.size(); Node* newnode = new Node(data); newnode->_next = _tables[hash0]; _tables[hash0] = newnode; _n++; return true; }
1.只有需要使用key的情况我们才用kot将key取出来,如果就是要使用容器存储的数据类型,比如节点构造,那么我们就需要直接用data
2.func主要用于进行取模运算的时候,此时需要key是一个可以取模运算的数据类型
(4)更改Find与Erase
Node* Find(const K& key) { Func fc; KeyOfT kot; size_t hash0 = fc(key) % _tables.size(); Node* cur = _tables[hash0]; while (cur) { if (kot(cur->_data) == key) return cur; cur = cur->_next; } return nullptr; } bool Erase(const K& key) { KeyOfT kot; Func fc; size_t hash0 = fc(key) % _tables.size(); Node* cur = _tables[hash0]; Node* prv = nullptr; while (cur) { if (kot(cur->_data) == key)//delete { if (prv == nullptr) { _tables[hash0] = cur->_next; } else { prv->_next = cur->_next; } delete cur; --_n; return true; } prv = cur; cur = cur->_next; } return false; }
(5)实现析构
~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; } }
1.2支持iterator
//哈希表前置声明 template<class K, class T, class KeyOfT, class Func > class HashTable; //迭代器实现 template<class K, class T, class KeyOfT, class Func > struct HTIterator { typedef HashTable<K, T, KeyOfT, Func> HT; typedef HashNode<T> Node; typedef HTIterator<K, T, KeyOfT, Func> self; //迭代器存储内容 HT* _ht; Node* _node; //构造 HTIterator(Node* node,HT* ht) :_ht(ht) ,_node(node) { } //运算符重载 T* operator->() { return &(_node->_data); } T& operator*() { return _node->_data; } self& operator++() { Func fc; KeyOfT kot; //当前桶没走完 Node* cur = _node; if (cur->_next) { _node = _node->_next; } else//当前桶走完了,寻找下一个桶的头结点 { size_t hashi = fc(kot(_node->_data)) % _ht->_tables.size(); hashi++; while (hashi < _ht->_tables.size())//没有走完哈希表 { if (_ht->_tables[hashi]) { _node = _ht->_tables[hashi]; break; } hashi++; } if (hashi == _ht->_tables.size()) { _node = nullptr; } } return *this; } bool operator!=(const self& s) { return _node != s._node; } bool operator ==(const self& s) { return _node == s._node; } };
1.模板参数的缺省参数只能给一次,不能在多个地方重复给,这样会可能导致缺省参数混乱,所以在语法层面禁止了
2.需要在迭代器前面前置声明一下HashTable,告诉编译器他是一个类
3.迭代器++的逻辑:
(1)当前桶没有走完,那么就直接让迭代器指向下一个node,然后返回迭代器
(2)当前桶走完了,需要寻找下一个有数据的桶,若找到了就指向新桶的第一个数据,没有找到就指向nullptr。
最后返回迭代器本身
4.由于迭代器++的逻辑中使用了哈希表的private成员变量,所以我们需要让迭代器成为哈希表的友元函数
接下来我们实现哈希表中的迭代器
//实现begin和end Iterator Begin() { //找第一个节点 for (size_t i = 0; i < _tables.size(); i++) { if (_tables[i]) { return Iterator(_tables[i], this); } } return End(); } Iterator End() { return Iterator(nullptr,this); }
begin:寻找哈希表中第一个节点,然后用该节点地址和哈希表本身地址构造迭代器
end:我们以nullptr表示没有节点了,所以用nullptr和哈希表本身地址构造迭代器
然后我们对unordered_set和unordered_map进行封装迭代器
注意要写在public修饰符内部
unordered_set:
typedef typename HashTable<K, K, SetOfT>::Iterator iterator; iterator begin() { return _ht.Begin(); } iterator end() { return _ht.End(); }
unordered_map:
typedef typename HashTable<K, pair<K, V>, MapOfT>::Iterator iterator; iterator begin() { return _ht.Begin(); } iterator end() { return _ht.End(); }
1.3支持const_iterator
支持const迭代器我们只需要在迭代器的模板参数中增加两个参数,Ref和Ptr即可,因为增加他们就可以控制运算符->和*的返回值类型
//迭代器实现 template<class K, class T, class Ref, class Ptr,class KeyOfT, class Func > struct HTIterator { typedef HashTable<K, T, KeyOfT, Func> HT; typedef HashNode<T> Node; typedef HTIterator<K, T,Ref,Ptr,KeyOfT, Func> self; //迭代器存储内容 const HT* _ht; Node* _node; //构造 HTIterator(Node* node,const HT* ht) :_ht(ht) ,_node(node) { } //运算符重载 Ptr operator->() { return &(_node->_data); } Ref operator*() { return _node->_data; } self& operator++() { Func fc; KeyOfT kot; //当前桶没走完 Node* cur = _node; if (cur->_next) { _node = _node->_next; } else//当前桶走完了,寻找下一个桶的头结点 { size_t hashi = fc(kot(_node->_data)) % _ht->_tables.size(); hashi++; while (hashi < _ht->_tables.size())//没有走完哈希表 { if (_ht->_tables[hashi]) { _node = _ht->_tables[hashi]; break; } hashi++; } if (hashi == _ht->_tables.size()) { _node = nullptr; } } return *this; } bool operator!=(const self& s) { return _node != s._node; } bool operator ==(const self& s) { return _node == s._node; } };
注意:这里我们修改了构造函数的参数,将HT*变为const属性,这是因为后续支持cosnt_iterator的时候会传递的对象是const属性,如果这里参数不是const属性会导致权限被放大。且这里我们即使加上了const也不会影响迭代器逻辑,因为迭代器中没有修改哈希表的部分
然后我们通过控制模板参数重命名一个const迭代器,并对unordered_set和unordered_map套上const迭代器
(1)在HashTable加上const迭代器
template<class K, class T, class KeyOfT,class Func = HashFunc<K>> class HashTable { //友元声明 template<class K, class T,class Ref,class Ptr ,class KeyOfT, class Func > friend struct HTIterator; typedef HashNode<T> Node; public: typedef HTIterator<K, T,T&,T*,KeyOfT, Func> Iterator; typedef HTIterator<K, T, const T&, const T*, KeyOfT, Func> Const_Iterator; HashTable(size_t size = 53) :_tables(size, nullptr) ,_n(0) { } //实现begin和end Iterator Begin() { //找第一个节点 for (size_t i = 0; i < _tables.size(); i++) { if (_tables[i]) { return Iterator(_tables[i], this); } } return End(); } Iterator End() { return Iterator(nullptr,this); } //实现const的begin和end Const_Iterator Begin() const { //找第一个节点 for (size_t i = 0; i < _tables.size(); i++) { if (_tables[i]) { return Const_Iterator(_tables[i], this); } } return End(); } Const_Iterator End() const { return Const_Iterator(nullptr, this); } bool Insert(const T& data) { KeyOfT kot; Func fc; //不允许冗余 if (Find(kot(data))) return false; //负载因子为1就扩容 if (_n == _tables.size()) { vector<Node*> newtable (2 * _tables.size()); for (size_t i = 0; i < _tables.size(); i++) { Node* cur = _tables[i]; while (cur) { //将旧的表中节点挪到新表中 Node* next = cur->_next; size_t hash0 = fc(kot(cur->_data)) % newtable.size(); //头插 cur->_next = newtable[hash0]; newtable[hash0] = cur; //更新cur cur = next; } _tables[i] = nullptr; } newtable.swap(_tables); } //头插 size_t hash0 = fc(kot(data)) % _tables.size(); Node* newnode = new Node(data); newnode->_next = _tables[hash0]; _tables[hash0] = newnode; _n++; return true; }
(2)给unordered_set和unordered_map加上const迭代器
unordered_map:
typedef typename HashTable<K, pair<K, V>, MapOfT>::Iterator iterator; typedef typename HashTable<K, pair<K, V>, MapOfT>::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(); }
unordered_set:
typedef typename HashTable<K, K, SetOfT>::Iterator iterator; typedef typename HashTable<K, K, SetOfT>::Const_Iterator const_iterator; const_iterator begin() const { return _ht.Begin(); } const_iterator end() const { return _ht.End(); } iterator begin() { return _ht.Begin(); } iterator end() { return _ht.End(); }
1.4将key现在可以修改的情况改为不可修改
只需要在unordered_set和unordered_map中把容器存储类型中的key变为const属性即可
在private部分,typedef部分修改
typedef typename HashTable<K, const K, SetOfT>::Iterator iterator; typedef typename HashTable<K, const K, SetOfT>::Const_Iterator const_iterator; const_iterator begin() const { return _ht.Begin(); } const_iterator end() const { return _ht.End(); } iterator begin() { return _ht.Begin(); } iterator end() { return _ht.End(); } bool insert(const K& key) { return _ht.Insert(key); } void print(const unordered_set<int>& s) { const_iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; it++; } cout << endl; } void testset() { unordered_set<int> s; s.insert(1); s.insert(2); print(s); } private: HashTable<K, const K,SetOfT> _ht;
typedef typename HashTable<K, pair<const K, V>, MapOfT>::Iterator iterator; typedef typename HashTable<K, pair<const K, V>, MapOfT>::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(); } bool insert(const pair<const K,V>& kv) { return _ht.Insert(kv); } private: HashTable<K, pair<const K, V>,MapOfT> _ht; };
1.5实现operator[]
operator[]的实现依赖于insert,而我们现在的insert还不是最终版本,接下来我们进行修改
第一步:修改Find()
Iterator Find(const K& key) { Func fc; KeyOfT kot; size_t hash0 = fc(key) % _tables.size(); Node* cur = _tables[hash0]; while (cur) { if (kot(cur->_data) == key) return {cur,nullptr}; cur = cur->_next; } return End(); }
若找到了就用当前的cur构建迭代器返回,没有找到就返回end迭代器
第二步:修改insert
pair<Iterator,bool> Insert(const T& data) { KeyOfT kot; Func fc; //不允许冗余 Iterator it = Find(kot(data)); if (it != End()) return {it,false}; //负载因子为1就扩容 if (_n == _tables.size()) { vector<Node*> newtable (2 * _tables.size()); for (size_t i = 0; i < _tables.size(); i++) { Node* cur = _tables[i]; while (cur) { //将旧的表中节点挪到新表中 Node* next = cur->_next; size_t hash0 = fc(kot(cur->_data)) % newtable.size(); //头插 cur->_next = newtable[hash0]; newtable[hash0] = cur; //更新cur cur = next; } _tables[i] = nullptr; } newtable.swap(_tables); } //头插 size_t hash0 = fc(kot(data)) % _tables.size(); Node* newnode = new Node(data); newnode->_next = _tables[hash0]; _tables[hash0] = newnode; _n++; return { {newnode,this},true }; }
第三步:实现operatoar[]
V& operator[](const K& key) { pair<iterator, bool> p = insert({ key,V() }); return p.first->second; }
1.6在unordered_set和unordered_map中增加一个模板参数
增加一个Func参数,主要是方便我们写仿函数控制key的数据类型可以转换为可以取模的类型
unordered_map的控制:
template<class K, class V, class Func = HashFunc<K>> class unordered_map { struct MapOfT { const K& operator()(const pair<const K,V>& data) { return data.first; } };
unordered_set的控制:
template<class K, class Func = HashFunc<K>> class unordered_set { struct SetOfT { const K& operator()(const K& key) { return key; } };