KMP算法

KMP算法

串的模式匹配即子串定位是一种重要的串操作。设S和T是给定的两个串,在主串S中找子串T的过程称为模式匹配。


一、BF算法

暴风(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。 —— [ 百度百科 ]

S(主串): S1S2...Sm
T(模式串): t1t2...tn

1.1. 思路

第一轮中, S1 t1 对齐,比较 S1 t1 。若 S1 t1 相同,则比较 S2 t2 ,以此类推。

Si tj 比较时(第一轮中i=j), Si 不等于 tj ,则重新开始下一轮比较,即 S2 t1 对齐,进行下一轮比较。

在这个过程中,如果T中的字符全部比较完,则匹配成功,成功的这一轮中,匹配起始位置为 i-j+1。

1.2. 准备

主串S与模式串T的0号单元存放串长,即s[0]=m,t[0]=n

从1号单元开始存放串值,即s[i]= si , t[j]= tj

1.3. 算法描述

int Index_BF(char *s, char *t)
{
    int i=1, j=1;      /*i为S串下标,j为T串下标*/
    while(i<=s[0] && j<=t[0])
        if(s[i]==t[j]) {
            i++;
            j++;
        }
        else {         /*回溯,本次S中起始位置为i-j+1,故下一次为i-j+2*/
            i=i-j+2;
            j=1;
        }
    if(j>t[0])         /*匹配成功,返回起始位置*/
        return i-j+1;  /*(i-1)-(j-1)+1=i-j+1*/
    return -1;
}

1.4. 时间复杂度分析

1.4.1 最佳情况:

T串每次都在第一个字符处匹配失败,如S=”aaaaaaaaabc”,T=”bc”。假设在 Si 处匹配成功,则前面共进行了i-1轮失败匹配,且每轮仅比较一次,最后一次成功匹配比较了n次,故总共比较了i-1+n次。

现假定从任一起始位置(i=1,2,…,m-n+1)匹配成功都是等可能的,概率是

Pi = 1mn+1

平均比较次数是

1mn+1mn+1i=1(i1+n)=m+n2

时间复杂度是O(m+n)

1.4.2 最坏情况:

T串每次都在最后一个字符处匹配失败,如S=”aaaaaaaaaab”,T=”ab”。假设在 Si 处匹配成功,则前面共进行了i-1轮失败匹配,且每轮比较次数为n,最后一次成功匹配比较了n次,故总共比较了i*n次,在等概率条件下,平均比较次数是

1mn+1mn+1i=1(in)=n(mn+2)2

时间复杂度为O(m*n)

1.5 结论

BF算法简单但效率较低,进行许多无效比较,故算法不够理想。

1.6举例

主串:abababc
模式串:abac
第一轮比较到第四个字符失配,第二轮从第一个字符开始失配,为无效的一轮,因为第一轮已经得到前三个字符为aba,所以可以跳过第二轮,直接进入第三轮匹配。

二、KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。 —— [ 百度百科 ]

造成BF算法速度慢的原因是回溯,即在某一轮失配后,对于S串要回到本轮开始字符的下一个字符,T串要回到第一个字符,而这些回溯并不是必要的。如果在某一轮 si tj 失配后,指针i不回溯,模式串T向右“移动”到某个位置上,使 tk si 对准,继续向右进行匹配,那么算法效率会得到提高。

模式串: t1t2...tn

2.1 分析示例

example 1: S=”abababac”, t=”abac”

t的前三位 t1t2t3 sisi+1si+2 匹配成功,即 sisi+1si+2 =”aba”,所以下一轮中,可以直接从 si+2 开始匹配,因为我们知道从 si+1 开始匹配必然是无效的。

example 2: S=”abcabcabcb”, t=”abcabcb”

t中 t1t2t3t4t5t6 sisi+1si+2si+3si+4si+5 匹配成功,即 sisi+1si+2si+3si+4si+5 =”abcabc”,所以在下一轮中,可以直接从 si+3 开始匹配,因为显然 si...si+5 中的后缀abc可以作为下一轮中与 t1t2t3 匹配的abc,所以下一轮直接从 si+3 匹配,从 sisi+1si+2 开始显然是无效的。

由上述两例分析可知,我们是根据上一轮中的 sisi+1...si+j2 来判断下一轮中应从哪一个位置开始匹配,显然 sisi+1...si+j2 t1t2...tj1 相同,所以 t1t2...tj1 决定下一轮模式串应该移动多少位。

回到example1和example2中,子串 t1t2...tj1 分别为”aba”与”abcab”。不难发现,我们是先找到其相同的最大前缀和后缀,”aba”中为a,”abcab”中为ab,然后模式串移动至其前缀与 sisi+1...si+j2 中相同的最大后缀对应的位置即可开始下一轮匹配。

2.2 准备

2.2.1 Substr(S, m, n)子串截取函数

T串中存在下列等式:
Substr(T, 1, k-1) = Substr(T, j-k+1, j-1) ——描述示例中的最大前缀与最大后缀

模式串T中每一个 ti 对应一个k值,且k值仅依赖于模式串T本身,与主串S无关。

2.2.2 Next()函数

现用next[j]表示 tj 对应的k值,含义是当 tj 匹配失败时,使第k个字符代替其进行匹配。
定义:next[j] =

0j=1max{ k | Substr(T, 1, k-1) = Substr(T, j-k+1, j-1) 1<k<j}1 t1tj0 t1=tj

2.3 思路

Next()函数求值问题可以看成一个模式匹配问题,整个模式串既是主串又是模式。

tj=tk (最大前缀与最大后缀同时增加一个 tk tj ),则next[j+1]=next[j]+1;

tjtk ,则Substr(T, 1, k) Substr(T, j-k+1, j),此时,应将模式向右滑动,next[k] = k1,使第k1个字符与主串中的第j个字符作比较 (这样做的原因是将其看成一个局部的KMP算法问题,主串和模式串均为T串)

tk1=tj ,则next[j+1]=next[k]+1,即Substr(T, 1, k1)=Substr(T, j-k1+1, k1);

tk1tj ,则继续将模式向右移动,next[k1] = k2,依次类推,直至匹配成功;或者不存在任何kn使得Substr(T, 1, kn)=Substr(T, j-kn+1, j),此时,若 t1tj+1 则next[j+1]=1,即从这一步开始重新用 t1 来匹配,主串不移动,否则next[j+1]=0,下一步重新开始用 t1 匹配,主串移动了一位。

2.4 算法描述

先看Index_KMP(),再看getNext()。

int Index_KMP(char *s, char *t)
{
    int i=1, j=1;
    while(i<=s[0] && j<=t[0])
        if(s[i]==t[j] || j==0){   /*匹配成功或者直接从下一位重新开始从T串第一位匹配*/
            i++;
            j++;
        }
        else
            j=next[j];            /*回溯,从第k个字符开始下一轮,如果k=0则会从下一位重新开始*/
    if(j>t[0])
        return i-j+1;
    return -1;
}

void getNext(char *t, int next[])
/*求取模式串T中每个单元的next值,存入next[]数组中*/
{
    int i=1, j=0;
    next[1]=0;
    while(i<t[0]){
        while(j>0 && t[i]!=t[j])  /*不断尝试匹配,直至找到可以成功匹配的第j位字符*/
            j=next[j];            /*跳出循环有两种情况,情况一是匹配成功,情况二是j=0*/
        i++;                      /*主串移动一位*/
        j++;                      /*模式移动一位*/
        if(t[i]==t[j])            
            next[i]=next[j];
        else                      
            next[i]=j;
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值