【数据结构】关于哈希表内部原理,你到底了解多少???(超详解)

前言:

🌟🌟本期讲解关于哈希表的内部实现原理,希望能帮到屏幕前的你。

🌈上期博客在这里:http://t.csdnimg.cn/7D225

🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客

目录

 📚️1.哈希表的概念

📚️2.哈希-冲突

2.1冲突-概念

 2.2冲突-避免

1.冲突-避免-哈希函数设计

2.冲突-避免-冲突因子

2.3冲突-解决

1.冲突-解决-闭散列

2.冲突-解决-开散列

 2.4性能分析

2.5与Java类集的关系

 📚️3.总结


 📚️1.哈希表的概念

       顺序结构以及平衡树中在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N);

      平衡树中为树的高度,即O(logN ),搜索的效率取决于搜索过程中元素的比较次数。例如上期的treeMap.

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

  • 插入元素
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
  • 搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功


方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(HashTable)(或者称散列表)

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

图解 :

注意:这里和计数排序差不多 ,但是如果我们加入11,会发现11的位置将直接把1给覆盖掉,那么此时就叫作哈希-冲突。

📚️2.哈希-冲突

2.1冲突-概念

对于两个数据元素的关键字ki 和kj (i != j),有ki !=kj ,但有:Hash(ki ) == Hash(kj ),即:不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞

 2.2冲突-避免

首先,我们需要明确一点,由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一个问题,冲突的发生是必然的,但我们能做的应该是尽量的降低冲突率

1.冲突-避免-哈希函数设计

哈希函数设计原理:

• 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间


• 哈希函数计算出来的地址能均匀分布在整个空间中


• 哈希函数应该比较简单

小编这里介绍两个比较常用的两个方法: 

1. 直接定制法
       取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况。

例如字符串中第一个只出现一次字符

代码实例如下:

class Solution {public int firstUniqChar(String s) {int[] array=new int[26];for(int i=0;i<s.length();i++){char ch=s.charAt(i);array[ch-'a']++;}for(int j=0;j<s.length();j++){if(array[s.charAt(j)-'a']==1){return j;}}return -1;}
}

思路:就是创建一个26个空间大小的数组,在遍历字符串时,将字符对应的位置加一,最后再次遍历谁为1,就返回第一个不重复的字符。 

注意:这种方法只适合要求范围小的数值内进行操作,如果范围过大,则会造成数组空间的浪费。

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

2.冲突-避免-冲突因子

负载因子与冲突率图片演示:

 

 已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。(我们不可能在源头上限制我们的需求)

2.3冲突-解决

1.冲突-解决-闭散列

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

线性探测:

这里就是,当我们先插入了一个1后,然后想插入11,此时发现取余地址冲突了,此时我们就要往后寻空位置,发现后进行插入。

图片演示:

注意:

      不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。

      产生冲突的数据堆积在一块

二次探测

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

图片演示:

当然这是一个极端的例子~~~ 

注意:比散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。

2.冲突-解决-开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

图解如下:

 注意:刚才我们提到了,哈希桶其实可以看作将大集合的搜索问题转化为小集合的搜索问题了,那如果冲突严重,就意味着小集合的搜索性能其实也时不佳的,这个时候我们就可以将这个所谓的小集合搜索问题继续进行转化,例如:
1. 每个桶的背后是另一个哈希表
2. 每个桶的背后是一棵搜索树

 开散列的代码实现模拟:

public class HashBucket {//结点链表的初始化static class Node{public int key;public int val;public Node next;public Node(int key,int val){this.key=key;this.val=val;}}//数组的初始化public Node[] array=new Node[10];public int usedSize;//插入元素public void put(int key,int value){/* int index=key% array.length;//遍历链表//头插法Node cur=array[index];while (cur!=null){if(cur.key==key){cur.val=value;return;}cur=cur.next;}Node node=new Node(key,value);node.next=array[index];array[index]=node;usedSize++;*///尾插Node node=new Node(key,value);int index=key% array.length;if(array[index]==null){array[index]=node;return;}Node cur=array[index];Node prev=null;while (cur!=null){if(cur.key==key){cur.val=value;return;}prev=cur;cur=cur.next;}prev.next=node;usedSize++;//负载因子是否大于0.75if(loadFactor()>0.75){resize();}}public int loadFactor(){return usedSize/ array.length;}//进行扩容public void resize(){Node[] tmpArray=new Node[array.length*2];for (int i = 0; i < array.length; i++) {Node cur=array[i];while (cur!=null){Node newCur=cur.next;int newIndex=cur.key% tmpArray.length;//进行头插法cur.next=tmpArray[newIndex];tmpArray[newIndex]=cur;cur=newCur;}}array=tmpArray;}//进行取出public int get(int key){//判断k在哪里int index=key%array.length;Node cur=array[index];while (cur!=null){if(cur.key==key){return cur.val;}cur=cur.next;}return -1;}

这里小编模拟了哈希表开散列的放入数据的两种插入方式,即链表的头插法以及链表的尾插法。

注意扩容:

1.进行原来数值的项管部位置的插入,因为扩容后的数组容量大小进行变化,必须进行重新取余。

2.在进行在新的扩容数组后,使用的cur要进行保留,否则进行新的数组插入后,cur无法回到原来数组进行遍历剩余的数值遍历。 

