【C++进阶】哈希 + unordered系列容器

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、哈希思想
  • 二、常见的哈希函数
      • 2.1 直接定址法
      • 2.2 除留余数法
  • 三、哈希冲突
      • 3.1 哈希冲突的原因
      • 3.2 如何解决哈希冲突
        • 闭散列(开放定址法)
        • 开散列(链地址法、开链法、哈希桶)
  • 四、unordered系列
      • 4.1 与map和set的区别
      • 4.2 map/set与unordered_map/unordered_set的性能测试

一、哈希思想

哈希是一种 映射 思想。它是将存储的值跟存储的位置建立映射关系,由这种思想而构成的数据结构称为 哈希表(散列表)

哈希表中插入数据和查找数据 的步骤如下:

  • 插入数据:根据当前待插入的元素的键值,通过哈希函数计算出哈希值,并存入相应的位置中

  • 查找数据:根据待查找元素的键值,计算出哈希值,判断对应的位置中存储的值是否与键值相等

例如:数据集合{1,7,6,4,5,9},哈希函数设置为:hash(key) = key % capacitycapacity为存储元素底层空间总的大小)

在这里插入图片描述

显然,这个哈希表并没有把所有位置都填满,数据分布无序且分散

因此,哈希表又称为散列表

那么如何建立映射关系呢?这就要涉及到哈希函数了。

二、常见的哈希函数

2.1 直接定址法

函数原型:Hash(key) = A * key + BA, B为常数)

  • 适用场景:值的分部范围比较集中。例如:统计字符字符出现的次数。
  • 缺点:需要提前知道键值的分布情况

2.2 除留余数法

函数原型:Hash(key) = key % m m为哈希表的大小)

  • 适用场景:范围不集中,分布分散的数据
  • 缺点:容易出现哈希冲突,需要借助特定方法解决

三、哈希冲突

3.1 哈希冲突的原因

哈希冲突:又称哈希碰撞,不同的值可能会映射到同一个位置。

在这里插入图片描述

如果继续插入元素 44哈希值 hash(44) = 44 % 10 = 4,此时哈希值为 4 的位置处已经有元素了,无法继续存入,此时就发生了 哈希冲突。

在这里插入图片描述

3.2 如何解决哈希冲突

闭散列(开放定址法)

当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的下一个空位置中去。

那么如何寻找下一个空位置呢?

  • 线性探测法:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止

例如,我们用除留余数法(hash(key)=key%10)将序列{1, 6, 10, 1000, 101, 18, 7, 40}插入到表长为10的哈希表中,插入过程如下:

在这里插入图片描述

通过上图可以看到,随着哈希表中数据的增多,产生哈希冲突的可能性也随着增加,最后在40进行插入的时候更是连续出现了四次哈希冲突。于是如果哈希表接近满的话,插入、查找、删除的效率都会越来越低。

  • 优化方案:二次探测,每次向后探测 i ^ 2。尽管如此,闭散列的效果还是不尽人意,实际中还是 开散列 用的更多一些
开散列(链地址法、开链法、哈希桶)

所谓 开散列 就在原 存储位置 处带上一个 单链表,如果发生 哈希冲突,就将 冲突的值依次挂载即可。因此也叫做 链地址法、开链法、哈希桶。

例如,我们用除留余数法将序列{1, 6, 15, 60, 88, 7, 40, 5, 10}插入到表长为10的哈希表中,当发生哈希冲突时我们采用开散列的形式,将哈希地址相同的元素都链接到同一个哈希桶下,插入过程如下:

在这里插入图片描述

  • 开散列 中进行插入时,如果对应位置的哈希值被占了,那么就在对应位置开一块链表进行存储。
  • 开散列中进行查找时,需要先根据哈希值找到对应位置,并在单链表中进行遍历。

一般情况下,单链表的长度不会太长的,因为扩容后,整体长度会降低。如果单链表真的过长了,我们还可以将其转为红黑树,此时效率依旧非常高。

在这里插入图片描述

  • 值得一提的是 哈希表(开散列法)最快时间复杂度为 O(N),平均是 O(1)

哈希表(开散列法)快排一样很特殊,时间复杂度不看最坏的,看 平均时间复杂度,因为 最快的情况几乎不可能出现

四、unordered系列

4.1 与map和set的区别

哈希表最厉害的地方在于 查找速度非常快,比红黑树还快,时间复杂度是O(1)(后面的性能测试见)。因此在 C++11 标准中,利用 哈希表 作为底层结构,重写了 set / map,就是 unordered_set / unordered_map

unordered系列的使用和map以及set几乎没区别,使用方面建议大家查文档:点击跳转

要说有区别如下:

在这里插入图片描述

