散列表(哈希表)(散列函数构造、处理冲突、查找)

 面介绍的查找是建立在比较的基础上,查找效率由比较次数决定,不仅与被查数据整体的存储结构有关,还与逻辑上可被查找的数据集合所含的数据个数有关,同时与待查记录在查找表中位置以及查找策略如查找方向有关。

理想的查找是不经过任何比较就能根据所查关键吗直接得到待查记录所在的存储位置。散列查找技术就是朝该方向努力,它在关键码和存储位置之间建立一种对应关系,散列函数,由该函数可计算出关键码唯一的地址。在存储元素时通过计算通过计算关键码的散列函数值确定存储地址。查找时,计算给定关键字的散列函数值得到存储地址,直接查找。

关键码和存储位置之间的对应函数是散列函数,也称为哈希函数,hash函数。

一、散列函数的构造方法

1、直接定址法

关键码本身和地址之间存在某个线性函数关系时,散列函数取为关键码的线性函数,即:H(key)=a*key+b,a、b均为常数。

散列表(哈希表)(散列函数构造、处理冲突、查找)_第1张图片

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

2、数字分析法

假设关键码完全已知,且每个关键码都是以某个数r为基数(例以10为基数的十进制数)的值,则关键码中若干位恰能构成分布比较均匀的散列地址空间时,可取关键码的若干位的组合作为散列地址。

3、除留余数法

通过选择适当的正整数p,按计算公式H(K)=Kmodp来计算关键码K的散列地址。

若关键码个数为n,散列表表长为m(一般m>=n),通常选p为小于或等于表长m的最大素数或不包含小于20的质因子的合数,一般也要求p>=n。

这种方法计算最简单,也不需根据全部关键码的分布情况研究如何从中析取数据,最常用。

4、平方取中法

将关键码K平方,取K^2中间几位作为其散列地址H(K)的值。

假如有以下关键字序列{421,423,436},平方之后的结果为{177241,178929,190096},那么可以取{72,89,00}作为Hash地址。

5、折叠法

将关键码从低位到高位(或从高位到低位)分割成位数相等的几段,最后一段可以短些,然后将这些段构成的数值按照某种叠加方法求和。最后,在散列地址范围限制下,取求和结果的最后几位作为关键码的散列函数值。

叠加方法:

(1)移位叠加:将各段数值最后一位对齐相加

(2)间界叠加:从各个数值段的一端到另一端来回折叠后(奇数段位正序,偶数为倒序),以最后一位对齐后相加

例:

key=12360324711202065,哈希表长度为1000,则应把关键字分成3位一段,在此舍去最低的两位65,分别进行移位叠加和折叠叠加,求得哈希地址为105907,如图所示。

1   2   3                    1   2   3

6   0   3                    3   0   6

2   4   7                    2   4   7

1   1   2                    2   1   1

+      0   2   0            +  0   2   0

        ————————            —————

                    1   1   0   5                    9   0   7

 

a)移位叠加             (b) 间界叠加

6、随机数法

采用随机函数作为散列函数H(Key)=random(Key),其中random为随机函数。

当关键码长度不等时,采用该方法较恰当。

二、冲突的处理方法

1、开放定址法(建立闭散列表)

开放定址指散列表的地址对任何记录数据都是开放的,即可存储使用。但散列表长度一旦确定,总的可用地址是有限的。闭散列表表长不小于所需存储的记录数,发生冲突总能找到空的散列地址将其存入。查找时,按照一种固定的顺序检索散列表中的相应项,直到找到一个关键字等于k或找到一个空单元将k插入,故是动态查找结构。

1)线性探测法

从发生冲突位置的下一个位置开始寻找空的散列地址。发生冲突时,线性探测下一个散列地址是:Hi=(H(key)+di)%m,(di=1,2,3...,m-1)。闭散列表长度为m。它实际是按照H(key)+1,H(key)+2,...,m-1,0,1,H(key)-1的顺序探测,一旦探测到空的散列地址,就将关键码记录存入。

