先前用红黑树封装出了map和set,现在就要用哈希来封装unordered_map和unordered_set(为了简化名称,后面称u_map和u_set),u_map和u_set在学习map时曾了解过,只知道是无序,我还在想,不能排序有啥用呢?原来用于查找数据效率高得很。有了封装map和set的经历,下面的封装就好理解多了,难点基本上都是迭代器和模板参数。
下图为哈希表接收的模板参数
本文的哈希是开散列的,因为开散列可以更好地解决哈希冲突,所以哈希表中存的就是每个桶中第一个节点的指针,而T就是节点用于存各种数据类型的,也就是说u_map和u_set给哈希表模板传的第二个模板参数就是想要哈希表节点存的类型。接来下看看两个容器在节点存的类型。

节点类内部实现
- template<class T>//T为节点所存数据
- struct HashNode
- {
- HashNode(const T& data)
- :_data(data)
- , _next(nullptr)
- {
- ;
- }
- T _data;
- HashNode
* _next; - };
u_map实例化的哈希表变量
如下图,u_map存在节点的是一个pair。至于为什么要对K加上const,我想如果了解过map和set的封装的同学肯定能猜到(后面会再提及)。

u_set实例化的哈希表变量
而u_set要节点存的就是一个K类型的,毕竟u_set和set是类似的,只存一个key值,为了让节点可以用一个变量,例如T _data,既可以存u_set的一个key值,又可以存u_map的key和value这两个值,只能让key和value绑在一个结构体上,总不能让节点内多加一个变量吧,u_set又用不了那么多,不就浪费了吗。

综上,传给哈希表的第二个模板参数是节点存的数据的类型,可能是一个pair,也可能是一个K类型的key值。但是哈希表还有其余的模板参数,这就要结合哈希表的实现才能说清楚,如下。
第一个就简单了,是专门提供给find这类参数是const K&key的,而T已经介绍过了。

而剩下的两个模板参数都在下面一行代码里了

由于我们前面将u_map的key和value统一为一个值,不仅方便了节点类内变量存储,insert也只要插入一个值就好了,这就说明插入的元素kv可能是个K类型的,也可能是一个pair。

如果是一个pair, 我们要取出里面的key,然后%_table.size()求下标,但是如果u_set调用insert,就可以直接取模,因为这时候的kv就是key, 这就导致这里对不同类型要做不同的处理,借鉴封装map的经验,我们可以用一个仿函数GetKey将key返回,但这也不意味着可以直接取模,如果key是字符串呢,这就要用到第四个模板参数,它就是将一些自定义类型转为整数,然后用于取模,这个仿函数介绍哈希成员函数时会出代码,所以这两个仿函数是互相配合。
map的GetKey

set的GetKey

当我们大致了解了哈希表的模板参数,还有个大问题前面提了却未解决,u_map和u_set的key都应该是不能修改的,那我们如何实现呢?
一般来说普通迭代器可以修改节点内的数据,也就是说u_set的普通迭代器可以修改节点内的数据,那不就意味着可以修改key?这样显然是不行的,所以我们要让外部取得iterator定义出的变量具有const_iterator的特性,还是老套路,用typedef重命名来欺骗使用者。

临场发问:typename的作用是什么?
上图中我们自认为是从HashBucketTable这个类内取一个类型,可编译器编译的时候哪知道,假设有一个静态变量a,
访问静态变量a的方式:HashBucketTable
先前没说为什么要给K加上const,现在可以说了,就是使得K不可修改。

仅仅只是我博客这些功能,u_set也可以通过传const K的方法去实现iterator有const_iterator的作用,但是库里大佬肯定比我想的细致,应该是我没考虑到这种方式对其余功能的阻碍。
这个迭代器的实现则与先前的有些不同,因为当我们++后,如果到空节点了,要去下一个不为空的桶,此时迭代器存的节点指针是没办法找到下一个桶的,这意味着我们需要访问哈希表,所以应该增加一个成员,哈希表指针。
还有个问题,我们需要从深入迭代器实现才可以解决,就是_ht调用返回的pair

