哈希表、哈希冲突解决办法

news2024/11/24 20:45:35

文章目录

    • 一、什么是哈希表?
    • 二、什么是哈希冲突?怎样解决?
    • 三、哈希表的大小为什么是质数?
    • 四、链表法
    • 五、开放地址法
      • 线性探测法
      • 平方探测法
      • 双哈希(Double Hashing)
    • 六、哈希表满了怎么办?
    • 七、完美哈希
    • 八、一些使用哈希解决的算法题

在我以往的认知中,哈希就是进行唯一映射,指定一个关键字,就能映射唯一的值。平时经常使用linux下的 md5sum命令用来比较两个文件是否相同。至于如何哈希原理,它们是怎样进行唯一映射的,一直没有去梳理,如今该填的洞还是得补上。

一、什么是哈希表?

我们知道,普通数组能够直接寻址,在O(1)时间内能访问到数组中的任意位置。它需要足够大的空间,为每一个关键字保留一个位置。当关键字取值的范围很大,储存空间又有限时,能不能同样用数组的形式实现O(1)查找?

哈希表(Hash Table)就是这样的数据结构,当实际储存的关键字集合,比所有可能的关键字的全集小许多时,使用一个长度有限的数组去储存这些关键字,从而节省大量的空闲空间。它也被称作为散列表,因为它的键是分散存储在数组中的。

如图,黄色区域为键的全集,范围为0~99,绿色区域为实际存储的键。直接寻址需要为所有的键开辟空间,而哈希映射仅需为需要储存的键分配空间,储存空间比直接寻址少得多。

在这里插入图片描述

哈希表的核心思想是将键转换为数组的索引位置,以便快速查找对应的值。它使用哈希函数将键映射到数组的索引位置,这个索引位置称为哈希值。哈希映射是唯一映射,一个键永远只能产生一个哈希值,这种映射关系,就是我们所说的哈希函数。常见的哈希函数包括MD5、SHA-1、SHA-256等。

二、什么是哈希冲突?怎样解决?

通过取某运算,保持了哈希表中查找一个元素只要O(1)时间的优势。但细心的朋友会发现,上图中当键为7,17时,两个键会映射到同一个哈希值。我们称这种情形为哈希冲突,由于哈希函数不够“随机”,不同的键通过哈希函数计算得到相同的哈希值。

有人会说,简单,将哈希表增大,某上一个更大的值函数不就行了吗?可是可以,但哈希的精髓在于尽可能的节省空间与查找时间。最普通的哈希表,就是一个直接寻址的数组。不过,还有其它更高效的方法解决哈希冲突,如链表法,开放地址法等,这个后面再讲。

当哈希表中存储的键越来越多时,冲突的概率会不断增大,总会有哈希表再也装不下键的时候。所以,合理的控制一个哈希表的大小也极为重要。通常,将哈希表的大小设置为一个质数,是需要储存的键的数量的两倍。

三、哈希表的大小为什么是质数?

当哈希表的大小选择为一个合数(非质数)时,键的哈希值可能与哈希表的大小存在公因子。这种情况下,多个键的哈希值可能会映射到相同的索引位置,导致哈希冲突的概率增加。

让我们通过一个例子来说明这个问题。假设我们有一个哈希表大小为10,键的哈希函数将键映射到0到9的索引位置。我们有两个键:15和25。它们的哈希值分别为5和5。由于它们的哈希值都映射到相同的索引位置5,发生了哈希冲突。

倘若我们选择一个质数作为哈希表的大小,比如13,再次计算这两个键的哈希值。键15的哈希值为2,键25的哈希值为12。由于13是质数,与13存在公因子的数字较少,因此键的哈希值在13以内相同的概率会少很多。

通过选择质数作为哈希表的大小,可以减少键的哈希值与哈希表大小之间存在公因子的情况,从而减少哈希冲突的概率。这样可以更均匀地分布键值对,提高哈希表的性能和效率。

四、链表法

链表法其实很简单,就是将映射到同一地址的元素都放在一个链表中。哈希表中每个槽都有一个指针,指向所有映射到同一位置的元素链表表头,如果当前槽位没有这样的元素,头指针为空。

在这里插入图片描述

