数据结构复习指导之散列表(哈希表)

文章目录

散列表

1.散列表的基本概念

知识总览

2.散列函数的构造方法

知识总览

2.1直接定址法

2.2除留余数法

2.3数字分析法

2.4平方取中法

3.处理冲突的方法

3.1开放定址法

3.2拉链法(链接法,chaining)

4.散列查找及性能分析

知识回顾


散列表

1.散列表的基本概念

知识总览

在前面介绍的线性表和树表的查找中,查找记录需进行一系列的关键字比较,记录在表中的位置与记录的关键字之间不存在映射关系,因此在这些表中的査找效率取决于比较的次数。

散列函数(也称哈希函数):一个把查找表中的关键字映射成该关键字对应的地址的函数,记为 Hash(key)=Addr(这里的地址可以是数组下标、索引或内存地址等)。

散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为冲突,这些发生冲突的不同关键字称为同义词

一方面,设计得好的散列函数应尽量减少这样的冲突;

另一方面,由于这样的冲突总是不可避免的,所以还要设计好处理冲突的方法。

散列表(也称哈希表):根据关键字而直接进行访问的数据结构。

也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系。

理想情况下,对散列表进行查找的时间复杂度为 O(1),即与表中元素的个数无关。

下面分别介绍常用的散列函数和处理冲突的方法。

2.散列函数的构造方法

知识总览

在构造散列函数时,必须注意以下几点

  • 1) 散列函数的定义域必须包含全部关键字,而值域的范围则依赖于散列表的大小。
  • 2) 散列函数计算出的地址应尽可能均匀地分布在整个地址空间,尽可能地减少冲突。
  • 3) 散列函数应尽量简单,能在较短的时间内计算出任意一个关键字对应的散列地址。

下面介绍常用的散列函数。

2.1直接定址法

直接取关键字的某个线性函数值为散列地址,散列函数为

                                                H(key)=key 或 H(key)=a×key + b

式中,a和b是常数。这种方法计算最简单,且不会产生冲突。

它适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。

2.2除留余数法

这是一种最简单、最常用的方法,假定散列表表长为 m,取一个不大于m 但最接近或等于 m的质数p(大于1的自然数,除了1和它本身以外不再有其他因数的自然数),利用以下公式把关键字转换成散列地址。散列函数为

                                                             H(key)= key % p

除留余数法的关键是选好p,使得每个关键字通过该函数转换后等概率地映射到散列空间上的任意一个地址,从而尽可能减少冲突的可能性。

2.3数字分析法

设关键字是r进制数(如十进制数),而r个数码在各位上出现的频率不一定相同,可能在某些位上分布均匀一些,每种数码出现的机会均等;

而在某些位上分布不均匀,只有某几种数码经常出现,此时应选取数码分布较为均匀的若干位作为散列地址。

这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。

2.4平方取中法

顾名思义,这种方法取关键字的平方值的中间几位作为散列地址。

具体取多少位要视实际情况而定。

这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均匀,

适用于关键字的每位取值都不够均匀或均小于散列地址所需的位数

在不同的情况下,不同的散列函数具有不同的性能,因此不能笼统地说哪种散列函数最好。

在实际选择中,采用何种构造散列函数的方法取决于关键字集合的情况

3.处理冲突的方法

应该注意到,任何设计出来的散列函数都不可能绝对地避免冲突。

为此,必须考虑在发生冲突时应该如何处理,即为产生冲突的关键字寻找下一个“空”的 Hash 地址。用 Hi 表示处理冲突中第 i 次探测得到的散列地址,假设得到的另一个散列地址 H1 仍然发生冲突,只得继续求下一个地址 H2,以此类推,直到 Hk 不发生冲突为止,则为关键字在表中的地址。

3.1开放定址法

所谓开放定址法,是指表中可存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开放。

其数学递推公式为
                                Hi=(H(key) + di) % m
式中,H(key)为散列函数;

i=1,2,…,k(k<=m-1);

m表示散列表表长;

di为增量序列。

取定某一增量序列后,对应的处理方法就是确定的。通常有以下4种取法:

命题追踪——堆积现象导致的结果(2014)】

1) 线性探测法,又称线性探测再散列法

di=1,2,…,m-1。它的特点是:冲突发生时,顺序查看表中下一个单元(探测到表尾地址 m-1时,下一个探测地址是表首地址 0),直到找出一个空闲单元(当表未填满时一定能找到一个空闲单元)或查遍全表。

线性探测法可能使第i个散列地址的同义词存入第i+1个散列地址,这样本应存入第i+1个散列地址的元素就争夺第i+2个散列地址的元素的地址……从而造成大量元素在相邻的散列地址上聚集(或堆积)起来,大大降低了查找效率。

2) 平方探测法,又称二次探测法

