字符串哈希 算法思想与模板

与一般哈希区别

一般哈希算法允许冲突,且重在处理冲突
字符串哈希,这里其实是字符串前缀哈希假定哈希结果无冲突


算法作用

快速判断两个字符串是否相等
最常见的,当然就是通过哈希数组来判断几个串是否相同。此处的操作呢,简单,就是对于每个串,我们通过固定的转换方式,将相同的串使其的“密”一定相同,不同的串尽量不同
此处有人指出:那难道不能先比对字符串长度,然后比对 ASCLL 码之和吗?事实上显然是不行的(比如 ab 和 ba ,并不是同一个串,但是做却会让其认为是相同的)。这种情况就叫做 hash 冲突,并且在如此的单向加密哈希中, hash 冲突的情况在所难免。
我们此处介绍的,即是最常见的一种哈希:进制哈希
进制哈希的核心便是给出一个固定进制k将一个串的每一个元素看做一个进制位上的数字,所以这个串就可以看做一个 k 进制的数,那么这个数就是这个串的哈希值;则我们通过比对每个串的的哈希值,即可判串是否相同


算法思想

字符串 0 0 0~ Q − 1 Q-1 Q1范围内的十进制数

首先,来了解一下“预处理字符串前缀”的概念

  • 1.把字符串看成p进制的数,从左到右,从高位到低位(低位为第0位)
    首先计算出p进制字符串对应的十进制值
  • 2.因为字符串可能很长,因而这个十进制值可能非常大,以至于无法存储,这时需要对它进行取模操作
    其次,对十进制结果模上一个值Q,使得十进制值在 0 0 0~ Q − 1 Q-1 Q1的范围内

经过这两步,即可把任意一个字符串映射到** 0 0 0~ Q − 1 Q-1 Q1范围内的数**。
tips:

根据以往经验
1:不能把任何一个字母映射成0
2:假定不存在冲突
根据以往经验,当p=13113331,Q=264时,99.99%的情况下都不存在冲突
这里的Q=264,模数Q是很巧妙的,可以利用unsign long long来存储计算的结果,这样可以完成对Q取模的效果,从而省略在代码里写额外的取模操作

字符串哈希 算法思想与模板_第1张图片


用这样的方法处理出一个长字符串的所有前缀

在这里插入图片描述
递推式: h [ i ] = h [ i − 1 ] × p + s t r [ i ] h[i] = h[i-1] \times p + str[i] h[i]=h[i1]×p+str[i];
h [ 0 ] h[0] h[0]赋初值为0,i从1开始


由前缀哈希求得任意一个子串的哈希值

字符串哈希 算法思想与模板_第2张图片
假设要求字符串str中第 L − R L-R LR之间的子串的哈希值,怎么求?
我们可以利用 h [ R ] h[R] h[R] h [ L − 1 ] h[L-1] h[L1]
1 1 1- L − 1 L-1 L1的前缀子串在计算时是0到 L − 2 L-2 L2位(从右到左)**
1-R的前缀子串在计算时是0到R-1位(从右到左)
后者是比前者长,且位数多的,但是它们其实是共享了高位
这时候我们可以让前者乘上一个数,相当于是让其左移,低位补0,让其与后者作差
此时的差就是所求的字符串str中第L-R之间的子串的哈希值。
公式如下:

h[R]-h[L-1]*pR-L+1


代码模板

核心思想:将字符串看成P进制数,P的经验值是13113331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出后的结果就是取模的结果

typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64

// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
    h[i] = h[i - 1] * P + str[i];
    p[i] = p[i - 1] * P;
}

// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

你可能感兴趣的:(#,基础算法模板总结,哈希算法,算法,散列表)