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

目录

一、哈希表概念

二、哈希函数

三、哈希冲突

Ⅰ、定义

Ⅱ、解决

①闭散列--开放定址法

线性探测

二次线性探测

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

问题:哈希表何时扩容?


一、哈希表概念

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

例如:存在数据集合{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从1开始。数据之间会相对稀疏。

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

上述例子的过程如下:

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

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

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

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

问题:哈希表何时扩容?

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

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

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

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

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


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

相关推荐
daiyang123...1 小时前
测试岗位应该学什么
数据结构
kitesxian1 小时前
Leetcode448. 找到所有数组中消失的数字(HOT100)+Leetcode139. 单词拆分(HOT100)
数据结构·算法·leetcode
薯条不要番茄酱3 小时前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
盼海5 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
搬砖的小码农_Sky11 小时前
C语言:数组
c语言·数据结构
先鱼鲨生13 小时前
数据结构——栈、队列
数据结构
一念之坤13 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
IT 青年13 小时前
数据结构 (1)基本概念和术语
数据结构·算法
熬夜学编程的小王13 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
liujjjiyun14 小时前
小R的随机播放顺序
数据结构·c++·算法