C++数据结构重要知识点(5)(哈希表、unordered_map和unordered_set封装)

news/2024/10/4 20:42:00/文章来源:https://blog.csdn.net/SGlow0708/article/details/141497548

1.哈希思想和哈希表

(1)哈希思想和哈希表的区别

哈希(散列、hash)是一种映射思想,本质上是值和值建立映射关系,key-value就使用了这种思想。
哈希表(散列表,数据结构),主要功能是值和存储位置建立映射关系它通过key-value模型中的key来定位数组的下标,将value存进该位置。

哈希思想和哈希表数据结构这两个概念要分清,哈希是哈希表的核心思想。

(2)unordered_map、unordered_set是什么?

C++11提供了新容器unordered_map、unordered_set,它们的底层都是hash,你可能会注意到这两个容器和set、map名字很像,其实这两个容器和map、set功能基本一样,都提供非常高效的搜索,但unordered_map、unordered_set中序遍历不是有序的,map、set中序有序不同

(3)哈希表的实现

由于unordered_map、unordered_set源于hash表,它们封装的方式和前面AVL树和红黑树的思路一致,所以本篇文章在封装这件事情上仅会简单讲解。

①初步流程

整个过程其实很好理解,就是一个一对一的函数关系,如果我们要存key,直接找到映射位置存进去即可,如果存的是key-value,单独提取key再做映射也是很轻松的。如果key是string等非整型类型,需要先转换一次,也就是需要两层映射。

②第一层映射

我们先前就说过,key有可能不是数字,所以这里要进行一次转换,为保证统一性,我们都写上转换函数,其中针对要处理的key写特化

这里的K就是key的类型,专门为string写了一个特化,其实库里面也是这么做的,string毕竟还是太常见了。

string直接将它的每一个字符对应的ASCII码值 * 31,最后加起来,对应转换后的key,经过它人的实验和证明,在这个时候重复的概率很低,比如"abcd"和"dcba"如果直接将ASCII值相加得到的转换的key就会重复。我们也可以自己去找转换的方式,这不是唯一的。

③第二层映射(哈希函数)

哈希函数是哈希里面最关键的函数,为什么?我们试想,如果我们按照取模的的思想,一个size为10的vector,10 % 10 == 0,所以10放在数组下标0这个位置。而当20要放进数组里,20 % 10 == 0,也要放在0,这个时候就冲突了,20就要放在10下一个位置,数组下标为1,这就是典型的哈希冲突。

哈希冲突其实是零和博弈的体现,即资源有限,不同的人之间互相竞争。

哈希冲突几乎无法避免,但可以通过不同的哈希函数缓解。

第一种哈希函数就是直接定址法,在计数排序中我们就见识过它了,它必须针对已知的数据来开辟数组。比如我明确知道要存放的数据范围是-200 ~ 600,我就直接开辟800个空间,保证所有数据都能不冲突地存放进来。这其实是用key的值映射一个绝对位置或相对位置。优点就是这种方法解决了哈希冲突并且效率高。但缺点最致命,就是不仅数据要集中,而且要事先知道数据的范围。这只能说过于严苛了,所以看似诱惑力大,但实际情况基本不用。

第二种哈希函数就是除留余数法,这也是最轻松、最好理解的办法。就是我前面举的例子,按照数组大小来取模确定位置。hashi = key % N, N是表的大小。这使得就算数据未知,范围波动大,但我们依然可以用取模的操作让它们强制约束在一个数组的范围内做选择。但接下来就必须面对另一个问题,哈希冲突。

④闭散列(开放定址法)

为什么叫闭散列?就是像我最开始举的那个例子,第一个位置不够了就去下一个位置,这个哈希冲突是在数组内部解决的,并没有向外部申请空间。

开放定址法又分为线性探测、二次探测、三次探测等。线性探测是把这个坑占了去找挨着的下一个,二次探测是是按照第一次走1^2,如果这个位置也被占了就走2^2,以此类推,三次探测也一样。这些都是缓解哈希冲突,不想让数据挨得太近,但也只是缓解了。

如果我们想要找到这个数据,我们只需要再次映射,先找到本来该待的位置,比较数据是否一样,一样就找到了,不一样就证明发生了哈希冲突,按照规则向后找。如果走到空还没找到就说明没有这个数据。

我们可以使用枚举来标明每个位置的状态。

哈希冲突算是解决了一部分,还个问题就是扩容怎么办,数组总是有限的,我们必须考虑扩容的情况。扩容后映射关系也变了,前面的所有数据要重新映射一次。

考虑到效率,当数组中的空位越来越少的时候,哈希冲突更容易发生,就像还剩一个位置的停车场走到哪基本都找不到停车位的。所以引入了负载因子,每添加一个数据就+1,删除-1,它和数组总大小之比大于0.6或0.7就说明很拥堵了,需要扩容。

这里也复用了insert,使得我们不用手写insert两次,后续代码如下

