字符串哈希


注意(Warning)

本文章内所有有关字符串下标的,统一都从 1 1 1 开始算起


定义

字符串哈希其实就是把一段字符串转化成一个数字。
在进行字符串匹配时不需要再 O ( s t r l e n ( s ) ) O(strlen(s)) O(strlen(s)) 匹配字符串本身,而只需要匹配两个字符串的哈希值就好了。


字符串哈希值计算

对于一个字符串 s s s ,定义它的哈希值为:

f ( s ) = ∑ i = 1 l s [ i ] ∗ p l − i f(s) = \sum ^{l} _{i = 1} s[i] * p ^ {l-i} f(s)=i=1ls[i]pli

即:

f ( s ) = s [ 1 ] ∗ p l − 1 + s [ 2 ] ∗ p l − 2 + ⋯ + s [ l ] ∗ p 0 f(s) = s[1] * p ^ {l - 1} + s[2] * p ^ {l -2} + \dots + s[l] * p ^ {0} f(s)=s[1]pl1+s[2]pl2++s[l]p0

其中,
p p p 是一个质数,
l l l 是字符串长度,
s [ i ] s[i] s[i] 转化为它在 A S C I I ASCII ASCII 码对应的数值
举个例子:字符串 “ a b c abc abc” 的哈希值为: a ∗ p 2 + b ∗ p + c a * p^2 + b * p + c ap2+bp+c


字符串前缀哈希值算

在代码中,我们会用到字符串的前缀的哈希值(至于为什么等会会说),
因此我们用 h u s h [ i ] hush[i] hush[i] 表示字符串 s [ 1 … i ] s[1 \dots i] s[1i] 的哈希值。

例如一个字符串 s 1 s 2 s 3 s 4 s 5 s_1 s_2 s_3 s_4 s_5 s1s2s3s4s5
h u s h [ 1 ] = s 1 hush[1] = s_1 hush[1]=s1
h u s h [ 2 ] = s 1 ∗ p + s 2 hush[2] = s_1 * p + s_2 hush[2]=s1p+s2
h u s h [ 3 ] = s 1 ∗ p 2 + s 2 ∗ p + s 3 hush[3] = s_1 * p ^ 2 + s_2 * p + s_3 hush[3]=s1p2+s2p+s3
h u s h [ 4 ] = s 1 ∗ p 3 + s 2 ∗ p 2 + s 3 ∗ p + s 4 hush[4] = s_1 * p ^ 3 + s_2 * p ^ 2 + s_3 * p + s_4 hush[4]=s1p3+s2p2+s3p+s4
h u s h [ 5 ] = s 1 ∗ p 4 + s 2 ∗ p 3 + s 3 ∗ p 2 + s 4 ∗ p + s 5 hush[5] = s_1 * p ^ 4 + s_2 * p ^ 3 + s_3 * p ^ 2 + s_4 * p + s_5 hush[5]=s1p4+s2p3+s3p2+s4p+s5.
那么就可以得到这样一个公式:
h u s h [ i ] = h u s h [ i − 1 ] ∗ p + s [ i ] hush[i] = hush[i - 1] * p + s[i] hush[i]=hush[i1]p+s[i]
当然 h u s h [ i ] hush[i] hush[i] 有可能溢出,所以再对一个数 m o d mod mod (最好是素数) 取模就好了。


子串哈希值计算

在字符串匹配中,我们实际上用的是模式串的哈希值主串的子串的哈希值进行比对。

因此,在比对过程中我们不能再对子串进行哈希值计算,因为这样合暴力匹配原串复杂度一样。

这时前缀哈希值就派上用场了。

依旧拿上面的例子

e g 1 : eg1: eg1:
要求 s 3 s 4 s_3 s_4 s3s4 的哈希值,它显然是 s 3 ∗ p + s 4 s_3 * p + s_4 s3p+s4
现在看如何用前缀哈希值来算。

从感觉上讲,这有点像前缀和,所以考虑如何用 h u s h [ 4 ] hush[4] hush[4] h u s h [ 3 − 1 ] hush[3 - 1] hush[31] 算出他。
h u s h [ 3 − 1 = 2 ] = s 1 ∗ p + s 2 hush[3 - 1 = 2] = s_1 * p + s_2 hush[31=2]=s1p+s2
h u s h [ 4 ] = s 1 ∗ p 3 + s 2 ∗ p 2 + s 3 ∗ p + s 4 hush[4] = s_1 * p ^ 3 + s_2 * p ^ 2 + s_3 * p + s_4 hush[4]=s1p3+s2p2+s3p+s4
貌似确实可以,只需要用 h u s h [ 4 ] − h u s h [ 3 − 1 ] ∗ p 2 hush[4] - hush[3 - 1] * p ^ {2} hush[4]hush[31]p2 即可得到。

