🌠专栏:C/C++
目录
五、 Insert() / Find() / Erase() 的改造
六、unordered_map 的 operator[ ] 的实现
本篇文章,重点是:unordered_map 和 unordered_set 模拟实现是如何进行封装的,封装的细节需要注意哪些,注意看仔细哦~
一、哈希表开散列的实现
1、要实现构造、析构(有资源需要清理)、插入(如何扩容)、查找、删除(注意桶里面数据的前后链接关系);
2、要解决存入数据为 字符串 如何取模的问题,这里采用的是模板特化;
想了解更多的细节,可以查看上一章的内容哦~
// HashTable.h
template<class K>
struct HashFunc//当是浮点数、有符号的负数、指针,此时取模都能完美的解决
{
size_t operator()(const K& key)
{
return (size_t)key;//强制转换
}
};
//模板特化(要有原模版才能特化)
template<>
struct HashFunc<string>//当是字符串时,返回数字能解决,取模的问题
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto e : s)
{
hash *= 31;//降低ASCII相加后的重复率
hash += e;//ASCII码相加
}
return hash;
}
};
namespace hash_bucket//开散列
{
template<class K,class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode(const pair<K,V>& kv)
:_kv(kv)
,_next(nullptr)
{}
};
template<class K,class V, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
//构造
HashTable()
{
_tables.resize(10, nullptr);
}
//要写析构,vector释放的时候,不会释放桶里面(挂在下面的链表)的数据(内置类型),指针不会调用析构函数
~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;
}
}
bool Insert(const pair<K, V>& kv)
{
Hash hs;
size_t hashi = hs(kv.first) % _tables.size();//取模算出hashi对应的位置出来
//控制负载因子
//负载因子==1 扩容
//负载因子越低,空间效率越低,时间效率越高
//负载因子越高,空间效率越高,时间效率越低
if (_n == _tables.size())//负载因子到1,进行扩容
{
//直接挪动结点
vector<Node*> newtables(_tables.size() * 2, nullptr);
for (size_t i = 0; i < _tables.size(); i++)//遍历原来的旧表
{
Node* cur = _tables[i];
while (cur)//遍历桶
{
//提前保存下一个结点,不然下次找不到
Node* next = cur->_next;
//直接把旧表中的结点,挪到新表重新映射的位置
size_t hashi = hs(cur->_kv.first) % newtables.size();//在新表找到对应的位置
//头插到新表
cur->_next = newtables[hashi];//我先指向桶的第一个
newtables[hashi] = cur;//把桶的第一个给我
cur = next;
}
_tables[i] = nullptr;//桶的最后一个指向空
}
_tables.swap(newtables);//交换表
}
//头插
Node* newnode = new Node(kv);
//找到位置,进行头插
newnode->_next = _tables[hashi];//(第一个的地址在表里面)表里面指向桶里的第一个
_tables[hashi] = newnode;//把桶里面的第一个给newnode(在桶里面头插)
++_n;
return true;
}
Node* Find(const K& key)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();//先算在表里面的位置
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
size_t hashi = key % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
//有这个结点,进行删除,分情况进行讨论
if (prev == nullptr)
{
//没有前一个,直接指向下一个
_tables[hashi] = cur->_next;
}
else
{
//有前一个,就让前一个的next指向我的后一个
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
//vector<list<pair<K, V>>> _tables;//指针数组
vector<Node*> _tables;//本质是一个指针数组
size_t _n = 0;//表中存储的数据个数数据
};
二、解决 KeyOfT 问题
我们使用模板来传递类型:
当传入的是 K,T 就为 K;
当传入的是 pair<K,V> ,T 就为键值对。
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
,_next(nullptr)
{}
};
KeyOfT 是什么? unordered_map和unordered_set的底层是哈希表,实际就是对哈希表进行封装,我们这里实现的 unordered_map和unordered_set 套用的是同一个哈希表,在实现 Find() 和 Erasr() 、Insert() 函数时,我们比较的是 Key(关键码) ,unordered_set里面存储的是唯一的Key,unordered_map里面存储的是Key和映射值,为了实现统一,我们需要获取 unordered_map里面的Key值,为此我们需要写一个获取Key的类:
// unordered_Set.h
namespace xlf
{
template<class K, class Hash = HashFunc<K>>
class UnorderdeSet
{
struct SetKeyOfT
{
const K& operator()(const K& key)// 仿函数 为取 pair 的 first
{
return key;
}
};
public:
private:
hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
};
}
// unordered_Map.h
namespace xlf
{
template<class K, class V, class Hash=HashFunc<K>>
class UnorderdeMap
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)// 仿函数 取 pair 的 first
{
return kv.first;
}
};
private:
hash_bucket::HashTable<K, pair<K,V>, MapKeyOfT, Hash> _ht;
};
}
在实现 Find() 和 Erasr() 、Insert() 函数时,只要是需要获取键值对的key值的位置,都需要对其做出修改:
KeyOfT kot;
Hash hs;
size_t hashi = hs(kot(data.first)) % _tables.size();
三、iterator 模拟实现
1、先在 底层(HashTable)里实现迭代器:
• 注意要前置声明,防止使用HashTable时,找不到;
• 迭代器里面要存 节点 和 哈希表对象的指针;
• Ptr 为 指针,Ref 为 &;
• 注意operator++ 的实现细节。
// HashTable.h
//前置声明(迭代器需要使用HashTable,但是哈希表在下面定义,编译器编译的时候只会往上面取查找)
template<class K, class T, class KeyOfT, class Hash>
class HashTable;
template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
struct HTIterator
{
typedef HashNode<T> Node;
typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;
Node* _node;
//const的作用: 防止const迭代器传参时,权限放大,从而导致编译不通过,普通迭代器不受影响(权限可以缩小)
const HashTable<K, T, KeyOfT, Hash>* _pht;//把哈希表对象的指针传过来
//const的作用: 防止const迭代器传参时,权限放大,从而导致编译不通过,普通迭代器不受影响(权限可以缩小)
HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht)
:_node(node)
,_pht(pht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_next)//桶不为空,一直访问桶里面的结点
{
//当前桶还有结点
_node = _node->_next;
}
else//桶为空
{
//当前桶走完了,找下一个不为空的桶
KeyOfT kot;
Hash hs;
size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();
++hashi;
while (hashi < _pht->_tables.size())
{
if (_pht->_tables[hashi])//这个桶不为空,就一直往下一个数据走
{
break;
}
++hashi;
}
if (hashi == _pht->_tables.size())//每个桶都走完了
{
_node = nullptr;//end()
}
else
{
//桶没走完
_node = _pht->_tables[hashi];//取桶里面的数据
}
}
return *this;
}
};
2、在哈希表里实现具体的 Begin() 和 End() ;
// HashTable.h
typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;
Iterator Begin()
{
if (_n == 0)
return End();
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);
}
3、先把哈希表的迭代器引入到unordered_set 和 unordered_map 里面,接着分别在 unordered_set 和 unordered_map 里面对哈希表里面的迭代器进行封装(“套壳”)。
// unordered_Set.h
typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
// unordered_Map.h
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin() const
{
return _ht.Begin();
}
四、const_iterator 模拟实现
1、在哈希表里面加上 const_Iterator;
typedef HTIterator<K, T, const T*, const T&, KeyOfT, Hash> ConstIterator;
ConstIterator Begin() const
{
if (_n == 0)
return End();
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return ConstIterator(cur, this);
}
}
return End();
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}
2、分别在unordered_set 和 unordered_map 里面,对哈希表里面的const迭代器进行封装(“套壳)
// unordered_Set.h
typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;
const_iterator begin() const
{
return _ht.Begin();
}
const_iterator end() const
{
return _ht.End();
}
// unordered_Map.h
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;
const_iterator begin() const
{
return _ht.Begin();
}
const_iterator end() const
{
return _ht.End();
}
五、 Insert() / Find() / Erase() 的改造
⭐Insert()
修改返回值:
bool --改为--> pair<Iterator,bool>;
false --改为--> make_pair(it,false);
true --改为--> make_pair(Iterator(newnode,this),true);
// HashTable.h
pair<Iterator,bool> Insert(const T& data)
{
//...
return make_pair(it,false);
//...
return make_pair(Iterator(newnode,this),true);
}
分别在unordered_Set 和 unordered_Map 里面实现 insert() :("套壳")
// unordered_Set.h
pair<iterator, bool> insert(const K& key)
{
return _ht.Insert(key);
}
// unordered_Map.h
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.Insert(kv);
}
⭐Find()
分别在unordered_Set 和 unordered_Map 里面实现 find() :("套壳")
// unordered_Set.h
iterator find(const K& key)
{
return _ht.Find(key);
}
// unordered_Map.h
iterator find(const K& key)
{
return _ht.Find(key);
}
⭐Erase()
分别在unordered_Set 和 unordered_Map 里面实现 erase() :("套壳")
// unordered_Set.h
iterator erase(const K& key)
{
return _ht.Erase(key);
}
// unordered_Map.h
iterator erase(const K& key)
{
return _ht.Erase(key);
}
六、unordered_map 的 operator[ ] 的实现
V& operator[](const K& key)
{
//如果这个值已经有了,它就会返回key对应所在的节点的迭代器
//没有就会先插入一个pair,pair的key为新插入的迭代器
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
八、完整代码
// HashTable.h
#pragma once
#include<iostream>
#include<vector>
#include<string>
using namespace std;
template<class K>
struct HashFunc//当是浮点数、有符号的负数、指针,此时取模都能完美的解决
{
size_t operator()(const K& key)
{
return (size_t)key;//强制转换
}
};
//模板特化(要有原模版才能特化)
template<>
struct HashFunc<string>//当是字符串时,返回数字能解决,取模的问题
{
size_t operator()(const string& key)
{
size_t hash = 0;
for (auto e : key)
{
hash *= 31;//降低ASCII相加后的重复率
hash += e;//ASCII码相加
}
return hash;
}
};
namespace hash_bucket
{
template<class T>
struct HashNode
{
T _data;// const K ,此时,data是const不能修改
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
//前置声明(迭代器需要使用HashTable,但是哈希表在下面定义,编译器编译的时候只会往上面取查找)
template<class K, class T, class KeyOfT, class Hash>
class HashTable;
template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
struct HTIterator
{
typedef HashNode<T> Node;
typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;
Node* _node;
//const的作用: 防止const迭代器传参时,权限放大,从而导致编译不通过,普通迭代器不受影响(权限可以缩小)
const HashTable<K, T, KeyOfT, Hash>* _pht;//把哈希表对象的指针传过来
//const的作用: 防止const迭代器传参时,权限放大,从而导致编译不通过,普通迭代器不受影响(权限可以缩小)
HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht)
:_node(node)
,_pht(pht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_next)//桶不为空,一直访问桶里面的结点
{
//当前桶还有结点
_node = _node->_next;
}
else//桶为空
{
//当前桶走完了,找下一个不为空的桶
KeyOfT kot;
Hash hs;
size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();
++hashi;
while (hashi < _pht->_tables.size())
{
if (_pht->_tables[hashi])//这个桶不为空,就一直往下一个数据走
{
break;
}
++hashi;
}
if (hashi == _pht->_tables.size())//每个桶都走完了
{
_node = nullptr;//end()
}
else
{
//桶没走完
_node = _pht->_tables[hashi];//取桶里面的数据
}
}
return *this;
}
};
// 1、实现哈希表
// 2、封装unordered_map和unordered_set 解决KeyOfT
// 3、iterator
// 4、const_iterator
// 5、修改Key的问题
// 6、operator[]
template<class K, class T, class KeyOfT, class Hash>
class HashTable
{
//模板参数的友元声明
template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>
friend struct HTIterator;
typedef HashNode<T> Node;
public:
typedef HTIterator<K, T, T*, T&, KeyOfT, Hash> Iterator;
typedef HTIterator<K, T, const T*, const T&, KeyOfT, Hash> ConstIterator;
Iterator Begin()
{
if (_n == 0)
return End();
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);
}
ConstIterator Begin() const
{
if (_n == 0)
return End();
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return ConstIterator(cur, this);
}
}
return End();
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}
//构造
HashTable()
{
_tables.resize(10, nullptr);
}
//要写析构,vector释放的时候,不会释放桶里面(挂在下面的链表)的数据(内置类型),指针不会调用析构函数
~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;
}
}
pair<Iterator,bool> Insert(const T& data)
{
KeyOfT kot;
Iterator it = Find(kot(data));
//不允许重复
if (it != End())
{
return make_pair(it,false);
}
Hash hs;
size_t hashi = hs(kot(data)) % _tables.size();//取模算出hashi对应的位置出来
//控制负载因子
//负载因子==1 扩容
//负载因子越低,空间效率越低,时间效率越高
//负载因子越高,空间效率越高,时间效率越低
if (_n == _tables.size())//负载因子到1,进行扩容
{
//新开结点
/*HashTable<K, V> newHT;
newHT._tables.resize(_tables.size() * 2);
for (size_t i = 0; i < _tables.size(); i++)//遍历原来的旧表
{
Node* cur = _tables[i];
while (cur)//遍历桶
{
newHT.Insert(cur->_kv);//把所以旧表的数据,插入到新表里面
cur = cur->_next;
}
}
_tables.swap(newHT._tables);//新表和旧表进行交换*/
//直接挪动结点
vector<Node*> newtables(_tables.size() * 2, nullptr);
for (size_t i = 0; i < _tables.size(); i++)//遍历原来的旧表
{
Node* cur = _tables[i];
while (cur)//遍历桶
{
//提前保存下一个结点,不然下次找不到
Node* next = cur->_next;
//直接把旧表中的结点,挪到新表重新映射的位置
size_t hashi = hs(kot(cur->_data)) % newtables.size();//在新表找到对应的位置
//头插到新表
cur->_next = newtables[hashi];//我先指向桶的第一个
newtables[hashi] = cur;//把桶的第一个给我
cur = next;
}
_tables[i] = nullptr;
}
_tables.swap(newtables);
}
//头插
Node* newnode = new Node(data);
//找到位置,进行头插
newnode->_next = _tables[hashi];//(第一个的地址在表里面)表里面指向桶里的第一个
_tables[hashi] = newnode;//把桶里面的第一个给newnode(在桶里面头插)
++_n;
return make_pair(Iterator(newnode,this),true);
}
//不允许冗余
Iterator Find(const K& key)
{
KeyOfT kot;
Hash hs;
size_t hashi = hs(key) % _tables.size();//先算在表里面的位置
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return Iterator(cur,this);
}
cur = cur->_next;
}
return End();
}
bool Erase(const K& key)
{
KeyOfT kot;
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
//有这个结点,进行删除,分情况进行讨论
if (prev == nullptr)
{
//没有前一个,直接指向下一个
_tables[hashi] = cur->_next;
}
else
{
//有前一个,就让前一个的next指向我的后一个
prev->_next = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
//vector<list<pair<K, V>>> _tables;//指针数组
vector<Node*> _tables;//本质是一个指针数组
size_t _n = 0;//表中存储的数据个数数据
};
}
// unordered_Set.h
#pragma once
#include"HashTable.h"
namespace xlf
{
template<class K, class Hash = HashFunc<K>>
class Unordered_Set
{
struct SetKeyOfT
{
const K& operator()(const K& key)// 仿函数 取 pair 的 first
{
return key;
}
};
public:
//const防止key被修改
typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;
typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin() const
{
return _ht.Begin();
}
const_iterator end() const
{
return _ht.End();
}
pair<iterator, bool> insert(const K& key)
{
return _ht.Insert(key);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
iterator erase(const K& key)
{
return _ht.Erase(key);
}
private:
hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
};
void Print(const Unordered_Set<int>& s)
{
Unordered_Set<int>::const_iterator it = s.begin();
while (it != s.end())
{
//*it += 1;//const迭代器不可以修改
cout << *it << " ";
++it;
}
cout << endl;
}
struct Date
{
int _year;
int _month;
int _day;
bool operator==(const Date& d) const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
};
struct HashDate
{
size_t operator()(const Date& key)
{
//减少重复(冲突)
//1月12日
//12月1日
return ((key._year * 31 + key._month) * 31 + key._day);
}
};
void test_set()
{
Unordered_Set<int> s;
int a[] = { 4,6,1,3,5,15,7,16,14,4,4,5 };
for (auto e : a)
{
s.insert(e);
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
Unordered_Set<int>::iterator it = s.begin();
while (it != s.end())
{
//*it += 1;//建议不修改key,会导致后面遍历的时候出现问题
cout << *it << " ";
++it;
}
cout << endl;
Unordered_Set<Date, HashDate> us;
us.insert({ 2024,10,11 });
us.insert({ 2024,10,12 });
Print(s);
}
}
// unordered_Map.h
#pragma once
#include"HashTable.h"
namespace xlf
{
template<class K, class V, class Hash = HashFunc<K>>
class Unordeer_Map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)// 仿函数 取 pair 的 first
{
return kv.first;
}
};
public:
//const防止key被修改
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin() const
{
return _ht.Begin();
}
const_iterator end() const
{
return _ht.End();
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.Insert(kv);
}
V& operator[](const K& key)
{
//如果这个值已经有了,它就会返回key对应所在的节点的迭代器
//没有就会先插入一个pair,pair的key为新插入的迭代器
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
iterator find(const K& key)
{
return _ht.Find(key);
}
iterator erase(const K& key)
{
return _ht.Erase(key);
}
private:
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
void test_map()
{
Unordeer_Map<string, string> dict;
dict.insert({ "sort","排序" });
dict.insert({ "left","左边" });
dict.insert({ "right","右边" });
dict["left"] = "左边,剩余";//修改
dict["insert"] = "插入";//插入+修改
dict["string"];//纯插入
Unordeer_Map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
//不能修改first,可以修改second
//it->first += 'x';
it->second += 'x';
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
}
}
如若对你有帮助,记得点赞、收藏、关注哦!
若有误,望各位,在评论区留言或者私信我 指点迷津!!!谢谢^ ^ ~