(基于进制的)字符串Hash本质是用一个数字代表一个字符串,从而降低字符串处理的复杂度。这是一个很大的数字,采用unsigned long long类型,可以自然溢出,也简化了取模的过程。
还需要一个进制数base,用于数字的进制。(base选取质数如131,1331......)
Hash数组h[ i ]表示字符串1~i的hash值,采用类似前缀和的形式求出任意子集的Hash值
对于学习过前缀和的朋友来说比较好理解,字符串Hash的作用基本相同。
这里建议各位先去看一下本人另一篇关于前缀和的blog。https://blog.youkuaiyun.com/lebrce/article/details/145243013https://blog.youkuaiyun.com/lebrce/article/details/145243013
一、Hash数组的初始化
using unsigned long long = ull;
const ull base=131;
ull h[N];
char s[N];
cin>>s+1;
int n=strlen(s);
for(int i=1;i<n;i++){
h[i] = h[i-1]*base + s[i];
}
二、Gethash函数
ull Gethash(ull h[],int l,int r){
return h[r]-h[l-1]*b[r-l+1] //b[i]为base的幂,需预处理
}
三、利弊
很明显字符串Hash的理解难度并不大(比起KMP自然是),但是由于Hash值及其大会需要更大的空间。
四、例题
小 Z 同学每天都喜欢斤斤计较,今天他又跟字符串杠起来了。
他看到了两个字符串 S1 S2 ,他想知道 S1 在 S2 中出现了多少次。
现在给出两个串 S1,S2(只有大写字母),求 S1 在 S2 中出现了多少次。
输入描述
共输入两行,第一行为 S1,第二行为 S2。
1<len(s1)<len(s2)<10^6,字符只为大写字母或小写字母。
输出描述
输出一个整数,表示 S1 在 S2 中出现了多少次。
#include <iostream>
#include <cstring>
// 定义无符号长整型别名
typedef unsigned long long ull;
// 哈希基数
const ull base = 131;
// 数组最大长度
const int N = 1e6 + 9;
// 定义字符串数组
char p[N], s[N];
// 存储基数的幂、字符串 p 和 s 的哈希值
ull b[N], h1[N], h2[N];
// 计算指定区间的哈希值
ull Gethash(ull h[], int l, int r) {
return h[r] - h[l - 1] * b[r - l + 1];
}
int main() {
// 输入字符串 p 和 s,从下标 1 开始存储
std::cin >> (p + 1);
int m = strlen(p + 1);
std::cin >> (s + 1);
int n = strlen(s + 1);
// 初始化基数的 0 次幂为 1
b[0] = 1;
// 计算字符串 p 的哈希值
for (int i = 1; i <= m; i++) {
h1[i] = h1[i - 1] * base + (ull)p[i];
}
// 计算字符串 s 的哈希值和基数的幂
for (int i = 1; i <= n; i++) {
h2[i] = h2[i - 1] * base + (ull)s[i];
b[i] = b[i - 1] * base;
}
// 统计字符串 p 在字符串 s 中出现的次数
int ans = 0;
for (int i = 1; i + m - 1 <= n; i++) {
if (Gethash(h1, 1, m) == Gethash(h2, i, i + m - 1)) {
ans++;
}
}
// 输出结果
std::cout << ans << std::endl;
return 0;
}