不允许你还没有了解哈希表、哈希桶、哈希冲突的解决,如何避免冲突

✏️✏️✏️今天给各位带来的是哈希桶、哈希冲突方面的知识。

清风的CSDN博客

😛😛😛希望我的文章能对你有所帮助,有不足的地方还请各位看官多多指教,大家一起学习交流!

动动你们发财的小手,点点关注点点赞!在此谢过啦!哈哈哈!😛😛😛

目录

 一、哈希表

1.1 概念

1.2 冲突

1.2.1 冲突概念

1.2.2 冲突避免 

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

1.2.4 负载因子调节 

1.2.5 冲突解决-闭散列

 1.2.6 冲突解决-开散列

 1.2.7 哈希桶K-V型实现

1.3 性能分析 


 一、哈希表

1.1 概念

顺序结构以及平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素时,必须要经过关键 码的多次比较 顺序查找时间复杂度为 O(N), 平衡树中为树的高度,即 O(\log_{2}N ),搜索的效率取决于搜索过程中元素的比较次数。

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

 当向该结构中:

  • 插入元素:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
  • 搜索元素:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
该方式即为  哈希(散列)  方法, 哈希方法中使用的转换函数称为哈希 ( 散列 ) 函数,构造出来的结构称为哈希表 (Hash Table)( 或者称散列表 )

例如:数据集合 {1 7 6 4 5 9}
哈希函数设置为: hash(key) = key % capacity ; capacity 为存储元素底层空间总的大小。

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。 问题是:按照上述哈希方式,向集合中插入元素44 ,会出现什么问题?这就引出了哈希冲突的概念。

1.2 冲突

1.2.1 冲突概念

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

1.2.2 冲突避免 

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

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

引起哈希冲突的一个原因可能是: 哈希函数设计不够合理
哈希函数设计原则:
  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

常见哈希函数: 

直接定制法--(常用)
取关键字的某个线性函数为散列地址: Hash Key = A*Key + B 优点:简单、均匀 缺点:需要事先知道关键字的分布情况 使用场景:适合查找比较小且连续的情况。
除留余数法--(常用)
设散列表中允许的 地址数为 m ,取一个不大于 m ,但最接近或者等于 m 的质数 p 作为除数,按照哈希函数: Hash(key) = key% p(p<=m), 将关键码转换成哈希地址。
平方取中法
假设关键字为 1234 ,对它平方就是 1522756 ,抽取中间的 3 227 作为哈希地址; 再比如关键字为 4321 ,对它平方就是18671041 ,抽取中间的 3 671( 710) 作为哈希地址 平方取中法比较适合:不知道关键字的分 布,而位数又不是很大的情况。 

折叠法 

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

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

数学分析法 

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

假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前 7 位都是 相同的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还可以对抽取出来的数字进行反转( 如1234改成 4321) 、右环位移 ( 1234 改成 4123) 、左环移位、前两数与后两数叠加 ( 1234 改成 12+34=46) 等方法。 数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀的情况。

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突 。

1.2.4 负载因子调节 

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

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

1.2.5 冲突解决-闭散列

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

闭散列: 

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

  •  线性探测:比如上面的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,下标为4,因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。那么就从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他
元素的搜索。比如删除元素 4 ,如果直接删除掉, 44 查找起来可能会受影响。因此线性探测采用标
记的伪删除法来删除一个元素。

  • 二次探测  
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为:
Hi= (H0+i^2)%m 或者:Hi= (H0-i^2)%m其中: i = 1,2,3… ,H0是通过散列函数Hash(x) 对元素的关键码 key 进行计算得到的位置,m是表的大小。 对于文章开头的表, 如果要插入 44 ,产生冲突,使用解决后的情况为:

研究表明:当表的长度为质数且表装载因子 a 不超过 0.5 时,新的表项一定能够插入,而且任何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a 不超过 0.5 ,如果超出必须考虑增容。

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

 1.2.6 冲突解决-开散列

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

 从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。 开散列,可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了。

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

 1.2.7 哈希桶K-V型实现

类的基本定义:

public class Hash_Map<K,V> {static  class Node<K,V>{public K key;public V val;public Node<K,V> next;public Node(K key, V val) {this.key = key;this.val = val;}}public Node<K,V>[] array;public int usedSize;//记录当前存放的元素个数public static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认负载因子public Hash_Map(){array=(Node<K, V>[]) new Node[10];}
}

存放元素:

    public void put(K key, V val){int hash = key.hashCode();int index = hash % array.length;Node<K,V> cur = array[index];while (cur!=null){if(cur.key.equals(key)){cur.val=val;return;}cur = cur.next;}Node<K,V> node = new Node<>(key,val);node.next=array[index];array[index]=node;usedSize++;if(doLOAD_FACTOR()>DEFAULT_LOAD_FACTOR){resize();}}//计算当前负载因子private float doLOAD_FACTOR(){return usedSize*1.0f / array.length;}  //如果哈希桶已满,扩容后重新哈希private void resize(){Node<K,V>[] newArray = new Node[array.length*2];for (int i = 0; i < array.length; i++) {Node<K,V> cur = array[i];while (cur!=null){//先生成整数,调用hashCodeNode<K,V> curNext = cur.next;int newHash = cur.key.hashCode();int newIndex = newHash % newArray.length;cur.next = newArray[newIndex];cur = curNext;}}array = newArray;}