4.2 map/set与unordered_map/unordered_set的性能测试

说到一个容器的性能,我们最关心的实际就是该容器增删查改的效率。我们可以通过下列代码测试set容器和unordered_set容器insertfind以及erase的效率。

【测试代码】

#include <iostream>
#include <unordered_set>
#include <unordered_map>
#include <set>
#include <map>
#include <vector>
using namespace std;void Performance_testing()
{// set和unordered_setconst int N = 1000000; // 一百万set<int> s;unordered_set<int> us;vector<int> v;v.reserve(N);srand(time(0));for (int i = 0; i < N; i++){v.push_back(rand());// v.push_back(rand() + i);// v.push_back(i);}// =========== 插入测试 =================size_t begin1 = clock();for (auto e : v){s.insert(e);}size_t end1 = clock();cout << "set的插入时间:" << end1 - begin1 << endl;size_t begin2 = clock();for (auto e : v){us.insert(e);}size_t end2 = clock();cout << "unordered_set的插入时间:" << end2 - begin2 << endl;// =========== 查找测试 =================size_t begin3 = clock();for (auto e : v){s.find(e);}size_t end3 = clock();cout << "set的find:" << end1 - begin1 << endl;size_t begin4 = clock();for (auto e : v){us.find(e);}size_t end4 = clock();cout << "unordered_set的find:" << end4 - begin4 << endl;cout << "set插入数据个数:" << s.size() << endl;cout << "unordered_set插入数据个数:" << us.size() << endl;// =========== 删除测试 =================size_t begin5 = clock();for (auto e : v){s.erase(e);}size_t end5 = clock();cout << "set删除:" << end5 - begin5 << endl;size_t begin6 = clock();for (auto e : v){us.erase(e);}size_t end6 = clock();cout << "unordered_set删除:" << end6 - begin6 << endl;
}int main()
{Performance_testing();return 0;
}
  • 1000个数做增删查改操作

在这里插入图片描述

我们发现:当数据量小的时候,它们的效率差不多。

  • 10000000个数做增删查改操作

在这里插入图片描述

当数据量达到一千万的时候,明显unordered系列快多了。

根据测试结果可以得出以下结论:

  • 当处理数据量小时,map/set容器与unordered系列容器增删查改的效率差异不大。

  • 当处理数据量大时,map/set容器与unordered系列容器增删查改的效率相比,unordered系列容器的效率更高。

因此,如果在unordered系列和map/set容器,应该首选unordered系列; 另外如果需要存储的序列为有序时,应该选用map/set容器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/707809.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为 OD 一面算法原题

2.2 亿彩票公布调查结果 昨天&#xff0c;闹得沸沸扬扬的《10 万中 2.2 亿》的彩票事件&#xff0c;迎来了官方公告。 简单来说&#xff0c;调查结果就是&#xff1a;一切正常&#xff0c;合规合法。 关于福利彩票事件&#xff0c;之前的推文我们已经分析过。 甚至在后面出现《…

鸿运(通天星CMSV6车载)主动安全监控云平台敏感信息泄露漏洞

文章目录 前言声明一、系统简介二、漏洞描述三、影响版本四、漏洞复现五、修复建议 前言 鸿运主动安全监控云平台实现对计算资源、存储资源、网络资源、云应用服务进行7*24小时全时区、多地域、全方位、立体式、智能化的IT运维监控&#xff0c;保障IT系统安全、稳定、可靠运行…

unity初学问题:如何修改图片的坐标

如图&#xff0c;我们想要修改图片的轴心点坐标&#xff08;Pivot&#xff09; 选择图片组 打开编辑器在里面修改即可&#xff08;最下面的Custom Pivot&#xff09;

golang使用gorm操作mysql1

1.mysql连接配置 package daoimport ("fmt""gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger" )var DB *gorm.DB// 连接数据库&#xff0c;启动服务的时候&#xff0c;init方法就会执行 func init() {username : "roo…

浅谈 Linux 网络编程 - 网络字节序

文章目录 前言核心知识关于 小端法关于 大端法网络字节序的转换 函数 前言 在进行 socket 网络编程时&#xff0c;会用到字节流的转换函数、例如 inet_pton、htons 等&#xff0c;那么为什么要用到这些函数呢&#xff0c;本篇主要就是对这部分进行介绍。 核心知识 重点需要记…

数仓项目6.0(二)数仓

中间的几步意义就在于&#xff0c;缓存中间处理数据样式&#xff0c;避免重复计算浪费算力 分层 ODS&#xff08;Operate Data Store&#xff09; Spark计算过程中&#xff0c;存在shuffle的操作&#xff0c;而shuffle会将计算过程一分为二&#xff0c;前一阶段不执行完&…

