哈希函数通常采用多项式哈希函数,设要计算哈希值的字符串的长度为 n,且该字符串为 S = s 1 s 2 s 3 . . . s n S = s_1s_2s_3...s_n S=s1s2s3...sn,则哈希函数为 h ( S ) = ∑ i = 1 n s [ i ] × p n − i ( m o d M ) h(S) = \sum_{i=1}^{n} s[i] \times p^{n-i} (mod \space M) h(S)=∑i=1ns[i]×pn−i(modM) ,其中, s [ i ] s[i] s[i] 一般为字符串中字符 s i s_i si 对应的 ASCII 值,一般情况下,对于 p 与 M 的取值只要保证 p 与 M 互质即可,其中 p 小于 M,p 与 M 尽可能取大(可以避免哈希碰撞)。
如果两个不同的字符串,通过哈希函数计算出来的哈希值相同,称为哈希碰撞。
字符串哈希值求解过程模拟
我们先模拟一遍使用哈希函数求字符串对应的哈希值的过程。
这里以单哈希法为例,求字符串 “ABCDE” 的哈希值。
字符串
哈希值
h 0 = 0 h_0 = 0 h0=0
A
h 1 = ( 97 × p 0 ) m o d M = ( h 0 × p + 97 ) m o d M h_1 = (97 \times p^0) mod \space M = (h_0 \times p + 97)mod \space M h1=(97×p0)modM=(h0×p+97)modM
AB
h 2 = ( 97 × p 1 + 98 × p 0 ) m o d M = ( h 1 × p + 98 ) m o d M h_2 = (97 \times p^1 + 98 \times p^0) mod \space M = (h_1 \times p + 98)mod \space M h2=(97×p1+98×p0)modM=(h1×p+98)modM
ABC
h 3 = ( 97 × p 2 + 98 × p 1 + 99 × p 0 ) m o d M = ( h 2 × p + 99 ) m o d M h_3 = (97 \times p^2 + 98 \times p^1 + 99 \times p^0) mod \space M = (h_2 \times p + 99)mod \space M h3=(97×p2+98×p1+99×p0)modM=(h2×p+99)modM
ABCD
h 4 = ( 97 × p 3 + 98 × p 2 + 99 × p 1 + 100 × p 0 ) m o d M = ( h 3 × p + 100 ) m o d M h_4 = (97 \times p^3 + 98 \times p^2 + 99 \times p^1 + 100 \times p^0) mod \space M = (h_3 \times p + 100)mod \space M h4=(97×p3+98×p2+99×p1+100×p0)modM=(h3×p+100)modM
ABCDE
h 4 = ( 97 × p 4 + 98 × p 3 + 99 × p 2 + 100 × p 1 + 101 × p 0 ) m o d M = ( h 4 × p + 101 ) m o d M h_4 = (97 \times p^4 + 98 \times p^3 + 99 \times p^2 + 100 \times p^1 + 101 \times p^0) mod \space M = (h_4 \times p + 101)mod \space M h4=(97×p4+98×p3+99×p2+100×p1+101×p0)modM=(h4×p+101)modM
由上表,求一个字符串的哈希值相当于求前缀和
求一个字符串的哈希值,可以由该字符串除去最后一个字符的子串的哈希值乘上 p ,在加上最后一个字符的 ASCII 值模 M 得到,即 h [ i ] = ( h [ i − 1 ] × p + s [ i ] ) m o d M h[i] = (h[i-1] \times p + s[i])mod \space M h[i]=(h[i−1]×p+s[i])modM
h C D = ( 99 × p 1 + 100 × p 0 ) m o d M h_{CD} = (99 \times p^1 + 100 \times p^0) mod \space M hCD=(99×p1+100×p0)modM
h C D = [ 97 × p 3 + 98 × p 2 + 99 × p 1 + 100 × p 0 − ( 97 × p 1 + 98 × p 0 ) × p 2 ] m o d M h_{CD} = [97 \times p^3 + 98 \times p^2 + 99 \times p^1 + 100 \times p^0 - (97 \times p^1 + 98 \times p^0) \times p^2] mod \space M hCD=[97×p3+98×p2+99×p1+100×p0−(97×p1+98×p0)×p2]modM
h C D = h A B C D − h A B × p 2 h_{CD} = h_{ABCD} - h_{AB} \times p^2 hCD=hABCD−hAB×p2
字符串 “ABCDE” 中 ‘D’ 的下标为 4,‘C’ 的下标为 3
所以可以令字符串 “ABCDE” 的子字符串 “CD” 的哈希值表示为 h C D = h [ 3 , 4 ] = h [ 4 ] − h [ 3 − 1 ] × p 4 − 3 + 1 h_{CD} = h[3, 4] = h[4] - h[3 - 1] \times p^{4 - 3 + 1} hCD=h[3,4]=h[4]−h[3−1]×p4−3+1
因此,字符串任一子字符串的哈希值为 h [ l , r ] = h [ r ] − h [ l − 1 ] × p r − l + 1 h[l, r] = h[r] - h[l-1] \times p^{r - l + 1} h[l,r]=h[r]−h[l−1]×pr−l+1
求一个字符串的子字符串的哈希值相当于求区间和。
字符串哈希方法
自然溢出法
自然溢出法,如,在C++中,可以将哈希函数的函数值的类型定义为 unsigned long long,我们只需指定 p 取何值即可,当字符串计算出来的哈希值大于 unsigned long long 时,就会产生无符号长整数的自然溢出,相当于计算出来的哈希值自动对 2 64 2^{64} 264 进行取模运算
单哈希法
单哈希法,就是只使用一个哈希函数,只指定一个 p 值和一个 M 值,使用这个哈希函数计算出来的哈希值作为字符串的哈希值。
双哈希法
双哈希法,就是使用两个两个哈希函数,指定两个 p 值和两个 M 值,使用这两个哈希函数分别计算字符串的哈希值,最终使用两个哈希函数计算出来的哈希值组成的数对作为字符串的哈希值。