字符串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;
} 

posted @ 2022-02-11 20:56  PassName  阅读(416)  评论(0编辑  收藏  举报