删除就极为简单,我前面说过可以给每个位置配一个枚举的状态。这里我们就可以直接修改标记。同时我们也要注意,删除的位置后面还有有效数据,当我们查找时要注意判空的条件要忽略掉删除部分。

⑤开散列(拉链法、哈希桶)

这种处理方式就是将vector设为指针数组,每个指针像单链表那样管理数据。我们一般将这种数组的每个位置叫做一个桶,很形象。当有数据映射到该位置时,我们就不需要向后走,直接像链子一样挂在桶里。这样不管增数据、找数据还是删数据都转换成了链表的操作,库里面就使用这种办法。

插入采用头插,这在链表中是效率最高的。

我前面说过,优秀的处理方法只会缓解哈希冲突而不会解决它,当每个桶足够深了,效率就变低了,我们仍然要引入负载因子来判断扩容。闭散列的负载因子永远不会超过1,因为它会被限制在数组大小内,而开散列可以很轻松地超过1。不过依然推荐负载因子控制在0.6 ~ 0.7之间。扩容过程和闭散列比起来就有点麻烦了。

由于是开散列,我们引入了额外的空间来处理哈希冲突,当我们扩容对原来的数据进行重映射时,我们自然希望继续利用好我们的空间,也就是直接将指针交给新的位置保管而不是先析构、再构造,这样每次扩容的消耗会很大,而且这是无用消耗,很值得我们去优化,所以复用insert的思路就不可行了。

思路捋清楚,其实也很简单。遍历,找到不为空的指针就遍历这个桶,为每个指针找到新的位置,将这个指针头插进新位置即可。

删除、查找纯粹是单链表的知识,就不多阐述了。

有的极端情况会出现不管怎么扩容,一个桶下面挂的数据也很多,这个时候有的会将单链表处理为红黑树,当然这个仅作了解。

2.unordered_map、unordered_set封装

查找上unordered_set的findO(1),set是O(logN),插入有序的时候红黑树性能更好,旋转次数少,在很多场景下unordered_map、unordered_set有自己的优势。

下面仅以unordered_map做简单讲解

(1)hash实现

和AVL树和红黑树一样,我们要传key,实际数据类型,以及实际数据类型中的key

Hash是第一次转换函数,不要搞混了

创建结点的方式也要改变,使其更兼容两种类型

(2)unordered_map实现

这里展示的是整体框架,注意传参,以及仿函数的调用,这些熟悉后其实也挺简单的

(3)迭代器实现

这又是老生常谈的问题,怎么找下一个结点。我们知道了当前结点的位置,如何找到下一个?所以直接传哈希表的指针是最优解。

在直接定址法里,找下一个很轻松,直接pos++即可

在哈希桶里,我们要先向前探一探,看看链表还有没有下一个结点,没有的话就走到下一个桶。

注意要先判断桶走完没

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

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

相关文章

linux基础IO——动静态库——进程编址、进程执行、动态库加载

前言:本节内容为基础IO部分的最后一节, 主要是为了讲一下动静态库里面的动态库如何加载到内存, 动态库的地址等等。 但是,这些内容牵扯到了程序的编址, 程序的加载, 进程的执行等等知识点, 所以…

【知识分享】MQTT实战-使用mosquitto客户端连接emqx服务器

一、简介 MQTT(Message Queuing Telemetry Transport)是一种轻量级的、基于发布/订阅模式的通信协议,旨在实现物联网设备之间的低带宽、高延迟的通信。MQTT协议设计简洁,使用TCP/IP协议进行通信,适用于各种网络环境&am…

145-Linux权限维持Rootkit后门Strace监控Alias别名Cron定时任务

