Rabin-Karp算法

Rabin-Karp算法:寻找字符串S中字符串T出现的位置或次数的问题属于字符串匹配问题。

最朴素的想法是,枚举所有的起始位置,再直接检查是否匹配,复杂度是O(nm)的算法。

这里介绍的是Rabin-Karp算法,这种算法也可以推广到二维情况的哈希算法。

算法原理:对于每个起始位置,不是O(m)地直接比较字符串是否匹配,而是O(l)地比较长度为m的字符串子串的哈希值与T的哈希值是否相等。虽然即使哈希值相等字符串也未必相等(鸵鸟法,哈希值发生冲突的概率也许比直观想象中来的高,根据生日攻击理论,对于哈希值再[0,n)均匀分布的哈希函数,首次冲突发生的期望不是O(n),而是O(根号n))。但如果哈希值是随机分布的话,不同的字符串哈希值相等的概率是很低的,可以当作这种情况是不发生的。

如果采用O(m)的算法计算长度为m的字符串子串的哈希值的话,那复杂度还是O(nm)。这是就要使用一个滚动哈希的优化技巧。选取两个合适的互素常数b和h(l<b<h),假设字符串C=c1c2...cm,定义哈希函数:

H(C)=(c1*b^(m-1)+c2*b^(m-2))+c3*b^(m-3)+...+cm*b^0)modh

其中b是基数,相当于把字符串看作b进制数。这样,字符串S=s1s2...sn从位置k+1开始长度为m的字符串子串S[k+1...k+m]的哈希值,就可以利用从位置k开始的字符串子串S[k...k+m-1]的哈希值直接进行计算:

H(S[k+1...k+m])=(H(s[k...k+m-1])*b-sk*b^m+s(k+m))modh

于是,只要不断地这样计算开始位置右移一位后的字符串子串的哈希值,就可以在O(n)时间内得到所有位置对应的哈希值,从而可以在O(n+m)时间内得到所有位置对应的哈希值,从而可以在O(n+m)时间内完成字符串的匹配。

typedef unsigned long long ll;

const ull B=100000007;

bool contain(string a,string b)
{
    int a1=a.length(),b1=b.length();
    if(a1>b1){
        return false;
    }
    ull t=1;
    for(int i=0;i<a1;i++){
        t*=B;
    }
    for(int i=0;i<a1;i++){
        ah=ah*B+a[i];
    }
    for(int i=0;i<a1;i++){
        bh=bh*B+b[i];
    }
    for(int i=0;i+a1<=b1;i++){
        if(ah==bh){
            return false;
        }
        if(i+a1<b1){
            bh=bh*B+b[i+a1]-b[i]*t;
        }
    }
    return false;
}

不光是右移一位,对于左移一位,左端或右端加长一位或是缩短一位的情况,也能够进行类似处理。

假设要求S的后缀和T的前缀相等的最大长度,也可以利用滚动哈希在O(n+m)的时间内高效求得。

typedef unsigned long long ull;

const ull B=100000007;

int overlap(string a,string b)
{
    int a1=a.length(),b1=b.length();
    int ans=0;
    ull ah=0,bh=0,t=1;
    for(int i=1;i<=min(a1,b1);i++){
        ah=ah+a[a1-i]*t;
        bh=bh*B+b[i-1];
        if(ah==bh){
            ans=i;
        }
        t*=B;
    }
    return ans;
}

猜你喜欢

转载自blog.csdn.net/zhouzi2018/article/details/82793716
今日推荐