字符串Hash
字符串hash是指将一个字符串s映射为一个,使得该整数可以尽可能唯一的代表也就是唯一标识。换言之,如果两个字符的hash值相同那么我们可以认为两者相同。
这里使用的hash策略,便是把一个字符串的每一位赋予权值
假设都是大写的英文字母。
我们设
H[i]代表前i个hash值
那么
H[i]=H[i−1]∗p+val[i]
这里因为是大写英文字母,只有26 中不同的表示,所以这里可以设p为26。(实际上就是把26进制数用10进制数表示)
接下的问题是如果字符串的位数很长,那么很可能所产生的hash值会超过技术表示的范围(以int和long类型为例)。
所以我们可以设置一个数mod,即去余
H[i]=(H[i−1]∗p+val[i]%mod
那么这样可以解决数字太大的问题,但是会造成的另一个问题是产生碰撞,即不同的字符串的hash值可能会相同。
根据相关资料,如果我们把p
设置为一个107(如10000019)大小,把mod设为一个109(如1000000009)
大小的数,那么发生碰撞的概率就非常低。
后来我问sns,sns说把p设为233就够啦,基本上碰撞的概率极小233。
一个延伸问题:如果我得到了一个字符串的hash值,那么怎么得到其中任意一个子串的hash?
首先易知:
H[i…j]=val[i]∗pj−i+val[i+1]∗pj−i−1+val[i+2]∗pj−i−2+…+val[j]∗p0
显然:
H[j]=H[i−1]∗pj−i+1+H[i…j]
所以:
H[i…j]=(H[j]−H[i−1]∗pj−i+1)
这个时候去模:
H[i…j]=((H[j]−H[i−1]∗pj−i+1)%mod+mod)%mod
#include<string.h>
#include<iostream>
using namespace std;
typedef unsigned long long ull;
const ull B = 100000007;//哈希的基数
//a 是否在 b中出现
bool contain(string a,string b){
int al = a.length(),bl = b.length();
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;
for(int i=0;i<al;i++){
ah = ah * B + a[i];
}
for(int i=0;i<al;i++){
bh = bh * B + b[i];
}
//对 b不断右移一位,更新哈希值并判断
for(int i=0;i + al <= bl;i++){
if(ah == bh)
return true;// b从位置 i开始长度为 al的字符串字串等于 a
if(i + al < bl)
bh = bh * B + b[i+al] - b[i] * t;
}
return false;
}
int main(){
string s1,s2;
cin>>s1>>s2;// bc abc
if(contain(s1,s2)){
cout<<"s2包含s1"<<endl;
} else{
cout<<"s2不包含s1"<<endl;
}
return 0;
}