随机生成数字组成列表_散列表、全域散列与完全散列

b89a85f1405f47744b0b4aaf93a5dfc7.png

散列(hash)又称哈希,是一种组织数据的方式。从其中文译名来看,有打乱排列的意味,事实上也正是这样。散列函数能够将一组数据随机排布到某个输出区间上,这种随机性使得我们可以借其实现更高性能的容器数据结构。

本文从基本的散列表(hash table)开始,介绍链接法和开放寻址法对冲突的不同解决方案,进一步引出全域散列(universal hashing)与完全散列(perfect hashing)的概念与实现方式,并推导它们的时间和空间复杂度。最后,看看实际中,Java是如何实现散列表的。

散列表的由来

考虑某一类数据,如果关键字的取值范围是有限的,比如所有由三位十进制数字组成的整数,其取值范围为0~999。那么我们可以直接创建一个大小为1000的数组,将每个元素保存在相同下标对应的位置上,需要访问哪个直接通过下标索引,时间复杂度为

这种方法的问题是,虽然有1000种可能的数据,但实际上总的数据量可能很小,也许只有10个,它们在数组中分布的很稀疏,相当于浪费了大量的存储空间。

此时,散列的作用就凸显出来了。我们可以使用某个散列函数将关键字的定义域

映射到
的范围内,只需要大小为10的数组即可装载这些数据。不过,问题也很明显,将这10个数据恰好映射到
范围内的不同位置恐怕不太容易,这样的散列函数是不容易找到的。况且就算找到了,针对不同的数据,这一性质肯定是无法保证的。

大多数情况下,至少会有两个数据被映射到了同一个位置,这种情况我们称为冲突。解决冲突的方式有两种,链接法和开放寻址法,我们将分别阐述这两种方法。

链接法

链接法在每个数组中链接一条链表,将冲突的元素依次放置在链表中。访问时,先根据散列函数计算数组的下标,然后遍历对应的链表,找到目标元素。下图展示了链接法的存储结构。

d7e67102e9abfcc0e97fddb89b0517a9.png

其中,

具有相同的散列值,因此以链表的形式挂接在数组的第二个位置上。其它元素同理。数组
的每个位置称为一个槽(slot),记数组
的长度为

散列表的原理很简单,但问题是这种数据结构的性能如何呢?

可以想象,最好的情况下,散列函数将关键字均匀地分布在整个数组上,此时,每个链表的平均长度很短。最坏情况下,散列函数将所有关键字恰好都映射到同一个下标,链表长度与数据总数相同,导致时间复杂度为

这样看来,散列表的性能比较依赖于散列函数的选取。我们将最好情况归纳为一个假设,称为“简单均匀散列”。该假设认为我们选择的散列函数可以将任何一个元素等可能地散列到

个槽中的任意一个。在这一假设下,每个槽链接的元素数平均为
个,于是平均情况下,访问任意一个元素的时间复杂度都应该不超过
。其中,
表示计算散列函数所需的时间。

我们将

称为装载因子(load factor)
,它的实际意义是每个链的平均长度。根据上文的分析,当装载因子为常数时,散列表的时间复杂度为

接下来的问题是如何选取散列函数,使其尽量接近简单均匀散列的假设。

对于前面提到的将

散列到
的案例,最简单的方法是对
取模,即
,其中,记散列函数为
。可惜这个散列函数并不优秀,因为
的值只依赖于该数的最后一位。如果最后一位在所有数据中的分布并不均匀,我们离均匀散列假设就渐行渐远了。当可以自行决定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值