 2.4性能分析

虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是O(1) 。

1.每个桶中的链表的长度是一个常数,并且可以进行调整。

2.负载因子的存在,使得在遍历时可以进数值过多的扩容。

2.5与Java类集的关系

1. HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set


2. java 中使用的是哈希桶方式解决冲突的


3. java 会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树)


4. java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key 的 equals 方法。所以如果要用自定义类作为 HashMap 的 key 或者 HashSet 的值,必须覆写 hashCode 和 equals 方法。

 📚️3.总结

💬💬本期小编讲解了关于哈希表的内部原理,以及它的重点内部原理冲突的避免以及冲突的解决。

本期主要是解释性语言较多,注重理解,唯一的难点是开散列的模拟代码实现。

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


                               💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

                                                         😊😊  期待你的关注~~~

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

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

相关文章

6.2K star!推荐一款开源混沌工程测试平台:Chaos Mesh

1、Chaos Mesh 介绍 Chaos Mesh是一个开源的混沌工程平台&#xff0c;旨在帮助用户在生产环境中测试、验证和优化其应用程序的可靠性和稳定性。通过引入故障注入和混沌工程原则&#xff0c;Chaos Mesh可以模拟各种故障场景&#xff0c;如网络延迟、节点故障、磁盘故障等&#…

JavaWeb JavaScript ⑧ DOM编程

在光芒万丈之前&#xff0c;我们都要欣然接受眼下的难堪和不易&#xff0c;接受一个人的孤独和无助&#xff0c;认真做好眼前的每一件事&#xff0c;你想要的都会有 —— 24.8.29 一、什么是DOM编程 简单来说&#xff1a;DOM(Document obiect Model)编程就是使用document对象的…

重大内幕!揭秘数据“零丢失”,全靠它

2017年&#xff0c;某运营商设备扩容&#xff0c;误删80万用户数据… 2020年初疫情期间&#xff0c;某电商公司恶意删库事件&#xff0c;导致业务停机3天&#xff0c;公司赔付1.5亿元人民币 “链家程序员删库”事件&#xff0c;恶意删除公司 9TB 数据&#xff0c;造成公司财务…

鸿蒙HarmonyOS开发:如何灵活运用服务卡片提升用户体验

文章目录 一、ArkTS卡片相关模块二、卡片事件能力说明三、卡片事件的主要使用场景3.1、使用router事件跳转到指定UIAbility3.1.1、卡片内按钮跳转到应用的不同页面3.1.2、服务卡片的点击跳转事件 3.2、通过message事件刷新卡片内容3.2.1、在卡片页面调用postCardAction接口触发…

网络安全的历史

如今&#xff0c;网络安全几乎成为各大公司和利益相关者关注的焦点。但在早期&#xff0c;网络安全的概念非常模糊。 直到多年以后&#xff0c;由于网络攻击和危险实体威胁的频繁发生&#xff0c;网络安全的发展才受到重视。这些措施的发展成为了网络安全的演变。 网络安全起…

CentOS全面停服,国产化提速,央国企信创即时通讯/协同门户如何选型?

01. CentOS停服带来安全新风险&#xff0c; 国产操作系统迎来新的发展机遇 2024年6月30日&#xff0c;CentOS 7版本全面停服&#xff0c;于2014年发布的开源类服务器操作系统——CentOS全系列版本生命周期画上了句号。国内大量基于CentOS开发和适配的服务器及平台&#xff0c…

500Kg载重无线遥控履带式无人车技术详解

500Kg载重无线遥控履带式无人车是一种专为复杂环境与多样化任务设计的高科技产品&#xff0c;具备卓越的机动性、承载能力和无线遥控功能。以下是对其技术特点的详细解析&#xff1a; 一、技术特点 履带式驱动系统 地形适应性&#xff1a;采用履带式驱动&#xff0c;能够…

实时图像编辑大革新!Adobe发布TurboEdit:可以通过文本来编辑图像,编辑时间<0.5秒!

