一、算法原理
我们不直接比较字符串
S
S
S 的字串和模式串
T
T
T 是否相等,而是比较二者的哈希值。
设字符串
S
S
S 的长度为
l
l
l,字符串
T
T
T 的长度为
m
m
m。
取两个互素的常数
b
b
b 和
h
h
h (
l
<
b
<
h
l < b < h
l<b<h),设字符串
C
=
c
1
c
2
.
.
.
c
m
C = c_1c_2...c_m
C=c1c2...cm,则哈希函数为:
H ( C ) = ( c 1 b m − 1 + c 2 b m − 2 + . . . + c m b 0 ) m o d h H(C)=(c_1b^{m-1}+c_2b^{m-2}+...+c_mb^0) \text~{mod}~h H(C)=(c1bm−1+c2bm−2+...+cmb0) mod h
其中 b b b 是哈希基数,相当于把字符串看作 b b b 进制数(,哈希函数就是将 b b b 进制转换为十进制)。
滚动哈希:这是一种优化技巧,用于优化字符串的匹配。
字符串
S
S
S 从下标
k
+
1
k + 1
k+1 开始的长度为
m
m
m 的子串,它的哈希值为
H
(
S
[
k
+
1
,
k
+
m
]
)
H(S[k + 1, k + m])
H(S[k+1,k+m]);字符串
S
S
S 从下标
k
k
k 开始的长度为
m
m
m 的子串,它的哈希值为
H
(
S
[
k
,
k
+
m
−
1
]
)
H(S[k, k + m - 1])
H(S[k,k+m−1])。二者的关系为:
H ( S [ k + 1 , . . . , k + m ] ) = ( H ( S [ k , k + m − 1 ] ) × b − s k b m + s k + m ) mod h H(S[k + 1, ..., k + m]) = (H(S[k, k + m - 1]) \times b - s_kb^m + s_{k + m})~\text{mod}~h H(S[k+1,...,k+m])=(H(S[k,k+m−1])×b−skbm+sk+m) mod h
证明:
H ( S [ k + 1 , . . . , k + m ] ) = ( s k + 1 b m − 1 + s k + 2 b m − 2 + . . . + s k + m b 0 ) m o d h H ( S [ k , k + m − 1 ] ) = ( s k b m − 1 + s k + 1 b m − 2 + . . . + s k + m − 1 b 0 ) m o d h H ( S [ k , k + m − 1 ] ) × b = ( s k b m + s k + 1 b m − 1 + . . . + s k + m − 1 b 1 ) m o d h H ( S [ k , k + m − 1 ] ) × b − s k b m + s k + m = ( s k + 1 b m − 1 + s k + 2 b m − 2 + . . . + s k + m b 0 ) m o d h ∴ H ( S [ k + 1 , . . . , k + m ] ) = ( H ( S [ k , k + m − 1 ] ) × b − s k b m + s k + m ) mod h \begin{split} H(S[k + 1, ..., k + m])&=(s_{k+1}b^{m-1}+s_{k+2}b^{m-2}+...+s_{k+m}b^0) \text~{mod}~h\\ \\ H(S[k, k + m - 1])&=(s_kb^{m-1}+s_{k+1}b^{m-2}+...+s_{k+m-1}b^0) \text~{mod}~h \\ H(S[k, k + m - 1]) \times b&=(s_kb^{m}+s_{k+1}b^{m-1}+...+s_{k+m-1}b^1) \text~{mod}~h \\ H(S[k, k + m - 1]) \times b - s_kb^m + s_{k + m}&=(s_{k+1}b^{m-1}+s_{k+2}b^{m-2}+...+s_{k+m}b^0) \text~{mod}~h\\ \\ \therefore H(S[k + 1, ..., k + m]) = (H(S[k, &k + m - 1]) \times b - s_kb^m + s_{k + m})~\text{mod}~h \end{split} H(S[k+1,...,k+m])H(S[k,k+m−1])H(S[k,k+m−1])×bH(S[k,k+m−1])×b−skbm+sk+m∴H(S[k+1,...,k+m])=(H(S[k,=(sk+1bm−1+sk+2bm−2+...+sk+mb0) mod h=(skbm−1+sk+1bm−2+...+sk+m−1b0) mod h=(skbm+sk+1bm−1+...+sk+m−1b1) mod h=(sk+1bm−1+sk+2bm−2+...+sk+mb0) mod hk+m−1])×b−skbm+sk+m) mod h
二、代码
typedef unsigned long long ull;
const ull B = 1e9 + 7;
// 计算字符串a的哈希值
ull h(string a)
{
ull ah = 0;
for (int i = 0; i < a.size(); i++) ah = ah * B + a[i];
return ah;
}
// a是否在b中出现
bool locate(string a, string b)
{
// a比b长,a不可能在b中
int al = a.size(), bl = b.size();
if (al > bl) return false;
// 计算B的al次方
ull t = 1;
for (int i = 0; i < al; i++) t *= B;
// 计算a和b长度为al的前缀对应的哈希值
ull ah = 0, bh = 0;
ah = h(a), bh = h(b);
// 右移窗口,更新哈希值并判断
if (bh == ah) return true;
for (int i = 1; i + al < bl; i++)
{
bh = bh * B + b[i + al] - b[i] * t;
if (bh == ah) return true;
}
return false;
}
注:
- 1、这里哈希基数为素数
1e9 + 7
,它还有一个孪生素数1e9 + 9
。 - 2、该代码取 h h h 为 2 64 2^{64} 264 ,利用数据溢出,省去了模运算。
完