浅谈hash
hash
算法介绍
hash说得通俗一点,就是给一个变量编上一个马甲
比如说一个人聪明可爱,举世无双,天资聪慧.活泼机灵...,那么就是叫我了(真不要脸
但是这样是不是显得些许麻烦?
于是人类发明了名字
比如你叫张三,那么可以理解为张三就是你的hash值,一提到张三就想到你了,编程中也是一样的
比如我给hello_word编上一个序号为233
那么233所对应的值即为hello_word,当然,hello_word对应值也是233
总之,hash就是将一个不常用的东西,用一个常用的东西取代
hash表示
那么hash值怎么确定呢?
首先明确,hash值是想怎么确定就怎么确定的
比如坐标(x,y),hash值可以设置为xy,xy*y,xy,yx,x+y......多种多样,但是怎么选择呢?
hash值定义方法:
1.尽量避免冲突
2.方便整洁
hash常用表达方法:折中表示,乘加迭代...
而我们的重点,则是乘加迭代
hash无法逆推
很显然,带mod的hash是无法逆推的,就像k%453==10不能推出k的值一个道理
乘加迭代
对于一串字符串,我们对于每个字符定义一个值(一般为ascll码)
则hash[i]=hash[i-1]*k+a[i]
(把它想象成k
进制)
这种方法是不会冲突的,而且也可以推出hash[l][r]=hash[i][r]-hash[i][l-1]*k^(r-l+1)(i<l)
但是hash值往往十分巨大,于是往往会设置一个为一个数取模的值,于是hash值便有了一个范围
但是数是无穷无尽的,根据鸽巢原理,一定会产生冲突
那么我们等像个办法解决它
hash冲突
hash冲突的解决方法也是多种多样的
我们平常一般用拉链法和双hash(常用)
拉链法
假设25631
hash值和45698
,4521
的冲突了,为233
那我们在可以设置一个25631->45698->4521的链表
那么我们查取4521是否出现过
那么就在hash值为233的链表里面查取就是了
双hash
这是一个指标不治本的方法
操作:我们对于两个数是否相同,不再通过一个hash值是否相等来判断,而是通过二个hash值是否相等来判断
那么这就大大降低了hash冲突的可能性
例题
思路:
暴力铁定会超时,于是我们想到了hash,只要我们求到子串的值,也就可以判断当前子串是否相等了
考验公式hash(子串l-r)=hash[r]-hash[l-1]*k^(r-l+1)
的运用
code:
#include <bits/stdc++.h>
#define ULL unsigned long long
using namespace std;
int ans,len_one,len_two;
ULL k,rec[1000010],p[1000010];
char a[1000010],b[1000010];
int main() {
p[0]=1;
scanf("%s%s",a+1,b+1);
len_one=strlen(b+1);
len_two=strlen(a+1);
for(register int i=1;i<=len_one;i++){
k=k*131+(ULL)(b[i]);
p[i]=p[i-1]*131;
}
for(register int i=1;i<=strlen(a+1);i++)
rec[i]=rec[i-1]*131+(ULL)(a[i]);
for(register int i=len_one;i<=strlen(a+1);i++)
if(rec[i]-rec[i-len_one]*p[len_one]==k)ans++;
printf("%d",ans);
}
函数库简单(即hash)的应用
#include<bits/stdc++.h>
using namespace std;
map <string,int>dis;
int main( ){
int n,i,j,k,l,m=0;
char f[10];
string h;
scanf("%d",&n);
while(n--){
scanf("%s",f+1);
getline(cin,h);
if(f[1]=='f'){
if(dis.count(h))printf("yes\n");
else printf("no\n");
}else{
dis[h]=1;
}
}
}
总结
hash公式hash(子串l-r)=hash[r]-hash[l-1]*k^(r-l+1)
hash常用函数库map
来表示