哈希冲突及解决方法

1、哈希冲突

1 来源

昨天学习了字符串哈希。但是,因为计算过程中存在取余,因此两个不同的字符串被哈希成同一个数字的概率是存在的。而且当数据量较大时,这个概率相对来说是比较大的。

2 概率

那概率究竟是多少呢?先来看一个例子:

假设一个班里有23个人,一年有365天,那么至少两个人生日在同一天的概率的多少?
通过写一个简单的程序就知道,这个概率为50%!

这就是著名的生日悖论。如果字符串生成的哈希值是近似随机的,输入的字符串也是随机的,那哈希冲突不过是一个加强版的生日悖论:
N − L + 1 N-L+1 NL+1 个在 0   M o d − 1 0~Mod-1 0 Mod1 之间的随机值,有多大的概率会有两个数相同?
所以只要我们生成一个长为 10 w 10w 10w 的随机字符串, N − L + 1 N-L+1 NL+1 尽量大,是有非常大的可能导致出现哈希冲突的。

回归正题。假设我们用哈希算法,通过哈希值进行了 n n n 次比较,每次比较的错误率为 1 / M 1/M 1/M ,则总错误率为 1 − ( 1 − 1 M ) n 1-(1-\frac{1}{M})^n 1(1M1)n 假设模数 P P P 大约为 1 e 9 1e9 1e9 ,错误率 M = 1 / P M=1/P M=1/P ,当 n = 1 e 7 n=1e7 n=1e7 时,代入到上面的式子中会得到总错误率约为 1 1% 1 20 20 20 组数据就是 1 − ( 1 − 0.01 ) 20 1-(1-0.01)^{20} 1(10.01)20 约为 0.2 0.2 0.2 !

2、解决

如果我们做两次不同的哈希(使用不同的模数和底数),通过判断这两个哈希值是否均相等,来判断字符串是否相等的话,冲突的概率就变小了,这就是双哈希。
以下是一个模板:

struct HASH{
	ll sed,mod,h[N],pw[N];
	void init(int sed_in,int mod_in){
		sed=sed_in,mod=mod_in;
		pw[0]=1;
		for(int i=1;i<N;i++){
			pw[i]=pw[i-1]*sed%mod;
		}
	}
	void make(string s){
		h[0]=s[0]%mod;
		for(int i=1;i<s.size();i++){
			h[i]=(h[i-1]*sed%mod+s[i])%mod;
		}
	}
	ll get(ll l,ll r){
		return (h[r]-h[l-1]*pw[r-l+1]%mod+mod)%mod;
	}
}s1,s2;

3、实践

不同子串

1题目描述

给你一个仅由小写字母组成的长度为 n n n 的字符串,问你其中有多少个不同的长度为 L L L 的子串。

2 思路

分别对两个字串济进行初始化并哈希,用pair存储,之后用first排序。最后遍历,如果前后哈希值不一样,就计数。

3 代码
#include<bits/stdc++.h>
#define ll long long
#define bug printf("---OK---")
#define pa printf("A: ")
#define pr printf("\n")
using namespace std;
#define N 1000005
const ll mod=998244353;
ll n,l,k,cnt;
string s;
pair<ll,ll> p[N];
ll ans;
ll sum1,sum2;
struct HASH{
	ll sed,mod,h[N],pw[N];
	void init(int sed_in,int mod_in){
		sed=sed_in,mod=mod_in;
		pw[0]=1;
		for(int i=1;i<N;i++){
			pw[i]=pw[i-1]*sed%mod;
		}
	}
	void make(string s){
		h[0]=s[0]%mod;
		for(int i=1;i<s.size();i++){
			h[i]=(h[i-1]*sed%mod+s[i])%mod;
		}
	}
	ll get(ll l,ll r){
		return (h[r]-h[l-1]*pw[r-l+1]%mod+mod)%mod;
	}
}s1,s2;
int main(){
	cin>>n>>l>>s;
	s1.init(128,1e9+7);
	s2.init(131,998244353);
	s=" "+s;
	s1.make(s);
	s2.make(s);
	for(int i=1;i<=1+n-l;i++){
		cnt++;
		p[cnt].first=s1.get(i,i+l-1);
		p[cnt].second=s2.get(i,i+l-1);
	}
	sort(p+1,p+1+cnt);
	for(int i=1;i<=cnt;i++){
		if(i==1||p[i]!=p[i-1]){
			ans++;
		}
	}
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值