该方法会产生堆积现象,即使是同义词也可能会争夺同一个地址空间,今后在其上的查找效率会降低。

2)二次探测法

发生冲突时,下一位置的探测采用公式:Hi=(H(key)+di)%m,(di=1^2,-1^2,2^2,-2^2,.....,q^2,-q^2,q<=根号下m)

在一定程度上可解决线性探测中的堆积现象。

3)随机探测法

di为{1,2,3,...,m-1}中的数构成的一个随机数列中顺序取的一个数

4)再散列函数法

除基本散列函数外,事先设计一个散列函数序列,RH1,RH2,...,RHk,k为某个正整数。RHi均为不同的散列函数。对任一关键码,若在某一散列函数上发生冲突,则再用下一个散列函数,直到不发生冲突为止。

5)建立公共溢出区(单链表或顺序表实现)

另外开辟一个存储空间,当发生冲突时,把同义词均顺序放入该空间。若把散列表看成主表或父表,则公共的同义词表就是一个次表或子表。查找时,现在散列表中查,找不到时再去公共同义词子表顺序查找。

2、拉链法(链地址法、建立开散列表)

将所有散列地址相同的记录存储在同一个单链表中,该单链表为同义词单链表,或同义词子表。该单链表头指针存储在散列表中。散列表就是个指针数组,下标就是由关键码用散列函数计算出的散列地址。初始,指针数组每个元素为空指针,相当于所有单链表头指针为空,以后每扫描到一条记录,按其关键码的散列地址,在相应的单链表中加入含该记录的节点。开散列表容量可很大,仅受内存容量的限制。

例:具体的关键字列表为(19,14,23,01,68,20,84,27,55,11,10,79),则哈希函数为H(key)=key MOD 13。则采用除留余数法和链地址法后得到的预想结果应该为:

散列表(哈希表)(散列函数构造、处理冲突、查找)_第2张图片

三、散列表上的查找

1、时间复杂度分析

查找成功时的平均查找长度ASLsucc、查找不成功时的平均查找长度ASLunsucc

1)具体闭散列表的查找效率分析

关键码{19,14,23,1,68,20,84,27,55,11,10,79},散列表长度m=16,除留余数法设计散列函数,H(key)=key%13,

(1)线性探测法处理冲突

只考虑关键码的比较,不考虑判空等操作,以下列出比较次数

散列地址 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
关键码   14 1 68 27 55 19 20 84 79 23 11 10      
成功时   1 2 1 4 3 1 1 3 9 1 1 3      
不成功时   12 11 10 9 8 7 6 5 4 3 2 1      
ASLsucc=(1*6+2*1+3*4+4*1+9*1)/n=30/12=2.5

ASLunsucc=(1+2+...+12)/m=78/16=4.375

在ASLunsucc的计算中,假设每个地址被查到的概率相等(等于1/m=1/16),

(2)二次探测法

散列地址 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
关键码 27 14 1 68 55 84 19 20   10 23 11   79    
成功时 3 1 2 1 2 3 1 1   3 1 1   5    
不成功时 2 6 4 4 3 5 5 1 2 3 1 1   1    

ASLsucc=(1*6+2*2+3*3+5*1)/n=24/12=2

ASLunsucc=(1*3+2*2+3*2+4*2+5*2+6*1)/m=37/16=2.3125

2)具体开散列表的查找效率分析

如上图拉链法处理冲突中的哈希表,

ASLsucc=(1*6+2*4+3*1+4*1)/n=21/12=1.75

ASLunsucc=(1*2+2*3+4*1)/m=12/16=0.75

一般情况下,处理冲突方法相同的散列表,其查找成功时的平均查找长度依赖于散列表的装填因子:a=表中填入的记录数/哈希表长度

a越小,发生冲突的可能性越小,反之,a越大,表中已填入记录越多,再填记录时,发生冲突的可能性越大,查找时给定值需与之进行比较关键码数目越多。

散列表(哈希表)(散列函数构造、处理冲突、查找)_第3张图片




你可能感兴趣的:(数据结构)