前提:
①:本博客是对哈希表(开散列)进行封装,因为闭散列不优秀(与库保持一致)
②:哈希表封装出unordered_set/_map和红黑树封装出ste/map是大同小异的,可以先看下:用红黑树封装出set和map -CSDN博客
③:本博客基于手撕哈希表-CSDN博客的基础上讲解,所以哈希表的起始代码如下:
#pragma once
#include<iostream>
#include<vector>
using namespace std;
template<class K>
//仿函数 用于得到kv对应的哈希值
//默认的是能直接转换成整形值的 比如int 地址 指针 这种
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
//特化
//string比较通用,所以特化
//采取DKBR
//使用循环遍历字符串中的每个字符e。
//将字符的ASCII值加到hash上,然后乘以一个常数131。
template<>
struct HashFunc<string>
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto e : s)
{
hash += e;
hash *= 131;
}
return hash;
}
};
//开散列(哈希桶)的实现
namespace hash_bucket
{
//这里叫HashNode 因为值存在链表中哦~
template<class K, class V>
struct HashNode
{
HashNode<K, V>* _next;//只想下一个节点的指针
pair<K, V> _kv;//存储的键值对
//节点的构造
HashNode(const pair<K, V>& kv)
:_next(nullptr)//next默认为空
, _kv(kv)
{}
};
//哈希表类 依旧是一个仿函数用于得到kv对应的哈希值
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
//哈希表的构造 默认开存储10个空指针的vector
//相比于闭散列的构造 多了置nullptr这一步
HashTable()
{
_tables.resize(10, nullptr);
_n = 0;
}
//析构 用于释放vector下面挂着的链表
//外层for循环->遍历vector
//内层while循环->遍历每一个桶中的节点
~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)
{
//在插入之前,首先调用Find函数检查kv是否已经存在于哈希表中。
//如果存在,则不需要插入,直接返回false。
if (Find(kv.first))
return false;
//仿函数实例化
Hash hs;
// 负载因子到1就扩容
if (_n == _tables.size())
{
//不再是开一个新哈希表对象了,而是一个新的2倍大小的vector
//这样效率更高
vector<Node*> newTables(_tables.size() * 2, nullptr);
//依旧是外层for循环->遍历原来的vector
//内层while循环->遍历每个桶内的节点
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();
//让当前节点的 next 指向新表的桶头
cur->_next = newTables[hashi];
//把当前节点设为新表的桶头。
newTables[hashi] = cur;
cur = next;
}
//清空旧表的桶
_tables[i] = nullptr;
}
//交换两个vector
_tables.swap(newTables);
}
//走到这里代表已经扩容完毕 或者不需要扩容
//直接仿函数对象得到哈希值hashi
size_t hashi = hs(kv.first) % _tables.size();
Node* newnode = new Node(kv);
//然后头插即可 切记vector里面放的是节点指针 指向一个桶的首节点
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
//插入n记得++
++_n;
return true;
}
//查找函数
//找到返回该节点的指针 反之nullptr
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;
}
//删除函数
//不能像闭散列那样find+delete 因为我们单向链表删除后还要链接删除节点的前后节点
//易错:得看前驱是否为空 即需要以防删除的就是首节点
bool Erase(const K& key)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
// 情况1:删除的是中间或尾节点
if (prev)//前驱不为空
{
prev->_next = cur->_next;// 前驱节点直接跳过当前节点
}
//情况2:删除的是头节点
else//前驱为空
{
_tables[hashi] = cur->_next;// 让桶头指向下一个节点
}
//释放
delete cur;
//n-1
--_n;
return true;
}
//没找到则继续遍历
prev = cur;
cur = cur->_next;
}
//遍历结束仍未找到
return false;
}
//测试我们写的哈希桶 测试内容如下
/*负载因子(load factor)
总桶数量(all bucketSize)
非空桶数量(bucketSize)
最长链表长度(maxBucketLen)
平均链表长度(averageBucketLen)*/
void Some()
{
size_t bucketSize = 0;
size_t maxBucketLen = 0;
size_t sum = 0;
double averageBucketLen = 0;
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
++bucketSize;
}
size_t bucketLen = 0;
while (cur)
{
++bucketLen;
cur = cur->_next;
}
sum += bucketLen;
if (bucketLen > maxBucketLen)
{
maxBucketLen = bucketLen;
}
}
averageBucketLen = (double)sum / (double)bucketSize;
printf("load factor:%lf\n", (double)_n / _tables.size());
printf("all bucketSize:%d\n", _tables.size());
printf("bucketSize:%d\n", bucketSize);
printf("maxBucketLen:%d\n", maxBucketLen);
printf("averageBucketLen:%lf\n\n", averageBucketLen);
}
private:
//与闭散列不同的是 vector里面存储的是节点指针了
vector<Node*> _tables; // 指针数组
size_t _n;
};
}
引入:想要完成封装,我们的哈希表有什么不足?
①:哈希表结构的优化
我们哈希表目前只能存储kv模型;所以结构需要优化,以便既能够成为K模型,又要能成为KV模型
以及我们要做到不论是k模型还是kv模型,都要取得到k值
②:两个仿函数的嵌套适用
我们要先通过仿函数得到k模型或者kv模型中的k值,然后再通过仿函数(哈希函数)得到k值对应的一个整形值;这是和红黑树封装的不同,红黑树只需要得到k值即可。
③:迭代器的实现
④:unordered_map的[ ]的实现
一:优化哈希表的结构
A:要想即能创建出k模型的unordered_set和kv模型的unordered_map:
和红黑树类似,哈希表也是从第二个模板参数进行操作,就能达到效果
B:要想无论是k模型还是kv模型,都要取得到k值
从仿函数入手即可
相关细节不再赘述,红黑树封装博客里面有类似的详细讲解:用红黑树封装出set和map -CSDN博客
直接看代码:
unordered_set代码:
#include"HashTable.h"
template<class K, class Hash = HashFunc<K>>//外界调用所需参数
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
private://创建一个哈希表对象
hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
};
unordered_map代码:
#include"HashTable.h"
template<class K, class V, class Hash = HashFunc<K>>//外界调用所需参数
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
private://创建一个哈希表对象
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
解释:对哈希表的第四个参数的解释
①: 创建哈希表中的第四个参数Hash,是实现在HashTable.h中的,且unordered_set和unordered_map文件中在包含了HashTable.h后,都是在模版中的Hash给上缺省值的,所以我们HashTable.h中,哈希表的模版中的Hash参数不需要再给缺省值
②:为什么在unordered_set/map中处理这个参数? 因为,使用者不可能直接用哈希表而是用unordered_set或unordered_map,所以一般是不需要对unordered_set/map参数中的哈希函数进行传参的,只有k值是特殊类型的时候,才需要自己实现哈希函数进行手动传参
所以哈希表的起始状态如下:
//哈希函数 以及模版参数为string的特化处理
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 += e;
hash *= 131;
}
return hash;
}
};
namespace hash_bucket
{
// T -> K
// T -> pair<K, V>
//哈希节点类 哈希桶中都是节点 所以叫节点类
template<class T>
struct HashNode
{
HashNode<T>* _next;
T _data;
HashNode(const T& data)
:_next(nullptr)
, _data(data)
{}
};
//哈希表类
template<class K, class T, class KeyOfT, class Hash>//Hash不需要缺省值
class HashTable
{
//节点类的缩写
typedef HashNode<T> Node;
public:
private:
vector<Node*> _tables; // 指针数组
size_t _n;
};
}
到这里,我们的哈希表既能创建出k模型的unordered_set和kv模型的unordered_map;也能无论是k模型还是kv模型,都要取得到k值;通过两个仿函数就可以办到~
二:两个仿函数的嵌套使用
我们的函数,也要进行改变,不再是之前的写死了对pair类型进行操作,现在获取到的是T类型的data值,我们要先对data进行取出其中的k,然后再用仿函数让k得到一个整形
所以插入 查找 删除函数改动如下:
//插入函数
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;
iterator it = Find(kot(data));
if (it != end())
return make_pair(it, false);
//仿函数对象的实例化
Hash hs;
// 负载因子到1就扩容
if (_n == _tables.size())
{
vector<Node*> newTables(GetNextPrime(_tables.size()), 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);
}
size_t hashi = hs(kot(data)) % _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)
{
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 iterator(nullptr, this);
}
//删除函数
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)
{
prev->_next = cur->_next;
}
else
{
_tables[hashi] = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
解释:
①:仿函数的实例化
//取出data里面的k值
KeyOfT kot;
//将k值转换为一个整形值
Hash hs;
②:仿函数嵌套适用:
hs(kot(data))
这就叫仿函数的嵌套使用,先拿出data里面的k值,然后得到k值对应的整形值
③:insert函数的返回值,和红黑树一样,是为了unordered_map的[ ]所准备的
④:扩容中用到了一个GetNextPrime函数,先别管,后面会讲
三:迭代器的实现
哈希表的迭代器的实现是一个重点,和以往的任何的迭代器实现都不同,以前的实现,要么是直接复用的指针(如底层连续的vector),要么就是对一个节点指针进行封装,去重载++ * != 等操作符;
哈希表的迭代器的问题:
Q:你对一个节点指针进行了封装,虽然可以* ++ !=....重载,但是一个桶走完了,怎么找到下一个桶?
A:所以哈希表的迭代器不再只对节点指针封装,且还要对哈希表指针封装,也就是有两个成员变量
注意:迭代器博主只实现了正向非const迭代器,其余的大同小异,不再赘述
1:迭代器代码:
//哈希桶的迭代器类
template<class K, class T, class KeyOfT, class Hash>
struct __HTIterator
{
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef __HTIterator<K, T, KeyOfT, Hash> Self;
//两个成员变量
Node* _node;
HT* _ht;
//迭代器的构造
__HTIterator(Node* node, HT* ht)
:_node(node)
, _ht(ht)
{}
//*重载
T& operator*()
{
return _node->_data;
}
//->的重载
T* operator->()
{
return &_node->_data;
}
//++的重载
Self& operator++()
{
if (_node->_next)
{
// 当前桶还是节点
_node = _node->_next;
}
else
{
// 当前桶走完了,找下一个桶
KeyOfT kot;
Hash hs;
size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
// 找下一个桶
hashi++;
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
hashi++;
}
// 后面没有桶了
if (hashi == _ht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
//!=的重载
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
2:迭代器代码解释:
①:成员变量
Node* _node; // 当前指向的哈希节点
HT* _ht; // 指向所属的哈希表
②:关键类型定义
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef __HTIterator<K, T, KeyOfT, Hash> Self;
Self:迭代器自身的类型,简化返回值类型声明(如 operator++ 返回 Self&)。
③:迭代器的核心操作
a:解引用操作 operator*
T* operator->()
{
return &_node->_data; // 返回节点数据的指针
}
b:成员访问操作 operator->
T* operator->()
{
return &_node->_data; // 返回节点数据的指针
}
c: 不等比较 operator!=
bool operator!=(const Self& s)
{
return _node != s._node; // 比较两个迭代器是否指向同一节点
}
d:最关键的 operator++
(跨桶遍历)
Self& operator++()
{
//情况1:当前桶还有下一个节点
if (_node->_next)
{
_node = _node->_next;
}
//来到这里 代表是情况2
//情况2:当前桶已遍历完,需跳转到下一个非空桶
else
{
//所以需要先计算当前节点所在的桶索引 hashi!
KeyOfT kot;
Hash hs;
//hashi即当前节点所在的桶索引
size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
hashi++; // 从下一个桶开始找
//hashi得一直++,直到找到一个不为空的桶(为空代表vector此槽位是nullptr)
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi]) // 找到非空桶
{
_node = _ht->_tables[hashi]; // 指向该桶的首节点
break;
}
hashi++;
}
//hashi一直++ 直到vector遍历完了 都没发现非空桶
//则将迭代器置为end()(因为end()就是nullptr)
if (hashi == _ht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
解释:代码逻辑转换为图片如下
四:map的[ ]的实现
在unordered_map中进行以下实现即可,和红黑树类似,不再赘述:
//[ ]函数
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
五:总代码
①:HashTable.h
#pragma once
#include<iostream>
#include<vector>
#include<unordered_set>
#include<unordered_map>
#include<set>
using namespace std;
//哈希函数 以及模版参数为string的特化处理
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 += e;
hash *= 131;
}
return hash;
}
};
//开散列的域
//开散列 也叫哈希桶
namespace hash_bucket
{
// T -> K
// T -> pair<K, V>
//哈希节点类 哈希桶中都是节点 所以叫节点类
template<class T>
struct HashNode
{
HashNode<T>* _next;
T _data;
HashNode(const T& data)
:_next(nullptr)
, _data(data)
{}
};
// 前置声明
template<class K, class T, class KeyOfT, class Hash >
class HashTable;
//哈希桶的迭代器类
template<class K, class T, class KeyOfT, class Hash>
struct __HTIterator
{
typedef HashNode<T> Node;
typedef HashTable<K, T, KeyOfT, Hash> HT;
typedef __HTIterator<K, T, KeyOfT, Hash> Self;
//两个成员变量
Node* _node;
HT* _ht;
//迭代器的构造
__HTIterator(Node* node, HT* ht)
:_node(node)
, _ht(ht)
{}
//*重载
T& operator*()
{
return _node->_data;
}
//->的重载
T* operator->()
{
return &_node->_data;
}
//++的重载
Self& operator++()
{
if (_node->_next)
{
// 当前桶还剩节点
_node = _node->_next;
}
else
{
// 当前桶走完了,找下一个桶
KeyOfT kot;
Hash hs;
size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
// 找下一个桶
hashi++;
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
hashi++;
}
// 后面没有桶了
if (hashi == _ht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
//!=的重载
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
//哈希表类
template<class K, class T, class KeyOfT, class Hash>// KeyOfT用于取出T中的k值 Hash则是得到这个取出的k值对应的整形
class HashTable
{
template<class K, class T, class KeyOfT, class Hash>
friend struct __HTIterator;
//节点类的缩写
typedef HashNode<T> Node;
public:
typedef __HTIterator<K, T, KeyOfT, Hash> iterator;
//迭代器的两个函数
//begin()函数
iterator begin()
{
for (size_t i = 0; i < _tables.size(); i++)
{
// 找到第一个桶的第一个节点
if (_tables[i])
{
return iterator(_tables[i], this);
}
}
return end();
}
//end()函数
iterator end()
{
return iterator(nullptr, this);
}
//构造函数
HashTable()
{
_tables.resize(10, nullptr);
_n = 0;
}
//析构函数
~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;
//素数序列
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
};
size_t i = 0;
for (i = 0; i < PRIMECOUNT; i++)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];//逻辑返回
}
//插入函数
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;
iterator it = Find(kot(data));
if (it != end())
return make_pair(it, false);
//仿函数对象的实例化
Hash hs;
// 负载因子到1就扩容
if (_n == _tables.size())
{
vector<Node*> newTables(GetNextPrime(_tables.size()), 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);
}
size_t hashi = hs(kot(data)) % _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)
{
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 iterator(nullptr, this);
}
//删除函数
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)
{
prev->_next = cur->_next;
}
else
{
_tables[hashi] = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
vector<Node*> _tables; // 指针数组
size_t _n;
};
}
②:My_unordered_set
#pragma once
#include"HashTable.h"
namespace bit
{
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public://复用哈希表类的迭代器
typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;
//复用哈希表的begin函数
iterator begin()
{
return _ht.begin();
}
//复用哈希表的end函数
iterator end()
{
return _ht.end();
}
//复用哈希表的插入函数
pair<iterator, bool> insert(const K& key)
{
return _ht.Insert(key);
}
//复用哈希表的查找函数
iterator Find(const K& key)
{
return _ht.Find(key);
}
//复用哈希表的删除函数
bool erase(const K& key)
{
return _ht.Eease(key);
}
private://创建一个哈希表对象
hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
};
//测试set 体现迭代器遍历出来是无序的 且set的k值不能被修改
void test_set1()
{
unordered_set<int> us;
us.insert(3);
us.insert(1);
us.insert(5);
us.insert(15);
us.insert(45);
us.insert(7);
unordered_set<int>::iterator it = us.begin();
while (it != us.end())
{
//*it += 100;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : us)
{
cout << e << " ";
}
cout << endl;
}
}
③:My_unordered_map
#pragma once
#include"HashTable.h"
namespace bit
{
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
//复用哈希表的begin()函数
iterator begin()
{
return _ht.begin();
}
//复用哈希表的end()函数
iterator end()
{
return _ht.end();
}
//复用哈希表的插入函数
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.Insert(kv);
}
//复用哈希表的查找函数
iterator Find(const K& key)
{
return _ht.Find(key);
}
//复用哈希表的删除函数
bool erase(const K& key)
{
return _ht.Eease(key);
}
//[ ]函数
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
private://创建一个哈希表对象
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};
//测试map 体现迭代器遍历出来是无序的 且map的k值不能被修改 v值可以修改
void test_map1()
{
unordered_map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("left", "左面"));
dict.insert(make_pair("right", "右面"));
for (auto& kv : dict)
{
//kv.first += 'x';
kv.second += 'y';
cout << kv.first << ":" << kv.second << endl;
}
}
//测试map的[]
void test_map2()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" };
unordered_map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
}
for (auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
cout << endl;
}
}
④:test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"MyOrderedMap.h"
#include"MyOrderedSet.h"
int main()
{
//测试封装后的OrderedSet和OrderedMap 体现无序性
bit::test_set1();
bit::test_map1();
//测试OrderedMap的[]
bit::test_map2();
return 0;
}
⑤:代码整合
当多份独立可运行的代码合并到一个项目中时,需要解决一些问题:
a:创建哈希表时候的第二个参数的写法
//My_unordered_set中:
const K
//My_unordered_map中:
pair<const K, V>
解释:这样写,对于My_unordered_set,k不能改;对于My_unordered_map ,k不能改,v可以改
和库保持一致,符合这种数据结构的特性
b:在迭代器之前要前置声明一下哈希表类,注意写法:
// 前置声明
template<class K, class T, class KeyOfT, class Hash >
class HashTable;
解释:
Q:为什么在 迭代器之前要前置声明哈希表类?
A:因为迭代器类中又需要用到 HashTable
的类型(如 typedef HashTable<K, T, KeyOfT, Hash> HT;
);这样迭代器类才能安全地引用 HashTable
,而不会报错“未定义的类型”。
c:哈希表类中要声明迭代器类为友元:
template<class K, class T, class KeyOfT, class Hash>
friend struct __HTIterator;
Q: 为什么要在哈希表类中要声明迭代器类为友元?
A:因为迭代器需要访问 HashTable
的 私有成员(如 _tables
和 _n
),但默认情况下,外部类(包括迭代器)无法访问私有成员;
例如,operator++
中需要计算哈希桶位置:
size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size(); // 访问 _tables
如果 _tables
是 private
,且迭代器不是友元,这段代码会编译失败。
Q:为什么不能直接把 _tables
改成 public
?
A:
-
破坏封装性,外部任何代码都可以随意修改哈希表内部结构,不安全。
-
友元是一种 受控的暴露,只允许特定的类(如迭代器)访问私有成员
d:GetNextPrime函数的作用
GetNextPrime
是哈希表扩容时用于 计算新容量 的函数,其核心目的是 返回一个比当前容量大的素数,作为哈希表的新桶数组大小。这是为了解决哈希表扩容时的效率问题,并减少哈希冲突。
因为,经过前辈研究,素数扩容是最好的,会有效的减少哈希冲突!
六:测试代码
①:测试My_unordered_set
void test_set1()
{
unordered_set<int> us;
us.insert(3);
us.insert(1);
us.insert(5);
us.insert(15);
us.insert(45);
us.insert(7);
unordered_set<int>::iterator it = us.begin();
while (it != us.end())
{
//*it += 100;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : us)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
解释:迭代器和范围for均正常,且符合无序的预期
②:测试My_unordered_map
void test_map1()
{
unordered_map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("left", "左面"));
dict.insert(make_pair("right", "右面"));
for (auto& kv : dict)
{
//kv.first += 'x';
kv.second += 'y';
cout << kv.first << ":" << kv.second << endl;
}
}
运行结果:
解释:符合无序的预期,且k值不能改变,但可以对v值进行了改变,正确
void test_map2()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" };
unordered_map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
}
for (auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
cout << endl;
}
运行结果:
解释:符合预期