采用双向链表结构是为了删除与插入操作都能控制时间在O(1)范围内。每一个槽位链表的长度就是它的负载因子。通过上述结构不难看出,查找需要遍历链表,查找的时间取决于链表的长度。并且,当散列不均匀时,某一些链过长,有一些链很短,查找的效率也是不稳定的。

所以,一个好的哈希函数不应该将太多的输入映射到相同的输出。

五、开放地址法

除了链表法解决哈希冲突外,还可以通过开放地址的方式来解决哈希冲突。之所以叫开放地址,是因为它们都是通过将冲突的键,存放到哈希表空闲的其它地址中去,将未使用的地址开放出来,直到表空间耗尽。开方地址有多种方法,下面一一介绍。

线性探测法

探测(Probe),是指当地址冲突后,会继续去寻找表中可用的地址。线性探测,则是顺着当前位置向后查找。

如图,将关键字以线性探测的方式插入到哈希表中,哈希表的长度为13。

在这里插入图片描述

不难看出,线性探测随着填充因子的增加,冲突会聚集在一起,形成聚集效应。负载因子需要控制在70%左右,否则冲突次数飙升。数据分布本身不均匀也会导致冲突分布不均匀。线性探测法实现简单,但随着负载增加,性能下降明显。

平方探测法

如果遇到冲突,就往(原始位置 + i2)的位置寻找空位,i为查找次数。

如图:

在这里插入图片描述

平方探测法是解决哈希冲突的一种开放地址法。当发生哈希冲突时,它按一定的平方序列探测下一个位置。

优点:

  1. 降低了聚集效应。冲突分配更加均匀。
  2. 减少了聚簇内的查找时间。
  3. 可以提高负载因子。平均查找长度较短。

缺点:

  1. 需要计算平方序列值,实现较为复杂。
  2. 当Step超过表大小时计算会溢出。
  3. 随机性较差,数据分布不均会影响性能。
  4. 二次方探测发生冲突的概率较大。
  5. 删除操作会产生空洞,需要惰性删除避免影响。

总体而言,平方探测法通过牺牲部分实现复杂度和冲突计算时间来减轻聚集效应,在较高负载下性能较好。仍需要考虑非全域性问题。

什么是惰性删除?

惰性删除(Lazy deletion) 是一种常用的哈希表优化方法,主要应用于开放定址法中的删除操作。其基本思路是:

  1. 当删除键值对时,先将其标记为"已删除",而不直接从散列表中删除记录。
  2. 对后续插入操作,会扫描标记的已删除槽位并进行插入。
  3. 直到被后续插入覆盖,标记的删除状态才真正删除。

优点是可以避免删除后产生的空槽位,保证使用空间的连续性,减少冲突次数,提高效率。

缺点是需要扫描已删除槽位,增加查询的开销。

主要应用在平方探测法、线性探测法等开放定址中的删除操作,使得开放定址法的执行效率大为提升。是哈希表优化的重要手段之一。也可视作是一种延迟紧缩、空间换时间的实现。

双哈希(Double Hashing)

除了第一个哈希函数外,还有一个哈希函数用于解决冲突。

如果遇到冲突,新位置 = 原始位置 + i * hash2(key),i为查找次数。

hash2(key) = R - (key % R)
R要取比数组尺寸小的质数。
例如,哈希表长度为13,R = 7,hash2(key) = 7 - (key % 7)
也就是说,第二次哈希结果在1-7之间,不会等于0。

举例:
在这里插入图片描述

双哈希法的主要优点:

  1. 采用双重散列,冲突分布更加均匀。
  2. 相比线性探测,可以有效减少聚集问题。
  3. 指数增长级别更低,平均查找长度较短。
  4. 算法和实现都比较简单。
  5. 允许删除而不产生过多空槽。

双哈希法通过两次散列的思想,改进了性能和冲突分布,是一种效率较好的开方地址方法。

六、哈希表满了怎么办?

再哈希(rehash)

  • 当哈希表的插槽被占用了70%后,查找效率会严重下降,需要对哈希表进行扩展。

  • 新表长度为原来长度的2倍,且为质数。

  • 将旧表中的关键字,通过新的哈希函数,重新填充到新表中。

