先前用红黑树封装出了map和set,现在就要用哈希来封装unordered_map和unordered_set(为了简化名称,后面称u_map和u_set),u_map和u_set在学习map时曾了解过,只知道是无序,我还在想,不能排序有啥用呢?原来用于查找数据效率高得很。有了封装map和set的经历,下面的封装就好理解多了,难点基本上都是迭代器和模板参数。
一 模板参数
从u_map和u_set传的模板参数来理解
下图为哈希表接收的模板参数
本文的哈希是开散列的,因为开散列可以更好地解决哈希冲突,所以哈希表中存的就是每个桶中第一个节点的指针,而T就是节点用于存各种数据类型的,也就是说u_map和u_set给哈希表模板传的第二个模板参数就是想要哈希表节点存的类型。接来下看看两个容器在节点存的类型。
节点类内部实现
template<class T>//T为节点所存数据
struct HashNode
{
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{
;
}
T _data;
HashNode<T>* _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值。但是哈希表还有其余的模板参数,这就要结合哈希表的实现才能说清楚,如下。
HashBucketTable其余模板参数介绍
第一个就简单了,是专门提供给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
二 使key不可被修改
当我们大致了解了哈希表的模板参数,还有个大问题前面提了却未解决,u_map和u_set的key都应该是不能修改的,那我们如何实现呢?
1 使u_set的key不可修改
一般来说普通迭代器可以修改节点内的数据,也就是说u_set的普通迭代器可以修改节点内的数据,那不就意味着可以修改key?这样显然是不行的,所以我们要让外部取得iterator定义出的变量具有const_iterator的特性,还是老套路,用typedef重命名来欺骗使用者。
临场发问:typename的作用是什么?
上图中我们自认为是从HashBucketTable这个类内取一个类型,可编译器编译的时候哪知道,假设有一个静态变量a,
访问静态变量a的方式:HashBucketTable<K, pair<const K, V>, GainMapKey>::a,这个形式和前面取iterator不是一模一样吗,编译器哪分得清,而且你类又没实例化,编译器又没办法核实,所以会报错,只能我们写个关键字typename,告诉编译器这是个类型,等实例化了再检查,现在先别报错。
2 使u_map的key不可修改
先前没说为什么要给K加上const,现在可以说了,就是使得K不可修改。
仅仅只是我博客这些功能,u_set也可以通过传const K的方法去实现iterator有const_iterator的作用,但是库里大佬肯定比我想的细致,应该是我没考虑到这种方式对其余功能的阻碍。
三 迭代器实现
这个迭代器的实现则与先前的有些不同,因为当我们++后,如果到空节点了,要去下一个不为空的桶,此时迭代器存的节点指针是没办法找到下一个桶的,这意味着我们需要访问哈希表,所以应该增加一个成员,哈希表指针。
还有个问题,我们需要从深入迭代器实现才可以解决,就是_ht调用返回的pair<iterator,bool>中的iterator和外面u_set的iterator是不同的,因为外部的iterator是const_iterator重定义的,而内部iterator就是普通迭代器,类型已经不匹配了,这是我们自己定义的类型,编译器无法强转,除非我们写了对应的构造函数,编译器才有可能偷偷帮我们转换一下,接来下就带着问题看看迭代器如何支持的这个类型转换。
迭代器代码
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<T> Node;
typedef HashIterator<K, T, Ptr, Ref, KeyofT, Hashfunc> Self;
重命名为Self, 方便下面使用,模板参数变量也只需要改这里
typedef HashIterator<K, T, T*,T&,KeyofT, Hashfunc> iterator;
HashIterator(Node* node, const HashBucketTable<K, T, KeyofT, Hashfunc>* 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<K, T, KeyofT, Hashfunc>* _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封装代码
u_map的成员函数都是复用_ht变量的函数,比较有意思的就是[]的实现了。
template<class K,class V>
class Unordered_map
{
public:
struct GainMapKey//用来返回pair中的key
{
const K& operator()(const pair<K, V>& data)
{
return data.first;
}
};
typedef HashNode<pair<const K, V>> Node;
typedef typename HashBucketTable<K, pair<const K, V>, GainMapKey>::iterator iterator;
typedef typename HashBucketTable<K, pair<const K, V>, GainMapKey>::const_iterator const_iterator;
pair<iterator,bool> insert(const pair<K,V>& 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<iterator,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<K, pair<const K,V>, GainMapKey> _ht; 偏特化
};
u_set封装代码
template<class K>
class Unordered_set
{
public:
struct GainSetKey 为了map和set传模板参数保持一致,set也要传一个GainSetKey
{
const K& operator()(const K& data)
{
return data;
}
};
typedef HashNode<K> Node;
typedef typename HashBucketTable<K,K, GainSetKey>::const_iterator iterator;
typedef typename HashBucketTable<K, K, GainSetKey>::const_iterator const_iterator;
pair<iterator,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<K, K, GainSetKey> _ht; 偏特化
};
四 哈希类成员函数介绍
template<class K>
struct DefaultHashfunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHashfunc<string> 将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<K>>
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<T> Node;
typedef HashIterator<K, T, T*, T&, KeyofT, Hashfunc> iterator;
typedef HashIterator<K, T,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<K, T, KeyofT, Hashfunc>& 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<iterator,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<Node*> 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<Node*> _table;
size_t _size = 0;
};