字符串匹配之Rabin-Karp算法

字符串匹配之Rabin-Karp算法
上一篇讲解了暴力匹配,暴力匹配最大的问题就是太慢了,而且太暴力了,不符合社会主义价值观。
因此,这里来讲解一个相对快一点的字符串匹配算法。
RabinKarp R a b i n − K a r p 算法是 Rabin R a b i n Karp K a r p 两位大佬在 1987 1987 年提出的。
为了能宏观的理解 RabinKarp R a b i n − K a r p 算法,这里先粗略的概括一下:
例如要在文本 T T 中找模式串 P P 的位置,用 S S 来表示偏移量, m m n n 分别表示 T T P P 的长度
字符串匹配之Rabin-Karp算法_第1张图片
此算法就是通过某种映射关系,把字符串转化为数字,然后通过比较相应位置上数字是否相等,如果数字不相等,说明它所对应的字符串一定不相等,通过这样的排除来缩小要比较的字符串的范围。

下面来做具体说明:
为了便于说明,假设有一串字符是{ 012...9 0 , 1 , 2...9 }的集合,(这里假定以d为基数表示每一个字符,可以理解为d进制,其中d的大小为集合的长度,ps这里看不懂没关系,先向后看)
例如,上述的字符集在 09 0 − 9 的范围,可以表示为 10 10 进制的数。因此,字符串” 31415 31415 ”对应的十进制数为 31415 31415

在比较数字之前,我们得先对文本串 T T 和模式串 P P 做好相应的映射关系,对于文本串 TS T , S 每向后偏移一格,需要把 T[S..S+m] T [ S . . S + m ] 的字符映射为一个数字
字符串匹配之Rabin-Karp算法_第2张图片
对于上图的映射,我们能在 O(m) O ( m ) 的时间计算出 31415 31415 ,在 O(nm) O ( n − m ) 的时间内映射出文本 T T 每一个偏移 S S 所对应的数字 ts t s

ts+1=10(ts10m1T[s+1])+T[s+m+1] t s + 1 = 10 ( t s − 10 m − 1 T [ s + 1 ] ) + T [ s + m + 1 ]

例如: t2=10(235901042)+2=35902 t 2 = 10 ∗ ( 23590 − 10 4 ∗ 2 ) + 2 = 35902

因此,我们只要在 O(nm+1) O ( n − m + 1 ) 的时间内遍历映射数组和 31415 31415 作比较,便能找出字符串匹配时的偏移量 S S

到这里,算法似乎已经接近尾声了,但是我们忽略了一个问题,映射出的数字太大以至于比较效率仍没有提高。解决的办法是,取一个合适的模 q q (一般选素数), q q 的选取条件为:对于 d d 进制的字母表{ 01...d1 0 , 1 , . . . , d − 1 },选取一个 q q 值,使得 dq d q 在一个计算机字长内,因此,映射的公式变为了:

ts+1=(d(tsT[s+1]dm1(modq))+T[s+m+1])modq t s + 1 = ( d ( t s − T [ s + 1 ] d m − 1 ( m o d q ) ) + T [ s + m + 1 ] ) m o d q

在加入取模 q q 后,映射的值相等并不能说明原来的字符串相等(不同的字符串可能映射出相同的值),但是可以通过这种关系排除大多数的不相等的字符串,从而降低字符串比较的代价。

//d为基数,q为素数
//处理一般ASCII常用字符,d取128,q取6999997
void RKSearch(const char* T,const char* P,int d = 128,int q = 6999997)
{
    int n = strlen(T);
    int m = strlen(P);
    int h = ((int)pow((double)d, m - 1)) % q;
    int p = 0, t = 0;

    int i = 0;
    for (; i < m; ++i)//预处理
    {
        p = (d*p + P[i]) % q;
        t = (d*t + T[i]) % q;
    }
    int s = 0;
    for (; s < n - m+1; ++s)//匹配
    {
        if (p == t)
        {
            int i = s, j = 0;
            for (; j < m - 1;)//进一步筛选
            {
                if (T[i] == P[j]) ++i, ++j;
                else break;
            }
            if (T[i] == P[j])
                printf("%d\n", s);
        }
        if (s < n - m)//更新t为ts+1
            t = (d*(t - T[s] * h%q+q) +( T[s + m])) % q;
    }
}

上述代码中, d d 表示字符集的个数,即 d d 进制,比如处理的字符在 ASSIC A S S I C 常用字符, d d 128 128 ,如果处理一般的 ASSIC A S S I C d d q q 可以直接取代码中的默认值。代码的第 19 19 行表示两个字符串取模相等的情况,第 21 28 21   28 行判断取模相等的字符串是否匹配,第 3031 30 、 31 行为更新 ts t s 。

RK R K 算法的预处理时间为 O(m) O ( m ) ,最坏的情况下匹配时间为 O((nm+1)m) O ( ( n − m + 1 ) m ) ,比如所每一个映射出的值都相等,这种情况比暴力算法还要多个预处理时间,这个算法的总体时间并不比暴力算法快多少,但它在综合情况下,对于某些情况很实用,比如说抄袭检测,只需要把库文件做一次预处理,就可以检测大量的模式串。

参考资料:《算法导论》

你可能感兴趣的:(算法)