参考 【权限维持】Linux&Rootkit后门&Strace监控&Alias别名&Cron定时任务_alias lsalerts(){ ls $* --colorauto;python -c "-CSDN博客 参考 FlowUs 息流 - 新一代生产力工具 权限维持-Linux-定时任务-Cron后门 利用系统的定时任务功能进行反弹Shell 1…

字节跳动一面

字节跳动一面【C后端开发】 base : 深圳 岗位:C后端开发 时间: 2024/8/30 文章目录 基本介绍C语言1. 堆栈内存是否连续,为什么?2. int i0; i ; 两个线程同时执行10000次,i最终的数值是多少?3.…

Mysql中的锁机制详解

一、概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中,除了传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决…

Golang | Leetcode Golang题解之第392题判断子序列

题目&#xff1a; 题解&#xff1a; func isSubsequence(s string, t string) bool {n, m : len(s), len(t)f : make([][26]int, m 1)for i : 0; i < 26; i {f[m][i] m}for i : m - 1; i > 0; i-- {for j : 0; j < 26; j {if t[i] byte(j a) {f[i][j] i} else {…

不会Excel怎么制作桑基图?用什么软件绘制比较好呢?推荐2款简单好用的图表制作工具

桑基图制作很简单&#xff0c;不需要任何基础一次就会&#xff01; 2个桑基图制作工具&#xff0c;帮你一键解决问题~ 1、Dycharts 推荐指数&#xff1a;☆☆☆☆☆ 点击链接直达>>dycharts.com Dycharts是国内一款专业的在线图表制作工具&#xff0c;0代码、无门槛&…

Linux Debian12安装原生版微信

1.原生版微信下载地址&#xff1a; https://archive.ubuntukylin.com/software/pool/partner/找到weixin&#xff0c;2022年05月23日最新版本&#xff0c;weixin_2.1.4_amd64.deb&#xff0c;下载。 2.微信安装&#xff1a; sudo dpkg -i weixin_2.1.4_amd64.deb3.登陆即可。…

C语言程序与设计第四版课后习题 - 1~8章大合集

前言 本文章是一个大合集&#xff0c;按照课后习题的命名方式命名&#xff0c;方便寻找&#xff0c;只需要在目录上点相对应的题号即可在这里插入图片描述 第一章课后习题 1.1 编写一个C程序 题目概述&#xff1a; 请参照本章例题&#xff0c;编写一个C程序&#xff0c;输…

一文说清什么是AI原生(AI Native)应用以及特点

引言&#xff1a;智能新纪元 如今&#xff0c;走在街头&#xff0c;哪儿不被智能科技包围&#xff1f;智能音箱、自动驾驶汽车、聊天机器人......这些都在用不同的方式提升我们的生活体验。然而&#xff0c;究竟什么才能称得上“AI原生应用”呢&#xff1f; 什么是AI原生&…

短信PHP接口平台可以为企业带来哪些优势

短信验证码在我们的日常生活中可以说是无处不在&#xff0c;并且短信验证码目前在市场中已经得到了广泛的使用&#xff0c;这种验证方法可以保证注册人事实名认证&#xff0c;并且可以防止恶意注册&#xff0c;不过也有人觉得短信验证码有一些累赘&#xff0c;那么短信验证码真…

GUI编程08:画笔paint

本节内容视频链接&#xff1a;10、画笔paint_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p10&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 package com.yundait.lesson03;import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.…

快排Java

快速排序的复杂度 快排代码 package leetcode;import java.util.Arrays;public class QuickSort {public static void quickSort(int[] array, int low, int high) {if (low < high) {int pivotIndex partition(array, low, high);quickSort(array, low, pivotIndex - 1);…

【JAVA基础】StringUtils.isEmpty、StringUtils.isBlank()、Objects.isNull()三者区别

&#x1f4dd;个人主页&#x1f339;&#xff1a;个人主页 ⏩收录专栏⏪&#xff1a;日常经验 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339;&#xff0c;让我们共同进步&#xff01; 总是区分不清楚这几个的差别&#xff1a;我们来直接验证一下&#…

Computer Exercise

每日一练 单选题 在计算机机箱前面板接口插针上&#xff08;     C   &#xff09;表示复位开关。 A.SPK    B.PWRLED    C.RESET    D.HDDLED每台PC机最多可接&#xff08;     B   &#xff09;块IDE硬盘。 A.2    B.4    C.6    D.8&#xff08;    …

linux服务器之top命令详解

top&#xff1a;系统资源管理器 top命令类似于windows的任务管理器&#xff0c;可以查看内存、cpu、进程等信息(动态查看系统资源信息)在linux系统中常用top命令查看资源性能分析工具 一、参数释义&#xff1a; 第一行 系统时间和平均负载 top&#xff1a;名称22:12:46&#…

【word导出带图片】使用docxtemplater导出word,通知书形式的word

一、demo-导出的的 二、代码操作 1、页面呈现 项目要求&#xff0c;所以页面和导出来的word模版一致 2、js代码【直接展示点击导出的js代码】 使用插件【先下载这五个插件&#xff0c;然后页面引入插件】 import docxtemplater from docxtemplater import PizZip from pizzip …

Java预备知识 - day2

1.IDEA的简单使用与介绍 1.1 IDEA的项目工程介绍 Day2_0904&#xff1a;项目名称 E:\0_code\Day2_0904&#xff1a;表示当前项目所在路径 .idea&#xff1a;idea软件自动生成的文件夹&#xff0c;最好不要动 src&#xff1a;srcsourse→源&#xff0c;我们的源代码就放在这…

HumanNeRF:Free-viewpoint Rendering of Moving People from Monocular Video 翻译

HumanNeRF&#xff1a;单目视频中运动人物的自由视点绘制 引言。我们介绍了一种自由视点渲染方法- HumanNeRF -它适用于一个给定的单眼视频ofa人类执行复杂的身体运动&#xff0c;例如&#xff0c;从YouTube的视频。我们的方法可以在任何帧暂停视频&#xff0c;并从任意新的摄…

Linux环境中安装java环境(JDK8环境)

需求背景&#xff1a; 给国产服务器&#xff08;银河麒麟V10&#xff09;中安装项目运行环境&#xff0c;安装java环境&#xff01;具体如下 下载jdk包 访问Oracle官网下载jdk包&#xff1a;Java Downloads | Oracle 中国 选择对应的cpu架构进行下载 https://download.csdn.…