心得——字符串哈希二次学习

如果手写哈希的话,主键key只能是int类型,如果主键是字符串类型,值是int类型的映射时,就不能手写哈希了,就要使用unordered_map了。

第一次见链接:哈希表、字符串哈希初步学习

字符串哈希

将一个字符串转化成一个整数,并保证字符串不同,其哈希值不同。类似于整数的二进制数表示的思想一样。

因此将一个字符串看成一个R进制数,对于一个字符串:S=s1s2…sn,如果是全都是小写字母,可以将每个字符转换成idx(si)=si-'a'+1,如果有大写,有小写,有数字不方便处理,可以直接用字符的ASCII码表示。

对字符的再次转换编码要注意不能从0开始,这就是为什么+1的原因。
假设a的编码为0,那么"aa"、“aaa”、“aaaa”的哈希值都是0,冲突严重。

哈希函数

将字符串(R进制数)转换成十进制数通过哈希函数来完成:Hash[i] = ( Hash[i-1]*R + idx(si) )%mod,i 从1 到 n,通过递推中途可以求出前缀子串"s1"、“s1s2”、“s1s2s3”……,一直到"s1s2……sn"的哈希值,即最终的Hash[n],我们的字符串S的哈希值,它的展开写法为:Hash[n]=(s1 × Rn-1 + s2 × Rn-2 + …… +sn-1 × R + sn )%mod

规定:为了统一化递推公式,Hash[0]=0

一般地,
R取131 或者13331 ;
mod取264 ,常利用unsigned long long 自然溢出,相当于自动对264取模。因为ULL也是64位

要注意,这种单哈希方法的转换并不能保证百分之百不冲突的,即可能存在两个字符串对应的哈希值(十进制数)一样的情况,但是不安全因素很小,可以忽略,如果实在碰到了,考虑双哈希方法

前缀子串

在我们求一个字符串的哈希值的时候,通常使用的是递推的方法实现的,而不使用它的展开式,因此必然会求出这个字符串的前缀子串,可以用一个数组记录下来,有了它的前缀子串,我们就可以只用O(1)的复杂度求出这个字符串任意子串的哈希值:通过前缀和的思想。

结论:求父串中从下标l下标r的字符串对应的哈希值:h[r]-h[l-1]×Rr-l+1 ULL h[N];

字符串:S=s1s2…sn,假设l=4,r=6,那么就是要求出s4s5s6的哈希值,即s4×R2 +s5×R+s6
我们已知h[6]=s1×R5 +s2×R4+s3×R3 + s4×R2 +s5×R+s6
 h[4-1]=h[3]=s1×R2 +s2×R+s3
  R3 ×h[3] =s1×R5 +s2×R4+s3×R3

但是观察到h[6]和h[3]由于幂不匹配不能直接减,所以要先给h[3]乘一个数,让其和h[6]的前三位幂匹配,得到后三位,这个数就是R6-4+1 ,即r和l-1之间相差的位数,直接减即可。

Rr-l+1 怎么求呢?
在我们求hash[n]递推求其前缀的子串的时候,也可以求出Rn ,这也是一个递推的过程:R^n = R^n-1 × R,用一个数组来维护,R[i]即为Ri

最后,存储字符串的数组,从下标0存储还是从下标1存储都行,但是为了和递推数组hash以及R保持一致,他们的递推式里都涉及了i-1,所以下标都从1开始比较好。

“hash[0]代表没有子串,hash[1]代表只有1个字符的前缀子串,hash[k]表示有k个字符的前缀子串

题目描述1

给定一个长度为n的字符串,再给定m个询问,每个询问包含四个整数l1,r1,l2,r2,请你判断[l1,r1]和[l2,r2]这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式
第一行包含整数n和m,表示字符串长度和询问次数。

第二行包含一个长度为n的字符串,字符串中只包含大小写英文字母和数字。

接下来m行,每行包含四个整数l1,r1,l2,r2,表示一次询问所涉及的两个区间。

注意,字符串的位置从1开始编号。 这是题目要求的

输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出“Yes”,否则输出“No”。

每个结果占一行。

数据范围
1≤n,m≤105

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes

算法实现

#include <iostream>
#define read(x,y) scanf("%d%d",&x,&y)

using namespace std;

