字符串hash

字符串hash

原理

我的理解是:这是一种很玄学的字符串匹配算法

比如我们现在有一个字符串:str = "abcde"

通过一个hash函数,我们假设为int mhash() [为什么要写成mhash?因为hash()在库函数已经有了]

那我们得到的str的哈希值str_hash = mhash(str)

接下来是mhash的实现代码:听说这种是比较不会发生冲突的了

typedef unsigned long long ull; // 因为hash值一般都很大,所以我们采用ull
ull mhash(char *str)
{
    // base 和 mod 都最好为质数
    ull base = 131;// 这里的base选择比较小,如果题目要求的str的length比较大的话,最好把base也选择大一点
    ull mod = 2147483587;
    
    ull ret = 0;
    int len = strlen(str);
    for(int i = 0;i<len;i++)
    {
        ret = (ret*base%mod+str[i])%mod;
    }
    return ret;
}

接下来讲一下这代码具体都在干什么

首先ret = 0,在ASCII中a = 97,b = 98 以此类推

在for循环了要循环5次

  1. ret = a = 97;
  2. ret = a * base + b = 97 * 131 + 98;
  3. ret = a * base * base + b * base +c;
  4. ret = a * base * base * base+ b * base * base +c * base + d;
  5. ret = a * base * base * base * base + b * base * base * base +c * base * base + d * base + e;

通过观察以上我们可以知道,这是一条与str有关的多项式,这就是hash

str_hash = str[0]*base^(len-1)+str[1]*base^(len-2)+....+str[len-2]*base+str[len-1]

将一个字符串转换成一个数字,其中base是基数,mod 是 取余的,防止出来的数太大

接下来是重点如何快速求子串的哈希值

比如我们有一个字符串str = "abcde"

我们知道hash(str) = h1

那么hash("bcd")=?

我们的第一个想法当然是直接再hash一次,但是如果我们的str_len = 10000000呢?

再hash一次显然又费时又费力

这个时候我们就可以用前缀和的思想来解决这个问题(不懂前缀和的同学,可以先去百度学习一下前缀和,特别简单的一种思想)

我们来分析一下 h1 = a*base^3+b*base^2+c*base^1+d

h2 = b*base^2+c*base^1+d

也就是说 h2 = h1 - a*base^3,知道这个之后就简单了,我直接上代码

ull gethash(int l, int r)
{
    // 获得l-r的哈希值
    // 获取子串的哈希值
    // mhash[r] 表示从开头到r的哈希值
    // por[k] 表示base^k
    if (l > r)
        return -1;
    return mhash[r] - mhash[l - 1] * por[r - l + 1];
}

例题

很有意思的一道题[狗头]

https://ac.nowcoder.com/acm/problem/15253

下面是我写的ac代码:

// https://ac.nowcoder.com/acm/problem/15253

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <set>


using namespace std;
typedef long long ll;
typedef unsigned long long ull;

const int maxl = 1e6 + 10;
// const int mod = 1e9+7;
const int mod = 2147483587;
// const ull base = 131;
const ull base = 25165843;
char t[maxl];
char s[maxl];
int tlen = 0;
ull thashs[maxl];
int thashs_l = 0;
ull por = 1;

ull mhash(char *str, int len)
{
    ull ans = 0;
    for (int i = 0; i < len; i++)
    {
        ans = (ans * base % mod + (ull)str[i]) % mod;
    }
    return ans;
}

void init()
{
    tlen = strlen(t);
    ull thash = mhash(t, tlen);
    thashs[thashs_l++] = thash;
    for (int i = 1; i < tlen; i++)
    {
        por = (por * base) % mod;
    }
    // 这个for循环减少了时间复杂度
    for (int i = 0; i < tlen; i++)
    {
        thash = (thash - (por * (ull)t[i])%mod + mod) % mod;
        thash = ((thash * base) % mod + t[i]) % mod;
        thashs[thashs_l++] = thash;
    }
    // 因为用set之后会超时,所以我就自己写了一个二分查找,查找之前先排序一下
    sort(thashs,thashs+thashs_l);
}

bool find(ull shash)
{
    // 二分查找
    int mid;
    int l = 0;
    int r = thashs_l;
    while(1)
    {
        if(l>r) return 0;
        mid = (l+r)/2;
        if(thashs[mid]==shash) return 1;
        if(thashs[mid]>shash){
            r = mid-1;
        }   
        else{
            l = mid+1;
        }
    }
    return 0;
}

int main()
{
    int n;
    scanf("%s%d", t, &n);
    init();
    while (n--)
    {
        int ans = 0;
        scanf("%s", s);
        int len = strlen(s);
        if(len<tlen) {
            printf("0\n");
            continue;
        }
        ull shash = mhash(s, tlen);
        if (find(shash))
            ans++;
        // 这个for循环也是为了来减少时间复杂度的
        for (int i = 0; i < len - tlen + 1; i++)
        {
            shash = (shash - (s[i] * por) % mod + mod) % mod;
            shash = (shash * base) % mod + s[tlen + i];
            if (find(shash))
                ans++;
        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值