本期我们来封装实现unordered系列,需要前置知识,没有看过哈希的建议先看看哈希,而且哈希的代码都在这里面,一会要用到
C++-哈希Hash-CSDN博客
目录
代码实现
迭代器
const迭代器
全部代码
代码实现
首先我们要把V改为T,因为我们不知道他是k结构还是kv结构
//unordered_set.h
namespace bai {template<class K>class unordered_set{private:hash_bucket::HashTable<K, K> _ht;};
}
namespace bai {template<class K,class V>class unordered_map{private:hash_bucket::HashTable<K,pair<K,V>> _ht;};
}
然后我们把框架搭好
我们对照着看一下,如果是set,这里就是key,data就是key,如果是map,这里就是pair,data就是pair
接下来还要修改insert,find等等,这里就因为不知道是pair还是k的原因,所以我们需要再次来写KeyOfT
struct SetKeyOfT{const K& operator()(const K& key){return key;}};struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};
写完后别忘记加上
bool Insert(const T& data){KeyOfT kot;if (Find(kot(data))){return false;}HashFunc hf;//负载因子到1时扩容if (_n == _table.size()){size_t newSize = _table.size() * 2;vector<Node*> newTable;newTable.resize(newSize, nullptr);//遍历旧表,把节点迁下来挂到新表for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;//头插到新表size_t hashi = hf(kot(cur->_data)) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hf(kot(data)) % _table.size();//头插Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return true;}Node* Find(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return cur;}cur = cur->_next;}return nullptr;}bool Erase(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){//头删_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}cur = cur->_next;}}
接着我们要修改insert,find和erase的代码,下面我们来看看修改了哪里,我们按顺序看
大概就是这些地方,具体的还要看上面的代码
我们简单测试一下,没有问题,下面我们来封装迭代器
迭代器
要实现迭代器,我们要想想,当前桶走完了,如何到下一个桶呢?也就是2号位的桶完了后如何到6号位
template<class T>struct HTiterator{typedef HashNode<T> Node;Node* _node;HTiterator(Node* node):_node(node){}operator++(){if (_node->_next){_node = _node->next;}else{}}};
我们先把框架写好,我们来看operator++,如果当前桶没有走完,就让他到下一个位置即可
如果我们知道当前位置是几号桶可以解决吗?也不行,因为我们还需要哈希表的对象,大家想一想,他的底层是一个指针数组,每一个指针又指向一个单链表,我们现在是需要在指针数组里移动
template<class K, class T, class KeyOfT, class HashFunc>struct HTiterator{typedef HashNode<T> Node;typedef HTiterator<K, T, KeyOfT, HashFunc> Self;Node* _node;HashTable<K, T, KeyOfT, HashFunc>* _pht;HTiterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node),_pht(pht){}Self& operator++(){if (_node->_next){//当前桶还没完_node = _node->_next;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();//从下一个位置开始查找下一个不为空的桶++hashi;while (hashi < _pht->_table.size()){if (_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{++hashi;}}_node = nullptr;}return *this;}};
所以我们需要把整个哈希表传过来,传一个pht的指针,而且这里和之前一样,不一定是可以直接取模,需要再套一层hashfunc,此时我们就可以计算出当前是第几个桶
接着我们开始找下一个不为空的桶,我们先对hashi++,从下一个位置开始寻找,如果当前桶不为空,我们就break,否则++hashi,循环结束后是有两种情况的,一种是可以找到,我们要让it指向下一个桶,另一种是我们已经找完了整个表,已经走到结尾了,我们让nullptr充当end的位置
另外这里可能会有人有疑问,为什么这里没有加typename? 只有不能区分静态变量和类型时才需要加
T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;} bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
我们顺手再把这几个也写一下
typedef HTiterator<K, T, KeyOfT, HashFunc> iterator;iterator begin(){//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return iterator(cur,this);}}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}
接着我们在HashTable里加上begin和end,我们构造迭代器时第一个传cur,找不到传nullptr,第二个传this即可,this就是哈希表的指针
下面我们再套一层,在unordered里封装一层
注意,这里就需要加typename了,我们之前封装map和set时也是一样的
下面我们要开始解决各种问题了
这里是一个相互依赖的问题
首先我们在哈希表里调用迭代器 ,迭代器在前,没有问题
但是我们在迭代器里又用了哈希表,这里谁在前谁在后都不行,不过还好这里用的是哈希表的指针,如果是对象就不好解决了
这里我们就要用到前置声明了,告诉编译器哈希表我们是有定义的,只不过可能在后面,先不要着急
此时问题就解决了
我们调用一下迭代器,再次编译,出现了这样的错误,我们仔细看可以发现,这里是私有的问题
这里在类外边是不能访问私有的 ,所以这里可以写一个gettable,或者使用友元
是迭代器要访问哈希表,所以迭代器是哈希表的友元,类模板的友元需要带上模板参数
此时我们的迭代器就可以跑起来了
接下来我们需要解决可以修改的问题,也就是要实现const迭代器了
const迭代器
和之前一样,要实现const迭代器我们需要传T*和T&
于是我们修改迭代器的模板参数和self
operator*和箭头的返回参数也要修改
友元声明也需要修改
const_iterator begin() const{//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return const_iterator(cur, this);}}return const_iterator(nullptr, this);}const_iterator end() const{return const_iterator(nullptr, this);}
然后要在HashTable里加上begin和end
接着我们对外层的set封装,无论是iterator还是const_iterator,它们其实都是const_iterator,而且只提供了const版本的begin和end,和库里面是一样的,这里的begin和end的返回值无论是const_iterator还是iterator都是可以的,因为普通对象是可以调用const成员函数的
此时我们的第一个问题就解决了
接着我们编译,发现这里编译不通过,不仅仅是这里,上面也是编译不通过的,这里报错是无构造函数可以接受源类型
我们来看,这里的const修饰的谁?
修饰的是*this
所以这里this的类型应该是这样的
我们画出来就是这样的,这里权限放大,是传不过去的 ,我们之前写的const迭代器没有遇到这样的问题,因为以前的const迭代器是不需要传哈希表的,而只需要传一个节点的指针过去即可
我们来看这种方法,我们重载了一个构造函数,并且直接把_pht改为了const
对于原来的构造函数,pht是const变为非const,是权限的缩小
那此时我们不要第一个构造,只要第二个可以吗?
也是可以的,普通迭代器传过来只有const,权限缩小
我们来看库里面是怎么解决的,首先是普通的迭代器
这里有一个
库里面写了两个迭代器,用两个迭代器类来实现
这里还都写成const了,相当于单独写了一个类来解决
我们的方法和库里面的方法,这两种方法都可以解决
map的话和以前一样,改为const K
修改这些地方
这样就解决了
下面我们来修改返回值
先修改一下find的返回值
然后是insert的
然后我们把map和set封装的insert也改一下,然后就是set这里的问题了
这里看起来是这样的
但是我们看这里
但是它们是不同区域的代码
set不允许修改key,所以选择用const迭代器替代普通迭代器
所以这里的两个pair是不一样的,pair是一个类模板,实例化为两个不一样的类,是不一样的类型
也就是说上面的pair是这样的,所以会报错
map为什么没报错?
因为map的iterator就是iterator
注意,set这里的问题和权限缩小之类无关,只有指针和引用才有权限缩小的概念,这里是类模板实例化不同的模板参数,导致它们是不同的类型
我们之前在封装map和set就讲过了,这是库里面list的实现办法,不太清楚的各位可以去我往期的map和set实现里再看一看,我把链接放在下面
map和set模拟实现-CSDN博客
下面这个圈主的函数,如果是普通迭代器,这里就是拷贝构造,是const迭代器时,是构造函数,是支持普通迭代器转换成const迭代器的构造,这是一个有双重意义的函数
我们来用这个举例子,我们想用aa1构造aa3
我们要加这样一个构造,但是这里报错了一个私有
原因是上面这里是A<T,Ref>
这里是A<T,T&>
当这里Ref也是T&时是同一个类,当Ref是const时就不是同一个类了
我们看为什么库里面不报错呢?
因为库里面是struct
我们去掉private就好了,这些都是细节,大家需要注意
现在回到我们的哈希表里
我们也加上这样一个构造
接着我们再次编译,发现了几个错误
首先是我们把这里改为it(这里之前是写错了)
然后是find返回时不能返回nullptr,这里返回end(),接着编译就可以通过了
我们上次在这里写的和这里是不一样的
这里我们是先接收pair,再用pair去调用它的构造
这样写是没问题的,这里相当于把两个参数提取了出来,单独调用pair构造,这里的ret.first是一个普通迭代器,在pair的初始化列表里,初始化列表对自定义类型会调用构造
而这里屏蔽的(我们之前写的),是编译器的不严格检查,如果检查的严格一点,是不通过的
首先这里调用pair的构造
我们上面的a<int>和a<const int>都不是同一个类型,这里也是一样的
所以我们把iterator取出来
然后直接调用它的构造,就会走它的初始化列表,初始化列表对于自定义类型就会调用它的构造
虽然这里两种方法都可以编译通过,但是我们还是建议使用方法2,不然换一个编译器可能就不通过了
此时我们的问题就解决了
我们上面那么多事情,都是因为它
insert要返回pair,就引发了普通迭代器转换const迭代器的问题,为什么insert要返回pair呢?
因为我们要实现operator[ ] ,这里都是相关联的
V& operator[](const K& key){pair<iterator,bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}
为什么我们这里接收时可以用iterator?
因为map的iterator就是iterator,和set不一样
此时我们就可以使用[ ] 了,和库里面一样
全部代码
#include"Hash.h"
//unordered_set.h
namespace bai {template<class K>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;const_iterator begin() const{return _ht.begin();}const_iterator end() const{return _ht.end();}pair<const_iterator, bool> insert(const K& key){//return _ht.Insert(key);pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);return pair<const_iterator, bool>(ret.first, ret.second);}private:hash_bucket::HashTable<K, K, SetKeyOfT> _ht;};
}
#include"Hash.h"
//unordered_map.h
namespace bai {template<class K,class V>class unordered_map{struct MapKeyOfT{const K& operator()(const pair<const K,V>& kv){return kv.first;}};public:typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::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<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;}private:hash_bucket::HashTable<K, pair<const K,V>, MapKeyOfT> _ht;};
}
#pragma once
#include<vector>
#include<iostream>
using namespace std;
//Hash.h
template<class K>
struct DefaultHashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct DefaultHashFunc<string>
{size_t operator()(const string& str){//BKDRsize_t hash = 0;for (auto ch : str){hash *= 131;hash += ch;}return hash;}
};namespace open_address {enum STATE //状态{EXIST,EMPTY,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;STATE _state = EMPTY;};//struct stringHashFunc//{// size_t operator()(const string& str)// {// return str[0];// }//};template<class K, class V, class HashFunc = DefaultHashFunc<K>>class HashTable{public:HashTable(){_table.resize(10);}bool Insert(const pair<K, V>& kv){if(Find(kv.first)){return false;}//扩容//if ((double)_n / _table.size() >= 0.7)if (_n * 10 / _table.size() >= 7){size_t newSize = _table.size() * 2;//重新映射HashTable<K, V, HashFunc> newHT;newHT._table.resize(newSize);for (size_t i = 0; i < _table.size(); i++){if (_table[i]._state == EXIST){newHT.Insert(_table[i]._kv);}}_table.swap(newHT._table);}//线性探测HashFunc hf;size_t hashi = hf(kv.first) % _table.size();while (_table[hashi]._state == EXIST){++hashi;hashi %= _table.size();//防止越界,这样可以回到数组开头}_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;}HashData<const K, V>* Find(const K& key){//线性探测HashFunc hf;size_t hashi = hf(key) % _table.size();while (_table[hashi]._state != EMPTY){if (_table[hashi]._state == EXIST&& _table[hashi]._kv.first == key){return (HashData<const K, V>*) & _table[hashi];}++hashi;hashi %= _table.size();}return nullptr;}bool Erase(const K& key){HashData<const K, V>* ret = Find(key);if (ret){ret->_state = DELETE;--_n;return true;}return false;}private:vector<HashData<K, V>> _table;size_t _n; //存储有效数据的个数};
}namespace hash_bucket
{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 HashFunc>class HashTable;template<class K, class T, class Ptr, class Ref,class KeyOfT, class HashFunc>struct HTiterator{typedef HashNode<T> Node;typedef HTiterator<K, T, Ptr, Ref, KeyOfT, HashFunc> Self;typedef HTiterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;Node* _node;const HashTable<K, T, KeyOfT, HashFunc>* _pht;/*HTiterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node),_pht(pht){}*/HTiterator(Node* node,const HashTable<K, T, KeyOfT, HashFunc>* pht):_node(node), _pht(pht){}//普通迭代器时是拷贝构造//cosnt迭代器时是构造HTiterator(const Iterator& it):_node(it._node), _pht(it._pht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_next){//当前桶还没完_node = _node->_next;}else{KeyOfT kot;HashFunc hf;size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();//从下一个位置开始查找下一个不为空的桶++hashi;while (hashi < _pht->_table.size()){if (_pht->_table[hashi]){_node = _pht->_table[hashi];return *this;}else{++hashi;}}_node = nullptr;}return *this;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}};//set -> hash_bucket::HashTable<K, K> _ht;//map -> hash_bucket::HashTable<K, pair<K,V>> _ht;template<class K,class T,class KeyOfT, class HashFunc = DefaultHashFunc<K>>class HashTable{typedef HashNode<T> Node;//友元声明template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc >friend struct HTiterator;public:typedef HTiterator<K, T,T*,T&, KeyOfT, HashFunc> iterator;typedef HTiterator<K, T,const T*,const T&, KeyOfT, HashFunc> const_iterator;iterator begin(){//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return iterator(cur,this);}}return iterator(nullptr, this);}iterator end(){return iterator(nullptr, this);}const_iterator begin() const{//找第一个桶for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];if (cur){return const_iterator(cur, this);}}return const_iterator(nullptr, this);}const_iterator end() const{return const_iterator(nullptr, this);}HashTable(){_table.resize(10,nullptr);}~HashTable(){for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}pair<iterator,bool> Insert(const T& data){KeyOfT kot;iterator it = Find(kot(data));if (it!=end()){return make_pair(it,false);}HashFunc hf;//负载因子到1时扩容if (_n == _table.size()){size_t newSize = _table.size() * 2;vector<Node*> newTable;newTable.resize(newSize, nullptr);//遍历旧表,把节点迁下来挂到新表for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;//头插到新表size_t hashi = hf(kot(cur->_data)) % newSize;cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newTable);}size_t hashi = hf(kot(data)) % _table.size();//头插Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return make_pair(iterator(newnode,this),true);}iterator Find(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur,this);}cur = cur->_next;}return end();}bool Erase(const K& key){HashFunc hf;KeyOfT kot;size_t hashi = hf(key) % _table.size();Node* prev = nullptr;Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key){if (prev == nullptr){//头删_table[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;return true;}prev = cur;cur = cur->_next;}--_n;return false;}void Print(){for (size_t i = 0; i < _table.size(); i++){printf("[%d]->", i);Node* cur = _table[i];while (cur){cout << cur->_kv.first << ":" <<cur->_kv.second <<"->";cur = cur->_next;}printf("NULL\n");}cout << endl;}private:vector<Node*> _table; //指针数组size_t _n = 0; //有效数据};
}
以上即为本期全部内容,希望大家可以有所收获
如有错误,还请指正