字符串哈希算法

本文介绍了字符串哈希算法,强调其不同于哈希表的概念。通过一个例题引出使用哈希算法解决子串判断问题的必要性,详细解析了哈希算法的自然溢出模板,并解释了计算子串前缀和的方法。讨论了基数选择的原因,以及在编程中避免导致TLE的错误。最后,文章提供了求哈希前缀和的区间和公式,并推荐了相关视频教程进行深入学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

字符串Hash入门

一个疑问

Hash算法自然溢出模板

一些解释:

思考

AC代码


例题引入:138. 兔子与兔子 - AcWing题库

字符串Hash入门


字符串Hash可以通俗的理解为,把一个字符串当中的每一个字符映射为一个整数。

如果我们通过某种方法,将字符串转换为一个整数,就可以便的确定某个字符串是否重复出现过,这是最简单的字符串Hash应用情景了。

哈希算法不等于哈希表!!!

一个疑问

对于该题,判断两个字串是否相等,为什么不能用头文件 <string> 当中的 substr(begin, pos)呢?这里就涉及到时间复杂度的问题:substr的时间复杂度为 O(n),n是查找字符串的长度,这是一个线性复杂度

substr 源码具体参考:(24条消息) subString源码分析_weixin_30765505的博客-CSDN博客

那么,这里就合理的引出了哈希算法,它对于查找子串操作的时间复杂度仅为 O(1) 

Hash算法自然溢出模板

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

typedef unsigned long long ull;
const int N = 1000010, base = 131;
ull h[N];	//h为字符串子串的前缀和
char s[N];

int main()
{
    scanf("%s", s + 1);	//从第1位开始录入数据方便计算 
    int len = strlen(s + 1);
    
    p[0] = 1;    //不是p[1] = 1,我们要使P[i] = base^i
    for(int i = 1; i <= len; i ++ )
    {
        h[i] = h[i - 1] * base + s[i] - 'a' + 1;	//计算哈希子串的前缀和,不要忘记加1 
        
    }
        
        for(int i = 1; i <= len; i ++)
        printf("%llu\n", h[i]);	//llu输出,实现自然溢出 
     
    return 0;
}

一些解释:

1.什么叫自然溢出:

(1)当我们赋给无符号类型一个超出它范围的数据,结果是初始值对无符号类型表示数值总数取模后的余数,这就是自然溢出
(2)当我们赋给带符号类型一个超出它表示范围的值,结果是未定义的。
此时,程序可能继续工作,可能崩溃,也可能产生垃圾数据。

2.如何计算子串前缀和

通过递推的方式推导出公式,一共有两步

h[i] = h[i - 1] * base ;将上一个子串的所有元素往后移动一位,将第一位空出来

h[i] = h[i] +(s[i] - 'a' + 1);添加当前新串多出来的元素,相当于添加在了第一位上

3.关于基数的问题,在将字符串中所有元素的值映射为一个数字值之后,按顺序排列,这时候字符串相当于一个base进制的编码,通常我们采用的是二进制,但是为了确保所有的子串尽可能没有重复的值,即,假如对于一个串:asdsdfg,使得其中一个字串 asd 和 dfg 的哈希值不会相同,实际上我们希望对于任何一个子串它的哈希值都不会相同,所以我们这里有两个基数经验值,131(更常用)和13331

4.开头的 scanf("%s", s + 1); 这里有一点需要注意,在涉及到前缀和的时候,尽量让数组或者字符串从下标 1 开始,这样可以方便计算, 所以 scanf 中为 s+1,这样就可以从字符数组的的一位开始录入数据了

5.一个不容易发现的会导致TLE的错误

代码:for( int i = 1; i <= strlen(s); i ++ )

因为在 for 循环中,每遍历一次,就会调用一次 strlen() 函数,而 strlen() 函数的时间复杂度为O(n),所以说这个 for 循环时间复杂度为 O(n^2) ,有点可怕!

6.关于 llu 和其他的一些类型说明:

(24条消息) %llu 64位无符号%d、%u、%x/%X、%o%f、%e/%E或%g/%G_CSDN博客-CSDN博客

思考

(1)对于该题,有点类似于前缀和的一个应用:求一个区间 [ L,R ] 的值。

在普通前缀和中,直接求 sum[R] - sum[L - 1] 即可,但在本题中,这就刚好进入了一个误区。

例如:假设我们要求区间 sum[ 4, 7 ] 的值,根据上图所示,很显然 sum[7] - sum[3 ]是不等于 sum[ 4, 7 ] 的值的。

(2)因为我们前面说过了,我们把一个字符串通过映射来达到把这个字符串转换成一个base进制编码的问题,其中,位置越往前的元素它的进制的位数越高(即base的指数越大),如上图所示,sum[ 7 ] 中第一个元素a1是p^6,而最后一个元素a7是p^0。

并且我们可以发现一个规律,每一个元素的位数等于它后面所有元素的数量。例如a1后面还有a2到a7一共6个元素,正好等于它的位数6;a7后面没有元素,所以它的位数是0。

也就是说,在元素个数不同的前缀和中,sum[ L , R ]的值是不相同的。所以说前缀和求区间之和的公式不适用于求哈希数组中的区间之和。

(3)但是,我们可以通过修改 原公式:sum[R] - sum[L - 1] 中的 sum[ L - 1 ] 使得sum[ L - 1 ]等于sum[R]当中的sum[ L - 1 ],具体操作就是提高sum[ L - 1 ]中a1,a2......a(L-1) 的位数大小,而这提高的位数,就恰好等于我们所要求的区间的长度(R - L + 1)。

至此,我们就可以得出求哈希前缀和中求一个区间 [ R, L ] 的和的公式了。

公式:sum[ R, L ] = sum[ R ] - sum[L] * p[R - L + 1]

(4)还记得 p 数组嘛?在前面提到过,p[ i ] 是用来保存 base 的 i 次方的一个数组,由这一步求前缀和我们也可以知道为什么p[0] = 1了,因为我们要让p[i] = base的 i 次方,这样就不用考虑+1或者-1的问题了。

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010, base = 131;

typedef unsigned long long ull;

int n, m;
char s[N];
ull h[N], p[N];

ull Hash(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    cin >> n >> m;
    cin >> s + 1;
    
    p[0] = 1;
    for(int i = 1; i <= n; i ++ )   p[i] = p[i - 1] * base;
    
    for(int i = 1; i <= n; i ++ )
    {
        //这里不可以写为s[i] - 'a'
        //否则就不能判断aaa.....中的a和aa
        //因为h[0] = 0,s['a'] = 'a' - 'a' = 0
        //所以h[1] = h[2] = h[3] = ... = 0
        h[i] = h[i - 1] * base + s[i] - 'a';
    }
    
    while (m -- )
    {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        if(Hash(a, b) == Hash(c, d))    puts("Yes");
        else    puts("No");
    }
    
    cout << Hash(1, 1) << ' ' << Hash(1, 3) << endl;
    
    return 0;
}

该题大佬的视频讲解(前35分钟):icpc.upc.edu.cn/flv/lyd20.html

关于哈希算法的大佬讲解:【算法学习】字符串Hash入门_Pengwill's Blog-CSDN博客_字符串hash

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值