文章目录
前言
本文参考文档:https://legacy.cplusplus.com/reference/unordered_map/
一、unordered系列关联式容器
1.1 unordered_map
1.unordered_map接口说明
函数声明 | 功能介绍 |
---|---|
unordered_map | 构造不同格式的unordered_map对象 |
2. unordered_map的容量
函数声明 | 功能介绍 |
---|---|
bool empty() const | 检测unordered_map是否为空 |
size_t size() const | 获取unordered_map的有效元素个数 |
3.unordered_map的迭代器(无序set和map的迭代器都是单向迭代器)
函数功能 | 函数介绍 |
---|---|
begin () | 返回unordered_map中第一个元素的迭代器 |
end() | 返回unordered_map中最后一个元素后一个位置的迭代器 |
cbegin() | 返回unordered_map中第一个元素的const迭代器 |
cend() | 返回unordered_map中最后一个元素后一个位置的const迭代器 |
4.unordered_map的元素访问
函数功能 | 函数介绍 |
---|---|
operator[] | 返回key值对应的val值(没有默认值) |
注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶
中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,
将key对应的value返回。
5.unordered_map查询
函数功能 | 函数介绍 |
---|---|
find() | 查询key值是否存在,存在返回key值对应的迭代器的位置,返回key在哈希桶中的位置 |
count | 返回哈希桶中关键码为key的键值对的个数 |
注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1
6. unordered_map的修改操作
函数功能 | 函数介绍 |
---|---|
insert | 向容器中插入键值对 |
erase | 删除容器中的键值对 |
void clear() | 清空容器中有效元素个数 |
void swap() | 交换两个容器中的元素 |
7.unordered_map的桶操作
函数功能 | 函数介绍 |
---|---|
size_t bucket_count()const | 返回哈希桶中桶的总个数 |
size_t bucket_size(size_t n)const | 返回n号桶中有效元素的总个数 |
size_t bucket(const K& key) | 返回元素key所在的桶号 |
1.2 unordered_set
unordered_set接口根map差不多一样,不过set只存key值且不能修改
这里就不过多赘述了,详情可自行观看文档
set文档资料
二、底层结构
2.1哈希概念(哈希是一种算法思想)
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O( l o g 2 N log_2 N log2N) ,搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以 不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
哈希/散列:映射 一个值和另一个值建立关系。
哈希表/散列表: 映射 关键字和存储位置建立一个关系。
2.2哈希冲突
不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突
或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
2.3 解决哈希冲突方法:
1.直接定址法(值和位置关系是唯一关系,每个人都有唯一位置,值很分散,直接定址法会导致空间开很大,资源的浪费)
2.闭散列
2.1 开放地址法/线性探测
当前位置被占用,在开放空间里按某种规则,找一个没有被占用的位置存储
- 线性探测 hashi + i(i >= 0)
- 二次探测 hashi + i^2(i >= 0)
多定义一个负载因子,存储有效关键字个数
当有效关键字个数超过表大小的6~8成就再次扩容,用此种方法可以有效的减少哈希冲突的次数。
以空间换效率。
注意:
负载因子太多:会影响效率。
负载因子太少:会浪费空间。
代码实现:
enum Stutas
{
//删除状态的意义:
//1.再插入,这个位置可以覆盖
//2.防止后面冲突的值,出现找不到的情况,遇到删除状态还要继续向后寻找
DELETE,
EMPTY,
EXIST
};
template<class K,class V>
struct HashData
{
pair<K,V> _kv;
Stutas _s = EMPTY;
};
//开放地址法
//线性查找,插入
template<class K>
struct Hashfunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
//struct HashfuncString
//{
// //BKDR算法
// size_t operator()(const string& key)
// {
// size_t hash = 0;
// //把他们的ascll码值加起来
// for (auto e : key)
// {
// hash += e;
// }
// return hash;
// }
//};
template<>
struct Hashfunc<string>
{
size_t operator()(const string& key)
{
size_t hash = 0;
//把他们的ascll码值加起来
for (auto e : key)
{
hash += e;
}
return hash;
}
};
template<class K,class V, class Hash = Hashfunc<K>>
class HashTable
{
public:
HashTable()
{
_tables.resize(10);//resize也直接会开初始化size的大小。reserve之会开capecity的大小
}
//提供find解决insert能够插入相同key的问题
HashData<K, V>* Find(const K& key)
{
Hash ht;
size_t hashi = ht(key) % _tables.size();
while (_tables[hashi]._s != EMPTY)
{
if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
{
//找到return找到位置的地址
//没找到return空
return &_tables[hashi];
}
hashi++;
//hashi超出table的size大小就给他%回来
hashi = hashi % _tables.size();
}
return nullptr;
}
bool Insert(const pair<K, V>& kv)
{
Hash ht;
if (Find(kv.first))
{
return false;
}
if ((double)_n / _tables.size() >= 0.7)
{
//不能直接扩容size的二倍,还要转移数据,重新映射关系(扩容之后值和空间的位置关系已经乱了)
//释放旧空间
size_t newsize = _tables.size() * 2;
HashTable<K,V,Hash> newHT;
newHT._tables.resize(newsize);
//遍历旧表,插入新表
for ( size_t i= 0; i < _tables.size(); i++)
{
if (_tables[i]._s == EXIST)
{
newHT.Insert(_tables[i]._kv);
}
}
_tables.swap(newHT._tables);
}
//size_t hashi = kv.first % _tables.size();//key不一定是整形,如果是string呢?用仿函数把kv.first的值转化整形值
//先用字符串映射一个整形
size_t hashi = ht(kv.first) % _tables.size();
while (_tables[hashi]._s == EXIST)
{
hashi++;
//hashi超出table的size大小就给他%回来
hashi = hashi % _tables.size();
}
_tables[hashi]._kv = kv;
_tables[hashi]._s = EXIST;
++_n;
}
bool Erase(const K& key)
{
HashData<K,V>* ret = Find(key);
//find找到会返回找到位置的地址
if (ret)
{
ret->_s = DELETE;
--_n;
return true;
}
return false;
}
void Print()
{
for (size_t i = 0; i < _tables.size(); i++)
{
if (_tables[i]._s == EXIST)
{
//cout << i << "->";
cout <<"["<<i<<"]->"<< _tables[i]._kv.first <<