由此,我们也能看到哈希表有以下缺点:

  • 当表中的元素逐渐增多时,哈希冲突的概率会增加,查找效率会下降。

  • 哈希表扩展时,需要将旧表的关键字重新哈希,迁移到新表中,性能成本大。

  • 一个好的哈希表,依赖哈希函数的合理性。

七、完美哈希

完美哈希(Perfect Hashing),是一种解决哈希冲突的方法,它通过构建一个无冲突的哈希函数来实现。

完美哈希通过两级哈希的方式来解决哈希冲突。首先,使用一个一级哈希函数将键映射到一组桶(bucket)中。然后,在每个桶中,使用二级哈希函数将键映射到具体的索引位置。通过这种方式,每个键都被映射到唯一的索引位置,从而实现了完全散列。

完美哈希在某些特定场景下非常有用,特别是当键的集合是已知的且静态的情况下。它可以在预处理阶段构建完全散列函数,然后在查询时实现O(1)的查找时间复杂度,而无需处理冲突。然而,构建完美哈希函数的过程可能会比较复杂且耗时,特别是在键的集合较大的情况下。

需要注意的是,完美哈希并不适用于动态的键集合,因为当键的数量发生变化时,可能需要重新构建哈希函数。

八、一些使用哈希解决的算法题

  1. 两数之和:给定一个整数数组和一个目标值,找出数组中和为目标值的两个数的索引。可以使用哈希表来存储数组元素和其索引的映射关系,然后遍历数组,查找目标值与当前元素的差值是否存在于哈希表中。
    两数之和 https://blog.csdn.net/qq_41099859/article/details/112464038

  2. 两个数组的交集:给定两个整数数组,求它们的交集。可以使用哈希集合来存储一个数组中的元素,然后遍历另一个数组,判断元素是否存在于哈希集合中。
    两个数组的交集 https://blog.csdn.net/ShenHang_/article/details/107319514

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

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

相关文章

PGP 遇上比特币

重复使用 PGP 密钥作为比特币密钥 介绍 在数字安全领域,密码学在确保数据的完整性和真实性方面发挥着至关重要的作用。 一种广泛使用的加密技术是使用 Pretty Good Privacy (PGP1)。 PGP 为安全通信(例如电子邮件、文件传输和数据存储)提供加…

4G执法记录仪在高铁、地铁、机场应急处突中的应用

4G执法记录仪:高铁、地铁、机场紧急应对新利器 随着时代的迅猛发展,公共交通安全管理面临着越来越复杂的挑战。其中,高铁、地铁、机场这类人流密集的区域,要求高效、准确的应急响应与指挥调度机制。在这种背景下,4G/5G执法记录仪…

大数据平台/大数据技术与原理-实验报告--MapReduce编程

