前面的查找是一系列关键字比较,查找速度相对较慢。HASH查找的优势来了
a. 哈系函数: 一个把查找表中的关键字映射成该关键字对应的地址的函数,adde = HASH(key)。 这里的地址可以是下标,索引,或内存地址。
b. 散列表:由根据关键字而直接进行访问的数据结构。散列表建立了关键字和存储地址之间的一种直接映射关系。Hash函数的构造方法
a.直接定址法:H(key) = a*key+b (a,b作为常数,计算简单,但只适合关键字分布连续的情况)
b.除留余数法:最简单也是最常用的方法,假定散列表表长为m,取一个不大于m但最接近或等于m的质数p,利用下列公式把关键字转换成散列地址。H(key)=key%p
c.数字分析法:设关键字是r进制,而r个数码在各位上出现的频率不一定相同,可能在某个位上分布均匀些,每种数码出现的机会相等。而某些位上分布不均匀,只有某几种数码经常出现,则应选取数码分布较为均匀的若干位作为散列地址。这种方法适合与已知关键字集合。(ps: 并不知道它在构造hash表时是怎么使用的)
d.平方取中法:取关键字的平方值的中间几位作为散列地址。使用与关键字的每一位取值都不够均匀或均小于散列地址所需的位数。
e. 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可能短一些),然后取这几部分的叠加和作为散列地址,这种方法称为折叠法。适用与关键字很多,且关键字中每一位上数字分布大致均匀时,可以采用该方法。
3.处理冲突的方法
上述几个构造函数可以看出,基本都不可避免产生冲突,故提出以下几种解决方案。
第一类: 开放定址法
可存放新表项的空闲地址既向同义词表项开放,也向非同义词表项开放。
Hi = (H(key)+di)%m
其中,i=1, 2, 3, …… , k(k<=m-1);m表示散列表表长,di为增量序列
a. 线性探测法:使用较多,发生冲突时,顺序查看表中下一个单元(当探测到表尾m-1时,下个探测地址就是首地址0)例如:addr = Hash(5) = 2。应该存入地址为2的单元,但这个已经被使用,则顺序探寻下一个地址。要么顺序查找找到,要么查找全表完毕。缺点明显:易聚集,查找起来困难,许多时候平均查找长度较大
b. 平方探测法:di=1^2,-1^2,2^2,-2^2, … ,k^2,-k^2。其中k<=m/2,m必须是一个可以表示成4K+3的质数,又称二次探测法。(可以看出,是在addr左右来回摆动探测)
c. 再散列法:di=Hash2(key).当第一个散列函数H(key)得到的地址发生冲突,则利用第二个散列函数Hash2(Key)计算该关键字的地址增量。再散列法中,最多经过m-1次探测会遍历表中所有位置,回到H0位置。
d. 伪随机数序列法:di= 伪随机数序列
注意:在开放地址法中,不能随便物理删除表中已有元素,因为若删除元素将会截断其他具有相同散列地址的元素的查找地址。所以要删除一个元素时,需要做一个删除标记,进行逻辑删除。副作用就是执行多次删除后,表面上看起来列表很满,实际上位置并没有利用到。
第二类:拉链法
避免非同义词发生冲突,可以把所有的同义词存储在一个线性链表中,这个链表由其散列地址唯一标识。假设散列地址为i的同义词链表存放在散列表的第i个单元中,因此查找,插入和删除操作主要在同义词链当中进行。拉链法适用与经常插入和删除的情况。
- 平均查找长度
散列表的查找效率取决于三个因素:散列函数,处理冲突的方法和装填因子A
A = (表中记录数n)/(散列表长度m)
例:
ASL(成功) = (1*7+2*4+3*1)/12 (12个元素)
ASL(不成功) = (1×6+2*2+3*3+4*1)/13 (表长为13,所以所有值都可以看成13个元素)