散列表查找为何如此之快

一、散列函数

散列是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。建立了关键字与存储位置的映射关系,公式如下:

 存储位置 =f(关键字)

设所有可能出现的关键字集合记为U(简称全集),实际发生(即实际存储)的关键字集合记为K(|K|比|U|小得多)。
散列方法是使用函数fU映射到表T[0..m-1]的下标上(m=O(|U|))。这样以U中关键字为自变量,以f为函数的运算结果就是相应结点的存储地址。从而达到在O(1)时间内就可完成查找。 

其中:

  • f:U(可能出现的全集)→{0,1,2,…,m-1} ,通常称f为散列函数(Hash Function)。散列函数f的作用是压缩待处理的下标范围,使待处理的|U|个值减少到m个值,从而降低空间开销。
  • T为散列表(Hash Table)。
  • f(Ki)(Ki∈U)是关键字为Ki结点存储地址(亦称散列值或散列地址)。
  • 将结点按其关键字的散列地址存储到散列表中的过程称为散列(Hashing)

 

二、直接寻址法

散列的概念属于查找,它不以关键字的比较为基本操作,采用直接寻址技术。

在最坏情况下,散列表的查找和删除的最坏时间代价为O(n),但是在实际中,散列表的性能是很好的,在一些合理的假设下,散列表的查找和删除操作的平均时间代价为O(1)。

 

在说直接寻址法之前,先举个例子:

现在有10个门,并且从1到10标好了号码,你手中有5号门的钥匙,那如何打开第五号门呢?肯定不是一个一个的去试,而是直接去开第5号门。

 

python的列表中也有通过key找到数据的思想,列表是可以直接访问的,通过列表的起始地址加上一个偏移量得到目标数据的存储地址。

list_a = [1,2,3,4]
list_a[2]
>>>3

这种方法是很快的,可以在O(1)时间内完成。直接寻址表就借助了列表这种可直接访问的优势。


如果我们现在要对0-100岁的人口数字统计表,那么我们对年龄这个关键字就可以直接用年龄的数字作为地址。此时f(key) = key。

地址年龄人数
000500万
011600万
022450万
.........
20201500万
.........

这个时候,我们可以得出这么个哈希函数:

f(0) = 0,f(1) = 1,……,f(20) = 20。

这个是根据我们自己设定的直接定址来的。人数我们可以不管,我们关心的是如何通过关键字找到地址。


如果我们现在要统计的是80后出生年份的人口数,那么我们对出生年份这个关键字可以用年份减去1980来作为地址。此时f (key) = key-1980。

地址出生年份人数
001980500万
011981600万
021982450万
.........
2020001500万
.........

假如今年是2000年,那么1980年出生的人就是20岁了,此时 f(2000) = 2000 - 1980,可以找得到地址20,地址20里保存了数据“人数500万”。


也就是说,我们可以取关键字的某个线性函数值为散列地址,即:

f(key) = a × key + b

这样的散列函数优点就是简单、均匀,也不会产生冲突,但问题是这需要事先知道关键字的分布情况,适合査找表较小且连续的情况。由于这样的限制,在现实应用中,直接寻址法虽然简单,但却并不常用。

 

三、除留余数法

除留余数法此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为:

f( key ) = key mod p ( p ≤ m )

mod是取模(求余数)的意思。事实上,这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。

 

一个例子

  • 很显然,本方法的关键就在于选择合适的p, p如果选得不好,就可能会容易产生同义词。下面我们来举个例子看看:

有一个关键字,它有12个记录,现在我们要针对它设计一个散列表。如果采用除留余数法,那么可以先尝试将散列函数设计为如下方法:

f(key) = key mod 12

比如29 mod 12 = 5,所以它存储在下标为5的位置。

下标01234567891011
关键字122538151629786756212247

不过这也是存在冲突的可能的,因为12 = 2×6 = 3×4。如果关键字中有像18(3×6)、30(5×6)、42(7×6)等数字,它们的余数都为6,这就和78所对应的下标位置冲突了。

甚至极端一些,对于下图的关键字,如果我们让p为12的话,就可能出现下面的情况,所有的关键字都得到了0这个地址数,这未免也太糟糕了点。

下标000000000000
关键字1224364860728496108120132144

但是我们如果不选用p=12来做除留余数法,而选用p=11,则结果如下:

下标1234567891001
关键字1224364860728496108120132144

这个时候就只有12和144有冲突,相对来说,就要好很多了。

 

如何合理选取p值

使用除留余数法的一个经验是,若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。

这句话怎么理解呢?再举个例子:某散列表的长度为100,散列函数H(k)=k%P,则P通常情况下最好选择哪个呢?

A、91 B、93 C、97 D、99

实践证明,当P取小于哈希表长的最大质数时,产生的哈希函数较好。所以选97,因为它是离长度值最近的最大质数。

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值