前言:
前面我们学习了unordered_map和unordered_set容器,比较了他们和map、set的查找效率,我们发现他们的效率比map、set高,进而我们研究他们的底层是由哈希实现。哈希是一种直接映射的方式,所以查找的效率很快。与学习红黑树和map、set的思路一样,我们现在学完了unordered_map和unordered_set,本章将模拟实现底层结构来封装该容器!
作者建议在阅读本章前,可以先去看一下前面的红黑树封装map和set——红黑树封装map和set
这两篇文章都重在强调泛型编程的思想,上一篇由于是初认识,作者讲解的会更详细一点~
目录
(一)如何复用一个哈希桶
1、结点的定义:
2、两个容器各自的模板参数类型编辑
3、改造哈希桶
(二)哈希桶的迭代器的模拟实现
1、begin()和end()的模拟实现
2、operator*和operator->及operator!=和operator==的模拟实现
3、operator ++的模拟实现
(三)迭代器和改造哈希桶的总代码
(四)封装unordered_map和unordered_set
我们学习过知道,unordered_map和unordered_set容器存放的结点并不一样,为了让它得到复用,我们就需要对哈希桶进行改造,将哈希桶改造的更加泛型一点,既符合Key模型,也符合Key_Value模型。
所以我们这里还是和封装map和set
时一样,无论是Key
还是Key_Value
,都用一个类型T来接收,这里高维度的泛型哈希表中,实现还是用的是Kye_Value
模型,K是不能省略的,同样的查找和删除
要用,故我们可以引出两个容器各自模板参数类型。
如何取到想要的数据:
同时我们再给每个容器配一个哈希函数。
通过上面1和2,我们可以把各自存放的数据泛化成data:
这样我们哈希桶的模板参数算是完成了
我们把上一篇文章封装的哈希桶拿来改造:
//K --> 键值Key,T --> 数据
//unordered_map ->HashTable, MapKeyOfT> _ht;
//unordered_set ->HashTable _ht;
template
class HashTable
{
template
friend class __HTIterator;
typedef HashNode Node;
public:
typedef __HTIterator iterator;
iterator begin()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return iterator(cur, this);
}
}
return end();
}
iterator end()
{
return iterator(nullptr, this);
}
~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;
}
}
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
static const size_t primeList[PRIMECOUNT] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
//获取比prime大那一个素数
size_t i = 0;
for (i = 0; i < PRIMECOUNT; i++)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
pair Insert(const T& data)
{
HashFunc hf;
KeyOfT kot;
iterator pos = Find(kot(data));
if (pos != end())
{
return make_pair(pos, false);
}
//负载因子 == 1 扩容 -- 平均每个桶挂一个结点
if (_tables.size() == _n)
{
//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
size_t newSize = GetNextPrime(_tables.size());
if (newSize != _tables.size())
{
vector newTable;
newTable.resize(newSize, nullptr);
//遍历旧表
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
//再对每个桶挨个遍历
while (cur)
{
Node* next = cur->_next;
size_t hashi = hf(kot(cur->_data)) % newSize;
//转移到新的表中
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
//将原表置空
_tables[i] = nullptr;
}
newTable.swap(_tables);
}
}
size_t hashi = hf(kot(data));
hashi %= _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 iterator(nullptr, this);
}
KeyOfT kot;
HashFunc hf;
size_t hashi = hf(key);
//size_t hashi = HashFunc()(key);
hashi %= _tables.size();
Node* cur = _tables[hashi];
//找到指定的桶之后,顺着单链表挨个找
while (cur)
{
if (kot(cur->_data) == key)
{
return iterator(cur, this);
}
cur = cur->_next;
}
//没找到返回空
return iterator(nullptr, this);
}
bool Erase(const K& key)
{
if (_tables.size() == 0)
{
return false;
}
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key);
hashi %= _tables.size();
//单链表删除结点
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
//头删
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
//指针数组
vector _tables;
size_t _n = 0;
};
主要改造的地方就是上述所注意的地方:
还有对扩容的二次思考:
研究表明:除留余数法,最好模一个素数
这两组和之前实现的一模一样,大家自行理解。
注:
思路:
注意:
unordered_map和unordered_set是不支持反向迭代器的,从底层结构我们也能很好的理解(单链表找不了前驱)所以不支持实现迭代器的operator- -
最后注意一点,我们需要知道哈希桶大小,所以不仅要传结点地址,还要传一个哈希桶,这样才能知道其大小,除此,由于哈希桶改造在后面,所以我们要在前面声明一下:
#include
#include
#include
using namespace std;
template
struct DefaultHash
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHash
{
size_t operator()(const string& key)
{
//BKDR
size_t hash = 0;
for (auto ch : key)
{
hash = hash * 131 + ch;
}
return hash;
}
};
namespace Bucket
{
template
struct HashNode
{
T _data;
HashNode* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
template
class HashTable;
//哈希桶的迭代器
template
class __HTIterator
{
typedef HashNode Node;
typedef __HTIterator Self;
public:
Node* _node;
__HTIterator() {};
//编译器的原则是向上查找(定义必须在前面,否则必须先声明)
HashTable* _pht;
__HTIterator(Node* node, HashTable* 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->_tables.size();
hashi++;
//找下一个不为空的桶 -- 访问到了哈希表中私有的成员(友元)
for (; hashi < _pht->_tables.size(); hashi++)
{
if (_pht->_tables[hashi])
{
_node = _pht->_tables[hashi];
break;
}
}
//没有找到不为空的桶,用nullptr去做end标识
if (hashi == _pht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
//K --> 键值Key,T --> 数据
//unordered_map ->HashTable, MapKeyOfT> _ht;
//unordered_set ->HashTable _ht;
template
class HashTable
{
template
friend class __HTIterator;
typedef HashNode Node;
public:
typedef __HTIterator iterator;
iterator begin()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return iterator(cur, this);
}
}
return end();
}
iterator end()
{
return iterator(nullptr, this);
}
~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;
}
}
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
static const size_t primeList[PRIMECOUNT] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
//获取比prime大那一个素数
size_t i = 0;
for (i = 0; i < PRIMECOUNT; i++)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
pair Insert(const T& data)
{
HashFunc hf;
KeyOfT kot;
iterator pos = Find(kot(data));
if (pos != end())
{
return make_pair(pos, false);
}
//负载因子 == 1 扩容 -- 平均每个桶挂一个结点
if (_tables.size() == _n)
{
//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
size_t newSize = GetNextPrime(_tables.size());
if (newSize != _tables.size())
{
vector newTable;
newTable.resize(newSize, nullptr);
//遍历旧表
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
//再对每个桶挨个遍历
while (cur)
{
Node* next = cur->_next;
size_t hashi = hf(kot(cur->_data)) % newSize;
//转移到新的表中
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
//将原表置空
_tables[i] = nullptr;
}
newTable.swap(_tables);
}
}
size_t hashi = hf(kot(data));
hashi %= _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 iterator(nullptr, this);
}
KeyOfT kot;
HashFunc hf;
size_t hashi = hf(key);
//size_t hashi = HashFunc()(key);
hashi %= _tables.size();
Node* cur = _tables[hashi];
//找到指定的桶之后,顺着单链表挨个找
while (cur)
{
if (kot(cur->_data) == key)
{
return iterator(cur, this);
}
cur = cur->_next;
}
//没找到返回空
return iterator(nullptr, this);
}
bool Erase(const K& key)
{
if (_tables.size() == 0)
{
return false;
}
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key);
hashi %= _tables.size();
//单链表删除结点
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
//头删
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
//指针数组
vector _tables;
size_t _n = 0;
};
}
有了上面的哈希桶的改装,我们这里的对map和set的封装就显得很得心应手了。
unordered_map的封装:
#include "HashTable.h"
namespace zc
{
template>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair& kv)
{
return kv.first;
}
};
public:
typedef typename Bucket::HashTable, MapKeyOfT, HashFunc>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair insert(const pair& kv)
{
return _ht.Insert(kv);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
V& operator[](const K& key)
{
pair ret = insert(make_pair(key, V()));
return ret.first->second;
}
private:
Bucket::HashTable, MapKeyOfT, HashFunc> _ht;
};
void test_map()
{
unordered_map dict;
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("left", "左边"));
dict.insert(make_pair("left", "下面"));
dict["string"];
dict["left"] = "上面";
dict["string"] = "字符串";
unordered_map::iterator it = dict.begin();
while (it != dict.end())
{
cout << it->first << " " << it->second << endl;
++it;
}
cout << endl;
for (auto e : dict)
{
cout << e.first << " " << e.second << endl;
}
}
}
这里unordered_map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。
对unordered_set的封装:
#include "HashTable.h"
#include "HashTable.h"
namespace zc
{
template>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
//2.48
typedef typename Bucket::HashTable::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair insert(const K& key)
{
return _ht.Insert(key);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
private:
Bucket::HashTable _ht;
};
struct Date
{
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
bool operator==(const Date& d) const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
int _year;
int _month;
int _day;
};
struct DateHash
{
size_t operator()(const Date& d)
{
//return d._year + d._month + d._day;
size_t hash = 0;
hash += d._year;
hash *= 131;
hash += d._month;
hash *= 1313;
hash += d._day;
//cout << hash << endl;
return hash;
}
};
void test_set()
{
unordered_set s;
//set s;
s.insert(2);
s.insert(3);
s.insert(1);
s.insert(2);
s.insert(5);
s.insert(12);
unordered_set::iterator it = s.begin();
//auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
unordered_set sd;
sd.insert(Date(2022, 3, 4));
sd.insert(Date(2022, 4, 3));
}
}
最后大家可以利用代码中给的测试函数进行测试!
感谢你的阅读!