C++ STL无序关联式容器:unordered_map容器、unordered_multimap容器、unordered_set容器、unordered_multiset容器,其底层构造都是基于哈希表封装的。如果读者还不具备哈希基础的话可自行跳转《哈希算法实例详解》学习。简单来说,无序关联式容器可以看作指针数组+链表的奇妙组合。原理可理解为:我们向堆区开辟了一段连续空间(可理解为数组),空间大小为n,每个单位空间被称为“桶”且能够存储一个头指针,头指针“牵引"着一个单向链表(双向链表太费内存了)。当我们输入一个键值对时,键会被哈希函数转变成哈希值hash,随后由hash%n(充分利用模运算的周期性)分配到该空间中,键值对作为一个整体被封装在链表节点中。

无序关联式容器底层构造

通过哈希定位到对应的链表,在理想情况下提供O(1)的访问效率,但在哈希冲突严重时可能退化为O(n)的链表查找性能。

那什么是”哈希冲突“呢?在实际使用时,哈希函数作用于不同键或者键值对数量大于空间时,都可能会产生相同的哈希值,此时会出现多个键值对会被映射到“同一个位置上”,为了解决这个问题,通常使用链地址法,即采用链表思想,把键值对“串”起来。查询时先通过哈希快速定位到桶(模糊区分),再在链表中遍历比较键的具体值(绝对区分)。

无序关联式容器内都维护着一个重要的成员变量——load_factor(),它叫做负载因子,数据类型为float,其值=插入键值对总量/桶数量,功能是分析键值对的分布情况,当负载因子<0.5时,说明不管是空间利用率还是访问效率都比较高,反之。下面我们通过代码来分析插入键值对对负载因子的影响。

#include <iostream>
#include<unordered_map>//需要包含头文件 
#include<string>
using namespace std;
/*无序关联式容器内的负载因子——load_factor()*/ 
void test()
{
unordered_map<string,string> ump{
{"C语言教程","https://www.dotcpp.com/BbNkeAOzdW.php/cms/archives?ref=addtabs"},
{"计算机二级C语言","https://www.dotcpp.com/course/erjic/"},
};//初始化列表
cout << "插入两个键值对时的负载因子为【"  << ump.load_factor() << "】\n";
cout << "插入多个键值对后:\n";
ump["数据结构教程"]="https://www.dotcpp.com/course/ds/";
ump["ACM与蓝桥杯竞赛指南"]="https://www.dotcpp.com/course/acm/";
ump["算法竞赛教程"]="https://www.dotcpp.com/course/algorithm/";
ump["单片机教程"]="https://www.dotcpp.com/course/scm/";
ump["Python教程"]="https://www.dotcpp.com/course/python/";
ump["Java教程"]="https://www.dotcpp.com/course/java/";
cout << "插入多个键值对时的负载因子为【"  << ump.load_factor() << "】\n";
 
}
int main() {
    test();
    return 0;
}

编译结果如下:

负载因子load_factor()

我们可以清晰地看到,随着键值对的不断插入,负载因子的值不断提高,而负载因子的增加会影响容器的空间利用率和元素的操作效率,我们该如何避免哈希冲突提高容器的性能呢?

1. 我们需要设计好的哈希函数,将键尽量均匀地分配到空间中。

2. 我们可以通过reserve()实现提前预定空间,尽量避免使用空间<插入键值对数量。

3. 尽可能将负载因子控制在0.7一下,同时使用较为稳定的数据类型作为键。(比如int比float稳定性好)

点赞(0)

C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:

一点编程也不会写的:零基础C语言学练课程

解决困扰你多年的C语言疑难杂症特性的C语言进阶课程

从零到写出一个爬虫的Python编程课程

只会语法写不出代码?手把手带你写100个编程真题的编程百练课程

信息学奥赛或C++选手的 必学C++课程

蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程

手把手讲解近五年真题的蓝桥杯辅导课程

Dotcpp在线编译      (登录可减少运行等待时间)