字符串哈希简介
- 字符串哈希,就是将不同的字符串映射成不同的整数。
- 字符串哈希可以用于快速判断两个字符串是否相等,因为我们不需要逐个遍历比较两个字符串中的每个字符,只需要比较两个字符串映射成的哈希值是否相等即可。
- 字符串哈希中,字符串与字符串对应的哈希值之间的映射关系称为哈希函数,通过哈希函数,我们可以计算出字符串对应的哈希值。
- 哈希函数通常采用多项式哈希函数,设要计算哈希值的字符串的长度为 n,且该字符串为 S = s 1 s 2 s 3 . . . s n S = s_1s_2s_3...s_nS=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_isi 对应的 ASCII 值,一般情况下,对于 p 与 M 的取值只要保证 p 与 M 互质即可,其中 p 小于 M,p 与 M 尽可能取大(可以避免哈希碰撞)。
- 如果两个不同的字符串,通过哈希函数计算出来的哈希值相同,称为哈希碰撞。
字符串哈希值求解过程模拟
- 我们先模拟一遍使用哈希函数求字符串对应的哈希值的过程。
- 这里以单哈希法为例,求字符串 “ABCDE” 的哈希值。
字符串 | 哈希值 |
h 0 = 0 h_0 = 0h0=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 Mh1=(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 Mh2=(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 Mh3=(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 Mh4=(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 Mh4=(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 Mh[i]=(h[i−1]×p+s[i])modM
字符串子串哈希值的求解
- 在上文中,我们已经可以求出一个字符串以字符串中各字符 s[i] 结尾的子串的哈希值
- 那么,我们是否可以利用求出的以字符串中各字符 s[i] 结尾的子串的哈希值来求字符串任一子字符串的哈希值,答案是可以的。
- 如,我们要求字符串 “ABCDE” 的子字符串 “CD” 的哈希值
- 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 MhCD=(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 MhCD=[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^2hCD=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 值,使用这两个哈希函数分别计算字符串的哈希值,最终使用两个哈希函数计算出来的哈希值组成的数对作为字符串的哈希值。
- 当然,你也可以使用多个不同的哈希函数,使用这若干个哈希函数分别计算字符串的哈希值,最终使用这若干个哈希函数计算出来的哈希值组成的元组作为字符串的哈希值。
- 但是使用两个哈希值组成的二元组就基本上避免了哈希碰撞,且使用多元组会消耗更多的时间,所以一般双哈希法就足够了。
三种哈希方法的比较
- 从速度上来看,自然溢出法快于单哈希法,单哈希法快于双哈希法。
- 从安全上来看,双哈希法发生哈希碰撞的概率小于单哈希法,即双哈希法比单哈希法安全,而单哈希法和自然溢出法的安全性主要取决于 p 和 M 的取值。
- 自然溢出法其实可以看成是单哈希法的特殊情况,M 的取值固定为 2 64 2^{64}264。