实验名称 MapReduce编程 实验性质 (必修、选修) 必修 实验类型(验证、设计、创新、综合) 综合 实验课时 2 实验日期 2023.10.30-2023.11.03 实验仪器设备以及实验软硬件要求 专业实验室(配有centos7.5系统…

burpsuite issue definitions

https://portswigger.net/burp/documentation/scanner/vulnerabilities-list 先从高危的开始学(四十能学剑,时人无此心): os command injection todo 未完待续

element 的 Notification 通知,自定义内容

通知事件: // 商户后台通知 MerchantBackgroundNotice() {// 禁止消息通知弹出多条if(this.notifyInstance) {this.notifyInstance.close();}const h this.$createElement; // 创建文本节点this.notifyInstance this.$notify({showClose: false, // 禁止关闭按钮…

Python语言学习笔记之三(字符编码)

本课程对于有其它语言基础的开发人员可以参考和学习,同时也是记录下来,为个人学习使用,文档中有此不当之处,请谅解。 什么是字符编码 计算机从本质上来说只认识二进制中的0和1,字符编码(Character Encoding) 是一种将…

Java(八)(可变参数,Collections,小案例:斗地主游戏小案例:斗地主游戏,Map集合,Stream流)

目录 可变参数 Collections 小案例:斗地主游戏 Map集合 Map的常用方法 map集合的遍历 键找值 键值对 Lambda 表达式 HashMap底层原理 集合的嵌套 Stream流 获取集合或数组的Stream流 Stream流的方法 可变参数 就是一种特殊的形参,定义在方法和构造器的形参列表中,…

视频没有字幕怎么办,怎么给视频增加字幕

文章目录 视频没有字幕怎么办,怎么给视频增加字幕前言软件准备制作字幕1. 导入视频2. 将视频拖拽到轨道3. 生成字幕4. 导出字幕 字幕实时翻译1. 播放视频2. 显示字幕设置3. 双语字幕显示 总结 视频没有字幕怎么办,怎么给视频增加字幕 前言 有时候下载的…

传音荣获2023首届全国人工智能应用场景创新挑战赛“智能家居专项赛”三等奖

近日,中国人工智能学会与科技部新一代人工智能发展研究中心联合举办2023首届全国人工智能应用场景创新挑战赛(2023 1st China’s Innovation Challenge on Artificial Intelligence Application Scene,以下简称CICAS 2023),吸引了…

10 个例子带你学会 AI 编程(含提示词)

大家好,我是伍六七。 AI 编程是一个程序员群体普遍关注的领域,但是真的使用 AI 编程实现提效的还是少数。 有的人没有大模型资源,有的人不知道可以在哪些方面使用 AI 进行提效,还有的人不相信使用 AI 可以提效。 今天&#xff…

国产Ai大模型和chtgpt3.5的比较

下面是针对国产大模型,腾讯混元,百度文心一言,阿里通义千问和chatgpt的比较,最基础的对一篇文章的单词书进行统计,只有文心一言和chatgpt回答差不多,阿里和腾讯差太多了

【机器学习】迁移学习

迁移学习:给定一个有标记的源域和一个无标记的目标域。这两个领域的数据分布不同。迁移学习的目的就是要借助源域的知识,来学习目标域的知识(标签)。或是指基于源域数据和目标域数据、源任务和目标任务之间的相似性,利用在源领域中学习到的知…

【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧拉角

专栏系列文章如下: 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——Eigen库 本章将介绍视觉SLAM的基本问题之一:如何…

【开源】基于JAVA的天然气工程运维系统

项目编号: S 022 ,文末获取源码。 \color{red}{项目编号:S022,文末获取源码。} 项目编号:S022,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程…

Win11修改用户名(超详细图文)

新买的电脑一般预装Windows11系统(家庭与学生版),新电脑初次开机使用微软邮箱账号登录,则系统将用户名自动设置成邮箱前5位字符。我的用户名便是一串数字【231xx】(qq邮箱前5位),看着很不舒服&a…

Docker搭建个人网盘NextCloud并接入雨云对象存储的教程

雨云服务器使用Docker搭建私有云盘NextCloud并接入雨云对象存储ROS的教程。 NextCloud简介 NextCloud由原ownCloud联合创始人Frank Karlitschek创建的,继承原ownCloud的核心技术又有不少的创新。在功能上NextCloud和ownCloud差不多,甚至还要丰富一些&a…

C语言:输入一行字符,分别统计出其中英文字母、空格、数字和其他字符的个数

分析: 在主函数 main 中,程序首先定义一个字符变量 c,以及四个整型变量 letters、k、s 和 o,并初始化它们的值为 0。然后使用 printf 函数输出提示信息,让用户输入一行字符。 接下来,程序通过 while 循环结…

编程难点:常见问题及解决方案

目录 1 前言2 学习成本高2.1 学习成本高的问题2.2 学习成本高的解决方法 3 程序bug多3.1 程序bug多的问题 4 程序的性能调试4.1 程序的性能问题4.1 程序的性能调试方法 5 跨平台兼容性差5.1 跨平台兼容问题5.1 跨平台兼容问题的解决方法 6 解决技术难题的方法总结7 总结 1 前言…

pikachu靶场:php反序列化漏洞

pikachu靶场:php反序列化漏洞 文章目录 pikachu靶场:php反序列化漏洞代码审计漏洞利用 代码审计 像这种反序列化的代码基本都是代码审计出的 // 定义一个名为S的类,该类有一个属性$test和一个构造函数 class S{ var $test "pikachu"; // $test是一个…

大数据平台/大数据技术与原理-实验报告--实战HDFS

实验名称 实战HDFS 实验性质 (必修、选修) 必修 实验类型(验证、设计、创新、综合) 综合 实验课时 2 实验日期 2023.10.23-2023.10.27 实验仪器设备以及实验软硬件要求 专业实验室(配有centos7.5系统的linu…