哈希表如何避免冲突

 系列文章:

                 1.   先导片--Map&Set之二叉搜索树

                 2.   Map&Set之相关概念

                 3.   哈希表如何避免冲突

目录

1.概念

2. 冲突-概念

3. 冲突-避免

3.1 冲突-避免-哈希函数设计

3.2 冲突-避免-负载因子调节

4. 冲突-解决

4.1 冲突-解决-闭散列

4.1.1 线性探测

4.1.2.二次探测

4.2 冲突-解决-开散列/哈希桶

5. 冲突严重时的解决办法


1.概念

        在探讨数据结构的效率时,我们常常关注于查找操作的速度。 在顺序结构中,如数组或列表,查找一个元素通常需要遍历整个结构,这样的时间复杂度为O(N),其中N是元素的总数。 类似地,在平衡树结构中,查找的时间复杂度与树的高度相关,也大致是O(N)。显然,这两种结构的查找效率都依赖于搜索过程中进行的比较次数。

        理想的搜索方法应该能够无需任何比较,直接从表中获取所需元素。这可以通过构造一种特殊的存储结构来实现,即通过一种称为哈希函数(hashFunc)的映射关系,将元素的关键码直接映射到其存储位置。这样,在查找时,只需通过哈希函数即可快速定位到元素的具体位置。

具体操作如下:

        - 插入元素:根据待插入元素的关键码,使用哈希函数计算出该元素的存储位置,并按此位置存放元素。
        - 搜索元素:对待查找元素的关键码应用相同的哈希函数,将得到的函数值作为元素的存储位置。在结构中按此位置取元素进行比较,如果关键码相等,则搜索成功。

        这种方法被称为哈希(散列)方法,使用的转换函数称为哈希(散列)函数,而构造出来的数据结构称为哈希表(Hash Table)或散列表。哈希表的优势在于,它能够在平均情况下提供近乎常数时间的查找效率,即O(1),从而极大地优化了数据的访问速度。

哈希函数:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

根据上面这个公式,我们将1,2,3,6,7,8放入下面这个表格中,得到: 

在使用哈希表进行搜索时,由于通过哈希函数可以直接计算出数据存储的位置,因此通常不需要进行多次关键码的比较,这使得搜索速度非常快。然而,尽管这种方法看起来非常有效,但它并非没有潜在的问题。例如,当我们尝试向表中插入一个关键码为33的元素时,可能会出现哈希冲突问题。这是因为33计算的哈希值和3计算得出的哈希值相同,导致它们在哈希表中的位置发生冲突。

2. 冲突-概念

对于两个数据元素,其关键字分别为i和j,如果它们通过哈希函数计算得出的哈希地址相同,即Hash(i) == Hash(j),这种现象称为哈希冲突或哈希碰撞。在这种情况下,具有不同关键字但具有相同哈希地址的数据元素被称为“同义词”

3. 冲突-避免

首先,我们需要明确一点,由于哈希表底层数组的容量通常小于实际要存储的关键字的数量,冲突的发生是不可避免的。然而,我们可以通过一些方法来尽量降低冲突率。

3.1 冲突-避免-哈希函数设计

引起哈希冲突的一个主要原因可能是哈希函数的设计不够合理。在设计哈希函数时,应遵循以下几个原则

  • 定义域覆盖:哈希函数的定义域必须包括需要存储的全部关键码。
  • 值域适配:如果散列表允许有m个地址,哈希函数的值域必须在0到m-1之间。
  • 均匀分布:计算出来的地址应能均匀分布在整个空间中,以避免某些区域过于拥挤,而其他区域则几乎没有数据。
  • 简单高效:哈希函数应该比较简单,便于计算和实施。

常见的哈希函数包括:

1. 直接定制法(常用):
   取关键字的某个线性函数为散列地址,例如:Hash(Key) = A*Key + B。这种方法简单且能使得散列地址较均匀,但需要事先知道关键字的分布情况。适合查找比较小且连续的情况。

2. 除留余数法(常用):
   设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key % p(p <= m),将关键码转换成哈希地址。

3. 平方取中法(了解):
   假设关键字为1234,对它平方得到1522756,抽取中间的3位227作为哈希地址。这个方法比较适合不知道关键字的分布,而位数又不是很大的情况

4. 折叠法(了解):
   折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。适合事先不需要知道关键字的分布,适合关键字位数比较多的情况

5. 随机数法(了解):
   选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key) = random(key),其中random为随机数函数。通常应用于关键字长度不等时采用此法。

6. 数学分析法(了解):
   设有n个d位数,每一位可能有r种不同的符号。这些符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等;在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址

