这一节介绍一下由Rabin和Karp提出的RK算法。

1,RK算法的基本思想

     HASH!

     如果两个字符串hash后的值不相同,则它们肯定不相同;如果它们hash后的值相同,它们不一定相同。

将模式串P的hash值跟主串S中的每一个长度为|P|的子串的hash值比较。如果不同,则它们肯定不相等;如果相同,则再诸位比较之。

2,RK算法的求解过程

    将我们用来比较的字符串的全集设为∑={a,b,…,z},设∑的长度为d=|∑|,则主串和模式串都可以看作是d进制数。例如只由数字组成的字符串,它的全集∑={0,1,2,3,4,5,6,7,8,9},d=10。

    设模式串为P,其长度为m,主串为S,其长度为n。则模式串P可以看作是一个m位的d进制数A,主串S可以看作是一个n位的d进制数。我们的模式匹配过程就是将A与主串中的每个长度为m的d进制数S[t…t+m-1] (t=0,1,2,…,n-m+1)的值做比较,所以整个模式匹配过程就变成了两个d进制数之间的比较过程。例如模式串为123,主串为65127451234,就是将十进制数123跟十进制数651, 512, 127, 274, 745, 451, 512, 123的逐个比较过程。

    明确了匹配过程,下面就是求解A和求解S[t…t+m-1] (t=0,1,2,…,n-m+1)的过程:

A = P[m-1] + d * (P[m-2] + d * (P[m-3] + …+ d * (P[1] + d*P[0])…))

       2)求解S[t…t+m-1]。为了方便表示,我们设S[t…t+m-1] = St,则S[t+1…t+m] = St+1

              假设已求得St,现在要求St+1,需要注意的是St+1是St去掉高位数据,其余的m-1位乘以d后再在最低位加一位得到。于是

                                            St+1 = d * (St – dm-1*S[t]) + S[t+m]

     公式比较晦涩,举个例子看看吧。比如上面例子中主串是65127451234。S2=127,那么S3=10×(127-102×1)+ 4 = 274

        现在的问题是,如果A的值太大,比较的过程会比较耗时,这个时候我们可以将这个大数mod q(q是一个大素数),同理,st也mod q,将两个取模之后的数相比较。

 3,RK算法的实现

#include <bits/stdc++.h>

using namespace std;


const int maxn = 1e9+7;

int RK(string a, string b)
{
    int hasha = 0;
    int hashb = 0;
    int d = 13;
    int h = 1;
    for(int i = 0; i < a.size()-1; i++) h*=d;
    for(int i = 0; i < a.size(); i++)
    {
        hasha = (d*hasha+a[i]-'a')%maxn;
        hashb = (d*hashb+b[i]-'a')%maxn;
    }

    for(int i = 0; i <= b.size()-a.size(); i++)
    {
        if(hasha == hashb) return i+1;
        if(i+a.size() < b.size())
            hashb = (d*(hashb - h*(b[i]-'a'))+(b[i+a.size()]-'a'))%maxn;
    }
    return -1;
}
int main()
{
    string a, b;
    while(cin >> b >> a)
    {
        cout << RK(a, b) << endl;
    }

    return 0;
}

4,算法的复杂度分析

  如果选择的素数q>=m, 则RK算法的期望运行时间为O(n+m), 如果m<<n,则算法的期望运行时间为O(n)。具体推理过程请参看《算法导论》第32章P562页。