- template<class K, class T,class KeyofT, class Hashfunc>
- class HashBucketTable;
-
- 要前置声明,不然迭代器内会不认识哈希表指针
-
- template<class K, class T, class Ptr, class Ref,class KeyofT, class Hashfunc>
- struct HashIterator K,KeyofT, Hashfunc是给哈希表的模板参数
- {
- typedef HashNode
Node; - typedef HashIterator
Self; - 重命名为Self, 方便下面使用,模板参数变量也只需要改这里
-
- typedef HashIterator
iterator; -
- HashIterator(Node* node, const HashBucketTable
* pht) - :_node(node)
- , _pht(pht)
- {
- ;
- }
- HashIterator(const iterator& it)
- :_node(it._node)
- , _pht(it._pht)
- {
- ;
- }
- Self& operator++()
- {
- KeyofT kot;
- Hashfunc hf;
- Node* cur = _node;
- if (cur->_next) 下一个节点不为空
- {
- _node = _node->_next;
- }
- else 去下一个桶找
- {
- size_t hashi = (hf(kot(cur->_data)) % _pht->_table.size()) + 1;
- while (hashi<_pht->_table.size()) 防止往后找不为空的桶时越界
- {
- if(!_pht->_table[hashi])
- hashi++;
- else
- {
- _node = _pht->_table[hashi];
- return *this;
- }
- }
- _node = nullptr; 先前还没返回,说明全是空桶,达到end位置
- }
- return *this;
- }
- Ref operator*() 通过控制Ptr和Ref来达到const迭代器的作用
- {
- 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;
- }
- Node* _node;
- const HashBucketTable
* _pht; 哈希表指针 - };
当HashIterator被实例化为const迭代器, 这个就可以给u_set的insert做类型转换使用,所以我们前面需要定义一个普通迭代器,就是为了这一步
有没有注意到这个成员加了const,首先加上const绝对是可以的,因为限制我们通过_pht这个指针修改哈希表也是合理的。

或许你会说,不对,是因为构造函数是用const的指针接收的。

那就看看下面的,哈希表指针是用this指针初始化的,而this指针的类型是 T*const,这个const不限制赋值,限制了感觉反而不好,举个例子,int* const p1=&a; int*p2=p1;你p1不能修改自己的值,总不能让p2也不能修改自己的值吧,好像不太合理,但如果是const迭代器,那this指针就是const T*const,所以构造函数需要加上const,否则无法匹配,也就是使得哈希表指针这个成员变量要加const。