通过这些方法,我们可以尝试减少哈希冲突的发生,但在实际应用中,冲突仍然是无法完全避免的。因此,选择合适的冲突解决方法,如链地址法或开放定址法,对于提高哈希表的性能同样重要。

3.2 冲突-避免-负载因子调节

散列表的载荷因子定义:α = 填入表中的元素个数 / 散列表的长度

一般情况下,我们的哈希表会有一个默认的负载因子--0.75f 。

负载因子和冲突率的关系粗略演示

负载因子和冲突率成反比, 所以当冲突率达到一个无法忍受的程度时,我们需要通过降低负载因子来变相的降低冲突率。
已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。

4. 冲突-解决

解决哈希冲突 两种常见的方法是: 闭散列 开散列

4.1 冲突-解决-闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以 key 存放到冲突位置中的 下一个 空位置中去。 那如何寻找下一个空位置呢?

4.1.1 线性探测

以下是处理哈希冲突的线性探测法的具体步骤:

以插入元素33为例,我们首先使用哈希函数计算出其理论上的存储位置,即下标为3。按照预期,33应该被插入到这个位置。然而,在该位置我们发现已经存在一个值为3的元素,这时就发生了哈希冲突。

为了解决这种冲突,我们采用线性探测的方法。这意味着我们从发生冲突的位置(下标3)开始,依次向后查找,直到找到一个空的位置。假设这个空位置的下标为j,我们就将新元素33插入到这个位置。

具体步骤如下:

1. 计算哈希地址:通过哈希函数确定待插入元素的目标位置。
2. 检查冲突:如果计算出的位置已经有元素存在,说明发生了哈希冲突。
3. 线性探测:从冲突发生的位置开始,顺序探测下一个位置,直至找到一个空位。
4. 插入元素:在找到的空位处插入新元素。

在闭散列技术中,处理哈希冲突时需要特别注意,不能简单地物理删除哈希表中已有的元素。这是因为直接删除一个元素可能会影响其他元素的搜索。例如,如果我们直接删除了元素3,那么在查找元素33时,可能会因为缺少原始键值3的信息而受到影响。

为了解决这个问题,线性探测采用了一种标记的伪删除法。这种方法不真正删除元素,而是将其标记为“已删除”。具体操作如下:

1. 标记删除:当需要“删除”一个元素时,实际上并不从哈希表中移除它,而是在该位置做一个特殊标记,比如将该位置的值设置为一个特殊状态(如“null”或其他标识)。

2. 更新搜索:在搜索时,如果遇到被标记为“已删除”的位,则跳过该位继续探测,就像处理哈希冲突时的线性探测一样。

3. 插入和查找:对于插入和查找操作,遇到被标记为“已删除”的位置,也按照线性探测的方式处理,即继续向后寻找合适的位置。

这种方法的优点在于,它避免了因删除元素而导致的连锁反应,即一系列的元素都需要移动来填补被删除空间的情况。这样可以保持哈希表的稳定性,并减少由于元素移动引起的潜在错误。

总结来说,闭散列中的线性探测通过标记的伪删除法来处理元素的删除,确保了哈希表的完整性和搜索的正确性,同时最小化了由于删除操作带来的复杂性和性能损耗。

4.1.2.二次探测


线性探测的缺陷在于,当发生哈希冲突时,会导致数据在表中堆积起来。这是因为线性探测在寻找下一个空位置时,会顺序地检查表中的每个位置,直到找到空位为止。为了解决这个问题,二次探测方法被提出来改善这一状况。

二次探测方法在寻找下一个空位置时,采用的计算公式为:Hi = (H0 + i^2) \% m,或者 Hi = (H0 - i^2) \% m。其中,i = 1, 2, 3…,H0 是通过散列函数 Hash(x) 对元素的关键码 key 进行计算得到的位置,m 是表的大小。

例如,如果要插入元素33,而初始哈希位置已经有冲突,使用二次探测方法可以找到合适的插入位置。

研究表明,当表的长度为质数且表的装载因子 a 不超过0.5时,新的表项总是能够被插入,并且不会重复探查任何一个位置。这意味着只要表中有至少一半的空位置,就不会出现表满的情况。在搜索时,我们不需要担心表是否已满,但在插入时,必须确保表的装载因子 a 不超过0.5。如果超过这个阈值,就必须考虑增大表的容量。

因此,相比于线性探测,二次探测的最大优点在于减少了冲突的发生,提高了空间利用率。然而,哈希表的这些方法都有其固有的缺陷,即空间利用率相对较低。

4.2 冲突-解决-开散列/哈希桶

