哈希函数的比较

下面从几个不同的角度对比一下商数类哈希函数和余数类哈希函数。

分辨能力的比较

由上述定理8可以得知,对于余数类哈希函数,其分辨能力\(D\)的大小是除数序列\(M\)的最小公倍数(LCM)。当除数数列中的元素相互互质【依据定理8】(或均为素数【依据定理7】)时,则分辨能力的大小为除数数列所有元素的乘积。

\[D=\prod \limits_{i=1}^n m_i = m_1×m_2×m_3×……×m_n\]

可以看出商数类哈希函数和余数类哈希函数的分辨能力\(D\)在表达形式上基本一致。因此从分辨能力这个角度来看,这两类哈希函数的优劣也就在伯仲之间。另外由于等价函数的概念存在,那么也意味着两者之间可以相互转化。

例如:对于商数类哈希函数\(f_{M_1}(k)\),选择除数数列\(M_1=\{256,256,256,256\}\),则其对应的哈希函数实际上就是按照256进制计数的。同样可以构造一个余数类哈希函数\(f_{M_2}(k)\) ,选择除数数列\(M_2=\{255,254,253,251\}\)。虽然这两个函数的计算方法和结果有所不同,但这两个函数的分辨能力\(D\)已经很接近。显然商数类哈希函数可以对每个bit物尽其用,而余数类哈希函数可能会有一定的浪费。

除数数列的选择

商数类哈希函数比较直观和简单,实际应用中多采用分段方式进行处理。其除数数列的选择一般依据常用计数进制即可。余数类哈希函数则与除数数列的选择有密切关系,因此选择时有一定的技巧。在实际应用中,为了减少查询时间复杂度,可以选取较大数。另外依据定理8所选择的除数数列比依据定理7所选择的除数数列则略有优势。

例如如下四组除数数列:

\(M_1=\{251\}\) ,\(M_2=\{2,127\}\) ,\(M_3=\{2,3,43\}\) ,\(M_4=\{4,8,31\}\)

利用这四组除数数列按照上述方式分别构建成的余数类哈希函数,具有差不多的分辨能力。但是时间复杂度明显不同,而且对数列\(M_4\) 来说,其中还存在冗余。

这里推荐一种余数类哈希函数常用的除数数列。依据定理7,按照降序列出从251至2的连续32个质数\(M=\{251,241,239……,83\}\)。还可以依据定理8,再进一步优化除数序列\(M^*=\{256,255,253,251,……,107\}\)。

这样安排的好处有如下几点:①实际应用中,哈希算法没必要每个质数都需要求余数,从大数开始有利于减少层次和查询次数;②所有的余数不超过256,可以通过字节数组表达余数数列;③可以形成足够大的总体分辨能力,降低冲突概率。

在关键字处理上的差异

当给定关键字为整数类型时,使用商数类哈希函数具有一定的简便性和快捷性。通用计算机的整数类型表达目前不超过64bit(即8个字节),很适合按照256进制进行分段处理。这个也是商数类哈希函数的主要优势。理论上要求的除法和取余运算,实际应用中可以通过简单快捷的位操作予以解决。

但是对于不定长字符串或者较长的二进制关键字时,商数类哈希函数就存在一定的问题:商数类哈希函数需要确定一个最大对齐长度,以确定分段方式。

例如:整数最多8个字节,按字节进行分段;英文单词字典可以设定最大单词长度为32,按照字母进行分段。实际应用中,程序设计需要考虑可能存在的关键字长度上溢。

余数类哈希函数的主要问题在于对于关键字要进行整数取余运算。而按照目前的CPU水平,取余的整数除法操作几乎不算什么难事,但也会消耗一定的CPU时间。对于不定长字符串或者较长的二进制关键字,余数类哈希函数均可以处理,只是所需时间长短不同而已。实际应用中,考虑到所能存储的数据记录数量,以及关键字的实际情况,程序设计中会刻意限定一个最大长度。这个限制是人为限制,而并非理论上的限制。

树结构的平衡性

适用两种哈希函数构建的哈希树会具有不同的平衡性。

相比较而言,余数类哈希函数的树平衡性会明显优于商数类哈希函数。