当前位置: 首页 > article >正文

【数据结构】-----哈希

目录

一、哈希表概念

二、哈希函数

三、哈希冲突

Ⅰ、定义

 Ⅱ、解决

①闭散列--开放定址法

线性探测

 二次线性探测

②开散列--链地址法(哈希桶)

问题:哈希表何时扩容?


一、哈希表概念

哈希表又称散列表,它是一种数据存储结构,该结构主要是通过某种哈希函数将元素的存储位置与元素的关键码(值)之间建立起一种一一映射的关系。因此在查找时能够通过该哈希函数直接找到对应的元素,效率上明显提高。

例如:存在数据集合{1,3,6,2,4};

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

假设这里的capacity大小为10,则各元素之间的存储位置如下:

用该方法进行搜索不必进行多次关键码的比较,因此速度会很快!

在理想情况下:哈希表的查找可以达到O(1)。而顺序查找和平衡树中,因为值和位置之间没有直接的关系,查找需要进行多次比较,顺序查找O(N),平衡树查找为树的高度,即O(log_2N)。搜索的效率取决于元素的比较次数,相比于哈希,挺慢!

二、哈希函数

哈希函数设计的原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间
  • 哈希函数计算出来的地址能均匀分布在整个空间中
  • 哈希函数应该比较简单

 常见哈希函数: 

  • 直接定址法(常用)

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

  • 除留取余法(常用)

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

优点:使用十分广泛,不受限制。

缺点:存在哈希冲突,哈希冲突越多,效率越低。

  • 平方取中 

 假设关键字为123,平方后15129,抽取中间3位512作为哈希地址。

再如关键字4321,平方后18671041,抽取中间3位671或者710作为哈希地址。

该方法适用于:不知道关键字的分布情况,而位数又不是很大的情况。

  • 折叠法 

折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这
几部分叠加求和,并按散列表表长,取后几位作为散列地址。

适用于:不知道关键字的分布情况,而位数多的情况。

如:123456789  ->分割:123|456|789 ->相加:123+456+789=1368

假设表长为10,那就取后两位68作为哈希地址。

  • 随机数法 

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

用得多还是前面两个,其他简单了解即可! 

三、哈希冲突

Ⅰ、定义

所谓哈希冲突,就是不同的关键码通过同一个哈希函数计算得到相同的存储地址。即相同位置上存在不同的值的现象。

引起哈希冲突的原因可能就是:哈希函数设计不合理引起的!

例如:在上述例子再插入11,计算后得到的地址为下标1,和原来的1发生的冲突,那么该怎么去解决呢?

 Ⅱ、解决

常见两种方法:闭散列 和 开散列

①闭散列--开放定址法

此法又称开放定址法,当发生哈希冲突时,如果哈希表未装满,说明有空位置,那么可以把key存放到冲突位置的下一个空位置去,这里寻找下一个空位置的方法又有两种

  • 线性探测

 这种方式是从发生冲突的位置开始,依次向后探测,直到发现下一个空位置为止

H=(Hash0+i)%m ,i=1,2,3……

Hash0:通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,即Hash(key)

m:散列表长度,这里取余,是为防止越界从而达到循环的效果。。

例如:上述例子插入11,通过哈希函数取余操作后发现和原来的1的位置冲突了,那就要继续向后探测,依次+1,+2,……,直到发现空位置,就放入该位置!

优点:实现简单

缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
低。所以需要扩容操作!

  •  二次线性探测

这种探测方式能够避免线性探测的数据堆积问题的,它和线性探测的区别就是:依次跨越i^2 个单位,i从1开始。数据之间会相对稀疏。

H=(Hash0+i^2)%m ,i=1,2,3……

上述例子的过程如下:

插入11,发现冲突,第一次探测先加1^2,第二次探测加2^2,发现了空位置直接放入即可

②开散列--链地址法(哈希桶)

开散列法又称为链地址法,当发生哈希冲突时,将相同地址的值归于同一个子集合每一个子集合称为一个桶,这个桶里面的元素采用链表的方式链接起来。即每一个桶存放的都是冲突的数据!

还是以上述例子举例。插入元素11,发生冲突,采用开散列的方式,无需向后寻找空位置,直接采用头插法。。

问题:哈希表何时扩容?

这里就需要引入负载因子的说法:负载因子\alpha=有效数据个数 / 散列表长度。

从上述公式可以看出:当数据个数越多,负载因子越高,冲突率越高,效率就越低;当长度越长,负载因子越低,冲突率越低,效率就越高,但此时空间利用率就越低。

哈希表的平均查找长度是负载因子\alpha的函数,即每个元素比较次数总和 / 元素总个数

  • 对于闭散列来说,一般严格将负载因子控制在0.7-0.8之间。超过0.8查表时,cpu缓存不命中按照指数曲线上升。因此采用闭散列的方式,保险起见超过0.7就需要扩容操作
  • 对于开散列来说,最好的情况是每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数(散列表长度)时,即负载因子等于1时,就可以给哈希表增容

具体如何扩容,请看下一回实现部分,因为看到这里,您也累了,休息一下咯!


好了,老铁今天的分享内容就到这里,觉得对你有帮助,欢迎点赞+关注!


http://www.kler.cn/a/287831.html

相关文章:

  • SQL面试题——抖音SQL面试题 最大在线用户数
  • STM32设计防丢防摔智能行李箱
  • MacOS下,如何在Safari浏览器中打开或关闭页面中的图片文字翻译功能
  • Python进程间通讯大揭秘:原理深度剖析与实战案例分享
  • Node.js GET/POST请求、WEB模块使用介绍 (基础介绍 八)
  • Kafka-Eagle的配置——kafka可视化界面
  • 科研绘图系列:R语言富集通路棒棒图(lollipop plot)
  • springboot中根据id查询用户信息
  • 家居设备的多彩世界,乐鑫ESP32芯片模组方案彩屏技术应用,启明云端乐鑫代理商
  • 大文件分块上传和续传
  • LLC电路全桥和半桥工作原理详解
  • 《深入浅出WPF》读书笔记.10资源
  • 鸿蒙开发 ImageKnife二次封装
  • 数据结构(邓俊辉)学习笔记】串 07——KMP算法:分摊分析
  • 【python因果推断库2】使用 PyMC 模型进行差分-in-差分(Difference in Differences, DID)分析
  • 【rk3588】环境搭建及系统编译
  • css实现元素居中显示
  • 【Qt窗口】—— 浮动窗口
  • jarbas 靶机渗透(cms 渗透)
  • 2700+存储过程的超复杂Oracle,国产化怎么办?
  • unreal engine5.4.3动画重定向
  • android so的加载流程(Android 13~14)
  • js代码如何和服务端进行通信
  • 开发软件国内镜像下载总结
  • Docker 安装消息队列RabbitMQ
  • OpenHarmony技术开发:Launcher架构应用启动流程分析