最后我说一下我之前的一个问题,我想it是const类型的,它的成员也是const类型的,为什么就能赋值给Node* _node, 现在我想明白了,it._node这个的类型是Node*const的,由前面的,这个const不限制,所以可以赋值。
u_map的成员函数都是复用_ht变量的函数,比较有意思的就是[]的实现了。
- template<class K,class V>
- class Unordered_map
- {
- public:
- struct GainMapKey//用来返回pair中的key
- {
- const K& operator()(const pair
& data) - {
- return data.first;
- }
- };
- typedef HashNode
const K, V>> Node; - typedef typename HashBucketTable
const K, V>, GainMapKey>::iterator iterator; - typedef typename HashBucketTable
const K, V>, GainMapKey>::const_iterator const_iterator; -
- pair
bool > insert(const pair& p1) - {
- return _ht.insert(p1);
- }
- bool Erase(const K& k1)
- {
- return _ht.Erase(k1);
- }
- iterator find(const K& key) 这里find和_ht.find()返回类型和下面的insert一样
- 类型是不匹配的,但是构造函数支持了这种类型转换。
- {
- return _ht.find(key);
- }
- iterator begin()
- {
- return _ht.begin();
- }
- iterator end()
- {
- return _ht.end();
- }
- const_iterator begin()const
- {
- return _ht.begin();
- }
- const_iterator end()const
- {
- return _ht.end();
- }
- V& operator[](const K& p1)
- {
- pair
bool> ret=_ht.insert(make_pair(p1,V())); - return (*ret.first).second;
-
- ret.first取得是iterator,*解引用就返回_node->_data的引用,
- 这个_data对于u_map而言就是一个pair,我们再取second就是[]需要返回的value
- }
- private:
- HashBucketTable
const K,V>, GainMapKey> _ht; 偏特化 - };
- template<class K>
- class Unordered_set
- {
- public:
- struct GainSetKey 为了map和set传模板参数保持一致,set也要传一个GainSetKey
- {
- const K& operator()(const K& data)
- {
- return data;
- }
- };
- typedef HashNode
Node; - typedef typename HashBucketTable
::const_iterator iterator; - typedef typename HashBucketTable
::const_iterator const_iterator; -
-
- pair
bool > insert(const K& k1) - {
- return _ht.insert(k1);
- }
- bool Erase(const K& k1)
- {
- return _ht.Erase(k1);
- }
- iterator find(const K& key)
- {
- return _ht.find(key);
- }
-
- 只提供const版本的begin和end函数,就是为了_ht调用的begin和end函数返回的是const_iterator
- const_iterator begin()const
- {
- return _ht.begin();
- }
- const_iterator end()const
- {
- return _ht.end();
- }
- private:
- HashBucketTable
_ht; 偏特化 - };
- template<class K>
- struct DefaultHashfunc
- {
- size_t operator()(const K& key)
- {
- return (size_t)key;
- }
- };
- template<>
- struct DefaultHashfunc
将string转为整数的仿函数 - {
- size_t operator()(const string& s)
- {
- int hash = 1;
- for (auto e : s)
- hash = hash * 131 + e; BKDR
- return (size_t)hash;
- }
- };
-
-
- template<class K, class T, class KeyofT, class Hashfunc = DefaultHashfunc
> - class HashBucketTable
- {
- public:
- template<class K, class T, class Ptr, class Ref,class KeyofT, class Hashfunc>//友元类的声明
- friend struct HashIterator;
- HashIterator要访问HashBucketTable的私有成员,所以是
- 在HashBucketTable对HashIterator友元声明
-
- typedef HashNode
Node; - typedef HashIterator
iterator; - typedef HashIterator
const T*,const T&, KeyofT, Hashfunc> const_iterator; - 通过传const T*,const T&来达到const迭代器的效果
-
- HashBucketTable()
- {
- _table.resize(5);
- }
-
- 析构函数函数
- ~HashBucketTable()
- {
- for (size_t i = 0; i < _table.size(); i++)
- {
- Node* cur = _table[i];
- while (cur) 找到一个非空桶,遍历链表,delete释放节点
- {
- Node* next = cur->_next;
- _table[i] = next;
- delete cur;
- cur = next;
- }
- }
- }
- iterator begin()
- {
- for (size_t i = 0; i < _table.size(); i++)//遍历哈希表ht
- {
- 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++)//遍历哈希表ht
- {
- 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);
- }
-
- 拷贝构造函数
- HashBucketTable(const HashBucketTable
& ht) - {
- KeyofT kot;
- Hashfunc hf;
- _table.resize(ht._table.size());
- for (size_t i = 0; i < ht._table.size(); i++)//遍历哈希表ht
- {
- Node* cur = ht._table[i];
- while (cur)//遍历该桶
- {
- Node* newnode = new Node(cur->_data);
- size_t hashi = hf(kot(cur->_data)) % _table.size();
- newnode->_next = _table[hashi];
- _table[hashi] = newnode;
- cur = cur->_next;//去桶的下一个节点
- }
- }
- }
-
- 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 iterator(nullptr,this);
- }
- bool Erase(const K& key)
- {
- KeyofT kot;
- Hashfunc hf;
- iterator ret=find(key);
- if(ret._node==nullptr) 没找到节点,返回false
- return false;
- size_t hashi = hf(key) % _table.size();
- Node* pre = nullptr;
- Node* cur = _table[hashi];
- while (cur)
- {
- if (kot(cur->_data) == key)
- {
- if (pre == nullptr)//头删
- {
- _table[hashi] = cur->_next;//链接
- }
- else
- {
- pre->_next = cur->_next;
- }
- delete cur;
- --_size;//负载因子要--
- return true;
- }
- pre = cur;
- cur = cur->_next;
- }
- return false; 没找到,erase失败
- }
- pair
bool > insert(const T& kv) - {
- KeyofT kot;
- Hashfunc hf;
- 查找该节点是否已经存在
- iterator it = find(kot(kv));
- if (it._node)//该节点存在,返回该节点迭代器,并且插入失败
- return make_pair(it, false);
- //扩容
- if (_size >= _table.size())
- {
- size_t newsize = _table.size() * 2;
-
- vector
newtable; - newtable.resize(newsize);
- for (size_t i = 0; i < _table.size(); i++)
- {
- Node* cur = _table[i]; 搬运旧表上的节点,省得自己new和delete
- while (cur)
- {
- Node* next = cur->_next;
- _table[i] = next;
- size_t hashi = hf(kot(cur->_data)) % newtable.size();//计算新表映射下标
- 搬到新表
- cur->_next = newtable[hashi];
- newtable[hashi] = cur;
- cur = next;//搬下个节点
- }
- }
- 新旧表交换
- _table.swap(newtable);
- }
-
- 正常插入节点
- size_t hasi = hf(kot(kv)) % _table.size();
- Node* newnode = new Node(kv);
- newnode->_next = _table[hasi];
- _table[hasi] = newnode;
- _size++;
- return make_pair(iterator(newnode,this),true);
- }
- private:
- vector
_table; - size_t _size = 0;
- };