字符串匹配之Rabin-Karp算法

Rabin-Karp算法是一种字符串匹配方法,通过数字映射和取模操作提高匹配效率。在最坏情况下时间复杂度为O((n-m+1)*m),但适用于部分特定场景,如抄袭检测。
摘要由CSDN通过智能技术生成

字符串匹配之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的位置,用 S S 来表示偏移量,m n n 分别表示T P P 的长度
这里写图片描述
此算法就是通过某种映射关系,把字符串转化为数字,然后通过比较相应位置上数字是否相等,如果数字不相等,说明它所对应的字符串一定不相等,通过这样的排除来缩小要比较的字符串的范围。

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

在比较数字之前,我们得先对文本串 T T 和模式串P做好相应的映射关系,对于文本串 TS T , S 每向后偏移一格,需要把 T[S..S+m] T [ S . . S + m ] 的字符映射为一个数字
这里写图片描述
对于上图的映射,我们能在 O(m) O ( m ) 的时间计算出 31415 31415 ,在 O(nm) O ( n − m ) 的时间内映射出文本 T T 每一个偏移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 的选取条件为:对于d进制的字母表{ 01...d1 0 , 1 , . . . , d − 1 },选取一个 q q 值,使得dq在一个计算机字长内,因此,映射的公式变为了:

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 进制,比如处理的字符在ASSIC常用字符, d d 128,如果处理一般的 ASSIC A S S I C d d 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 ) ,比如所每一个映射出的值都相等,这种情况比暴力算法还要多个预处理时间,这个算法的总体时间并不比暴力算法快多少,但它在综合情况下,对于某些情况很实用,比如说抄袭检测,只需要把库文件做一次预处理,就可以检测大量的模式串。

参考资料:《算法导论》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值