浅谈字符串哈希

一、引入

        哈希算法是通过一个哈希函数H,将一种数据(如字符串)转化为另一种数据(通常转化为整形数值),有些题可用map做,但数据一大就要用到字符串哈希

二、字符串哈希

        寻找长度为n的主串S中的匹配串T(长度为m)出现的位置或次数属于字符串匹配问题。朴素算法(或称为暴力)就是枚举所有子串的起始位置,每枚举一次就要使用O(m)的时间,总共要O(nm)的时间。当然字符串匹配可以用KMP做,但这里介绍一下字符串哈希。

        字符串哈希就是将每个字符串转化为一个数值,然后遍历主串,判断在主串起始位置为i长度为m的字符串的哈希值与匹配串的哈希值是否相等即可,每次判断为O(1)的时间。这样就可以转化为O(n)的时间完成判断。那么问题来了,怎么预处理哈希值呢?

        我们选用两个互质常数base和mod,假设匹配串T=abcdefg……z(注意这里不是指T只有26位)那么哈希值为              H(T)=(a*base^(m-1)+b*base^(m-2)+c*base^(m-3)+……+z)%mod。相当于把每个字符串转换为一个base进制数,所以对于每道题我们取base时,要大于每一位上的值(避免重复),例如我们用的十进制数每一位都是小于10的。

例如字符串C="ABDB",则H(C)=‘A’+'B'*base+'D'*base^2+'B'*base^3(本人习惯直接取字符askII码值,也可以使‘A’=1)

         那么怎么判断主串起始位置为i长度为m的字符串的哈希值与匹配串的哈希值是否相等呢?这里有个公式,若求字符串中第i位到第j位的哈希值(i<j),则这个值为H(j)-H(i-1)*base^(j-i+1)。有了这个公式,我们可以预处理一个数组H[i]表示字符串从第一位到第i位的哈希值和数组power[i]表示base^i。加上判断的时间,总时间为O(n+m)。

        在计算时,我们可以使用无符号类型(通常本人习惯使用unsigned long long)的自然溢出,这样就可以不用%mod,包括减法也方便许多。

        当然哈希会有可能重复,base值越大重复可能性越小,本人通常取131或233317。也可使用双哈希,即两个不同的mod

那么举个栗子玩玩:Power Strings(Poj2406)

【问题描述】

      给定若干个长度小于等于1 000 000的字符串,询问每个字符串最多由多少个相同的子串重复连接而成。如:ababab最多由3个ab连接而成。

【输入格式】

若干行,每行一个字符串,遇“.”结束

【输出格式】

每行一个数,表示最多由多少子串连成

【样例输入】

    abcd

    aaaa

    ababab

    .

【样例输出】

    1

    4

    3

【题目解析】

外循环枚举子串长度,如果整除总长度,那么内循环判断,代码如下:

#include <iostream>
#include <cstdio>
#include <cctype>
#include <climits>

using namespace std;

typedef unsigned long long ull;
const int N=1e6+5;
const ull prime=233317;//表示base 
ull power[N]={1},h[N]; 

int main()
{
    int l,i,j;
    string a;
    for(i=1;i<N;i++)  
        power[i]=power[i-1]*prime;//预处理 
    while(cin>>a)
    {
        if(a==".")
            break;
        l=a.size();
        h[0]=ull(a[0]);
        for(i=1;i<l;i++)
            h[i]=h[i-1]*prime+ull(a[i]);//预处理从第一位到第i位的hash值,自然溢出不用取模 
        for(i=1;i<=l;i++)//枚举长度 
        {
            if(l%i)
                continue;
            bool f=1;
            for(j=i-1;j<l;j+=i)
                if(h[j]-h[j-i]*power[i]!=h[i-1])//公式 
                {
                    f=0;//不成立 
                    break;
                }
            if(f)//如果成立 
            {
                cout<<l/i<<endl;
                break;
            }
        }
    }
    return 0;
}

参考文献:黄新军等《信息学奥赛一本通·提高篇》

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值