字符串哈希(BKDH-Hash)
字符串哈希中,字符串与字符串对应的哈希值之间的映射关系称为哈希函数。
哈希函数通常采用多项式哈希函数,设要计算哈希值的字符串的长度为 n,且该字符串为 S=s1s2s3...sn,则哈希函数为 h(S)= s[i] × (mod M) ,其中,s[i] 一般为字符串中字符 si对应的 ASCII 值,一般情况下,对于 p 与 M 的取值只要保证 p 与 M 互质即可,其中 p 小于 M,p 与 M 尽可能取大(可以避免哈希碰撞)。
如果两个不同的字符串,通过哈希函数计算出来的哈希值相同,称为哈希碰撞。
对比
一般比较方法需对字符串的每一位字符进行比较来确认两字符串是否相等,它们的时间复杂度由字符串长度来决定,所以字符串越长时间复杂度越大,耗时越久,显然不够方便;这个时候就可以用到字符串哈希,将不同字符串变为不同的数字,再对转换后的数字进行比较,时间复杂度降为O(1),十分的快捷。
注意
字符串与数字一一对应;
不应存在一对多。
理解
Hash本质上将一个string类型转化成一个x进制的数字。
e.g. 将string类型123,1234,12345转化为数字123,1234,12345,不难想到这样的哈希方式一定是一一对应。
十进制就是从十个空间中任选一个出来进行组合。
e.g. 将string类型 abc , xyz 转化为26进制数字再化为10进制的hash-code,不难看出每一个字符串理论上都有一个数字与它一一对应。
小细节
26进制就是从26个空间100进制就是从100个空间25进制就是从25个空间
若将a~z共26个字母的集合放进100的空间内,26个字母依旧可以找到存放空间,可以算出各自的hash-code;
但是若将a~z共26个字母的集合放进25的空间内,26个字母将有一个字母无空间存放,则无法算出hash-code;
即给定的进制空间应大于字符串的集合。
特别情况
不是所有情况下的hash-code都能被存下,如下:
假设有9999个字符,我们取10000进制,我们从9999字符中取x个组成字符串,则当x过大时,就会爆内存,导致程序无法运行。
解决
我们可以通过对hash-code取模运算解决这一问题,即 hash-code%y,但是这又会引出一个新问题即冲突,不同的hash-code取模可能会得到同一个结果从而产生冲突,因此我们希望y越大越好,y越大,发生冲突的概率就越小。
通常我们取y=,即 (hash-code)%,更为巧妙的是,我们如果用unsigned long long类型来存放hash-code,那么在位数溢出时它会自动对取模,十分快捷;另外,有计算机科学家统计过,在进制空间取131,1331,13331……这些数字时产生冲突的概率极低(挺魔幻的)。
当然,发生冲突其实是无可避免的,当冲突发生时,我们可以用如下方法解决:
1.检查核实
检查发生冲突的两个或以上的字符串是否相同,但每次检查都会增加时间复杂度,不推荐。
2.双哈希
就是用不同的数取两次模得到两个hash-code,两次取模相同的概率十分小,因此可以有效降低发生冲突的概率。
3.布隆过滤(之后会发文章讲解)
代码实现
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int x=1331;
int main()
{
string s="abcde";
ULL hash_code=s[0];
for(int i=1;i<s.size();i++)
{
hash_code=hash_code*x+s[i];
}
cout<<hash_code;
}
上述代码是最简单易懂的hash字符串的实现,图解如下:
有了如上用法,我们还可以用不同的hash-code来表示子字符串。
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int x=1331;
vector<ULL> h,j;
void hash_code(string s)
{
h[0]=s[0];
for(int i=1;i<s.size();i++)
{
h[i]=h[i-1]*x+s[i];
j[i]=j[i-1]*x;
}
}
int main()
{
string s;
cin>>s;
h.resize(s.size());
j.resize(s.size());
hash_code(s);
string ss;
cin>>ss;
ULL a=0;
for(int i=0;i<ss.size();i++)
{
a=a*x+ss[i];
}
cout<<a;
}
主要过程图解:
左右边界的判定:
ULL get_hash(int l,int r)
{
if(!l)
{
return h[r];
}
else
{
return h[r]-h[l-1]*j[r-l+1];
}
}