一:对unordered_set与unordered_map的认识
二:改造哈希桶
2.1:改造迭代器
2.2:类中相互typedef前置的声明
2.3:友元关系
三:封装unordered_set与unordered_map
3.1:封装unordered_set/map
3.2:unordered_map的operator[]
3.3:const_iterator
3.4:仿函数
四;总结
//////
一:对unordered_set与unordered_map的认识:
如上所示,我们可以看到,unordered_set和unordered_map是C++11时才有的,如同set/map一样,unordered_set/map也通过mult系列的接口,允许键值冗余,
那么unordered_set/map和普通的set/map不同什么地方呢?
其实从名字就可以看出来,unordered的中文意思是无序的,我们知道,set/map有着排序+去重的作用,那么unordered_set/map就只有去重的作用了,具体如下所示:
通过测试用例我们可以看到,以set与unordered_set为例,set做到排序+去重,而unordered_set却只有去重,没有排序,那么看到这,我们不禁要问,unorder_set/map能做的set/map也能做,为什么要在C++11中加入unordered_set/map呢?为了解答这个问题,我们可以测一下它们的性能,具体如下所示:
通过随机数种子,生成1000000个数,分别测试他们插入,查找,删除所花费的时间;
通过上述三种用例的测试,我们可以得出结论,unordered_set/map在插入删除查找等方面都强于set/map,所花费的时间都相较于少,所以,unordered_set/map除了不能进行排序外,其他的效率都比map/set效率要高,在乱序的场景下,unordered_set/map的使用频率更高。而效率之所以高,是因为unordered_set/map的底层是哈希桶,哈希结构,而非set/map底层的树形结构;而这些,就是unordered_set/map存在的意义!
unordered_set/map的成员函数:
unordered_set/map和set/map有大量的重新接口,这正是STL库设计的优点,一通则百通,相同的成员函数就不再介绍,主要介绍与set/map不同的成员函数;
这是一些关于哈希桶的成员函数,比如,获取桶的个数,最大的桶的个数,桶的大小等等;
这是一些关于荷载因子的成员函数,比如获取荷载因子,改变荷载因子等等,
这是一些其他的成员函数,比如获取哈希函数等等,
以上的这些成员函数是和set/map不同的,但是在现实中使用的情况较少,稍微了解了解即可;
需要注意这里的迭代器,set/map的迭代器是双向迭代器,而unordered_set/map中,迭代器只是单向迭代器,因为unordered_set/map的底层是哈希桶,哈希桶的结构是数组+链表,迭代器在对链表进行迭代时,只能单向迭代;
//////
二:改造哈希桶:
如set/map共用一颗红黑树那样,unordered_set/map也共用一个哈希桶,
所以我们需要将之前的哈希桶(【C++】哈希表/哈希桶_KL4180的博客-CSDN博客)改造成为泛型,使其满足K,KV模型的不同需求,存储的数据只有一个类型,
将节点中,原本两个模板参数的K,KV模型变成一个模板参数T,并且在哈希桶中将节点的类型也做相应的改变。
///
2.1:改造迭代器:
在之前的哈希桶的博客中,我们没有分析和实现迭代器,但是封装unordered_set/map需要迭代器,所以在此分析实现一下,同时因为哈希桶只有单向迭代,就只能向后遍历++,那么哈希桶中的迭代器是如何实现++的呢?
第一种情况很容易实现,但是第二种情况有点复杂,大概实现思路应该是:当现在的桶下面的链表访问完后,我们先记录一下当前桶映射的位置,然后映射位置+1,就找到后面的第一个桶,然后在让迭代器it指向后面一个桶的头结点;具体迭代器的搭建如下所示:
这是迭代器的大致结构,下面开始具体实现++操作:
使用KeyOfT仿函数获取当前数据的key值(因为不知道是map还是set在调用)。
再使用Hash仿函数将key值转换成可以模的整形(因为不知道key是整形还是字符串再或者其他自定义类型)。
然后开始寻找下一个桶:
从当前哈希表下标开始向后寻找,直到找到下一个桶,将桶的头节点地址赋值给_node。
如果始终没有找到,说明没有桶了,也就是没有数据了,it指向end,这里使用空指针来代替end。
其中,这里面辨别K,KV模型的仿函数keyOFT和将类型转为整数的Hash函数都在前面的博客中实现过,这里就不做过多的赘述;
同时,这里面的keyOFT和Hash都没有给缺省值,因为你不能在哈希桶里面将其写死,具体原因等下面封装的时候再说,
///
2.2:类中相互typedef前置的声明:
因为迭代器中有一个成员变量是哈希桶的指针,如上图所示,所以我们需要创建一个哈希桶指针,所以在迭代器中typedef了HashBucket成为 HB,方便我们使用。同时我们使用迭代器,我们又在哈希桶里面typedef了迭代器,
这样就造成了你中有我,我中有你的局面,即哈希桶需要用迭代器,但是迭代器需要哈希桶指针,为了避免其争议,我们在迭代器的前面加上前置声明,
///
2.3:友元关系:
虽然,上面解决了类中相互typedef的问题,但是迭代器++又出现问题,因为在迭代器的类中,要实现迭代器++就有可能重新映射,而重新映射,我们必须要访问哈希桶指针,但是哈希桶指针是私有属性的,此时,我们有两种方法:
1:在哈希桶中通过公有成员函数,get_tables(),通过这个公用函数去获取哈希桶指针,
2:将迭代器类变成哈希桶类的友元类,这样就可以访问到哈希桶内的私有成员;
这里,我们以第二种,也就是通过友元类解决此类问题,(虽然可能会破坏封装);具体如下所示:
类模版的友元声明需要加上其模版参数,要不然会报错;
至此,迭代器的改造就基本上完成了,剩下的如operator*,operator->等等这些的运算符,和以前的大差不差,将在最后的源码给出,这里不做过多的赘述;
//////
三:封装unordered_set与unordered_map:
封装的步骤和用红黑树封装set,map一样,下面直接给出:
3.1:封装unordered_set/map:
unordered_map的封装:
//这里给Hash函数的缺省值,因为要将仿函数的比较权放在封装的unordered_set中
template<class K,class V,class Hash= MKL::HashFunc<K>>
class un_map
{
public:
//仿函数比较,告诉底层的哈希桶,比较的值,将其key取出;
struct mapkeyOFT
{
K operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
//迭代器是封装哈希桶的迭代器;
typedef typename MKL::HashBucket<K, pair<const K, V>, mapkeyOFT,Hash>::iterator iterator;
//剩下的成员函数全部调用哈希桶中的成员函数;
iterator begin()
{
return umap1.begin();
}
iterator end()
{
return umap1.end();
}
bool Insert(const pair<K, V>& kv)
{
return umap1.Insert(kv);
}
bool Find(const K& key)
{
return umap1.Find(key);
}
bool Erase(const K& key)
{
return umap1.Erase(key);
}
private:
//底层用哈希桶创建
MKL::HashBucket<K, pair<const K, V>, mapkeyOFT,Hash> umap1;
};
unordered_set的封装:
//这里给Hash函数的缺省值,因为要将仿函数的比较权放在封装的unordered_set中
template<class K,class Hash=MKL::HashFunc<K>>
class un_set
{
public:
//仿函数比较,告诉底层的哈希桶,比较的值,将其key取出;
struct setkeyOFT
{
K operator()(const K& key)
{
return key;
}
};
//迭代器是封装哈希桶的迭代器;
typedef typename MKL::HashBucket<K, K, setkeyOFT,Hash>::const_iterator iterator;
//剩下的成员函数全部调用哈希桶中的成员函数;
bool Insert(const K& key)
{
return uset1.Insert(key);
}
iterator begin()
{
return uset1.begin();
}
iterator end()
{
return uset1.end();
}
Find(const K& key)
{
return uset1.Find(key);
}
bool Erase(const K& key)
{
return uset1.Erase(key);
}
private:
//底层用哈希桶创建
MKL::HashBucket<K, K, setkeyOFT,Hash> uset1;
};
同时注意,用哈希桶的迭代器封装unordered_set/map的迭代器时,要记得加typename,告诉编译器这是一个类型而不是静态变量。
///
3.2:unordered_map的operator[]:
和map一样,既然要实现operator[],那么我们需要改一下插入和查找,让插入的返回类型是键值对pair<iterator,bool>;让查找的返回类型为迭代器;
首先,修改哈希表中的Find,让其返回迭代器,如果存在,返回key所在位置的迭代器,如果不存在,返回末尾的迭代器。
然后修改哈希表的插入接口,返回值改为键值对pair<iterator,bool>;
同时,既然底层的插入查找的返回值改变了,那么unordered_set/map的插入查找也要改:
这样我们就可以实现unordered_mapd的operator[],具体如下所示:
下面给出一个示例看看能不能跑通:
通过测试用例可以看出,operator[]基本上没有问题了;
///
3.3:const_iterator:
当我们在自己封装的unordered_set中试着修改迭代器的值时,发现可以修改,这与我们的要求的相悖的,set的值是不能被修改的,所以,我们去学一学源码是如何做的,
从STL源码中可以看到,const迭代器和普通迭代器不是同一个类,而是又重新创建了一个const_iterator类,并不是和我们之前红黑树封装set,map那样,复用一个类,这是为什么呢?先来看看和之前一样复用一个类会发生什么?
显然,又发生了和set,map封装时,一样的问题,底层哈希桶的iterator是普通迭代器,而unordered_set中,K值不允许修改,所以普通迭代器是const迭代器,const迭代器也是const迭代器,返回值与返回类型不一样,普通迭代器不能转换为const迭代器;
因此,我们现在有两种解决方法:
1:像STL库中那样,const迭代器与普通迭代器分开写,当是普通迭代器时,用的就是普通迭代器,当是const迭代器时,用的就是const迭代器,
2:像红黑树中那样的解决,再创建一个构造函数,当普通迭代器调用时,就是拿普通迭代器去拷贝构造,当是const迭代器时,就是拿普通迭代器去构造const迭代器;
这里,我们还是和红黑树那样,用普通迭代器构建const迭代器的方法解决这个问题,具体如下所示:
//
3.4:仿函数:
前面说过,底层的类模板和迭代器的类模板中的仿函数keyOFT,Hash函数都没有给缺省值,这是为什么呢?
因为,我们不能在底层的哈希桶里面写死,要不然当传自定义类型的类时,如日期类时,无法通过底层写死的仿函数去比较,
我们应该将仿函数的实现与比较权放在封装的unordered_set/map中,这样,不论当你封装的unordered_set/map处理什么样的类型,在unordered_set/map写相应的仿函数,就可以通过仿函数进行比较;这里就不给示例了,有兴趣可以自己搞一个自定义类型试一试;
//////
四;总结:
在封装unordered_set/map时,一定要特别注意类模板的使用,因为封装时 类模板被大量使用,而且迭代器也是我们的重点,了解迭代器的构造以及++操作,同时仿函数也举足轻重,
总结出仿函数取key的结论:
一个类型要做unordered_set/map的key,要满足:有支持取模的仿函数+支持==的运算符
一个类型要做set/map的key,要满足:支持<的比较;,
还要知道两个类在互相typedef时,需要有类的前置声明。一个类访问其他类中的私有成员的两种方法(公有成员函数和友元),总而言之,对于现阶段来说,很多的细节和知识点是非常有意义的;
至此,封装unordered_set/map的全过程就到此结束,我将我封装时的代码放在下面;
底层哈希桶:
#pragma once
#include <iostream>
#include <vector>
using namespace std;
namespace MKL
{
template<class T>//只有一个模板参数
struct HashData
{
HashData<T>* _next;//指向下一个节点
T _Data;//桶中的数据
//节点构造
HashData(const T& data)
:_next(nullptr)
, _Data(data)
{
}
};
//默认支持的仿函数比较,整形
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
//模版特化,默认支持的仿函数比较,字符串;
template<>
struct HashFunc<string>
{
size_t operator()(const string& s)
{
size_t hashi = 0;
for (auto e : s)
{
hashi = hashi + e;
hashi *= 31;
}
return hashi;
}
};
//这是显示调用的字符串仿函数,因为需要显示调用,不如将其模板特化,就不用显示调用了
//struct HashFuncstring
//{
// size_t operator()(const string& s)
// {
// size_t hashi = 0;
// for (auto e : s)
// {
// hashi = hashi + e;
// hashi *= 31;
// }
// return hashi;
// }
//};
//迭代器:
template<class K, class T, class keyOFT, class Hash>
class HashBucket;
template<class K, class T,class Ref,class Ptr, class keyOFT, class Hash>
struct hash_iterator
{
typedef HashData<T> node;
typedef hash_iterator<K, T,Ref,Ptr, keyOFT, Hash> self;
typedef HashBucket<K, T, keyOFT, Hash> HB;
typedef hash_iterator<K, T,T&,T*, keyOFT, Hash> iterator;//始终构建一个普通迭代器
//node* _node;
//const HB* _hb;
//hash_iterator(node* node, const HB* hb)
// :_node(node)
// , _hb(hb)
//{
//}
node* _node;
HB* _hb;
hash_iterator(node* node, HB* hb)
:_node(node)
,_hb(hb)
{
}
hash_iterator(const iterator& it)//用普通迭代器构造const迭代器
:_node(it._node)
, _hb(it._hb)
{
}
Ref operator*()
{
return _node->_Data;
}
Ptr operator->()
{
return &_node->_Data;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
self& operator++()
{
if (_node->_next != nullptr)//如果当前位置的下一个位置不为空;
{
_node = _node->_next;//那么迭代器it++就指向其下一个节点;
}
else//如果当前位置的下一个位置为空,需要算出下一个桶的位置;
{
keyOFT kot;//仿函数,取出传过来的数据的K值;
Hash hash;//通过哈希函数取整数,方便除留取余;
size_t hashi = hash(kot(_node->_Data)) % _hb->_tables.size();//算出当前桶的位置;
++hashi;//位置+1,找到下一个桶;
while (hashi < _hb->_tables.size())
{
if (_hb->_tables[hashi] != nullptr)//如果后面不是一个空桶,
{
_node = _hb->_tables[hashi];//让迭代器it变到这个位置上;然后退出
break;
}
else//如果后面是空桶
{
++hashi;//向后再继续找桶;
}
}
if (hashi == _hb->_tables.size())//如果找到最后全是空桶;
{
_node = nullptr;//迭代器就直接指向空,迭代器结束;
}
}
return *this;//返回此时迭代器的位置;
}
};
//哈希桶:
template<class K, class T, class keyOFT,class Hash >
class HashBucket
{
typedef HashData<T> node;
public:
template<class K, class T, class Ref, class Ptr, class keyOFT, class Hash>
friend struct hash_iterator;
typedef hash_iterator<K, T, T& ,T*, keyOFT, Hash> iterator;
typedef hash_iterator<K, T, const T&,const T*, keyOFT, Hash> const_iterator;
iterator begin()
{
node* cur = nullptr;
for (size_t i = 0; i < _tables.size(); ++i)//找到哈希桶中第一个不为空的节点,
{
if (_tables[i] != nullptr)
{
cur = _tables[i];
break;
}
}
return iterator(cur, this);//返回第一个不为空的节点,同时迭代器需要哈希桶指针,所以将this指针传过去
}
iterator end()
{
return iterator(nullptr, this);
}
const_iterator begin() const
{
node* cur = nullptr;
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i] != nullptr)
{
cur = _tables[i];
break;
}
}
return const_iterator(cur, this);
}
const_iterator end() const
{
return const_iterator(nullptr, this);
}
pair<iterator,bool> Insert(const T& data)//operator[]需要插入的时候返回键值对
{
keyOFT kot;//仿函数,取出传过来的数据的K值;
iterator it = Find(kot(data));//是否已经存在
//如果要找的值已经存在于桶中
if (it != end())
{
return make_pair(it,false);//返回当前的迭代器
}
Hash hash;//通过哈希函数取整数,方便除留取余;
if (_tables.size() == n)
{
size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
vector<node*> newhb;
newhb.resize(newsize, nullptr);
for (auto& cur : _tables)
{
while (cur != nullptr)
{
node* next = cur->_next;
size_t hashi = hash(kot(cur->_Data)) % newhb.size();
cur->_next = newhb[hashi];
newhb[hashi] = cur;
cur = next;
}
}
_tables.swap(newhb);
}
size_t hashi = hash(kot(data)) % _tables.size();
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)
{
if (_tables.size() == 0)//如果桶中没有节点
{
return end();//直接返回空
}
keyOFT kot;//仿函数,取出传过来的数据的K值;
Hash hash;//通过哈希函数取整数,方便除留取余;
size_t hashi = hash(key) % _tables.size();//找到它映射在桶里面的位置;
node* cur = _tables[hashi];//找到映射到桶里的头节点
while (cur != nullptr)
{
if (kot(cur->_Data) == key)//如果找到了
{
return iterator(cur,this);//返回当前的位置,
}
else///没有找到,继续向后找
{
cur = cur->_next;
}
}
return end();
}
bool Erase(const K& key)
{
keyOFT kot;//获取K值,map,与set的后一个类型不同;
Hash hash;//仿函数比较;
size_t hashi = hash(key) % _tables.size();
node* prev = nullptr;
node* cur = _tables[hashi];
while (cur != nullptr)
{
if (kot(cur->_Data) == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
cur = nullptr;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
--n;
return false;
}
private:
vector<node*>_tables;//指针数组
size_t n = 0;//记录数组中的元素个数
};
}
封装的unordered_set:
#pragma once
#include "hash.h"
#include "hash1.h"
namespace un_set
{
template<class K,class Hash=MKL::HashFunc<K>>
class un_set
{
public:
struct setkeyOFT
{
K operator()(const K& key)
{
return key;
}
};
typedef typename MKL::HashBucket<K, K, setkeyOFT,Hash>::const_iterator iterator;
typedef typename MKL::HashBucket<K, K, setkeyOFT, Hash>::const_iterator const_iterator;
//返回值为键值对
pair<iterator,bool> Insert(const K& key)
{
return uset1.Insert(key);
}
//返回值为迭代器
iterator Find(const K& key)
{
return uset1.Find(key);
}
iterator begin()
{
return uset1.begin();
}
iterator end()
{
return uset1.end();
}
const_iterator begin() const
{
return uset1.begin();
}
const_iterator end() const
{
return uset1.end();
}
bool Erase(const K& key)
{
return uset1.Erase(key);
}
private:
MKL::HashBucket<K, K, setkeyOFT,Hash> uset1;
};
void un_settest1()
{
un_set<int> us1;
us1.Insert(1);
us1.Insert(2);
us1.Insert(4);
us1.Insert(3);
us1.Insert(5);
un_set<int>::iterator it = us1.begin();
while (it != us1.end())
{
cout << *(it) << " ";
++it;
}
cout << endl;
}
void un_settest2()
{
un_set<int> us1;
us1.Insert(2);
us1.Insert(3);
us1.Insert(1);
us1.Insert(2);
un_set<int>::iterator it = us1.begin();
while (it != us1.end())
{
//*(it) = 1;
cout << *(it) << " ";
++it;
}
}
}
封装的unordered_map:
#pragma once
#include "hash.h"
#include "hash1.h"
namespace un_map
{
template<class K,class V,class Hash= MKL::HashFunc<K>>
class un_map
{
public:
struct mapkeyOFT
{
K operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
typedef typename MKL::HashBucket<K, pair<const K, V>, mapkeyOFT,Hash>::iterator iterator;
typedef typename MKL::HashBucket<K, pair<const K, V>, mapkeyOFT, Hash>::const_iterator const_iterator;
iterator begin()
{
return umap1.begin();
}
iterator end()
{
return umap1.end();
}
const_iterator begin() const
{
return umap1.begin();
}
const_iterator end() const
{
return umap1.end();
}
//返回值为键值对
pair<iterator, bool> Insert(const pair<K, V>& kv)
{
return umap1.Insert(kv);
}
//返回值为迭代器
iterator Find(const K& key)
{
return umap1.Find(key);
}
V& operator[](const K& key)
{
pair<iterator, bool> temp = Insert(make_pair(key, V()));
return temp.first->second;
}
bool Erase(const K& key)
{
return umap1.Erase(key);
}
private:
MKL::HashBucket<K, pair<const K, V>, mapkeyOFT,Hash> umap1;
};
void un_maptest1()
{
un_map<int, int> um1;
um1.Insert(make_pair(1, 1));
um1.Insert(make_pair(2, 2));
um1.Insert(make_pair(4, 4));
um1.Insert(make_pair(3, 3));
um1.Insert(make_pair(5, 5));
um1.Insert(make_pair(11, 11));
um1.Insert(make_pair(12, 12));
un_map<int, int>::iterator it = um1.begin();
while (it != um1.end())
{
cout << it->first << ":" << it->second << " ";
++it;
}
cout << endl;
}
void un_maptest2()
{
un_map<string, int> um2;
string arr[] = { "西瓜","苹果","菠萝","梨","西瓜", "西瓜", "菠萝","苹果","梨","梨" };
for (auto& e : arr)
{
um2[e]++;
}
for (auto e : um2)
{
cout << e.first << ":" << e.second << " ";
}
cout << endl;
}
class Date
{
friend struct Datefunc;
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
//一个类型要做unordered_set/map的key,要满足:有支持取模的仿函数+支持==的运算符
//一个类型要做set/map的key,要满足:支持<的比较;
bool operator==(const Date& d)const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
friend ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
struct Datefunc
{
size_t operator()(const Date& d)
{
size_t hashi = 0;
hashi = hashi + d._year;
hashi *= 31;
hashi = hashi + d._month;
hashi *= 31;
hashi = hashi + d._day;
hashi *= 31;
return hashi;
}
};
void un_maptest3()
{
Date d1(2023, 7, 1);
Date d2(2023, 7, 2);
Date d3(2023, 7, 3);
Date d4(2023, 7, 2);
Date d5(2023, 7, 2);
Date d6(2023, 7, 1);
Date arr[] = { d1,d2,d3,d4,d5,d6 };
un_map<Date, int, Datefunc> um3;
for (auto& e : arr)
{
um3[e]++;
}
for (auto& e : um3)
{
cout << e.first << ":" << e.second << " ";
}
cout << endl;
}
}