字符串哈希
-
定义
f
(
s
)
f(s)
f(s),表示字符串
s
s
s映射到整数的函数。
f
f
f为哈希函数。
-
哈希函数有两个性质
-
在 Hash 函数值不一样的时候,两个字符串一定不一样;
-
在 Hash 函数值一样的时候,两个字符串不一定一样(但有大概率一样,且我们当然希望它们总是一样的)。
-
Hash 函数值一样时原字符串却不一样的现象我们成为哈希碰撞。
-
定义哈希函数公式等于(定义
l
e
n
(
s
)
len(s)
len(s)表示字符串
s
s
s的长度)
f
(
s
)
=
∑
i
=
1
l
e
n
(
s
)
i
n
t
(
s
[
i
]
)
∗
b
a
s
e
l
e
n
(
s
)
−
i
%
M
f(s)=\sum_{i=1}^{len(s)}int(s[i])*base^{len(s)-i}\%M
f(s)=∑i=1len(s)int(s[i])∗baselen(s)−i%M简单来说,比如字符串abc,(其中我们令a=1,依次往后推)我们定义
f
(
s
)
=
(
b
a
s
e
3
∗
1
+
b
a
s
e
2
∗
2
+
b
a
s
e
1
∗
3
)
%
M
f(s)=(base^3*1+base^2*2+base^1*3)\%M
f(s)=(base3∗1+base2∗2+base1∗3)%M可以类比二进制,这里是base进制。
-
这里
M
M
M需要一个质数,哈希碰撞的概率为
(
l
−
1
)
/
M
(l-1)/M
(l−1)/M所以应该尽可能选择大的模数。
-
代码实现(这里使用unsigned long long 来实现取模,c++自带的unsigned long long自动取模
2
63
−
1
2^{63}-1
263−1,可以看到哈希冲突的概率基本为零。)
#define unsigned long long ull
ull Hash[N],poww[N],base[N];
string s;
ull base=233333
void init()
{
Hash[0] = 0;
poww[0] = 1;
upd(i, 1, len)
{
Hash[i] = Hash[i - 1] * base + s[i] - 'a';
}
upd(i, 1, len)poww[i] = poww[i - 1] * base;
}
ull querry(int l, int r)
{
return Hash[r] - Hash[l - 1] * (poww[r - l + 1]);
}
注意一下代码的细节。针对查询操作。比如我们要对比两个字符川在[L,R]区间是不是一样的。由于
f
(
s
)
f(s)
f(s)函数的性质,哈希[L,R]区间的函数值
v
a
l
=
f
(
r
)
−
f
(
l
)
∗
b
a
s
e
r
−
l
+
1
val=f(r)-f(l)*base^{r-l+1}
val=f(r)−f(l)∗baser−l+1。
为什么呢。比如abcd的哈希值
f
(
s
=
=
a
b
c
d
)
=
b
a
s
e
4
∗
a
+
b
a
s
e
3
∗
b
+
b
a
s
e
2
∗
c
+
b
a
s
e
1
∗
d
f(s==abcd)=base^{4}*a+base^{3}*b+base^{2}*c+base^{1}*d
f(s==abcd)=base4∗a+base3∗b+base2∗c+base1∗d。
如果我们计算区间[2,3]的哈希值,
f
(
s
=
=
a
b
c
)
=
b
a
s
e
3
∗
a
+
b
a
s
e
2
∗
b
+
b
a
s
e
1
∗
c
f(s==abc)=base^{3}*a+base^{2}*b+base^{1}*c
f(s==abc)=base3∗a+base2∗b+base1∗c
然后是
f
(
s
=
=
a
)
=
b
a
s
e
1
∗
a
f(s==a)=base^{1}*a
f(s==a)=base1∗a那么区间[2,3]的哈希值就等于
f
(
s
=
=
a
b
c
)
−
f
(
s
=
=
a
)
∗
b
a
s
e
2
=
b
a
s
e
3
∗
a
+
b
a
s
e
2
∗
b
+
b
a
s
e
1
∗
c
−
b
a
s
e
1
∗
a
∗
b
a
s
e
2
=
b
a
s
e
2
∗
b
+
b
a
s
e
1
∗
c
f(s==abc)-f(s==a)*base^{2}=base^{3}*a+base^{2}*b+base^{1}*c-base^{1}*a*base^{2}=base^{2}*b+base^{1}*c
f(s==abc)−f(s==a)∗base2=base3∗a+base2∗b+base1∗c−base1∗a∗base2=base2∗b+base1∗c
这就是哈希字符串bc的哈希值。也就是
f
(
b
c
)
=
=
f
(
r
)
−
f
(
l
)
∗
b
a
s
e
r
−
l
+
1
=
=
f
(
s
=
=
a
b
c
)
−
f
(
s
=
=
a
)
∗
b
a
s
e
2
f(bc)==f(r)-f(l)*base^{r-l+1}==f(s==abc)-f(s==a)*base^{2}
f(bc)==f(r)−f(l)∗baser−l+1==f(s==abc)−f(s==a)∗base2。
依据这个性质,我们能够对比任意等长字符串是不是同一个字符传。
入门题目 :https://www.luogu.com.cn/problem/P3370、
1.值得注意的是,尽管这样做哈希冲突的概率极小,但仍然有可能。所以可以使用两个哈希函数。选取不同的base即可。不放心还可以同时选取不同的base和模数。然后每一次比较哈希值的时候,两个同时比较。只有两个同时向邓,才能表示这两个字符串的哈希值相等。
2.不一定只有字符串才能用这种哈希,只要是区间对比是不是一样的题目,或者其他变形题目,都可以用以上的方法,进行哈希然后O(1)复杂度判断是否相等。