开散列法,也称为链地址法或开链法,首先使用散列函数为关键码集合计算散列地址。具有相同地址的关键码被归入同一个子集合,每个这样的子集合被称为一个“桶”。每个桶中的元素通过一个单链表链接起来,而这些链表的头结点则存储在哈希表中。

这种方法有效地将具有相同散列地址的元素组织在一起,通过单链表解决冲突问题,确保了元素的有序存储和高效访问。

5. 冲突严重时的解决办法

刚才我们提到了,哈希桶实际上可以将一个大集合的搜索问题转化为多个小集合的搜索问题。然而,如果冲突严重,这意味着这些小集合的搜索性能也会受到影响。在这种情况下,我们可以进一步优化这个所谓的小集合搜索问题,例如:

1. 每个桶的背后是另一个哈希表:这种方法实际上是对冲突元素进行再次散列,形成一个多层次的哈希结构。这样可以进一步减少冲突,但增加了操作的复杂性。

2. 每个桶的背后是一棵搜索树:这种方法使用搜索树(如二叉搜索树、平衡树等)来替代链表,用于处理那些冲突的元素。这样可以利用搜索树的特性,提高搜索、插入和删除的效率,尤其是在冲突较多的情况下。

这两种方法都是对基本哈希桶方法的改进,旨在提高在高冲突环境下的性能。选择合适的方法取决于具体的应用场景和数据特性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RMAN Backup Job is Completed Netbackup NBU

RMAN Backup Job is Completed But RMAN Sessions are Not Released in Database When Using Netbackup (Doc ID 2903139.1)​编辑To Bottom In this Document Symptoms Changes Cause Solution APPLIES TO: Oracle Database - Enterprise Edition - Version 19.8.0.0.0 and …

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质…

YOLOv8+Deepsort+PyQt+GUI 语义分割+目标检测+姿态识别 三者合一(集成于一套系统)综合视觉分析系统

综合视觉分析系统 技术栈&#xff1a; YOLOv8&#xff1a;用于目标检测&#xff0c;是一个快速且准确的目标检测框架。DeepSORT&#xff1a;用于目标跟踪&#xff0c;结合了深度学习特征提取和卡尔曼滤波器来预测目标轨迹。GUI&#xff1a;提供一个直观易用的图形用户界面&am…

云计算和传统IT相比,有哪些优势?

云计算相比于传统的IT基础设施&#xff0c;具有以下一些显著的优势&#xff1a; 成本效益&#xff1a; 云计算通常采用按需付费模式&#xff0c;用户只需为实际使用的资源支付费用&#xff0c;避免了高昂的前期硬件投资和维护成本。 弹性计费方式使得企业可以根据业务需求灵活调…

汽车免拆诊断案例 | 捷豹 E-type怠速不稳定

故障现象 一辆3.8E型直列6缸的捷豹&#xff0c;该车的故障原因表现为怠速不稳定&#xff0c;转速不均匀&#xff0c;以及在节气门全开&#xff08;WOT工况&#xff09;时“回火”和加速踏板不能及时提速。这是该车型一个值得关注的典型案例。车主表示&#xff0c;几年前&#…

C++ STL 适配器

系列文章目录 模板特例化&#xff0c;偏特化&#xff0c;左右值引用 https://blog.csdn.net/surfaceyan/article/details/126794013 C STL 关联容器 https://blog.csdn.net/surfaceyan/article/details/127414434 C STL 序列式容器(二) https://blog.csdn.net/surfaceyan/arti…

基于准静态自适应环型缓存器(QSARC)的taskBus万兆吞吐实现

文章目录 概要整体架构流程技术名词解释技术细节1. 数据结构2. 自适应计算队列大小3. 生产者拼接缓存4. 高效地通知消费者 小结1. 性能表现情况2. 主要改进3. 源码和发行版 概要 准静态自适应环形缓存器&#xff08;Quasi-Static Adaptive Ring Cache&#xff09;是taskBus用于…

STM32F407VET6开发板RT-Thread MSH 串口的适配

相关文章 STM32F407VET6开发板RT-Thread的移植适配 环境 STM32F407VET6 开发板&#xff08;魔女&#xff09;&#xff0c;http://www.stm32er.com/ Keil MDK5&#xff0c;版本 5.36 串口驱动 RT-Thread 通过适配 串口驱动&#xff0c;可以使用 MSH shell 当前手动搭建的 …

详细分析linux中的MySql跳过密码验证以及Bug(图文)

目录 1.问题所示2. 基本知识3. 解决方法3.1 跳过验证Bug3.2 设定初始密码 1.问题所示 发现密码验证错误&#xff0c;遗失密码 2. 基本知识 停止MySQL服务&#xff1a;sudo systemctl stop mysql 以跳过权限表模式启动MySQL&#xff1a;sudo mysqld_safe --skip-grant-tables …

