哈希查找是面试中常见的问题。本文为自己梳理一下知识点。
对于大多数查找算法,其查找效率取决于查找过程的比较次数。比如二叉查找树,二分查找。而有一种查找不需要经过元素间的比较,而直接让表中数据元素的关键字与其在内存中的存储位置之间建立关系,在查找时直接由关键字获得数据元素的位置 。这就是哈希表查找。哈希查找特别适用于集合元素无明显关系的集合。
哈希表
哈希存储的基本思想是以关键字(key)为自变量,通过散列函数或哈希(Hash)函数,计算出函数值(哈希地址),作为数据元素的地址,并将数据元素存入相应地址的存储单元中。查找时再根据要查找的关键字使用同样的散列函数计算出哈希地址,然后直接到相应的存储单元中取出元素即可。
例:假设有一个集合:S={16,76,63,57,40},使用Hash法存储该集合。现选取哈希函数:h(K)=K%m. 即关键字值对m(正整数)取余作为哈希地址。本例中集合元素个数n等于5,为了能他们的使哈希值不重复,这里m可以取11.每个元素分别计算哈希地址得:
h(16) = 16%11 = 5
h(57) = 57%11 = 2
… …
h(40) = 40%11 = 7
产生哈希表如下图:
在查找时,同样对查找关键字进行一次哈希函数,所得值即为元素所有地址。本例中若要查找16,h(16)=5,在地址(下标)位置取出元素即可。
对于含有n个数据元素的集合,只要m(存储单元的个数)足够大,总能找到关键字与哈希地址一一对应。比如,最大关键字为m,此时可以分配m个存储单元,散列函数选取h(key)=key,但如果集合元素不是连续的(通常不会是连续的),就会造成巨大的存储空间浪费,甚至不可能分配到这么大的空间。
哈希冲突
由于存储空间有限,也避免空间的浪费,通常取哈希地址集合要少于关键字集合(关键字的取值空间),因此经过哈希函数变换后,可能会出现不同的关键字映射到同一个哈希地址上,这种现象称为哈希冲突。比如在上面例子中,若插入27至集合中,h(27)= 27%11 = 5,哈希值就会与16的哈希地址相同。
实际上冲突是不可避免的,只能尽可能的减少并且采取一定的策略解决冲突。综上,使用哈希表需要解决两个问题:
(1) 构造合适的哈希函数,尽可能减少冲突。
(2) 制定好应对冲突的方案。
常用的哈希函数
在实际工作中根据不同的情况采用不同的哈希函数,通常考虑的因素有:
(1)计算哈希函数所需要的时间
(2)关键字的长度
(3)哈希表的大小
(4)关键字的分布情况
(5)记录的查找频率
1.取余法
该方法以一个略不天哈希地址集合中地址个数m的质数去除关键字,取余数作为哈希地址。即
H(key) = key%p,(p<=m)
使用取余法选取的p非常重要,若选取不当,会造成严重的冲突。比如p=2^k或者p=10^k,虽然计算容易,但本质上只使用了key的几个位(右移位运算),没有充分利用关键字的特征,会使哈希地址分布不均匀,易造成冲突。数学计算表明,p最好是一个小于中等于m的某个最大素数(此时求最大素数的算法就有应用场景了)。
2.直接地址法
取关键字的线性函数为哈希函数。即:
H(key) = a*key +b (a,b为常数)
直接地址法(线性地址法)的特点是哈希函数简单,并且对不同的关键字不会产生冲突。但在实际问题中应用很少,原因是关键字集合的元素往往是离散的,而且关键字集合通常比哈希地址集合大,用此方法会造存储空间大量浪费。
3.数字分析法
对于关键字的位数比存储区域的地址码位数多的情况,可以采取对关键字的各位进行分析,丢掉分布不均匀的位,留下分布均匀的位作为哈希地址,这种方法称为数字分析法。
数字分析法的特点是哈希函数依赖全体关键字集合,对不同的关键字,所保留的地址可能不同。此方法仅适用于能预先估计出全体关键字的每一位数字出现的频度的情况。
4.折叠法
折叠法是将关键字自左到右分成位数相同的几部分,最后一部分位数可以不同,然后将这几部分叠加求和,并按哈希表的表长,取最后的几位作为哈希地址。常用的叠加方法有两种:
(1) 移位叠加法,将分割后的各部分最低部对齐,求和
(2) 间界叠加法,从一端向另一端沿分割界来回折叠,然后对齐最后一位,求和。
对于数位比较多且每一位符号分布均匀的关键字可以采用此方法求哈希地址。
5.平方取中法
平方取中法的算法是取关键字平方的中间几位作为哈希地址。此方法取得的哈希地址同关键字每一位都有关,使得哈希地址具有较好的分散性。平方取中法适用于关键字中的每一位取值都不够分散或者较分散的位数小于哈希地址所需要的位数的情况。
6.随机数法
取关键字的随机函数值作为其哈希地址关键字,即H(key)=random(key),random为固定算法的随机函数,当关键字长度不相等时,此方式比较适用。
处理冲突的方法
合适的哈希函数可以减少冲突,但冲突是不可避免的,需要合理的处理冲突方法。
1.开放定址法
开放定址法处理冲突的基本思想是当冲突发生时,形成一个地址序列,沿着这个序列逐个探测,直到找到一个“空”的开放地址,将发生冲突的关键字值存入到该地址中。开放地址法可以表示为:
Hi = (H(key)+di)%m (i=1,2,...k(k<=m-1)
H(key)是关键字key的哈希函数,m为哈希表长,di为每次再探测时的地址增量。地址增量的取法主要有线性探测法、二次探测法和双哈希函数探测法。
2.链地址法
3.公共溢出区法
4.再哈希法
(未完待续)