字符串Hash

字符串 Hash

字符串 Hash 的思想

Hash 的核心思想在于,将输入映射到一个值域较小、可以方便比较的范围。而在字符串中主要是为了方便比较,而且为了减少 Hash 冲突,一般映射的范围较大。

我们定义一个把 字符串映射到整数 的函数 ff ,这个 ff 称为是 Hash 函数。我们希望这个函数可以方便地帮我们判断两个字符串是否相等, 有两点很重要。

  1. 在 Hash 函数值不一样的时候,两个字符串一定不一样;
  2. 在 Hash 函数值一样的时候,两个字符串不一定一样(但有大概率一样,且我们当然希望它们总是一样的)。

Hash 函数值一样时字符串却不一样, 我们称为 哈希冲突

如何 Hash

一般采用多项式的方法,对于一个长度为 l\large{l} 的字符串 ss ,我们可以定义

f(s)=i=1ls[i]×bi1(modm) f(s) = \sum_{i = 1}^{l} s[i] \times b^{i - 1}\pmod m

例如:对于字符串 xyzxyz ,其多项式为 xb2+yb1+zb0xb^{2} + yb^{1} + zb^{0} ,所以对字符串 Hash 的另一种理解就是将字符串转化为 bb 进制数。

有以下几点值得注意:

  • 如果要从 11 开始存储字符串,我们可以使用 scanf("%s", s + 1) 来读取输入。
  • 对于 bb 的取值,我们一般取 131131 或者 1333113331(经验值,字符串哈希冲突较少)
  • 对于模数 MM,我们可以取一个大指数,如 1e9 + 7,998244353,212370440130137957ll 或者使用 unsigned long long 自动对 2642 ^{64} 取模。
  • 对于减少哈希冲突,我们还可以通过双 Hash ,也就是分别用两个不同的 basebaseMM 来计算。

Hash 复杂度为 O(N)O(N),查询某段子串的 Hash 复杂度为 O(1)O(1)

子串查询原理: hash[1]=s[1]b0hash[2]=s[1]b1+s[2]0hash[3]=s[1]b2+s[2]1+s[3]0hash[4]=s[1]b3+s[2]3+s[3]1+s[4]0hash[5]=s[1]b4+s[2]3+s[3]2+s[4]1+s[5]0 \begin{aligned} hash[1] &= s[1] b^{0} \\\\ hash[2] &= s[1] b^{1} + s[2]^{0}\\\\ hash[3] &= s[1] b^{2} + s[2]^{1} + s[3]^{0}\\\\ hash[4] &= s[1] b^{3} + s[2]^{3} + s[3]^{1} + s[4]^{0}\\\\ hash[5] &= s[1] b^{4} + s[2]^{3} + s[3]^{2} + s[4]^{1} + s[5]^{0} \end{aligned}

假如我们要查询子串 [3,5][3,5] ,我们会发现 hash[5]hash[5]hash[2]hash[2],如果不看 b 的幂次,正好是答案,所以我们将 hash[2]hash[2] 的幂次每一位补齐到与 hash[5]hash[5] 一样。 hash[2]×b3=s[1]b4+s[2]b3hash[5]=s[1]b4+s[2]3+s[3]2+s[4]1+s[5]0 \begin{aligned} hash[2] \times b^{3} &= s[1]b^{4} + s[2]b^{3}\\ hash[5] &= s[1] b^{4} + s[2]^{3} + s[3]^{2} + s[4]^{1} + s[5]^{0} \end{aligned} 此时将两者相减所得的值正好是 子串的 hash 值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
typedef unsigned long long ull;
const ull base = 131, M = 1e5 + 7, mod = 1e9 + 7;
char s[M];
ull myhash[M], power[M]; //myhash 里面存算到某位时的 Hash 值,power 存 base 的幂次(子串查询才需要)

ull get_hash(char *s) {
    ull value = 0;
    power[0] = 1;
    for (int i = 1; s[i]; i++) {
        value = (value * base + s[i]) % mod;
        myhash[i] = value;
        power[i] = power[i - 1] * base % mod;
    }
    return value;//返回最终字符串的 Hash 值
}
//计算某段的 Hash 值
ull get_sub_hash(int l, int r) {
    return (myhash[r] - (myhash[l - 1] * power[r - l + 1]) % mod + mod) % mod;
}
Licensed under CC BY-NC-SA 4.0