论文解读 | ACL2024 Outstanding Paper:因果指导的主动学习方法:助力大语言模型自动识别并去除偏见...

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 点击阅读原文观看作者直播讲解回放&#xff01; 作者简介 孙洲浩&#xff0c;哈尔滨工业大学SCIR实验室博士生 概述 尽管大语言模型&#xff08;LLMs&#xff09;展现出了非常强大的能力&#xff0c;但它们仍然…

MATLAB-基于高斯过程回归GPR的数据回归预测

目录 目录 1 介绍 1. 1 高斯过程的基本概念 1.2 核函数&#xff08;协方差函数&#xff09; 1.3 GPR 的优点 1.4. GPR 的局限 2 运行结果 3 核心代码 1 介绍 高斯过程回归&#xff08;Gaussian Process Regression, GPR&#xff09;是一种强大的非参数贝叶斯方法&…

如何用GPU算力卡P100玩黑神话悟空?

精力有限&#xff0c;只记录关键信息&#xff0c;希望未来能够有助于其他人。 文章目录 综述背景评估游戏性能需求显卡需求CPU和内存系统需求主机需求显式需求 实操硬件安装安装操作系统Win11安装驱动修改注册表选择程序使用什么GPU 安装黑神话悟空其他 综述 用P100 PCIe Ge…

一台手机一个ip地址吗?手机ip地址泄露了怎么办

在数字化时代&#xff0c;‌手机作为我们日常生活中不可或缺的一部分&#xff0c;‌其网络安全性也日益受到关注。‌其中一个常见的疑问便是&#xff1a;‌“一台手机是否对应一个固定的IP地址&#xff1f;‌”实际上&#xff0c;‌情况并非如此简单。‌本文首先解答这一问题&a…

Linux_kernel移植rootfs10

一、动态更改内核 1、low level&#xff08;静态修改&#xff09; 【1】将led_drv.c拷贝到kernel/drivers/char/目录中 【2】修改当前目录下的Makefile文件 obj-y led_drv.o #将新添加的驱动文件加入到Makefile文件中 【3】退回kernel目录&#xff0c;执行make uImage …

C语言学习笔记 Day16(C10文件管理--下)

Day16 内容梳理&#xff1a; C语言学习笔记 Day14&#xff08;文件管理--上&#xff09;-CSDN博客 C语言学习笔记 Day15&#xff08;文件管理--中&#xff09;-CSDN博客 目录 Chapter 10 文件操作 10.5 文件状态 10.6 文件的随机读写 fseek()、rewind() &#xff08;1&…

【初阶数据结构】详解栈和队列(来自知识星空的一抹流光)

文章目录 前言1. 栈1.1 栈的概念及结构1.2 栈的实现1.2.1 "栈"实现的选择 1.3 栈的代码实现1.3.1 栈的结构体定义&#xff08;用的是顺序表&#xff09;1.3.2 栈的头文件设置1.3.3 栈的各功能的实现 2. 队列2.1 队列的概念及结构2.2 "队列"实现的选择2.3 队…

【即时通讯】轮询方式实现

技术栈 LayUI、jQuery实现前端效果。django4.2、django-ninja实现后端接口。 代码仓 - 后端 代码仓 - 前端 实现功能 首次访问页面并发送消息时需要设置昵称发送内容为空时要提示用户不能发送空消息前端定时获取消息&#xff0c;然后展示在页面上。 效果展示 首次发送需要…

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中&#xff0c; "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时&#xff0c;经常听到第一范式&#xff08;1NF&#xff09;、第二范式&#xff08;2NF&#xff09;、第三范式&#xff08;3NF&#xff09;以及 BCNF&#xff08;Boyce-…

滑动窗口在算法中的应用

滑动窗口是一种经典的算法技巧&#xff0c;就像在处理一系列动态数据时&#xff0c;用一扇可以滑动的“窗口”来捕捉一段连续的子数组或子字符串。通过不断地移动窗口的起点或终点&#xff0c;我们能够以较低的时间复杂度来解决一系列问题。在这篇文章中&#xff0c;我们将通过…

维信小程序禁止截屏/录屏

一、维信小程序禁止截屏/录屏 //录屏截屏,禁用wx.setVisualEffectOnCapture({visualEffect:hidden});wx.setVisualEffectOnCapture(Object object) 测试安卓手机&#xff1a; 用户截屏&#xff0c;被禁用 用户录屏&#xff0c;录制的是空白内容/黑色内容的视频。 二、微信小…