🪐🪐🪐欢迎来到程序员餐厅💫💫💫
主厨:邪王真眼
主厨的主页:Chef‘s blog
所属专栏:c++大冒险
总有光环在陨落,总有新星在闪烁
前言:
之前我们学完红黑树后对他进行了改造,使之成为map和set的底层容器,今天我们则要把哈希表进行修改并以此为基础实现unordered_set和unordered_map
一.哈希桶(修改版)
1.1.节点
template<class T>
struct HashNode
{
HashNode<T> _next;
T _data;
HashNode(const T& data = T())
:_next(nullptr)
, _data(data)
{}
};
注意事项:
- 数据类型是T,因为要同时适用unordered_set(存储键值)和unordered_map(存储键值对)
类比咱们用红黑树写map和set时的做法。
1.2.迭代器
哈希表的迭代器类型是单向迭代器,没有反向迭代器
1.2.1成员变量
template<class K, class T, class KeyOfT, class HF>
class HashTable;
template<class K,class T, class Ref,class Ptr,class KeyOfT, class HF>
struct HashIterator
{
typedef HashNode<T> Node;
typedef HashIterator<K, T, Ref, Ptr, KeyOfT, HF> self;
typedef HashIterator<K, T, T&, T*, KeyOfT, HF> iterator;
typedef HashTable<K, T, KeyOfT, HF> HT;
Node* _node;
HT* _ht;
};
注意事项:
- 增加了_ht成员变量,因为在重载++时,当一条单链表走到空,则需要走到下一个哈希桶的位置,需要通过哈希表的vector成员找下一个位置
- 因为HashTable是在后面实现的,所以我们要先写一个声明
1.2.2简单函数实现
HashIterator(Node*&node=nullptr,Node*&ht=nullptr)
:_node(node)
,_ht(ht)
{}
HashIterator(const iterator&it)
:_node(it._node)
, _ht(it._ht)
{}
Ref& operator*()const
{
return _node->_data;
}
Ptr& operator->()const
{
return &(_node->_data);
}
bool operator==(const self&se)
{
return _node == se._node;
}
bool operator!=(const self& se)
{
return _node != se._node;
}
注意事项:
- 拷贝构造函数可以以普通迭代器拷贝出普通迭代器(普通迭代器调用时)以及const迭代器(const迭代器调用时)
1.2.3operator++
self& operator++()
{
Node* node = _node;
if (node->_next)
{
_node = node->_next;
return *this;
}
else
{
KeyOfT kot;
size_t hash = HF(kot(_node->_data)) % _ht->_tables.size()+1;
for (hash; hash < _ht->_tables.size(); hash++)
{
if (_ht->tables[hash])
{
_node = _ht->tables[hash];
return *this;
}
}
_node = nullptr;
return *this;
}
}
self operator++(int)
{
self new_self=*this;
(*this)++;
return new_self;
}
思路:
- 前置++的思路:
- 下一个结点不为空,则跳到下一位
- 下一个结点为空,则先取模算出哈希地址,再往后探测不为空的哈希桶
- 后置++:复用前置++,返回临时对象
1.3哈希桶本身
1.3.1成员变量
template<class K, class T, class KeyOfT, class HF>
class HashTable
{
template<class K, class T, class Ref, class Ptr, class KeyOfT, class HF>
friend struct HashIterator;
typedef HashNode<T> Node;
public:
protected:
vector<Node*> _tables;
size_t _size = 0;//有效数据个数
};
注意事项:
- 迭代器要访问哈希桶的私有成员,所以要声明友元
- 模板参数KeyOfT是为了从类型T中取出键值
- 模板参数HF即是HashFunc,哈希函数,用于辅助键值转化为整型进行取模操作
1.3.2构造函数和析构函数
HashTable(size_t size = 10)
{
_tables.resize(size);
}
~HashTable()
{
for (auto hash_node : _tables)
{
while (hash_node)
{
Node* new_node = hash_node->_next;
delete hash_node;
hash_node = new_node;
}
}
}
1.3.3 begin和end
typedef HashIterator< K,T, T&, T*, KeyOfT, HF> iterator;
typedef HashIterator< K, T, const T&, const T*, KeyOfT, HF> const_iterator;
iterator begin()
{
for (size_t i = 0; i < _tables.size(); i++)
if (_tables[i])
return iterator(_tables[i], this);
return iterator(nullptr, this);
}
const_iterator begin()const
{
for (size_t i = 0; i < _tables.size(); i++)
if (_tables[i])
return const_iterator(_tables[i], this);
return const_iterator(nullptr, this);
}
iterator end()
{
return iterator(nullptr, this);
}
const_iterator end()
{
return const_iterator(nullptr, this);
}
注意事项:
- begin返回最开始不为空的哈希桶的迭代器,而不是最开始的哈希桶的迭代器
- end返回空迭代器
- 构造迭代器需要传入哈希表本身的地址,这里直接传this指针即可
1.3.4Find函数
iterator Find(const K&key)
{
HF hf;
KeyOfT kot;
size_t hash = hf(key) % _tables.size();
Node* cur = _tables[hash];
while (cur)
{
if (kot(cur->_data) == key)
return iterator(cur,_tables);
cur = cur->_next;
}
return iterator(nullptr,_tables);
}
注意事项:
- 返回值类型是迭代器
- 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型
1.3.5 Insert函数
pair<iterator,bool> Insert(T& data)
{
KeyOfT kot;
HF hf;
iterator it = Find(kot(data));
if (it._node)
{
return make_pair(it, false);
}
if (_size == _tables.size())
{
vector<Node*> new_tables(_size * 2);
for (auto node : _tables)
{
while (node)
{
Node* next = node->_next;
size_t hash = hf(kot(node->_data)) % new_tables.size();
node->_next = new_tables[hash];
new_tables[hash] = node;
node = next;
}
}
_tables.swap(new_tables);
}
size_t hash = hf(kot(data)) % _tables.size();
Node* cur = _tables[hash];
Node* p(data);
p->_next = cur;
_tables[hash] = p;
_size++;
return make_pair(iterator(p,this),true);
}
注意事项:
- 返回值类型是pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
- 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型
1.3.6Erase函数
bool Erase(const K& key)
{
HF hf;
KeyOfT kot;
size_t hash = hf(key) % _tables.size();
Node* cur = _tables[hash];
Node* pre = nullptr;
while (cur)
{
if (kot(cur->data) == key)
break;
pre = cur;
cur = cur->_next;
}
if (cur == nullptr)
return false;
_size--;
if (pre == nullptr)
_tables[hash] = cur->_next;
else
pre->_next = cur->_next;
delete cur;
return true;
}
二、unordered_set
2.1成员变量及仿函数
template<class K,class HF=HashFunc<K>>
class unordered_set
{
public:
struct SetKeyOfT
{
K& operator()(const K& key)
{
return key;
}
};
protected:
HashTable<K, K, SetKeyOfT, HF> _hf;
};
注意事项:
- 1.这里的数据存储类型就是键值Key本身,所以SetKeyOfT直接返回key就行
- 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中
2.2 begin和end
typedef typename HashTable<K, K, SetKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT, HF>::const_iterator const_iterator;
iterator begin()
{
return _ht.begin();
}
const_iterator begin()const
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
const_iterator end()const
{
return _ht.end();
}
注意事项:
- 在C++中,编译器默认iterator为变量名,如果作为类型名,需要在前面加上typename加上typename关键字。
- unordered_set中存储的键值key不允许修改,所以其普通迭代器和const迭代器均为哈希表的const迭代器
- 我们称set的begin为A,哈希的begin为B,A的实现是对B的调用,在A的参数是普通迭代器时,B也是普通迭代器,B的返回值也是普通迭代器,但A的返回值是const迭代器,所以这里需要用普通迭代器创建一个const迭代器的临时变量,这就用到之前写的拷贝构造函数了。
2.3Find
iterator Find(const K&key)
{
return _ht.Find(key);
}
注意事项:
- 此时也有从普通迭代器到const迭代器的转换
2.4Insert
pair<iterator, bool> Insert(const K& key)
{
return _ht.Insert(key);
}
注意事项:
- 函数形参类型是K
- 此时也有从普通迭代器到const迭代器的转换
2.5Erase
bool Erase(const K& key)
{
return _ht.Erase(key);
}
三、unordered_map
unordered_map和unordered_set的实现有众多相似之处,
3.1 成员变量与仿函数
template<class K,class V,class HF=HashFunc<K>>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>&kv)
{
return kv.first;
}
};
protected:
HashTable<K, pair<const K, V>, MapKeyOfT, HF> _ht;
};
注意事项:
- 1.这里节点存的数据类型是pair<K,V>,我们的MapKeyOfT作用是返回其中的键值key
- 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中
3.2 begin和end
typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::const_iterator const_iterator;
iterator begin()
{
return _ht.begin();
}
const_iterator begin()const
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
const_iterator end()const
{
return _ht.end();
}
注意事项:
- 加上typename关键字,编译器才能识别类型
- unordered_map不允许修改key,故普通迭代器和const的pair中的K都要加上const修饰,但是允许修改存储的data,所以普通和const迭代器一一对应(好好想想这点map和set的处理差异)
3.3 find
iterator Find(const K& key)
{
return _ht.Find(key);
}
3.4Insert
pair<iterator, bool> Insert(const pair<const K, V>& kv)
{
return _ht.Insert(kv);
}
注意事项:
- 形参类型是键值对而不是键值
3.5 operator[ ]
V& operator[](const& K key)
{
return (*(_ht.Insert(make_pair(key, V()))).first).second;
}
//或者你也可以这么写
V& operator[](const K& key)
{
pair<iterator, bool> cur = insert(make_pair(key, V()));
return cur.first->second;
}
注意事项:
- 利用operator[]进行插入和修改操作是很方便的,所以要学会灵活运用哦
- 返回值是value的引用,我们可以直接进行修改
3.6 Erase
bool Erase(const K& key)
{
return _ht.Erase(key);
}
如果你对该系列文章有兴趣的话不妨点个关注哦,我会持续更新相关博客的
🥰创作不易,你的支持对我最大的鼓励🥰
🪐~ 点赞收藏+关注 ~🪐