e g 2 : eg2: eg2:
s 2 s 3 s 4 s_2 s_3 s_4 s2s3s4 的哈希值,是 s 2 ∗ p 2 + s 3 ∗ p + s 4 s_2 * p ^ 2 + s_3 * p + s_4 s2p2+s3p+s4
h u s h [ 2 − 1 = 1 ] = s 1 hush[2 - 1 = 1] = s_1 hush[21=1]=s1
h u s h [ 4 ] = s 1 ∗ p 3 + s 2 ∗ p 2 + s 3 ∗ p + s 4 hush[4] = s_1 * p ^ 3 + s_2 * p ^ 2 + s_3 * p + s_4 hush[4]=s1p3+s2p2+s3p+s4
可以用 h u s h [ 4 ] − h u s h [ 2 − 1 ] ∗ p 4 − 2 + 1 = 3 hush[4] - hush[2 - 1] * p ^ {4 - 2 + 1 = 3} hush[4]hush[21]p42+1=3 算出来。

整体可以发现,
如果要算 s [ l … r ] s[l \dots r] s[lr] 的哈希值,
那么 h u s h [ r ] hush[r] hush[r] 就包含了所有要计算的部分,
但是还多了 s [ 1 ] ∗ p r − 1 + s [ 2 ] ∗ p r − 2 + ⋯ + s [ l − 1 ] ∗ p r − l + 1 s[1] * p ^ {r - 1} + s[2] * p ^ {r - 2} + \dots + s[l - 1] * p ^ {r - l + 1} s[1]pr1+s[2]pr2++s[l1]prl+1 一部分,
h u s h [ l − 1 ] = s [ 1 ] ∗ p l − 2 + s [ 2 ] ∗ p l − 3 + ⋯ + s [ l − 1 ] hush[l - 1] = s[1] * p ^ {l - 2} + s[2] * p ^ {l - 3} + \dots + s[l - 1] hush[l1]=s[1]pl2+s[2]pl3++s[l1]
发现只需要给 h u s h [ l − 1 ] hush[l - 1] hush[l1] 乘以 p r − l + 1 p ^ {r - l + 1} prl+1,就可以得到多出的那一部分,
再用 h u s h [ r ] hush[r] hush[r] 减去就可以了。
但由于取模的存在,以上这个式子还要取模。

综上可得:要计算 s [ l … r ] s[l \dots r] s[lr] 的哈希值就用:
( ( h u s h [ r ] − h u s h [ l − 1 ] ∗ p r − l + 1 ) % m o d + m o d ) % m o d ((hush[r] - hush[l - 1] * p ^ {r - l + 1}) \% mod + mod) \% mod ((hush[r]hush[l1]prl+1)%mod+mod)%mod O ( 1 ) O(1) O(1) 算出。

注:之所以这么写是因为 h u s h [ r ] hush[r] hush[r] h u s h [ l − 1 ] hush[l - 1] hush[l1] 都是已经被取模过的,也就是说
      \space\space\space\space\space       h u s h [ r ] − h u s h [ l − 1 ] ∗ p r − l + 1 hush[r] - hush[l - 1] * p ^ {r - l + 1} hush[r]hush[l1]prl+1 可能小于 0 0 0,只有这样才能保证子串哈希值是正的。

如此一来,计算字串的哈希值就轻而易举了。


双值哈希

双值哈希是为了解决哈希冲突而设计的。

简单来说,就是取两个 p p p,两个 m o d mod mod,分别对字符串进行哈希值的计算,
当且仅当两个哈希值相同时,两个字符串才相同。

