查找
8.7 散列表
8.7.1 散列表的基本概念
- 所谓查找,实际上就是要确定关键字(Key)等于给定值(k)的数据元素的存储地址(Addr)。故查找问题本质上是确定关键字集合K到地址空间A的映射(即函数):H:K->A。因而,任何查找算法,都是计算函数H(k)的值的过程。
- 由于k与H(k)之间没有简单直接的对应关系,函数H()完全是一个隐式函数,故无论是顺序表查找、索引查找还是树表的查找都是通过给定值(k)与数据原始的关键字的比较来确定要查找的数据元素的存储地址(A)。因此,求H(k)值的时间不仅与数据表的存储结构有关,而且与数据表的大小n有关。
- 函数H把关键字转换成一个地址,因此这种存储技术称为键变换。函数H的构造往往与关键字的分布有关。大多采用拼凑方式,只要函数值计算不过于复杂,并且尽量均匀地把数据表中数据元素散布在某存储空间内,因此键变换技术又称杂凑(Hash)技术或散列技术。相应地,存储空间称为散列表,函数H称为杂凑(散列)函数。数据表的这种存储方式叫散列存储。
- 通过散列函数建立了从数据元素的关键字集合到散列表地址集合的一个映射。有了散列函数,就可以根据关键字确定数据元素在散列表中唯一的存放地址。【优点】由于使用这种方法进行查找不必进行多次关键字的比较,因此查找速度比较快。在某些操作系统或大型软件中进行文件管理时常常使用这种方法。
- 一般来说,散列函数是一个压缩映象函数。通常关键字集合比散列表地址集合大得多。因此有可能经过散列函数的计算,把不同的关键字映射到同一个散列地址上,这就产生了冲突。对不同的关键字,通过做列函数的计算,符到了同一散列地址,这种现象被称为冲突。散列地址相同的不同关键字被称为同义词。 冲突太多会降低查找效率,因此,希望能找到一个不容易产生冲突的散列函数,即构造一个地址分布比较均匀的散列函数,使得关键字集合中的任何一个关键字经过这个散列函数的计算,映射到地址集合中所有地址的概率相等,以减少冲突。
- 但实际上,由于关键字集合比地集合大得多,冲突很难避免,所以对于做列方法,需要讨论以下两个问题。
1)对于给定的一个关键字集合,选择一个计算简单且地址分布比较均匀的散列函数,避免或尽量减少冲突。
2)制订解决冲突的方案。
8.7.2 散列函数
在构造散列函数时有几点需要加以注意:
(1)散列函数的定义域必须包括需要存储的全部数据元素的关键字,而如果散列表允许有m个地址时,其值域必须在0~m-1;
(2)散列函数计算出来的地址应能均匀分布在整个地址空间中,若kcy是从关键字集合中随机抽取的一个关键字,散列函数应能以同等概串取0到m-1中的每一个值;
(3)散列函数应是简单的,能在较短的时间内计算出结果。
- 直接定址法
①此类函数取关键字的某个线性函数值作为散列地址:Hash(key)=a*key+c (其中a、c是整常数)。
②【优点】这类散列函数是一对一的映射,一般不会产生冲突。
③但是,【缺点】它要求散列地址空间的大小与关键字集合的大小相同(这种要求是很苛刻的)。特别是当关键字集合很大而且又不连续时,这种方法就不太适宜。 - 数字分析法
①设数据表的长度为n,数据元素的关键字是一个d位数,关键字上每一位可能有r种不同的符号。这r种不同的符号在各位上出现的概率不一定相同,可能在某些位上分布均匀,出现的机会均等;在某些位上分布不均匀,只有某几种符号经常出现。
②数字分析法就是根据散列表的大小,在关键字中选取某些分布均匀的若干位作为散列地址。
计算各位的符号分布均匀度的公式为:
λk=Σ(i=1~r) (aik - n/r)2,k=1,…,d
其中,a表示在第k位上第i个符号出现的次数,n/r表示各个符号在所有关键字中均匀出现的期望值。计算出的λk值越小,表明在该位(第k位)上各种符号分布得越均匀。
③如果散列地址由三位数字组成,则可取各关键字的四、五、六位作为数据元素的散列地址;也可以把第一、二、三、四和第七位相加,舍去进位位,变成一位数,与第五、六位合起来作为散列地址。还有其他方法,显然,数字分析法仅适用子事先明确知道数据表中所有关键字每一位上数值符号的分布情况,它完全依赖于关键字集合,如果换一个关键字集合,选择哪几位要重新决定。 - 除留余数法
①设散列表地址空间大小为m,取一个不大于m,但最接近于或等于m的质数p,或者选取一个不含有小于20的质因数的合数作为除数,除留余数法的散列函数为:Hash(key)=key %p ,p<=m。
其中,“%”是整数除法取余运算,且p应避免取2的幂。如果选p为2的幂,就有p=2i。那么由散列函数Hash(key)计算出来的地址就是key的最低的i位二进位数字。如果 key是十进制数字,那么p也应避免取10的幂。因为这样得到的地址分布太集中,会导致冲突增多。
②例如,设做列表的大小m=25,取质数p=23,散列函数为 Hash(key)=key % 23。则对于关键字为key=7463,有 Hash(7463)=7463%23=11。
可以按此计算出的“地址”存放数据元素。需要注意的是,使用上面的散列函数计算出来的地址范围是0到22,而23、24 这两个放列地址实际上在一开始是不可能用散列函数计算出来的,只可能在处理冲突时用到这些地址。 - 乘余取整法
①先让关键字key乘上一个常数a(0<a<1),提取乘积的小数部分,然后再用整数n乘以这个值,对结果向下取整,把它作为散列地址。其散列丽数为:Hash(key)=⌊n(akey%1)⌋*。其中,akey%1表示取akey的小数部分。
②例如,设列表的大小n=10000。取常数 a=0.6180339,则对于关键字为 key=7463的数据元素,其散列地址为:Hash(7463)=⌊10000(0.6180339+7463%1)⌋=731。
③【优点】是对n的选择不很关键。通常,若地址空间为p位,就选n=2p。 Knuth认为,虽然a取任何值都可以,但对某些值效果更好。最佳的选择与待散列的数据表的特征有关。一般取a=0.6180339比较好。 - 平方取中法
① 取关键字平方的中间位数作为散列地址。
② 例如,关键字4321,那么它的平方就是 18671041,抽取中间的 3 位就可以是671,也可以是 710,用做散列地址。
③平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况。 - 随机数法
①Hash(key)=Random(key)。
②当关键字的长度不等时采用这个方法构造散列函数是比较合适的。 - 折叠法
①将关键字从左到右分割成位数相等的几部分,最后一部分位数不够时可以短些,然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
② 比如关键字是9876543210,散列表表长是3位,将其分为四组,然后叠加求和:987 + 654 + 321 + 0 = 1962,取后3位962作为散列地址。
③折叠法中数位折叠又分为移位叠加和边界叠加两种方法,移位叠加是将分割后是每一部分的最低位对齐,然后相加;边界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
④折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。
总结
- 几个方法的比较:
方法 | 散列函数 | 适用性 | 优点/缺点 |
---|---|---|---|
直接定址法 | H(key)=a*key+c | 适用于查找表较小且连续的情况 | 【优】散列函数一对一映射,一般不会产生冲突【缺】如果关键字分布不连续,会浪费空间 |
数字分析法 | 各位的符号分布均匀度的公式 λk=Σ(i=1~r) (aik - n/r)2 | 仅适用于事先明确知道数据表中所有关键字每一位上数值符号的分布情况 | 依赖于关键字集合,如果换一个关键字集合,选择哪几位要重新决定 |
除留余数法 | H(key)=key %p | 不仅可以对关键字直接取模,也可在折叠、平方取中后再取模 | 若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数,可以更好的减小冲突;除留余数法的地址计算方法简单,而且在许多情况下效果较好 |
乘余取整法 | H(key)=⌊n*(a*key%1)⌋ | 若地址空间为p位,就选2p;最佳的选择与带散列的数据表的特征有关,一般取a=0.6180339 | 【优】对n的选择不很关键 |
平方取中法 | 关键字平方的中间位数 | 适用于不知道关键字的分布,而位数又不是很大的情况 | |
随机数法 | H(key)=Random(key) | 适用于关键字的长度不等的情况 | 【优】对不同的关键字得到的哈希函数值不易产生冲突,由此产生的哈希地址也较为均匀;造表和查找都很方便 |
折叠法 | 将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位) | 适用于关键字位数较多,而且关键字中每一位上数字分布大致均匀的情况 |
- 有一些实现算法,转载自:https://blog.csdn.net/zhhf96/article/details/4862