获取key位置的val值:

    public V getValue(K key){int hash = key.hashCode();int index = hash % array.length;Node<K,V> cur = array[index];while (cur!=null){if(cur.key.equals(key)){//引用类型不可使用等号比较!!!!!!return cur.val;}cur = cur.next;}return null;}

1.3 性能分析 

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

希望各位看官读完文章后,能够有所提升。

🎉好啦,今天的分享就到这里!!

创作不易,还希望各位大佬支持一下!

👍点赞,你的认可是我创作的动力!

⭐收藏,你的青睐是我努力的方向!

✏️评论:你的意见是我进步的财富!

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

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

相关文章

springboot项目yml文件中使用${}配置

1、传统写法 &#xff08;1&#xff09;配置服务启动端口 # 服务端口 server:port: 9898 &#xff08;2&#xff09;使用idea启动 &#xff08;3&#xff09;使用jar包启动 2、使用${}写法 格式&#xff1a;${自定义参数名:默认值} 作用&#xff1a; 项目启动时动态配置变量…

社区无人零售:投资新热点,创业新机遇

社区无人零售是一个备受关注的创业项目&#xff0c;被视为投资的“爆点”。与其他国家相比&#xff0c;无人零售在国内市场远未达到饱和&#xff0c;因此成为了当下的新风口。今天&#xff0c;我将详细分析这个创业项目&#xff0c;以帮助感兴趣的朋友们了解更多。 首先&#x…

邀请报名|11月24日阿里云原生 Serverless 技术实践营 深圳站

活动简介 “阿里云云原生 Serverless 技术实践营 ” 是一场以 Serverless 为主题的开发者活动&#xff0c;活动受众以关注 Serverless 技术的开发者、企业决策人、云原生领域创业者为主&#xff0c;活动形式为演讲、动手实操&#xff0c;让开发者通过一个下午的时间增进对 Ser…

动 态 规 划

一、&#xff08;what&#xff1f;&#xff09; 二、&#xff08;why&#xff1f;&#xff09; 三、&#xff08;how&#xff1f;&#xff09; 四、典型例题分析&#xff1a; 例题1&#xff1a;神奇的兔子序列 输入&#xff1a;月数 输出&#xff1a;兔子数 思路&#xff1…

SecureCRT的“New line mode“

New line mode选中与不选中啥区别 在SecureCRT中&#xff0c;"New line mode"是一个关键配置项&#xff0c;主要用于解决不同操作系统之间的换行问题。当不选中"New line mode"时&#xff0c;SecureCRT会将接收到的数据按照原样发送&#xff0c;不会对数据…

基于Vue+SpringBoot的高校学生管理系统 开源项目

项目编号&#xff1a; S 029 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S029&#xff0c;文末获取源码。} 项目编号&#xff1a;S029&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生管理模块2.2 学院课程模块2.3 学…

回 溯 法

一、&#xff08;what&#xff1f;&#xff09; 二、&#xff08;why&#xff1f;&#xff09; 三、&#xff08;how&#xff1f;&#xff09; 四、典型例题分析&#xff1a; 例题1&#xff1a;大卖场购物车2——0-1背包问题 问题分析&#xff1a; 算法设计&#xff1a; 图…

for,while,do-while,死循环,嵌套循环,跳转关键字,随机数

1.for循环 public class ForDemo1 {public static void main(String[] args) {for (int i 0; i < 5; i) {System.out.println("HelloWorld");}System.out.println("--------------------------------------------");for (int i 1; i <10 ; i) {Sy…

反激变压器计算方法_笔记

反激变压器计算方法_笔记 匝数比原边电感选定磁芯线圈匝数线径 原视频链接 匝数比 5V 是想要得到的输出电压 0.7V为二极管导通的压降 185Vx根号2是有效值 最大占空比取0.4。得出最小匝数为30。 更改某些值可能得出来的匝数比就不一定是30了&#xff0c; 这其实也是反激变压器…

PS学习笔记——新建文档/修改文档

文章目录 新建文档文档属性像素/分辨率颜色模式背景内容高级选项存储预设 修改文档 新建文档 方法一&#xff1a;ctrlN快捷键可直接打开新建文档界面 方法二&#xff1a;点击菜单栏中 文件->新建&#xff0c;即可打开新建文档界面 文档参数可按需调节(标题可以提前设定或者…

分布式服务与分布式框架

分布式副武其实就是根据某个粒度&#xff0c;将服务拆分&#xff0c;而分布式框架就是将这些服务协调&#xff0c;管理起来。分布式框架&#xff0c;我认为服务调用是他的基础能力&#xff0c;该能力是所有分布式框架的基础能力&#xff0c;其次是服务注册与发现。 在这个维度…

Mac M1 M1 pro安装 protobuf 2.5.0

因为项目中的protobuf是2.5.0版本&#xff0c;但是旧版本的protobuf 不支持M1&#xff0c;此时需要修改源码重新编译 操作步骤&#xff1a; 从git上面下载对应版本的protobuf&#xff0c;地址&#xff1a;Release Protocol Buffers v2.5.0 protocolbuffers/protobuf GitHub…