字符串算法学习笔记(二):字符串哈希

核心思想

考虑定义一个函数 f f f,从字符串映射到整数。并且我们希望这个函数 f f f 可以在某些方面上帮助我们。
比如:快速判断两个字符串是否相等?
对于任意两个字符串 s 1 s_1 s1 s 2 s_2 s2,哈希函数 f f f 具有如下性质:

  • f ( s 1 ) ≠ f ( s 2 ) f(s_1) \neq f(s_2) f(s1)=f(s2),则 s 1 ≠ s 2 s_1 \neq s_2 s1=s2
  • f ( s 1 ) ≠ f ( s 2 ) f(s_1) \neq f(s_2) f(s1)=f(s2),则大概率可能 s 1 = s 2 s_1 = s_2 s1=s2,但若此时 s 1 ≠ s 2 s_1 \neq s_2 s1=s2,则称哈希函数 f f f s 1 s_1 s1 s 2 s_2 s2 上出现了哈希碰撞(简称碰撞)。(当然我们总是希望不出现哈希碰撞。)

经典哈希函数

朴素的算法

通常使用的哈希函数
h ( s ) = ∑ i − 1 n s [ i ] × b n − i ( m o d M ) h(s) = \sum_{i-1}^n s[i] \times b^{n-i} \pmod M h(s)=i1ns[i]×bni(modM)
其中, b b b 为任意正整数, M M M 为一个大素数( M ≥ ∣ Σ ∣ M \ge |\Sigma| M∣Σ∣)。
代码实现(效率低下版本,但是我好像不会其他写法了 T-T):

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=998244353;
const int b=233;
int h(const string &s)
{
	int res=0;
	for (int i=0;i<=s.length()-1;i++)
	{
		res=((ll)res*b+s[i])%M;
	}
	return res;
}
bool cmp(const string &s1,const string &s2)
{
	return h(s1)==h(s2);
}

减少碰撞?

根据公式,我们可以得到哈希函数 h ( s ) h(s) h(s) 的值域为 [ 0 , M − 1 ] [0,M - 1] [0,M1],大小为 M M M。假定我们现在一共有 n n n 个字符串,则不出现哈希碰撞的概率为:
P = ∏ i = 0 n − 1 M − i M P=\prod_{i=0}^{n-1} \frac{M - i}{M} P=i=0n1MMi
代入数据, M = 998244353 , n = 1 0 6 M = 998244353, n=10^6 M=998244353,n=106,在随机数据下表现优秀。
但倘若我们为了更加安全,可是使用双哈希的技术,也就是对两个模数分别取模,这样的话可以扩大 h ( s ) h(s) h(s) 的值域,减少出现碰撞的概率。

加速?

单次计算哈希函数的时间复杂度为 O ( n ) O(n) O(n) n n n 为字符串长度),与暴力计算无异。当我们需要多次计算哈希函数时,这种算法的效率便显得低下。
一般考虑采取计算前缀的方法(此处 s s s 1 1 1 开头)。我们考虑计算 h ( s [ 1 ⋯ i ] ) h(s[1 \cdots i]) h(s[1i]) 的值,按照定义有:
h ( s [ 1 ⋯ i ] ) = s [ 1 ] × b i − 1 + s [ 1 ] × b i − 2 + ⋯ + s [ i − 1 ] × b + s [ i ] ( m o d M ) h(s[1 \cdots i]) = s[1] \times b^{i-1} + s[1] \times b^{i-2} + \cdots + s[i - 1] \times b + s[i] \pmod M h(s[1i])=s[1]×bi1+s[1]×bi2++s[i1]×b+s[i](modM)
类似于前缀和,我们考虑计算 h ( s [ l ⋯ r ] ) h(s[l \cdots r]) h(s[lr]) 的值。易得:
h ( s [ l ⋯ r ] ) = s [ l ] × b r − l + s [ l + 1 ] × b r − l − 1 + ⋯ + s [ r − 1 ] × b + s [ r ] ( m o d M ) h(s[l \cdots r]) = s[l] \times b^{r - l} + s[l + 1] \times b^{r - l - 1} + \cdots + s[r-1] \times b + s[r] \pmod M h(s[lr])=s[l]×brl+s[l+1]×brl1++s[r1]×b+s[r](modM)
观察一下,得到:
h ( s [ l ⋯ r ] ) = h ( s [ 1 ⋯ r ] ) − h ( s [ 1 ⋯ l − 1 ] ) × b r − l + 1 h(s[l \cdots r]) = h(s[1 \cdots r]) - h(s[1 \cdots l-1])\times b^{r-l+1} h(s[lr])=h(s[1r])h(s[1l1])×brl+1
由此,我们可以 O ( n ) O(n) O(n) 地预处理出 b r − l + 1 b^{r-l+1} brl+1 的值,查询哈希值时 O ( 1 ) O(1) O(1) 根据公式计算即可。

应用

允许 k k k 次失配的字符串匹配

给定长为 n n n 的文本串 s s s 和长为 m m m 的模式串 p p p,求出 s s s 中有多少子串与 p p p 匹配。注意:在本题中,我们称 s ′ s' s s s s 匹配,当且仅当 ∣ s ′ ∣ = ∣ s ∣ |s'| = |s| s=s 且至多有 k k k 个字符不同。其中 1 ≤ n , m ≤ 1 0 6 , 0 ≤ k ≤ 5 1 \le n,m \le 10^6, 0 \le k \le 5 1n,m106,0k5
考虑使用哈希 + 二分解决。我们枚举所有可能匹配的子串 s ′ s' s,通过哈希 + 二分快速找到 s ′ s' s p p p 失配的第一个位置,之后删除 s ′ s' s 以及 p p p 在失配位置之前的字符串,继续查找下一个失配位置。这个过程至多发生 k k k 次。
时间复杂度 O ( m + k n log ⁡ m ) O(m+kn \log m) O(m+knlogm)

最长公共子串

给定 m m m 个长度为 n n n 的字符串,求出所有字符串的最长公共子串,若有多个,任意输出其中一个。 其中 1 ≤ m , n ≤ 1 0 6 1 \le m,n \le 10^6 1m,n106
子串具有一个递推性质。即如果长度为 x x x 的公共子串存在,则长度为 x − 1 x-1 x1 的公共子串一定存在。由此我们可以二分所求子串的长度,假设当前长度为 c u r cur cur,则 check(cur) 的逻辑就是所有字符串的所有长度为 c u r cur cur 的子串进行哈希,然后放入 n n n 个不同的哈希表中,最后求交集。
时间复杂度 O ( n log ⁡ n m ) O(n \log \frac{n}{m}) O(nlogmn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值