哈希表存储的基本思想是:以数据表中的每个记录的关键字 k为自变量,通过一种函数H(k)计算出函数值。把这个值解释为一块连续存储空间(即数组空间)的单元地址(即下标),将该记录存储到这个单元中。在此称该函数H为哈希函数或散列函数。按这种方法建立的表称为哈希表或散列表。
k1≠k2,但H(k1)=H(k2),这种现象称为冲突。
具有相同函数值的关键字对该散列函数来说称做同义词
哈希函数的构造方法
构造好的哈希函数的方法,应能使冲突尽可能地少,因而应具有较好的随机性。
1. 直接定址法
哈希函数为关键字的线性函数
H(key) = key 或者
H(key) = a * key + b
这种哈希函数计算简单,并且不可能有冲突发生。当关键字的分布基本连续时,可使用直接定址法的哈希函数。否则,若关键字分布不连续将造成内存单元的大量浪费。
2. 除留余数法
设定哈希函数为:
H(key) = key MOD p (% 、 取余)
其中, p≤m (表长) 并且 p 应为不大于 m 的素数或是不含 20 以下的质因子的合数
给定一组关键字为:12, 39, 18, 24, 33, 21,若取 p=9, 则他们对应的哈希函数值将为:3, 3, 0, 6, 6, 3
可见,若 p 中含质因子 3, 则所有含质因子 3 的关键字均映射到“3 的倍数”的地址上,从而增加了“冲突”的可能。
3.数值分析法
若事先知道所有可能的关键字的取值时,可通过对这些关键字进行分析,发现其变化规律,构造出相应的哈希函数。
例:对如下一组关键字通过分析可知:每个关键字从左到右的第l,2,3位和第6位取值较集中,不宜作哈希地址。
剩余的第4,5,7和8位取值较分散,可根据实际需要取其中的若干位作为哈希地址。
若取最后两位作为哈希地址,则哈希地址的集合为下表所示:
4.平方取中法
取关键字平方后的中间几位作为哈希函数地址(若超出范围时,可再取模)。
设有一组关键字ABC,BCD,CDE,DEF,……其对应的机内码如表所示。假定地址空间的大小为1000,编号为0-999。现按平方取中法构造哈希函数,则可取关键字机内码平方后的中间三位作为存储位置。计算过程如下表所示:
5.折叠法
这种方法适合在关键字的位数较多,而地址区间较小的情况。
将关键字分隔成位数相同的几部分。然后将这几部分的叠加和作为哈希地址(若超出范围,可再取模)。
示例:设给定的关键码为 key = 23938587841,若存储空间限定 3 位, 则划分结果为每段 3 位. 上述关键码可划分为 4段:
239 385 878 41
超出地址位数的最高位删去, 仅保留最低的3位,做为可用的散列地址。
冲突的解决方法
1.开放定址法
用开放定址法处理冲突就是当冲突发生时,形成一个地址序列。沿着这个序列逐个探测,直到找出一个“空”的开放地址。将发生冲突的关键字值存放到该地址中去。
如 Hi=(H(k)+d(i)) % m, i=1,2,…k (k<m-1)
其中H(k)为哈希函数,m为哈希表长,d为增量函数,d(i)=dl,d2…dn-l。
增量序列的取法不同,可得到不同的开放地址处理冲突探测方法。
(1)线性探测法
线性探测法是从发生冲突的地址(设为d)开始,依次探查d+l,d+2,…m-1(当达到表尾m-1时,又从0开始探查)等地址,直到找到一个空闲位置来存放冲突处的关键字。
若整个地址都找遍仍无空地址,则产生溢出。
线性探查法的数学递推描述公式为:
d0=H(k)
di=(di-1+1)% m (1≤i≤m-1)
【例】已知哈希表地址区间为0~10,给定关键字序列(20,30,70,15,8,12,18,63,19)。哈希函数为H(k)=k%ll,采用线性探测法处理冲突,则将以上关键字依次存储到哈希表中。试构造出该哈希表,并求出等概率情况下的平均查找长度。
假设数组为A, 本题中各元素的存放过程如下:
H(20)=9,可直接存放到A[9]中去。
H(30)=8,可直接存放到A[8]中去。
H(70)=4,可直接存放到A[4]中去。
H(15)=4,冲突;
d0=4
d1=(4+1)%11=5,将15放入到A[5]中。
H(8)=8,冲突;
d0=8
d1=(8+1)%11=9,仍冲突;
d2=(8+2)%11=10,将8放入到A[10]中。
H(12)=l,可直接存放到A[1]中去。
H(18)=7,可直接存放到A[7]中去。
H(63)=8,冲突;
d0=8
d1=(8+1)%11=9,仍冲突;
d2=(8+2)%11=10,仍冲突;
d3=(8+3)%11=0,将63放入到A[0]中。
H(19)=8,冲突;
d0=8
d1=(8+1)%11=9,仍冲突;
d2=(8+2)%11=10,仍冲突;
d3=(8+3)%11=0,仍冲突;
d4=(8+4)%11=1,仍冲突;
d5=(8+5)%11=2,将19放入到A[2]中。
由此得哈希表如图所示
等概率情况下成功的平均查找长度为:
(1*5+2+3+4+6)/9 =20/9
利用线性探查法处理冲突容易造成关键字的“堆积”问题。这是因为当连续n个单元被占用后,再散列到这些单元上的关键字和直接散列到后面一个空闲单元上的关键字都要占用这个空闲单元,致使该空闲单元很容易被占用,从而发生非同义冲突。造成平均查找长度的增加。
为了克服堆积现象的发生,可以用下面的方法替代线性探查法。
(2)平方探查法
设发生冲突的地址为d,则平方探查法的探查序列为:d+1^2,d+2^2,…直到找到一个空闲位置为止。
平方探查法的数学描述公式为:
d0=H(k)
di=(d0+i^2) % m (1≤i≤m-1)
【例】已知哈希表地址区间为0~10,给定关键字序列(20,30,70,15,8,12,18,63,19)。哈希函数为H(k)=k%ll,采用平方探查法处理冲突。
【解】
H(20)=9,可直接存放到A[9]中去。
H(30)=8,可直接存放到A[8]中去。
H(70)=4,可直接存放到A[4]中去。
H(15)=4,冲突;
d0=4
d1=(4+12) % 11=5,放到A[5]中
H(8)=8,冲突;
d0=8
d1=(8+12) % 11=9,仍冲突
d2=(8+22) % 11=1 放到A[1]中。
H(12)=l,冲突;
d0=1
d1=(1+12) % 11=2,放到A[2]中。
H(18)=7,可直接存放到A[7]中去。
H(63)=8,冲突;
d0=8
d1=(8+12) % 11=9,仍冲突
d2=(8+22) % 11=1,仍冲突
d3=(8+32) % 11=6,放到A[6]中。
H(19)=8,冲突;
d0=8
d1=(8+12) % 11=9,仍冲突
d2=(8+22) % 11=1,仍冲突
d3=(8+32) % 11=6,仍冲突
d4=(8+42)% 11= 2,仍冲突
d5=(8+52) % 11=0,放到A[0]中
在等概率情况下成功的平均查找长度为:
(1*4+2*2+3+4+6)/9 =21/9
平方探查法是一种较好的处理冲突的方法,可以避免出现堆积问题。它的缺点是不能探查到哈希表上的所有单元,但至少能探查到一半单元。
例如,若表长m=13,假设在第3个位置发生冲突,则后面探查的位置依次为4、7、12、6、2、0,即可以探查到一半单元。
若解决冲突时,探查到一半单元仍找不到一个空闲单元。则表明此哈希表太满,需重新建立哈希表。
2.链地址法
用链地址法解决冲突的方法是:把所有关键字为同义词的记录存储在一个线性链表中,这个链表称为同义词链表。并将这些链表的表头指针放在数组中(下标从0到m-1)。这类似于图中的邻接表和树中孩子链表的结构。
链地址法查找分析
由于在各链表中的第一个元素的查找长度为l,第二个元素的查找长度为2,依此类推。因此,在等概率情况下成功的平均查找长度为:
(1*5+2*2+3*l+4*1)/9=16/9
虽然链地址法要多费一些存储空间,但是彻底解决了“堆积”问题,大大提高了查找效率。
哈希表的查找及性能分析
哈希法是利用关键字进行计算后直接求出存储地址的。当哈希函数能得到均匀的地址分布时,不需要进行任何比较就可以直接找到所要查的记录。但实际上不可能完全避免冲突,因此查找时还需要进行探测比较。
在哈希表中,虽然冲突很难避免,但发生冲突的可能性却有大有小。这主要与三个因素有关。
第一:与装填因子有关
所谓装填因子是指哈希表中己存入的元素个数n与哈希表的大小m的比值,即 =n/m。
当 越小时,发生冲突的可能性越小,越大(最大为1)时,发生冲突的可能性就越大。
第二:与所构造的哈希函数有关
若哈希函数选择得当,就可使哈希地址尽可能均匀地分布在哈希地址空间上,从而减少冲突的发生。否则,若哈希函数选择不当,就可能使哈希地址集中于某些区域,从而加大冲突的发生。
第三:与解决冲突的哈希冲突函数有关
哈希冲突函数选择的好坏也将减少或增加发生冲突的可能性。
哈希表中查找成功和不成功时的平均查找长度
首先明确一个概念装载因子,装载因子是指所有关键子填充哈希表后饱和的程 度,它等于 关键字总数/哈希表的长度查找成功是探测位置的次数
查找不成功就是从查找位置开始直到一个位置为空需要比较的次数。