di=1²,-1²,2²,-2²,...k²,-k²,其中k<=m/2,散列表长度m必须是一个可以表示成 4k+3的素数(在大于1的自然数中,除了1和该数自身外, 无法被其他自然数整除 的数)。

平方探测法是一种处理冲突的较好方法,可以避免出现“堆积”问题,它的缺点是不能探测到散列表上的所有单元,但至少能探测到一半单元

3) 双散列法

di=i×Hash₂(key)。需要使用两个散列函数,当通过第一个散列函数 H(key)得到的地址发生冲突时,则利用第二个散列函数 Hash₂(key)计算该关键字的地址增量。它的具体散列函数形式如下:

                        Hᵢ=(H(key)+ i×Hash₂(key)) % m

初始探测位置H₀ =H(key)%m。i是冲突的次数,初始为 0。

4) 伪随机序列法

d ᵢ =伪随机数序列。

命题追踪——散列表中删除部分元素后的查找效率分析(2023)】

注意:采用开放定址法时,不能随便物理删除表中已有元素,否则会截断其他同义词元素的查找路径。

因此,要删除一个元素时,可以做一个删除标记,进行逻辑删除。

但这样做的副作用是:执行多次删除后,表面上看起来散列表很满,实际上有许多位置未利用。

3.2拉链法(链接法,chaining)

显然,对于不同的关键字可能会通过散列函数映射到同一地址,为了避免非同义词发生冲突,可以把所有的同义词存储在一个线性链表中,这个线性链表由其散列地址唯一标识。

假设散列地址为 i 的同义词链表的头指针存放在散列表的第 i 个单元中,因而查找、插入和删除操作主要在同义词链中进行。

拉链法适用于经常进行插入和删除的情况。

例如,关键字序列为{19,14,23,01,68,20,84,27,55,11,10,79},散列函数 H(key)=key%13用拉链法处理冲突,建立的表如图7.33所示(学完下节内容后,可以尝试计算本例的平均查找长度 ASL)。

4.散列查找及性能分析

命题追踪——散列表的构造及查找效率的分析(2010、2018、2019)】

散列表的查找过程与构造散列表的过程基本一致。

对于一个给定的关键字 key,根据散列函数可以计算出其散列地址,执行步骤如下:

初始化:Addr=Hash(key);

① 检测査找表中地址为 Addr 的位置上是否有记录,若无记录,返回查找失败

若有记录,比较它与 key 的值,若相等,则返回查找成功标志,否则执行步骤②。

② 用给定的处理冲突方法计算“下一个散列地址”,并把 Addr 置为此地址,转入步骤①。

例如,关键字序列{19,14,23,01,68,20,84,27,55,11,10,79}按散列函数H(key)=key%13和线性探测处理冲突构造所得的散列表L如图 7.34 所示。

给定值 84的查找过程为:

  • 首先求得散列地址H(84)=6,因L[6]不空且L[6]≠84,则找第一次冲突处理后的地址 H₁=(6+1)%16=7,而L[7]不空且L[7]≠84,
  • 则找第二次冲突处理后的地址 H₂ =(6+2)%16=8,L[8]不空且L[8]=84,查找成功,返回记录在表中的序号 8。

给定值 38 的查找过程为:

  • 先求散列地址H(38)=12,L[12]不空且L[12]≠38,则找下一地址 H₁=(12+1)%16=13,由于L[13]是空记录,所以表中不存在关键字为 38 的记录。

查找各关键字的比较次数如图 7.35 所示。

平均查找长度 ASL为

                                               ASL=(1×6+2+3×3+4+9)/12=2.5

对同一组关键字,设定相同的散列函数,则不同的处理冲突的方法得到的散列表不同,它们的平均查找长度也不同,本例与上节采用拉链法的平均查找长度不同。

从散列表的查找过程可见:

1) 虽然散列表在关键字与记录的存储位置之间建立了直接映像,但由于“冲突”的产生,使得散列表的查找过程仍然是一个给定值和关键字进行比较的过程。

因此,仍然需要以平均查找长度作为衡量散列表的查找效率的度量

命题追踪——影响散列表查找效率的因素(2011、2022)】

2) 散列表的查找效率取决于三个因素:散列函数、处理冲突的方法和装填因子

装填因子。散列表的装填因子一般记为α,定义为一个表的装满程度,即

      α=(表中记录数n)/(散列表长度m)

散列表的平均査找长度依赖于散列表的装填因子α,而不直接依赖于 n或 m

直观地看,α越大,表示装填的记录越“满”,发生冲突的可能性越大;

反之发生冲突的可能性越小。

读者应能在给出散列表的长度、元素个数及散列函数和解决冲突的方法后,在求出散列表的基础上计算出查找成功时的平均查找长度和查找不成功的平均查找长度。

知识回顾

  • 34
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心碎烤肠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值