计算机基础数据结构讲解第二篇-散列查找
这篇文章将介绍如何使用散列表进行查找。
一:散列表的基本概念
1.散列函数
散列函数是把查找表中的关键字映射成该关键字对应的地址的函数,记为Hash(key)=Addr。它可能会把两个或两个以上的不同关键字映射到同一地址,这种情况称之为冲突,这些冲突的关键字称之为同义词。因为冲突是无法避免的,所以在设计哈希函数的时候还要设计好处理冲突的方法。
2.散列表
散列表是根据关键字而直接进行访问的数据结构,建立了关键字和存储地址之间的一种直接映射关系。
理想条件下,散列表查找到时间复杂度为O(1),与表中的元素个数无关。
散列表又叫哈希表。
二:散列函数的构造方法
在构造散列函数的时候,要注意以下几点:
(1)散列函数的定义域包括所有的关键字,值域的范围依赖于散列表的大小或地址范围。
(2)散列表计算出来的地址等概率,均匀分布在整个地址空间中,尽量减少哈希冲突。
(3)散列函数应尽量简单,计算地址很快。
下面介绍常用的散列函数:
1.直接定址法
直接去关键字的某个线性函数值作为散列地址。
优点:最简单,不会产生冲突。
缺点:关键字的分布基本连续,若分布不连续,会导致空位过多,造成存储空间浪费。
适合:查找表较小且连续的情况。
2.除留余数法
假定散列表长度为m,取一个质数p<=m,p和m要最接近,然后利用公式H(key)=key%p获得各个关键字的地址。
3.数字分析法
如果关键字是位数较多的数字(比如手机号),且这些数字部分存在相同规律则可以采用抽取剩余不同规律部分作为散列地址,将关键字分配到散列表的各个位置。
适合:关键字位数比较大的查找表。
4.平方取中法
取关键字平方的中间位数作为散列地址,这种方法得到的散列地址与关键字的每位都有关系,因此得到的散列地址分布比较均匀。
适合:不知道关键字的分布,而位数又不是很大的情况。
三:处理冲突的方法
散列表不可能绝对地避免冲突,处理冲突有好多种方法,这里介绍常用的开放定址法和拉链法。
1.开放定址法
开放定址法也叫闭散列,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
当一个关键字和另一个关键字发生冲突时,使用某种探测技术在Hash表中形成一个探测序列,然后沿着这个探测序列依次查找下去,当碰到一个空的单元时,则插入其中。基本公式为:
H(i) = (H(key)+di)mod m。
其中H(i)为散列函数,di为增量序列,m为表长。根据di的取值方法不同我们又可以分为各种探测法,常用的有线性探测法,平方(二次)探测法,再散列探测法。
(1)线性探测法
以增量序列1,2,……,(M -1)循环试探下一个存储地址,即di = i。如果找到的位置为空则进行插入,反之试探下一个增量。
缺点:会造成元素聚集现象,降低查找效率。
(2)平方探测法
以增量序列1,-1,4,-4…,k2,-k2且k ≤ m/2 循环试探下一个存储地址。又称二次探测法
优点:避免出现"堆积"问题。
缺点:不能探测到散列表上的所有单元,但至少能探测到一半单元。
(3)再散列法
di为i* H2(key),H2(key)是另一个散列函数。又称双散列法。探测序列成:H2(key),2H2(key),3H2(key),……。对任意的key,H2(key)不为0。探测序列还应该保证所有的散列存储单元都应该能够被探测到。它的具体散列函数形式如下:
H(i) = (H(key)+i*H2(key))mod m
初始探测探测位置H0 = H(key)%m。i是冲突的次数,初始为0。最多经过m-1次探测就会遍历表中所有位置,回到H0位置。
(4)注意事项
对于开放定址法的删除操作,不能简单的进行物理删除,因为对于同义词来说,这个地址可能在其查找路径上,若物理删除的话,会中断查找路径,故只能设置删除标志。但多次删除后,会有很多位置没有被利用,可以把删除标记的元素按需要物理删除。
2.拉链法
拉链法又叫链地址法,拉链法就是把具有相同散列地址的关键字(同义词)值放在同一个单链表中,称为同义词链表。有m个散列地址就有m个链表,同时用指针数组T[0..m-1]存放各个链表的头指针,凡是散列地址为i的记录都以结点方式插入到以T[i]为指针的单链表中。T中各分量的初值应为空指针。
拉链法查找成功和不成功的平均查找长度如下: