数据结构与算法之查找算法——哈希表(又称散列表)

哈希表也称为散列表,也是用来查找指定元素的一种方法。

散列表是根据关键字直接进行访问的数据结构。散列表通过散列函数将关键字映射到存储地址,建立了关键字和存储地址之间的一种直接映射关系。这里的存储地址可以是数组下标、索引、内存地址等。

利用哈希表查找元素需要解决两个问题:构造哈希表和处理冲突。
在这里插入图片描述
在图8-75中,如果要查找48,就可以通过散列函数得到其存储地址,直接找到该关键字。散列表查找的时间复杂度与表中的元素个数无关。理想情况下,散列表查找的时间复杂度为O(1)。

但是,散列函数可能会把两个或两个以上的关键字映射到同一地址,发生“冲突”,这种发生冲突的不同关键字称为同义词。例如,13通过散列函数计算的映射地址也是3,与48的映射地址相同,13和48为同义词。因此,设计散列函数时应尽量减少冲突,如果冲突无法避免,则需要设计处理冲突的方法。

散列函数

散列函数(Hash function),又称为哈希函数,是将关键字映射到存储地址的函数。记为hash(key)=Addr。设计散列函数时需要遵循以下2个原则。

  • 1)散列函数要尽可能简单,能够快速计算出任一关键字的散列地址
  • 2)散列函数映射的地址应均匀分布整个地址空间,避免聚集,以减少冲突。

散列函数设计原则简化为4字箴言:简单、均匀

常见的散列函数如下

(1)直接定址法

在这里插入图片描述
在这里插入图片描述

(2)除留余数法

在这里插入图片描述
为什么要选择p为素数?
选择p为素数的原因是为了避免冲突。因为在实际应用中,数据往往具有某种周期性,若周期与p有公共的素因子,则冲突的概率将急剧上升。例如,手表中的齿轮,两个交合齿轮的齿数最好是互质的,否则出现齿轮磨损绞断的概率很大。因此,发生冲突的概率随着p所含素因子的增多而迅速增大,素因子越多,冲突越多。

(3)随机数法

在这里插入图片描述

处理冲突的方法

无论如何设计散列函数,都无法避免冲突问题。如果发生冲突,就需要进行冲突处理。冲突处理方法分为3种:开发地址法、链地址法、建立公共溢出区。

1.开放地址法

在这里插入图片描述

(1)线性探测法

在这里插入图片描述
· 查找成功的平均查找长度
在这里插入图片描述

(2)二次探测法

二次探测法采用前后跳跃式探测的方法,发生冲突时,向后1位探测,向前1位探测,向后22位探测,向前22位探测……跳跃式探测,避免堆积。

(3)随机探测法

在这里插入图片描述

2.链地址法

链地址法又称为拉链法。如果不同关键字通过散列函数映射到同一地址,这些关键字为同义词,将所有的同义词存储在一个线性链表中。查找、插入、删除操作主要在这个链表中进行,拉链法适用于经常进行插入、删除的情况。

例如,一组关键字(14, 36, 42, 38, 40, 15, 19, 12, 51, 65, 34, 25),若表长为15,散列函数为hash(key)=key%13,采用链地址法处理冲突,构造该散列表。

算法图解

按照关键字顺序,根据散列函数计算散列地址,如果该地址空间为空,则直接放入;如果该地址空间已存有数据,则采用链地址法处理冲突。

hash(14)=14%13=1,放入1号空间后面的单链表中。
hash(36)=36%13=10,放入10号空间后面的单链表中。
hash(42)=42%13=3,放入3号空间后面的单链表中。
hash(38)=38%13=12,放入12号空间后面的单链表中。
hash(40)=40%13=1,放入1号空间后面的单链表中。
hash(15)=15%13=2,放入2号空间后面的单链表中。
hash(19)=19%13=6,放入6号空间后面的单链表中。
hash(12)=12%13=12,放入12号空间后面的单链表中。
hash(51)=51%13=12,放入12号空间后面的单链表中。
hash(65)=65%13=0,放入0号空间后面的单链表中。
hash(34)=34%13=8,放入8号空间后面的单链表中。
hash(25)=25%13=12,放入12号空间后面的单链表中。
在这里插入图片描述

性能分析

(1)查找成功的平均查找长度

假设查找的概率均等(12个关键字,每个关键字查找概率为1/12),查找成功的平均查找长度等于所有关键字的比较次数乘以查找概率之和。从图8-91中可以看出,1次比较成功的有8个,2次比较成功的有2个,3次比较成功的有1个,4次比较成功的有1个。其查找成功的平均查找长度为:
ASLsucc=(1×8+2×2+3+4)/12=19/12

(2)查找失败的平均查找长度

本题中散列函数为hash(key)=key%13,计算得到的散列地址为0, 1, …, 12,一共有13种情况。假设查找失败的概率均等(13种失败情况,每种情况的概率为1/13),查找失败的平均查找长度等于所有关键字的查找失败的比较次数乘以概率之和。

当hash(key)=0时,如果该空间为空,则比较1次即可确定查找失败;如果该空间非空,则在其后面的单链表中查找,直到空时,确定查找失败。如果单链表中有两个节点,则需要比较3次才能确定查找失败。类似地,hash(key)=1, …,12时也如此计算,如图8-92所示。
在这里插入图片描述
在这里插入图片描述

3.建立公共溢出区

除了以上处理冲突的方法之外,也可以建立一个公共溢出区,发生冲突时,将关键字放入公共溢出区中。查找时,先根据待查找关键字的散列地址,在散列表中查找,如果为空,则查找失败;如果非空且关键字不相等,则到公共溢出区中查找;如果仍未找到,则查找失败。

散列查找及性能分析

散列表虽然建立了关键字和存储位置之间的直接映像,但冲突不可避免。在散列表的查找过程中,有的关键字可以通过直接定址1次比较找到,有的关键字可能仍然需要和若干个关键字比较,查找不同关键字的比较次数不同,因此散列表的查找效率通过平均查找长度衡量。其查找效率取决于3个因素,即散列函数、装填因子和处理冲突的方法。

1.散列函数

衡量散列函数好坏的标准是:简单、均匀。即散列函数计算简单,可以将关键字均匀地映射到散列表中,避免大量关键字聚集在一个地方,发生冲突的可能性就小。

2.装填因子

在这里插入图片描述

3.处理冲突的方法

在这里插入图片描述
在这里插入图片描述
例如:hash(key)=key mod 13,那么散列函数的映射地址为0~12,一共13个,r=13。计算查找失败的比较次数时,不管时线性探测、二次探测、还是链地址,遇到空才会停止,空也算作一次比较

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

猜你喜欢

转载自blog.csdn.net/qq_44631615/article/details/121061682