1.应用背景
普通查找方法特点:通过将关键字值与给定值比较,来确定位置。效率取决比较次数。
理想的方法是:不需要比较,根据给定值能直接定位记录的存储位置。这样,需要在记录的存储位置与该记录的关键字之间建立一种确定的对应关系,使每个记录的关键字与一个存储位置相对应。
这就是我们要提起的哈希表
2.哈希(也称为Hash、杂凑、散列)基本思想
在记录的存储地址和它的关键字之间建立一个确定的对应关系;这样,不经过比较,一次存取就能得到元素。 哈希函数——在记录的关键字与记录的存储位置之间建立的一种对应关系。 哈希函数是一种映象,是从关键字空间到存储位置空间的一种映象。哈希函数可写成:
addr(ai)=h(ki)
ai是表中的一个元素 ,addr(ai)是ai的存储位置信息 ki是ai的关键字
哈希表——应用哈希函数,由记录的关键字确定记录在表中的位置信息,并将记录根据此信息放入表中,这样构成的表叫哈希表。 哈希查找——利用哈希函数进行查找的过程。
3.常用的哈希函数
(1)直接定址法
构造:取关键字或关键字的某个线性函数作哈希地址,即H(key)=key 或 H(key)=a·key+b 特点: 直接定址法所得地址集合与关键字集合大小相等,不会发生冲突 实际中能用这种哈希函数的情况很少。
此法仅适合于: 地址集合的大小 = = 关键字集合的大小
(2)数字分析法
构造:对关键字进行分析,取关键字的若干位或其组合作哈希地址。
例 有80个记录,关键字为8位十进制数,哈希地址为2位十进制数
此方法仅适合于: 能预先估计出全体关键字的每一位上各种数字出现的频度。
(3)平方取中法
以关键字的平方值的中间几位作为存储地址。求“关键字的平方值” 的目的是“扩大差别” ,同时平方值的中间各位又能受到整个关键字中各位的影响。
(4)折叠法
构造:将关键字分割成位数相同的几部分,然后取这几部分的叠加和(舍去进位)做哈希地址。 种类 移位叠加:将分割后的几部分低位对齐相加 间界叠加:从一端沿分割界来回折送,然后对齐相加。
此方法适合于: 关键字的数字位数特别多,且每一位上数字分布大致均匀情况。
(5)除留余数法
构造:取关键字被某个不大于哈希表表长m的数p除后所得余数作哈希地址,即H(key)=key % p,pm 特点: 简单、常用,可与上述几种方法结合使用 p的选取很重要;p选的不好,容易产生同义词
(6)随机数法
构造:取关键字的伪随机函数值作哈希地址,即H(key)=random(key) 适于关键字长度不等的情况
说明: 哈希函数构造不应太复杂 不存在绝对好和坏的函数
4.存在问题
除了特别简单的应用,在大多数情况下,所构造出的哈希函数是多对一的(非单射函数)。即可能有多个不同的关键字,它们对应的哈希函数值是相同的,这意味着不同记录由哈希函数确定的存储位置是相同的。这种情况被称为冲突。 即: 若key1不等于key2,而h(key1)=h(key2)
Hash查找适合于关键字可能出现的值的集合远远大于实际 关键字集合的情形。
根据抽屉原理,冲突是不可能完全避免的,所以,要解决: (1)构造一个性能好,冲突少的Hash函数 (2)如何解决冲突
5.冲突解决
★用哈希(散列)方法处理冲突(碰撞)时可能出现堆积(聚集)现象,下列选项中,会受堆积现象直接影响的是 ()
A存储效率 B散列函数 C装填因子 D平均查找长度
产生堆积现象,会导致平均查找长度增大,而存储效率,散列函数,填装因子都不会变化
A)开放定址法
方法:当冲突发生时,形成一个探查序列;沿此序列逐个地址探查,直到找到一个空位置(开放的地址),将发生冲突的记录放到该地址中,即 Hi=(H(key)+di) % m,i=1,2,……k(k<=m-1) 其中:
H(key)——哈希函数
m——哈希表表长
di——增量序列
分类 线性探测再散列:di=1,2,3,……m-1
二次探测再散列:di=1²,-1²,2²,-2²,3²,……±k²(k<=m/2)
伪随机探测再散列:di=伪随机数序列
开放定址法的几个问题 1)删除:只能作标记,不能真正删除 2)溢出 3)聚集问题
B) 链地址法
方法:将所有关键字为同义词的记录存储在一个单链表中,并用一维数组存放头指针。
例 :已知关键字(19,14,23,1,68,20,84,27,55,11,10,79) 哈希函数: H(key)=key % 13 用链地址法处理冲突
6.哈希查找效率分析
在哈希表上进行查找的过程和哈希造表的过程基本一致。给定 K 值,根据造表时设定的哈希函数求得哈希地址,若表中此位置上没有记录,则查找失败;否则比较关键字,若和给定值相等,则查找成功:否则根据造表时设定的处理冲突的方法找“下一个地址”,直至哈希表中某个位置为“空”或者表中所填记录的关键字等于给定值时为止。
成功的话,对应着一个成功查到所遍历的长度,失败的话,也对应一个确认为失败所需要遍历的长度。(如果这句话你没看懂,请不要着急,看完最后的例子你就会明白了)
所以,对哈希表查找效率的量度,用平均查找长度来衡量。
查找过程中,关键字的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
1.哈希函数是否均匀;
2.处理冲突的方法;
3.哈希表的装填因子。
分析这三个因素,尽管哈希函数的“好坏”直接影响冲突产生的频度,但一般情况下,我们总认为所选的哈希函数是“均匀的”,因此,可不考虑哈希函数对平均查找长度的影响。处理冲突的方法不同,哈希表也会不同,所以也影响平均查找长度。这里主要介绍装填因子对查找效率的影响
哈希表的装填因子定义为
α是哈希表装满程度的标志因子。由于表长是定值,α与“填入表中的元素个数”成正比,所以,α越大,填入表中的元素较多,产生冲突的可能性就越大;α越小,填入表中的元素较少,产生冲突的可能性就越小。
实际上,哈希表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。以下给出几种不同处理冲突方法的平均查找长度:
个人理解:
注意这里的平均查找长度是针对几种不同的处理冲突方法的普遍性规律,具体问题还要具体分析,
例如用Hi=(H(key)+di) % 7)线性探测再散列:di=1,2,3,……
数据1:7,14,21,49.数据2:7,8,9,10.
显然这两个查找成功的平均查找长度是不一样的,不能死板的套用上面的公式,或者你可以这样理解:上面的公式是考虑了所有情况给出的数学期望值,是一种宏观把控,不是微观上的。
下面就从具体问题分析一下成功和失败的情况
成功:关键字通过哈希函数得到的地址上得到想要的数据,或者发生冲突,于是根据造表时设定的处理冲突的方法找“下一个地址”,最终找到要查的数据。
失败:关键字通过哈希函数得到的地址上没有数据,或者有数据但发生冲突,于是根据造表时设定的处理冲突的方法找“下一个地址”,最终没有找到要查的数据。
查找成功说白了就是要找的那个值在哈希表里面。查找失败说白了就是要找的那个值没有在哈希表里面。都用平均查找长度来刻画效率。(可能你觉得失败没有效率的说法,你不妨把他理解为【判断哈希表中没有某个值】的效率)
查找成功的 ASL 指查找到哈希表中已有元素的平均探测次数,它是找到表中各个已有元素的探测次数的平均值。
这个比较容易理解,就不单独举例子说明了。
查找失败的 ASL 是指表中所有可能散列的位置上要插入新元素时,为找到一个空位置 的探测次数 的平均值。(如果不理解请看例子,看完例子,请再回头理解这句话)
举个例:
假如你的哈希函数是key=x mod 10,填充后的哈希表如下所示:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
10 | 1 | 12 | 15 | 25 | 18 | 19 | 29 |
如果你要查找这个哈希表里面有没有0这个数,那你就会去序号0下面找,这个地方被10填充了,那就往后找,后面依次是1、12,都不等于0,再往后就为空,说明这个表里面没有0。总共查找了4次。
如果你要查找这个哈希表里面有没有2这个数,那你就会去序号2下面找,做一次比较,下面是12,不相等,往后面找,后面是空,那查找结束。总共查找了2次。
如果你要找29,那就会在序号9下面找,这里被19填充了,于是往后,找到29。总共查找了2次。
所以,每次查找不成功的查找长度就等于从序号找到第一个空的格子的距离。(注:)
在我举的这个例子里面,ASL=(4+3+2+1+1+3+2+1+4+3)/10
注:一般遇到的题目中,哈希表的长度会比插入数据的长度多,所以会有空格。但如果有意外,请记住最根本的原则:查找失败的 ASL 其实就是确认一个值不在哈希表中所付出的平均代价==>本来按照处理冲突的方法,要查找的值应进入到A位置,但A存的不是该值,于是从A往后找,找遍了所有这个值可以放的位置,都没找到这个值。这样花费的代价便是确认一个值不在表中的代价,也就是查找失败的代价。
7.练习
将关键字序列(7,8,30,11,18,9,14)散列存储到散列表中,散列表的存储空间是一个下标从0开始的一维数组,散列函数为:H(key)=(key×3) MOD 7,处理冲突采用线性探测再散列法,要求装填(载)因子为0.7。
(1)请画出所构造的散列表。
(2)分别计算等概率情况下查找成功和不成功的平均查找长度
!!不成功的时候尤其注意!!!