一. 概念
一.什么是字符串哈希
hash,其实就是将一个东西映射成另一个东西,类似Map,key对应value。
那么字符串Hash,其实就是:构造一个数字使之唯一代表一个字符串。但是为了将映射关系进行一一对应,也就是,一个字符串对应一个数字,那么一个数字也对应一个字符串。
用字符串Hash的目的是,我们如果要比较一个字符串,我们不直接比较字符串,而是比较它对应映射的数字,这样子就知道两个“子串”是否相等。从而达到,子串的Hash值的时间为 O(1),进而可以利用“空间换时间”来节省时间复杂的。
二.什么是哈希函数
哈希函数是哈希的关键,首先理论上任何一个函数都能做哈希函数,但是在字符串哈希中,为了避免冲突采用了一种进制哈希的方式(BKDRHash)。
字符串哈希【算法】_字符串哈希算法-CSDN博客 (原文摘自)
原理:设定一个进制 P,需要计算一个字符串的哈希值时,把每个字符看成每个进制位上的一个数字,这个串转化成了一个基于进制 P 的数,最后对 M 取余数,就得到了这个字符串的哈希值。为简化计算可以取空间大小为 M=264是 unsigned long long 的长度,一个 unsigned long long 型的哈希值 H,当 H 值大于 M 时会自动溢出,等价于自动对 M 取余,这样能避免低效的取余运算。
这里我们给出一个构造哈希函数的模版
ull hash(string s){
ull res=0;
for (int i=0;s[i];i++){
res=(res*base1+(ull)s[i])%mod1;
}
return res;
二.例题分析
一.P3370 【模板】字符串哈希
https://www.luogu.com.cn/problem/P3370
如题,给定 N 个字符串(第 i 个字符串长度为 Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N 个字符串中共有多少个不同的字符串。
友情提醒:如果真的想好好练习哈希的话,请自觉。
输入格式
第一行包含一个整数 N,为字符串的个数。
接下来 N 行每行包含一个字符串,为所提供的字符串。
输出格式
输出包含一行,包含一个整数,为不同的字符串个数。
输入输出样例
输入 #1复制
5 abc aaaa abc abcc 12345
输出 #1复制
4
说明/提示
对于 30% 的数据:N≤10,Mi≈6,Mmax≤15。
对于 70% 的数据:N≤1000,Mi≈100,Mmax≤150。
对于 100% 的数据:N≤10000,Mi≈1000,Mmax≤1500。
样例说明:
样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。
一道字符串哈希的模版题,这里可以更这代码了解一下字符串哈希的题目的解题过程
单哈希(自然溢出)
#include<bits/stdc++.h>
using namespace std;
#define N 1000005
typedef unsigned long long ull; //重命名,ull代表unsigned long long
ull base=131; //映射的进制
ull f[N]; //存储字符串映射后的数字
char s[N]; //存储原字符串
int n,ans=1;
ull haxi(char s[]) //hash函数
{
int len=strlen(s);
ull sum=0;
for(int i=0;i<len;i++){
sum=sum*base+(ull)s[i]; //具体的映射过程
}
return sum;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%s",s);
f[i]=haxi(s); //将映射后的值赋值给f数组
}
sort(f+1,f+n+1); //升序排列,方便后面比对有多少重复的
for(int i=1;i<n;i++){
if(f[i]!=f[i+1]) //如果不是重复相同的字符串,结果加1
ans++;
}
cout<<ans<<endl;
return 0;
}
以上是通过单哈希的方式,其实我们在做题中可能会遇到不同的字符串却对应的相同的值,这时就会发生哈希冲突,为了减少哈希冲突,就可以通过双哈希的方式解决。
三.双Hash方法
用字符串Hash,最怕的就是,出现冲突的情况,即不同字符串却有着相同的hash值,这是我们不想看到的。所以为了降低冲突的概率,可以用双Hash方法。
将一个字符串用不同的Base和MOD,hash两次,将这两个结果用一个二元组表示,作为一个总的Hash结果。
相当于我们用不同的Base和MOD,进行两次 单Hash方法 操作,然后将得到的结果,变成一个二元组结果,这样子,我们要看一个字符串,就要同时对比两个 Hash 值,这样子出现冲突的概率就很低了。
那么对应的 Hash 公式为:
#include<bits/stdc++.h> //双哈希操作和单哈希操作其实是一致的,只是多一次哈希
using namespace std;
typedef unsigned long long ull;
ull base=131;
struct data
{
ull x,y;
}a[10010]; //结构体存储两个哈希值
char s[10010];
int n,ans=1;
ull mod1=19260817; //mod值改变
ull mod2=19660813;
ull hash1(char s[]) //两次哈希操作
{
int len=strlen(s);
ull ans=0;
for (int i=0;i<len;i++)
ans=(ans*base+(ull)s[i])%mod1;
return ans;
}
ull hash2(char s[])
{
int len=strlen(s);
ull ans=0;
for (int i=0;i<len;i++)
ans=(ans*base+(ull)s[i])%mod2;
return ans;
}
bool comp(data a,data b) //结构体排序内部比较函数
{
return a.x<b.x;
}
main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%s",s);
a[i].x=hash1(s);
a[i].y=hash2(s);
}
sort(a+1,a+n+1,comp); //结构体排序,升序
for (int i=2;i<=n;i++)
if (a[i].x!=a[i-1].x || a[i-1].y!=a[i].y) //两次比较
ans++;
printf("%d\n",ans);
}
如果还对字符串哈希不明白的可以在b站上晓董老师的视频讲解(链接已附上)。