typedef unsigned long long ULL;
const int N=1e5+10,R=131;
char str[N];
ULL hs[N]; 维护每个前缀子串的哈希值,"hs[0]代表没有子串,hs[1]代表只有1个字符的前缀子串,hs[k]表示有k个字符的前缀子串"
ULL r[N]={1}; //维护R^i,r^0=1,其它初始为0,也定义为ULL类型,以防万一

ULL getSub(int l,int h) // low high
{
    return hs[h]-hs[l-1]*r[h-l+1];    
}

int main()
{
    int n,num;
    read(n,num);
    scanf("%s",str+1);//从下标1开始存储
    
    //预处理
    for (int i=1;i<=n;i++) {
        hs[i]=hs[i-1]*R+str[i];
        r[i]=r[i-1]*R;  "前提,r[0]=1"
    }
    
    int l1,r1,l2,r2;
    while (num--) {
        read(l1,r1),read(l2,r2);
        getSub(l1,r1)==getSub(l2,r2)?puts("Yes"):puts("No");
    }
    
    return 0;
}

题目描述2

给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模板串P在模式串S中多次作为子串出现。

求出模板串P在模式串S中所有出现的位置的起始下标。

输入格式
第一行输入整数N,表示字符串P的长度。

第二行输入字符串P。

第三行输入整数M,表示字符串S的长度。

第四行输入字符串S。

输出格式
共一行,输出所有出现位置的起始下标(下标从0开始计数),整数之间用空格隔开。

数据范围
1≤N≤105
1≤M≤106

输入样例:

3
aba
5
ababa

输出样例:

0 2

算法实现

#include <iostream>
#define read(x) scanf("%d",&x)

using namespace std;

typedef unsigned long long ULL;
const int N=1e5+10,M=1e6+10,R=131;
char ft[M],sn[N]; //ft父串,sn子串
ULL hs[M],h,r[M]={1};
//hs数组记录ft父串的每个前缀子串的哈希值,h只记录sn子串的哈希值,r记录r^i
"hs[0]代表没有子串,hs[1]代表只有1个字符的前缀子串,hs[k]表示有k个字符的前缀子串"

ULL getSub(int l,int h)
{
    return hs[h]-hs[l]*r[h-l];
}

int main()
{
    int n,m;
    read(n);
    scanf("%s",sn+1); //都从下标1开始存储字符串,和hs数组保持一致
    read(m);
    scanf("%s",ft+1);
    //预处理父串
    for (int i=1;i<=m;i++) {
        hs[i]=hs[i-1]*R+ft[i];
        r[i]=r[i-1]*R;
    }
    //预处理子串
    for (int i=1;i<=n;i++) h=h*R+sn[i];
    
    for (int i=1;i<=m-n+1;i++) { //子串能到父串的的最后一个位置下标:m-n+1
        if (getSub(i-1,i+n-1)==h) printf("%d ",i-1);   //减一是因为我们的数组下标从1开始,题目里是从1开始
    }         "提前在这里i-1, hs[i+n-1]-hs[i-1] "
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
字符串哈希算法是一种将字符串映射为数字的算法,常用于字符串的比较和匹配。在C++中,可以使用字符串哈希算法来加速字符串的比较操作。 引用\[1\]中的代码示例展示了一个使用字符串哈希算法的C++代码。该代码使用了前缀和数组和字符串数组来存储字符串,并通过计算哈希来比较两个子串是否相等。其中,哈希的计算使用了前缀和数组和幂运算。 引用\[2\]中的解释指出,使用字符串哈希的目的是为了比较字符串时不直接比较字符串本身,而是比较它们对应映射的数字。这样可以将子串的哈希的时间复杂度降低到O(1),从而节省时间。 引用\[3\]中的代码示例也展示了一个使用字符串哈希算法的C++代码。该代码使用了前缀和数组和字符串数组来存储字符串,并通过计算哈希来比较两个子串是否相等。与引用\[1\]中的代码类似,哈希的计算也使用了前缀和数组和幂运算。 综上所述,字符串哈希算法是一种将字符串映射为数字的算法,常用于字符串的比较和匹配。在C++中,可以使用前缀和数组和幂运算来计算字符串哈希,并通过比较哈希来判断两个子串是否相等。 #### 引用[.reference_title] - *1* [C++算法题 # 33 字符串哈希](https://blog.csdn.net/weixin_44536804/article/details/123425533)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [字符串哈希(c++)](https://blog.csdn.net/qq_41829492/article/details/120980055)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [AcWing 841. 字符串哈希(C++算法)](https://blog.csdn.net/YSA__/article/details/108453403)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值