目录
一,前言
二,封装层框架(哈希底层以哈希桶为例)
三,迭代器
1. operator++
2. operator[]
3. 仿函数优化
3. 解决unordered_set中Key可以修改的Bug
代码区
Hash_map_set.h
HashTable.h
下节预告:哈希应用!!
结语
一,前言
在学习封装unordered_map与unordered_set前,建议先学习如何封装map & set该篇文章用红黑树封装map&set【C++】-CSDN博客
这样更能理解其中的封装思想(两种封装方式,主题思路大体相似)
同时用哈希表封装 unordered_map和undordered_set,在封装之前,我们首先是要学会哈希表基本的精华,哈希实现建议大家先学习从底层认识哈希表【C++】-CSDN博客
二,封装层框架(哈希底层以哈希桶为例)
由于我们前面已经学习过map与set的封装,在学习unordered_map(set) 封装这里我们直接给出基本的框架代码啦!
本质上:还是对哈希表进行封装,在unordered_map层面调用哈希底层。
用哈希表封装 unordered_map & unordered_set,思想核心:就是让unordered_set如何适应unordered_map,看到这里的都是已经学习过红黑树封装map与set的童鞋了吧
namespace hash_map_set
{
template <class K>
class unordered_set
{
public:
struct SetkeyofT // 从T类型中提取Key
{
const K& operator()(const K& key)
{
return key;
}
};
bool insert(const K& data)
{
return table.insert(data);
}
private:
hash_barrel::HashTable<K, K, SetkeyofT> table;
};
template <class K, class V>
class unordered_map
{
public:
struct MapkeyofT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
bool insert(const pair<const K, V>& kv)
{
return table.insert(kv);
}
private:
hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT> table;
};
}
// 哈希表底层部分变化,为了避免冗余,就只展现部分代码
template <class T>
struct Node_Data
{
typedef Node_Data<T> Node_data;
T _data;
Node_data* _downstars = nullptr;
Node_Data(const T& pa = T())
:_data(pa)
{}
};
template <class K, class T, class KeyofT ,class Hashstr = Hashstr<K>>
class HashTable
{
public:
typedef Node_Data<T> Node_Data;
.......
三,迭代器
首先我们先完成 迭代器基本框架 和 基本“ * ”, “ & ”,“ ->”, " != "实现,代码其实比较简单,重要的是逻辑如何行云流水的形成,多写多看多思考吧!!
template <class T>
struct Node_Data
{
typedef Node_Data<T> Node_data;
T _data;
Node_data* _downstars = nullptr;
Node_Data(const T& pa = T())
:_data(pa)
{}
};
template <class type>
struct Hashstr
{
int operator()(const type& sd)
{
return sd;
}
};
template<>
struct Hashstr<string>
{
size_t operator()(const string& str)
{
size_t sum = 0;
for (auto e : str)
{
sum += e;
sum *= 31; // 别问,问就是实验的结果
}
return sum;
}
};
// 由于迭代器需要使用HashTable作为成员变量,因此需要前置声明
template <class K, class T, class KeyofT, class Hashstr = Hashstr<K>>
class HashTable;
template <class K, class T, class KeyofT, class Ref, class Ptr, class Hash>
struct HT_iterator
{
typedef Node_Data<T> Node_Data;
typedef HashTable<K, T, KeyofT, Hash> HashTable;
typedef HT_iterator<K, T, KeyofT, Ref, Ptr, Hash> HT_iter; // 正常迭代器
Node_Data* _node; // 当前位置
HashTable* _ht; // 当前哈希表
size_t _bucket = -1; // 当前桶
HT_iterator(Node_Data* node, HashTable* ht)
:_node(node)
, _ht(ht)
{
Hash hashstr;
KeyofT keyofT;
if (node) // node为空的时候,我们会对nullptr进行访问,所以规避一下
_bucket = hashstr(keyofT(_node->_data)) % _ht->Get_tables().size();
}
// HT_iterator(const HT_iterator)
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &(_node->_data);
}
bool operator!=(const HT_iter& it)
{
return _node != it._node;
}
};
1. operator++
思路:从第一个桶开始访问数据,访问完一个数据后,接着下一个桶,直至结束。
思路比较简单,但迭代器设计,逻辑比较难处理。
// 哈希迭代器只往前单向
HT_iter operator++()
{
// 三种情况:
// 1. 下一个为空,下一个桶存在
// 2. 下一个为空,下一个不存在
// 3. 下一个存在
if (_node->_downstars)
{ // 3.
_node = _node->_downstars;
return *this;
}
else
{ // 1 , 2
寻找下一个不为空的桶
++_bucket;
while (_bucket < _ht->Get_tables().size())
{
_node = (_ht->Get_tables())[_bucket];
if (_node) // 找到后中断寻找
break;
++_bucket;
}
return *this;
}
}
2. operator[]
在实现operator[]时,我们需要先将insert,find函数进行优化:
让insert返回值从bool ——> 返回pair<iterator, bool>;
find()从返回数据指针——> 返回迭代器;
// Hashtable——哈希类中
T& operator[](const T& pa)
{
pair<HT_iterator, bool> it = insert(pa);
return *(it.first);
}
// 封装层 unordered_map
V& operator[](const K& pa)
{
pair<HT_iterator, bool> ret = insert(make_pair(pa, V()));
return ret.first->second;
}
// unordered_set
K& operator[](const K& pa)
{
return table[pa];
}
3. 仿函数优化
在哈希实现过程中,我们实现了该仿函数,目的就是为了解决当 K不是int的时候(比如string类)无法转化为size_t类型,来寻找哈希地址。
那么问题来了!! 我如果想将Day类型(处理年,月,日的类)作为K呢?? 请问这怎么将这个日期类转化为size_t???
为了方便分析,我将这个将其他数据转化为size_t的类叫做转化类
我们会发现,封装在底层的中转化类(int与string类)在目前看来,应该将其放在unordered_map与unordered_set的封装层;同时在两个封装层添加一个新的参数,作为转化类的行参。
这样做的好处是,在我们不知道K类型时,用户可以导入其自己设定的转化类,来实现各种类(各种类型)转化为size_t类型。
3. 解决unordered_set中Key可以修改的Bug
在前面的文章中(用红黑树封装map&set【C++】-CSDN博客),我们已经修复过一次了,这里就只展现修改位置了。
代码区
Hash_map_set.h
namespace hash_map_set
{
template <class type>
struct Hashstr
{
int operator()(const type& sd)
{
return sd;
}
};
template<>
struct Hashstr<string>
{
size_t operator()(const string& str)
{
size_t sum = 0;
for (auto e : str)
{
sum += e;
sum *= 31; // sum为啥都要乘31这个数??1.减少数据聚集在一个桶中。2.31这个数是大量实验的最佳数
}
return sum;
}
};
template <class K, class Hash = Hashstr<K>>
class unordered_set
{
public:
struct SetkeyofT // 从T类型中提取Key
{
const K& operator()(const K& key)
{
return key;
}
};
// 关于为什么要这样操作?原因:底层(HT_iterator)实例化后,再取得模板,而不是自己设定
typedef typename hash_barrel::HashTable<K, K, SetkeyofT, Hash>::const_HT_iterator HT_iterator;
typedef typename hash_barrel::HashTable<K, K, SetkeyofT, Hash>::const_HT_iterator const_HT_iterator;
const_HT_iterator begin() const
{
return table.begin();
}
const_HT_iterator end() const
{
return table.end();
}
HT_iterator begin()
{
return table.begin();
}
HT_iterator end()
{
return table.end();
}
pair<HT_iterator, bool> insert(const K& data)
{
return table.insert(data);
}
K& operator[](const K& pa)
{
return table[pa];
}
HT_iterator find(const K& pa)
{
return table.find(pa);
}
bool erase(const K& pa)
{
return table.erase(pa);
}
private:
hash_barrel::HashTable<K, K, SetkeyofT, Hash> table;
};
template <class K, class V, class Hash = Hashstr<K>>
class unordered_map
{
public:
struct MapkeyofT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
typedef typename hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>::HT_iterator HT_iterator;
typedef typename hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>::const_HT_iterator const_HT_iterator;
const_HT_iterator begin() const
{
return table.begin();
}
const_HT_iterator end() const
{
return table.end();
}
HT_iterator begin()
{
return table.begin();
}
HT_iterator end()
{
return table.end();
}
pair<HT_iterator, bool> insert(const pair<const K, V>& kv)
{
return table.insert(kv);
}
V& operator[](const K& pa)
{
pair<HT_iterator, bool> ret = insert(make_pair(pa, V()));
return ret.first->second;
}
HT_iterator find(const K& pa)
{
return table.find(pa);
}
bool erase(const K& pa)
{
return table.erase(pa);
}
private:
hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash> table;
};
}
HashTable.h
namespace hash_barrel
{
template <class T>
struct Node_Data
{
typedef Node_Data<T> Node_data;
T _data;
Node_data* _downstars = nullptr;
Node_Data(const T& pa = T())
:_data(pa)
{}
};
// 由于迭代器需要使用HashTable作为成员变量,因此需要前置声明
template <class K, class T, class KeyofT, class Hashstr>
class HashTable;
template <class K, class T, class KeyofT, class Ref, class Ptr, class Hash>
struct HT_iterator
{
typedef Node_Data<T> Node_Data;
typedef HashTable<K, T, KeyofT, Hash> HashTable;
typedef HT_iterator<K, T, KeyofT, Ref, Ptr, Hash> HT_iter; // 正常迭代器
typedef HT_iterator<K, T, KeyofT, T&, T*, Hash> iterator;
Node_Data* _node; // 当前位置
const HashTable* _ht; // 当前哈希表,添加const原因:1.迭代器不会修改哈希表 2.const迭代器传入的是const的哈希表,所以需要适配
size_t _bucket = -1; // 当前桶
HT_iterator(Node_Data* node, const HashTable* ht)
:_node(node)
, _ht(ht)
{
Hash hashstr;
KeyofT keyofT;
if (node) // node为空的时候,我们会对nullptr进行访问,所以规避一下
_bucket = hashstr(keyofT(_node->_data)) % _ht->Get_tables().size();
}
// 1. 当对象是普通迭代器时,这是拷贝构造
// 2. 当对象是const迭代器时,是利用普通迭代器构造const迭代器的构造函数
HT_iterator(const iterator& it)
:_node(it._node)
,_ht(it._ht)
,_bucket(it._bucket)
{}
// HT_iterator(const HT_iterator)
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &(_node->_data);
}
bool operator!=(const HT_iter& it)
{
return _node != it._node;
}
// 哈希迭代器只往前单向
HT_iter operator++()
{
// 三种情况:
// 1. 下一个为空,下一个桶存在
// 2. 下一个为空,下一个不存在
// 3. 下一个存在
if (_node->_downstars)
{ // 3.
_node = _node->_downstars;
return *this;
}
else
{ // 1 , 2
寻找下一个不为空的桶
++_bucket;
while (_bucket < _ht->Get_tables().size())
{
_node = (_ht->Get_tables())[_bucket];
if (_node)
break;
++_bucket;
}
return *this;
}
}
};
template <class K, class T, class KeyofT ,class Hash>
class HashTable
{
public:
typedef Node_Data<T> Node_Data;
typedef HT_iterator<K, T, KeyofT, const T&, const T*, Hash> const_HT_iterator; // const迭代器
typedef HT_iterator<K, T, KeyofT, T&, T*, Hash> HT_iterator; // 普通迭代器
~HashTable()
{
for (auto bucket : _tables)
{
Node_Data* cur = bucket;
while (cur)
{
Node_Data* next = cur->_downstars;
delete (cur);
cur = next;
}
}
}
const_HT_iterator begin() const
{
size_t i = 0;
Node_Data* cur = nullptr;
for (; i < _tables.size(); i++)
{
cur = _tables[i];
if (cur)
break;
}
return const_HT_iterator(cur, this); // this就是哈希表对象
}
const_HT_iterator end() const
{
return const_HT_iterator(nullptr, this);
}
HT_iterator begin()
{
size_t i = 0;
Node_Data* cur = nullptr;
for (; i < _tables.size(); i++)
{
cur = _tables[i];
if (cur)
break;
}
return HT_iterator(cur, this); // this就是哈希表对象
}
HT_iterator end()
{
return HT_iterator(nullptr, this);
}
// 在实现 operator[]时,底层用到insert(),没找到,插入新值返回迭代器;找到直接返回迭代器
pair<HT_iterator,bool> insert(const T& pa)
{
KeyofT keyofT;
HT_iterator ret = _find(keyofT(pa));
if (ret != end())
return make_pair(ret, true);
// 考虑扩容:负载因子为1,2,3都可以
Hash hashstr;
if (_tables.size() == 0 || _n * 10 / _tables.size() >= 10)
{
size_t new_size = _tables.size() == 0 ? 10 : _tables.size() * 2;
// 开始扩容
vector<Node_Data*> new_tables;
new_tables.resize(new_size);
for (auto& data : _tables)
{
// 处理桶内的数据,重新插入新节点
Node_Data* cur = data;
while (cur)
{
Node_Data* room = cur->_downstars;
size_t new_hashi = hashstr(keyofT(cur->_data)) % new_tables.size();
// 处理结点关系
Node_Data* tmp = new_tables[new_hashi];
new_tables[new_hashi] = cur;
cur->_downstars = tmp;
cur = room;
}
}
_tables.swap(new_tables);
}
size_t hashi = hashstr(keyofT(pa)) % _tables.size();
Node_Data* new_node = new Node_Data(pa);
new_node->_downstars = _tables[hashi];
_tables[hashi] = new_node;
_n++;
return make_pair(HT_iterator(new_node, this), true);
}
HT_iterator find(const K& order)
{
return _find(order);
}
bool erase(const K& order)
{
Hash hashstr;
HT_iterator cur = find(order);
if (!(cur._node))
{
cout << "擦除失败: 不存在" << endl;
return false;
}
size_t index = hashstr(order) % _tables.size();
Node_Data* tmp = _tables[index];
while (tmp)
{
if (cur._node == tmp)
{
break;
}
else if (cur._node == tmp->_downstars)
{
break;
}
tmp = tmp->_downstars;
}
// 开始处理节点
if (tmp == _tables[index]) // 如果擦除的是头,那要置空的包括指针数组
{
_tables[index] = cur._node->_downstars;
}
else
{
tmp->_downstars = cur._node->_downstars;
}
delete (cur._node);
cout << "擦除成功" << endl;
return true;
}
T& operator[](const T& pa)
{
pair<HT_iterator, bool> it = insert(pa);
return *(it.first);
}
size_t Get_n()
{return _n; }
vector<Node_Data*>& Get_tables()
{
return _tables;
}
// 需要为const 对象进行重载
const vector<Node_Data*>& Get_tables() const
{
return _tables;
}
private:
// Node_Data* _find(const K& order)
HT_iterator _find(const K& order)
{
if (!_tables.size())
return end();
Hash hashstr;
KeyofT keyofT;
size_t hashi = hashstr(order) % _tables.size();
auto cur = _tables[hashi];
while (cur)
{
if (keyofT(cur->_data) == order)
return HT_iterator(cur, this);
cur = cur->_downstars;
}
return end();
}
vector<Node_Data*> _tables; // 存放各个哈希地址的第一个结点地址的指针数组
size_t _n = 0; // 哈希桶中,数据个数
};
}
下节预告:哈希应用!!
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。