哈希表如何避免冲突
系列文章:
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.概念
理想的搜索方法应该能够无需任何比较,直接从表中获取所需元素。这可以通过构造一种特殊的存储结构来实现,即通过一种称为哈希函数(hashFunc)的映射关系,将元素的关键码直接映射到其存储位置。这样,在查找时,只需通过哈希函数即可快速定位到元素的具体位置。
具体操作如下:
- 插入元素:根据待插入元素的关键码,使用哈希函数计算出该元素的存储位置,并按此位置存放元素。
- 搜索元素:对待查找元素的关键码应用相同的哈希函数,将得到的函数值作为元素的存储位置。在结构中按此位置取元素进行比较,如果关键码相等,则搜索成功。
这种方法被称为哈希(散列)方法,使用的转换函数称为哈希(散列)函数,而构造出来的数据结构称为哈希表(Hash Table)或散列表。哈希表的优势在于,它能够在平均情况下提供近乎常数时间的查找效率,即O(1),从而极大地优化了数据的访问速度。
哈希函数:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。
根据上面这个公式,我们将1,2,3,6,7,8放入下面这个表格中,得到:

在使用哈希表进行搜索时,由于通过哈希函数可以直接计算出数据存储的位置,因此通常不需要进行多次关键码的比较,这使得搜索速度非常快。然而,尽管这种方法看起来非常有效,但它并非没有潜在的问题。例如,当我们尝试向表中插入一个关键码为33的元素时,可能会出现哈希冲突问题。这是因为33计算的哈希值和3计算得出的哈希值相同,导致它们在哈希表中的位置发生冲突。
2. 冲突-概念
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 冲突-解决-闭散列
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. 每个桶的背后是一棵搜索树:这种方法使用搜索树(如二叉搜索树、平衡树等)来替代链表,用于处理那些冲突的元素。这样可以利用搜索树的特性,提高搜索、插入和删除的效率,尤其是在冲突较多的情况下。
这两种方法都是对基本哈希桶方法的改进,旨在提高在高冲突环境下的性能。选择合适的方法取决于具体的应用场景和数据特性。
相关文章:
哈希表如何避免冲突
系列文章: 1. 先导片--Map&Set之二叉搜索树 2. Map&Set之相关概念 3. 哈希表如何避免冲突 目录 1.概念 2. 冲突-概念 3. 冲突-避免 3.1 冲突-避免-哈希函数设计 3.2 冲突-避免-负载因子调节 4. 冲突-解决 4.1 冲突-解决-闭散列 4.1.1 线性探…...
内核模块驱动开发
内核模块开始学习前,一定是最先接触到内核模块三要素(面试),驱动入口、驱动出口和协议的遵循。 1.内核模块三要素(面试)//修饰模块化驱动的入口函数module_init(demo_init);//修饰模块化驱动的出口函数module_eixt(demo_exit);//遵循GPL开源协议MODULE_…...
Linux 下 alsa 库录音并保存为 WAV 格式
麦克风列表: [jnjn build]$ arecord -l **** List of CAPTURE Hardware Devices **** card 0: AudioPCI [Ensoniq AudioPCI], device 0: ES1371/1 [ES1371 DAC2/ADC]Subdevices: 1/1Subdevice #0: subdevice #0 card 1: Camera [2K USB Camera], device 0: USB Aud…...
使用stripe进行在线支付、退款、订阅、取消订阅功能(uniapp+h5)
stripe官网:Stripe 登录 | 登录 Stripe 管理平台 然后在首页当中打开测试模式,使用测试的公钥跟私钥进行开发 测试卡号 4242 4242 4242 4242 1234 567 在线支付 stripe的在线支付有两种,第一种就是无代码,第二中就是使用api进行自定义,一般来说推荐第二种进行开发 无…...
深度学习中常见的损失函数
关注B站可以观看更多实战教学视频:hallo128的个人空间 深度学习中常见的损失函数 损失函数的作用 损失函数是衡量神经网络输出与真实标签之间差距的指标。在训练过程中,神经网络的目标是最小化损失函数的值。常见的损失函数包括均方误差(MS…...
认识Linux及Linux的环境搭建
目录 1、什么是Linux2、Linux环境搭建2.1 下载安装 Xshell2.2 下载安装 VMware Workstation Pro2.3 选择适合自己系统 1、什么是Linux Linux,一般指GNU/Linux(单独的Linux内核并不可直接使用,一般搭配GNU套件,故得此称呼ÿ…...
Java之线程篇三
目录 线程状态 观察线程的所有状态 线程状态及其描述 线程状态转换 代码示例1 代码示例2 线程安全 概念 线程不安全的代码示例 线程不安全的原因 线程安全的代码示例-加锁 synchronized关键字 synchronized的特性 小结 形成死锁的四个必要条件 …...
Bootstrap动态设置表格title项
页面searchType <form id"formId"><div class"select-list"><ul><li><select name"searchType" id"searchType"><option value"1">按各节点统计</option><option value"…...
Arrays.sort()方法在Java中的使用:理论与实践
目录 一.概述 二.实现方式 三.具体介绍 1.基本数据类型数组 2.对象数组 1)使对象实现Comparable接口 2)为对象再专门实现一个比较器类 四.进阶技巧 1.基础类型数组实现自定义比较 2.如何进行逆序排序 3.lambda表达式实现比较器类 4.List的排序方法Collection.sort()…...
用AI写论文,千万不要这样用ChatGPT生成参考文献References!!
ChatGPT作为一种先进的语言大模型,被广泛用于生成文本,虽然用ChatGPT辅助论文写作已是大势所趋,但是,用于生成参考文献References的部分还是要谨慎对待。 在学术写作中,参考文献References扮演着至关重要的角色&#…...
Debian 12如何关闭防火墙
在Debian 12中,默认的防火墙管理工具是ufw(Uncomplicated Firewall)。您可以使用以下命令来关闭防火墙: 关闭防火墙: sudo ufw disable查看防火墙状态: sudo ufw status如果需要重新开启防火墙:…...
windows C++-并行编程-PPL任务并行(二)
延续任务 在异步编程中,一个异步操作在完成时调用另一个操作并将数据传递到其中的情况非常常见。 传统上,这使用回调方法来完成。 在并发运行时中,延续任务提供了同样的功能。 延续任务(也简称为“延续”)是一个异步任务,由另一个…...
快速了解 servlet(SpringMVC 的底层)
Servlet 是 Java EE(现 Jakarta EE)中用于处理 Web 请求的核心组件。它在 Web 应用程序的服务器端运行,负责接收和处理客户端(如浏览器)的请求,并生成响应。 尽管现代Web开发更多采用SpringMVC等框架&…...
QT中tr的作用是什么
在Qt框架中,tr() 函数是一个非常重要的宏,它用于国际化和本地化(i18n和l10n)支持。tr() 函数使得Qt应用程序能够根据不同的语言环境(locale)显示相应的翻译文本,从而支持多种语言。 具体来说&a…...
OpenCV结构分析与形状描述符(7)计算轮廓的面积的函数contourArea()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 计算轮廓的面积。 该函数计算轮廓的面积。与 moments 类似,面积是使用格林公式计算的。因此,返回的面积与你使用 drawCo…...
内网环境使用Docker部署Qwen2模型-vLLM篇
在此之前,我们已成功利用Docker与Ollama框架,在内网环境中部署了Qwen2模型。下面我们再来看一下使用Docker与vLLM框架部署Qwen2模型。 准备vLLM镜像 在一台具备网络环境的机器上执行以下命令,拉取vLLM的镜像: # 官方镜像 docke…...
Rust的常数、作用域与所有权
【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学,之一 -CSDN博客 Rust到底值不值得学,之二-CSDN博客 Rust的数据类型-CSDN博客 3.7 常…...
Spring 源码解读:解决循环依赖的三种方式
引言 在复杂的应用开发中,循环依赖是一个常见的问题。简单来说,循环依赖是指两个或多个Bean之间互相依赖,导致程序无法正常实例化这些Bean。Spring容器通过依赖注入(DI)来管理Bean的创建与生命周期,并在遇…...
Web3 详解
1. 使用 Web3 库 Web3 是一个 JavaScript 库,可用于通过 RPC 通信与以太坊节点通信。 Web3 的工作方式是,公开已通过 RPC 启用的方法,这允许开发利用 Web3 库的用户界面,以便与部署在区块链上的合约进行交互。 一旦 Geth JavaScri…...
Spring 中依赖注入注解的区别详解
一、依赖注入的基本概念 依赖注入是一种设计模式,通过将对象的依赖以参数的形式传入类中,而不是在类中自行创建依赖对象。这样做有几个好处: 降低耦合度:类与类之间的依赖关系变得更清晰,避免了硬编码依赖。提高可测试性:通过依赖注入,可以轻松地进行单元测试,因为可以…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