今天给大家介绍Adobe研究院新的研究TurboEdit&#xff0c;可以通过文本来编辑图像&#xff0c;通过一句话就能改变图像中的头发颜色、衣服、帽子、围巾等等。而且编辑飞快&#xff0c;<0.5秒。简直是图像编辑的利器。 相关链接 项目&#xff1a;betterze.github.io/TurboE…

问题-解决

1. 在collection中的SetTest4_Student上面 此时我想解决LinkedHashSet的自定义降序身高问题是现在不行了 难道只能在构造器里面完成吗 还是说明只能为int类型 Double.compare(o1.getHeight(),o2.getHeight()) 在这道题中我在使用为什么不通过 Map<String, Integer>…

视频结构化从入门到精通——图像算法类型介绍

视频结构化主要图像算法 1 认识“数组、矩阵和张量” 1.1 什么是维度 在图像算法中&#xff0c;“维度”这个概念非常重要&#xff0c;它描述了数据的结构和形状。在不同的上下文中&#xff0c;维度可能有不同的含义&#xff0c;但总体来说&#xff0c;它们都与数据的排列方式…

【WiFi主要技术学习2】

WiFi协议学习2 WiFi SPEC理解频段信道带宽协商速率安全与加密WiFi主要技术理解BP直接序列扩频(Direct Sequence Spread Spectrum,DSSS)BPSKQPSK正交幅度调制(Quadrature Amplitude Modulation,QAM)互补码键控(Complementary Code Keying,CCK)正交频分复用(Orthogonal…

基于mallat小波变换的图像分解和重建算法matlab仿真,对比不同分解层数图像重建质量

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频&#xff09…

axios取消请求CancelToken的原理解析及用法示例

文章目录 一、axios的实例与请求流程二、CancelToken 的作用三、CancelToken 的实现原理四、取消请求的流程五、CancelToken用法六、利用拦截器取消请求1、axios请求拦截器2、axios响应拦截器3、利用路由导航守卫取消请求 一、axios的实例与请求流程 下图是axios实例属性的简图…

Java技术栈 —— Spark入门(三)之实时视频流

Java技术栈 —— Spark入门&#xff08;三&#xff09;之实时视频流转灰度图像 一、将摄像头数据发送至kafka二、Kafka准备topic三、spark读取kafka图像数据并处理四、本地显示灰度图像(存在卡顿现象&#xff0c;待优化) 项目整体结构图如下 参考文章或视频链接[1] Architectur…

破解“目录名称无效”难题:数据恢复实战指南

在数字化生活日益普及的今天&#xff0c;数据存储与管理成为了我们日常不可或缺的一部分。然而&#xff0c;当您尝试访问某个文件夹时&#xff0c;却遇到了“目录名称无效”的错误提示&#xff0c;这无疑会让人感到焦虑和困惑。本文将深入探讨“目录名称无效”这一问题的根源&a…

Excel中使用VBS自定义函数将中文转为拼音首字母

1、在“开发工具”中&#xff0c;点击“Visual Basic”。如果没有“开发工具”&#xff0c;则添加。 2、添加“模块”&#xff0c;在窗口中添加自定义函数。 Function MyGetPYChar(char) MyCodeNumber 65536 Asc(char) If (MyCodeNumber > 45217 And MyCodeNumber <…

2d椭圆拟合学习

算法来自论文《 Direct Least Square Fitting of Ellipses》 《NUMERICALLY STABLE DIRECT LEAST SQUARES FITTING OF ELLIPSES》 相关文章 论文阅读&#xff1a;直接拟合椭圆 Direct Least Square Fitting of Ellipseshttps://zhuanlan.zhihu.com/p/645391510Fitting Elli…

线段树离散化、二分搜索、特别修改

699. 掉落的方块 - 力扣&#xff08;LeetCode&#xff09; 1.如果直接按照原落点的值构造线段树&#xff0c;空间开辟会过大&#xff0c;所以收集所有出现过的点进行离散化 2.方块a落在1--3点&#xff0c;b落在3--4点&#xff0c;如果直接按照落点修改&#xff0c;查询3时位置…

基于Docker搭建Graylog分布式日志采集系统

文章目录 一、简介二、Graylog1、主要特点2、组件3、工作流程介绍4、使用场景 三、Graylog 安装部署1、 安装 docker2、安装docker compose3、 安装graylog4、Graylog控制台 四、springboot集成Graylog 一、简介 Graylog是一个开源的日志管理工具&#xff0c;主要功能包括日志…

go 切片slice学习总结

切片的结构 切片的底层结构&#xff1a; type SliceHeader struct {Data uintptr // 指向底层数组的指针 Len int //长度Cap int //空间容量 } 切片的初始化 1 通过数组或者已有的slice创建新的slice 1.1 使用数组创建切片 通过数组的一部分来初始化切片。 …