Hash
update:2021.7.15 补充了更广泛,更丰富的乱搞Hash方法
求hash的方法:
1.除留余数法:
h(k)=k mod m,m一般为质数
2.平方取中法:
h(2333)=2333*2333=5442889
3.折叠法:
h(123456789)=123+456+789
4.乘积取整法
...
哈希冲突?
哈希这个算法本来就离谱有冲突不是很正常的事吗
1.线性探测法(线性散列法)
添加元素时,使用散列函数确定元素的插入位置,如果此空间有值:
1.该值是所要插入元素的关键码,不进行插入。
2.产生冲突,依次查看其后的下一个桶,如果发现空位置插入新元素

2.二次探测法(二次散列法)
一次散列法有个缺点就是元素碰撞容易引起连锁反应,形成较长连续被占单元。
于是我们对解决冲突的方法进行优化:
H产生冲突,依次查看H+1^2,H+2^2,H+3^2...,如果发现空位置插入新元素
但是,这样写仍然有不足:
乘法和取模运算会使计算复杂性增加而变得不实用。
于是我们可以将乘法移位转化为加法移位:
从H+i^2移到H+(i+1)^2,移动了2*i+1个位置。
乘法变加法,岂不美哉?
等等,你问为什么二次探测法会比线性探测法优?
那么我会告诉你:
OI不需要证明
定理:如果采用二次探测法,且单元长度为质数,如果表至少有一般空单元,新的元素总能被插入。而且插入过程中,没有一个单元被探测两次。
证明:
设有H个单元被探测到了两次,则有:
H+i^2≡H+j^2(mod M)
i^2-j^2≡0(mod M)
(i+j)(i-j)=0,与我们的假设矛盾。
所以没有一个单元被探测两次。
3.拉链法(再散列法)
你问我还有没有更NB的方法?
当然有!

对于Hash值相同的数,我们不假装看不到,也不乱七八糟地移来移去,我们用vector数组将Hash值相同的元素以链表的形式存下来。
相较于前面奇奇怪怪的方法,这种方法看起来是最正常的了。。。
字符串Hash
https://www.luogu.com.cn/blog/dlp/zi-fu-chuan-hash
以前写的Hash都是假的...
用一道板题来进行Hash的学习:
题意:给定一个·只包含A,G,C,T的字符串,求长度为k的相同连续子串的最大总数。
Code:
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 const int N=5e6+5; 5 const int BASE=131;//Hash的思想就是将一个字符串转化为一个BASE进制的数 6 const int MOD=4999957;//转化后的数是很大的,所以需要模一个数。MOD的大小一般>1e6 7 string s; 8 int k; 9 int maxn=0; 10 int now; 11 int h[N],p[N],ans[N]; 12 //h[i]:前i位字符串的hash值 13 //p[i]:BASE^i 14 //ans[i]:记录hash值为i的子串的个数 15 signed main(){ 16 cin>>s>>k; 17 int len=s.size(); 18 s=" "+s; 19 p[0]=1;h[0]=0; 20 for(int i=1;i<=len;i++){ 21 h[i]=(h[i-1]*BASE+s[i]-'A')%MOD;//hash值的计算 22 p[i]=(p[i-1]*BASE)%MOD; 23 } 24 for(int l=1;l+k-1<=len;l++){ 25 now=((h[l+k-1]-p[k]*h[l-1])%MOD+MOD)%MOD;//计算[l,l+k-1]的hash值,前缀和的思想 26 ans[now]++; 27 maxn=max(maxn,ans[now]); 28 } 29 cout<<maxn; 30 return 0; 31 }

浙公网安备 33010602011771号