std::unordered_map原理1
参考博客:剖析 std::unordered_map O(1) 原理
- 在C++11中引入了一个容器叫做
std::unordered_map
,这个容器是一个关联容器,和Python中的字典比较像,是一个键值对的数据形式。即<key,value>
介绍
std::unordered_map
的实现原理是依靠哈希表来实现的。std::unordered_map
是C++标准库中的一个关联容器,它根据哈希值存储键值对。std::unordered_map
的模版参数包括键类型key
、值类型value
、哈希函数hash
、键比较函数KeyEqual
、和内存分配器Allocator
等。unordered_map
是指定类型参数具体如下:
1 | template< |
unordered_map使用
定义unordered_map
-
创建
unordered_map
、初始化unordered_map
、指定unordered_map
中桶的数量、输出键值对
-
我们可以用如下方式定义
unordered_map
,如下我们定义了一个键Key
为字符串类型,值Value
为整型的一个键值对
1 |
|
- 还可以在定义的时候,对该键值对初始化一下,初始化过程如下
1 |
|
- 我们还可以在创建一个
unordered_map
时指定桶的数量
1 |
|
- 定义完之后逐个输出键值对
1 |
|
插入元素
- 可以使用
insert
、emplace
进行元素的插入,接下来逐一介绍操作 - 使用方法
insert
可以插入一个键值对。例如:
1 |
|
- 还可以使用
emplace
插入一个键值对,该方法允许在容器中就地构造元素,避免复制操作。
1 |
|
查找元素
- 可以使用
find
方法查找一个元素,find
方法返回一个迭代器,如果找到指定的键,则返回键指向该元素的迭代器,否则返回end()
。 - 所以
find()
方法要接受一个键作为参数,然后返回的是一个迭代器。
1 |
|
删除元素
- 使用
clear
方法可以删除unordered_map
实例中的所有元素。
1 |
|
unordered_map底层原理
-
C++中的
unordered_map
的实现是依靠数据结构中的哈希表hashtabel
实现的。 -
数组可以通过索引
index
在时间复杂度O(1)
下得到指定内存中元素,但是如果不知道对应的索引值,就需要O(N)
的时间复杂度查找对应的索引值,再获取该索引里面的值。 -
而哈希表并没有遍历获取索引值这个缺点。哈希表的是利用如下过程来避免查找索引的。
- 定义一个键值对数据
{key,value}
,我们先计算key
中的哈希值hashcode = hash_func(key)
- 然后再通过
模运算
,使得该值在数组索引的范围内 - 这样我们就可以直接通过数组索引
a[hashcode%m]=value
直接存储和获取数据
- 定义一个键值对数据
-
unordered_map
就是利用这一原理。实现了时间复杂度O(1)
的存取。接下来先说明一下在unordered_map
中的一些名词哈希桶数组
:我们将unordered_map
用于存储value的这块数组称为哈希桶数组
哈希桶(buckets)
:是哈希表中的具体存储单元,用来存放具有相同哈希值的键值对bucket_count
:用来指明哈希桶数组的长度
基本实现原理
- 我们可以通过如下过程来理解
unordered_map
的存储原理,也就是上面所说的:计算key
的哈希值,再对该值进行取模运算,得到的结果就是value
所存储的数组索引值。
1 |
|
hash冲突
-
如果使用
hash值+取模
的这种运算就会出现一个问题,如果计算的哈希值虽然相同的可能性不大,但是经过模运算的结果相同的可能性非常大,这样就会导致同一个数组索引,存储了多个值。 -
如果遇到这种情况,我们就会把该数组索引到的地方,存储一个哨兵结点(也就是存储一个指向链表的指针),这个链表来存储模运算后结果相同的键值对。
- 这样桶的索引值,仍然是计算
key
的哈希值,然后再经过模运算计算索引。 - 但是插入到索引值里面,是通过头插法插入哨兵结点所指向的链表,这样插入的时间复杂度还是
O(1)
- 这样桶的索引值,仍然是计算
-
这个方法叫做开链法,使用这种方法就可以解决hash冲突的问题,但是这种方法也存在一个问题。
-
当一个链表元素非常多的时候,这时我们通过链表获取键值对,就需要去链表中遍历得到相应的键值对,删除一个键值对也是一样。所以这时对该桶的操作时间复杂度就变成了O(N),
hash表
就退化成了链表
-
如图所示,这就是使用开链法的存储结构:
hash退化
-
为了解决
hash
表退化成链表
,即hash退化
这个问题,我们引入了两个相关概念。 -
负载因子和最大负载因子
- 负载因子(load_factor):是存入
hashtable
的元素个数与hashtable
的桶之间的比值。(注意是总的元素个数,而不是发生hash冲突的元素个数) - 最大负载因子(max_load_factor):是负载因子的上限。
- 负载因子(load_factor):是存入
-
当负载因子的值大于最大负载因子的时候,哈希桶数组就会被分配更多的内存空间,以保持负载因子小于最大负载因子。