更具体的:
h u s h 1 [ i ] = h u s h 1 [ i − 1 ] ∗ p 1 % m o d 1 hush1[i] = hush1[i - 1] * p1 \% mod1 hush1[i]=hush1[i1]p1%mod1
h u s h 2 [ i ] = h u s h 2 [ i − 1 ] ∗ p 2 % m o d 2 hush2[i] = hush2[i - 1] * p2 \% mod2 hush2[i]=hush2[i1]p2%mod2
要比较字符串 s t r 1 str1 str1 s t r 2 str2 str2
当且仅当:
   \space\space    s t r 1 str1 str1 h u s h 1 [ s t r l e n ( s t r 1 ) ] hush1[strlen(str1)] hush1[strlen(str1)] 等于 s t r 2 str2 str2 h u s h 1 [ s t r l e n ( s t r 2 ) ] hush1[strlen(str2)] hush1[strlen(str2)]
   \space\space    s t r 1 str1 str1 h u s h 2 [ s t r l e n ( s t r 1 ) ] hush2[strlen(str1)] hush2[strlen(str1)] 等于 s t r 2 str2 str2 h u s h 2 [ s t r l e n ( s t r 2 ) ] hush2[strlen(str2)] hush2[strlen(str2)] 时,
两字符串相等。

这种双值哈希十分可靠,可以直接使用,而且不会被卡掉。


最后一些细节及优化

  1. 在选定 p p p m o d mod mod 时,两者都最好选一个较大的质数,可以降低哈希的冲突率。
    在这里推荐 p p p 131 131 131 1331 1331 1331 1313131 1313131 1313131 m o d mod mod 一般取 23333333 23333333 23333333

  2. 更为方便地, m o d mod mod 可以取 2 64 − 1 2^{64} - 1 2641,即 u n s i g n e d   l o n g   l o n g unsigned \ long \ long unsigned long long 所能储存的最大
    值,然后将 h u s h hush hush 数组开为 u n s i g n e d   l o n g   l o n g unsigned \ long \ long unsigned long long,这样就可以用 u n s i g n e d   l o n g   l o n g unsigned \ long \ long unsigned long long自然溢出代替取模

  3. 如果有多个模式串,一个一个算它们的哈希值和暴力没什么区别。
    不如将他们都拼成一个总模式串,整体算一次,要用哪个模式串的哈希值时,直接
    用:
    ( ( h u s h [ p o s [ i ] + s t r l e n ( t [ i ] ) − 1 ] − h u s h [ p o s [ i ] − 1 ] ∗ p s t r l e n ( t [ i ] ) ) % m o d + m o d ) % m o d ((hush[pos[i] + strlen(t[i]) - 1] - hush[pos[i] - 1] * p ^ {strlen(t[i])}) \% mod + mod) \% mod ((hush[pos[i]+strlen(t[i])1]hush[pos[i]1]pstrlen(t[i]))%mod+mod)%mod
    其中, t [ i ] t[i] t[i] 表示第 i i i 个模式串, p o s [ i ] pos[i] pos[i] 表示第 i i i 个模式串的第一个字符在
    模式串
    中的下标。

  4. 在计算 p r − l + 1 p ^ {r - l + 1} prl+1 时,可以用快速幂,也可以用一个数组 p o w p i powp_i powpi 预处理好 p p p
    i i i 次方,注意别忘了取模


关于字符串哈希会被卡掉

总而言之,最好用双值哈希。

字符串哈希算法是一种将字符串映射为数字的算法,常用于字符串的比较和匹配。在C++中,可以使用字符串哈希算法来加速字符串的比较操作。 引用\[1\]中的代码示例展示了一个使用字符串哈希算法的C++代码。该代码使用了前缀和数组和字符串数组来存储字符串,并通过计算哈希来比较两个子串是否相等。其中,哈希的计算使用了前缀和数组和幂运算。 引用\[2\]中的解释指出,使用字符串哈希的目的是为了比较字符串时不直接比较字符串本身,而是比较它们对应映射的数字。这样可以将子串的哈希的时间复杂度降低到O(1),从而节省时间。 引用\[3\]中的代码示例也展示了一个使用字符串哈希算法的C++代码。该代码使用了前缀和数组和字符串数组来存储字符串,并通过计算哈希来比较两个子串是否相等。与引用\[1\]中的代码类似,哈希的计算也使用了前缀和数组和幂运算。 综上所述,字符串哈希算法是一种将字符串映射为数字的算法,常用于字符串的比较和匹配。在C++中,可以使用前缀和数组和幂运算来计算字符串哈希,并通过比较哈希来判断两个子串是否相等。 #### 引用[.reference_title] - *1* [C++算法题 # 33 字符串哈希](https://blog.csdn.net/weixin_44536804/article/details/123425533)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [字符串哈希(c++)](https://blog.csdn.net/qq_41829492/article/details/120980055)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [AcWing 841. 字符串哈希(C++算法)](https://blog.csdn.net/YSA__/article/details/108453403)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值