链表之“带头双向循环链表”

目录 ​编辑 1.链表的分类 2.带头双向循环链表的实现 1.创建结构体 2.创建返回链表的头节点 3.双向链表销毁 4.双向链表打印 5.双向链表尾插 6.双向链表尾删 7.双向链表头插 8.双向链表头删 9.双向链表查找 10.双向链表在pos的前面进行插入 11.双向链表删除pos位…

ECLIP

denote the representation of the positive prompt produced by the momentum model as h ξ i h_{\xi}^{i} hξi​ 辅助信息 作者未提供代码

蓝桥杯前端Web赛道-课程列表

蓝桥杯前端Web赛道-课程列表 题目链接&#xff1a;0课程列表 - 蓝桥云课 (lanqiao.cn) 题目要求如下&#xff1a; 分析题目我们发现其实就是需要我们手写一个分页的功能&#xff0c;根据题目的要求&#xff0c;分析如下 需要通过axios获取数据每页显示5条数据&#xff0c;默…

11.vue学习笔记(组件生命周期+生命周期应用+动态组件+组件保持存活)

文章目录 1.组件生命周期2.生命周期应用2.1通过ref获取元素DOM结构2.2.模拟网络请求渲染数据 3.动态组件3.1.A&#xff0c;B两个组件 4.组件保持存活&#xff08;销毁期&#xff09; 1.组件生命周期 每个Vue组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置…

Rocky Linux安装部署Elasticsearch(ELK日志服务器)

一、Elasticsearch的简介 Elasticsearch是一个强大的开源搜索和分析引擎&#xff0c;可用于实时处理和查询大量数据。它具有高性能、可扩展性和分布式特性&#xff0c;支持全文搜索、聚合分析、地理空间搜索等功能&#xff0c;是构建实时应用和大规模数据分析平台的首选工具。 …

Linux学习之system V

目录 一&#xff0c;system V共享内存 快速认识接口 shmget(shared memory get) shmat(shared memory attach) shmdt(shared memory delete) shmctl (shared memory control) 编写代码 综上那么共享内存与管道通信有什么区别&#xff1f; system v消息队列 system v信号…

【深度学习笔记】深度卷积神经网络——NiN

网络中的网络&#xff08;NiN&#xff09; LeNet、AlexNet和VGG都有一个共同的设计模式&#xff1a;通过一系列的卷积层与汇聚层来提取空间结构特征&#xff1b;然后通过全连接层对特征的表征进行处理。 AlexNet和VGG对LeNet的改进主要在于如何扩大和加深这两个模块。 或者&am…

Linux------进程地址空间

目录 一、进程地址空间 二、地址空间本质 三、什么是区域划分 四、为什么要有地址空间 1.让进程以统一的视角看到内存 2.进程访问内存的安全检查 3.将进程管理与内存管理进行解耦 一、进程地址空间 在我们学习C/C的时候&#xff0c;一定经常听到数据存放在堆区、栈区、…

4、正则表达式、本地存储

一、正则表达式 1、定义 用事先定义好的一些特定字符&#xff0c;这样的字符组合&#xff0c;组合成一个“规则字符串” 2、正则的组成 特殊字符 字母、数字、下划线、中文、特殊字符… 元字符&#xff08;常用&#xff09; 1、\d 匹配至少有一个数字 var reg /\d/ /…

SpringBoot整合rabbitmq-直连交换机队列(二)

说明&#xff1a;本文章主要是Direct定向/直连类型交换机的使用&#xff0c;它的大致流程是将一个队列绑定到一个直连交换机上&#xff0c;并赋予一个路由键 routingkey&#xff0c;当一个消息携带着路由值为routingkey&#xff0c;这个消息通过生产者发送给交换机时&#xff0…

【冲击蓝桥篇】动态规划(下):你还在怕动态规划!?进来!答题模板+思路解析+真题实战

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

Python中检查一个数字是否是科技数的完整指南

目录 前言 什么是科技数&#xff1f; 如何判断一个数字是否是科技数&#xff1f; 分割数字并计算平方 Python实现科技数检测的示例代码 科技数的应用场景 1. 数字游戏 2. 数据处理 3. 算法优化 4. 数据结构设计 总结 前言 科技数&#xff08;Tech Number&#xff09;是一…

(二十三)Flask之高频面试点

目录&#xff1a; 每篇前言&#xff1a;Q1&#xff1a;为什么把request和session放在一起&#xff1f;Q2&#xff1a;Local对象的作用&#xff1f;Q3:&#xff1a;LocalStack对象的作用&#xff1f;Q4&#xff1a;一个运行中的Flask应用程序分别包括几个Local/LocalStack&#…