对KMP算法的认识和总结

本文深入讲解KMP算法的原理及应用,包括next数组的构造、匹配过程和代码实现,并介绍了如何利用next数组发现循环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

KMP算法的认识和总结

I.什么是KMP算法

KMP算法适用于模式串对文本的快速匹配,在暴力的匹配算法中,需要遍历文本串,设文本串的长度为n,模式串的长度为m,时间复杂度为O(n*m),在大数据下显然不行的。KMP算法则可以在最坏情况O(n+m)下完成匹配。



II.KMP算法中的匹配过程

next[]数组是KMP算法的精髓,首先给出例子:ababacab,字符串的下标从0开始。此时的next[]{-1,-1,0,1,2,-1,0,1};(具体怎么来的可以先无视)即,next[i]=k,表示的是,如果第i+1个字符无法匹配,则模式串应回到k位置上进行匹配,即拿模式中的第k+1个字符和文本串中当前要匹配的字符进行匹配。

比如:

i:0 1 2 3 4 5

ab a b a a b a b a c a b --- String s

k:a b a b a c a b --- String p

01 2 3 4

当前匹配到k=4,i=5(表示前面的都匹配成功了),此时p[k+1]!= s[i],即c!=a,按朴素的匹配算法的话,模式串p应该向右移动一格,即从p[0]s[1]开始匹配起。而KMP算法则根据next[]对应的值去尽可能往右移动更多一点----因为根据next[]可以知道,之前的一些匹配是必然不成功的。那么刚才的例子的话,应该是这样移动的:

i: 0 1 2 3 4 5

ab a b a a b a b a c a b --- String s

k:- - a b a b a c a b --- String p

01 2

即拿p[3]s[5]匹配。注意到next[4]=2,说明了,若4+1=5无法匹配,那么模式串p应该移动到自己的位置2中,即拿2+1=3s[5]匹配。例子中p[3]仍然不等于s[5],又next[2]=0,故下一步p回到了自己0的位置,即拿0+1=1s[5]匹配。如下所示:

i: 0 1 2 3 4 5

ab a b a a b a b a c a b --- String s

k: - - - - a b a b a c a b --- String p

0

此时p[1]仍然不等于s[5],这时next[0]=-1,已经无法移动了,那么将p整体向右移动一格。

i: 0 1 2 3 4 5

k: a b a b a a b a b a c a b --- String s

- - - - - a b a b a c a b --- String p

0

接下来,便匹配成功了。

III.next[]数组的求法以及匹配过程的代码实现

可以知道,next[0]= -1,因为0前面没有字符了。再考虑p[1],如果p[1]= p[0],显然next[1]= 0,因为当p[2]无法匹配时,说明p[0]p[1]是已经完成匹配的了,即p[0]= s[i]p[1]= s[i+1],而p[0]= p[1],显然可以拿p[0]去匹配s[i+1]。所以next[1]= 0,表示1+1=2匹配失败时,p串回到0,拿0+1=1s[i+2]匹配。相反,若p[1]!= p[0],则next[1]= -1

所以,next[]数组的求法如下:

void getNext()
{
    next[0] = -1;
    int k = -1;
    for(int i=1; i<n; i++)
    {
        while(k>-1 && p[k+1]!=p[i])
            k = next[k];
        if(p[k+1] == p[i])
            ++k;
        next[i] = k;
    }
}

匹配过程的代码实现:


int KMP()
{
    int lens = (int)strlen(s);
    int lenp = (int)strlen(p);
    int k = -1,ret = 0;
    for(int i=0; i<lent; i++)
    {
        while(k > -1 && p[k+1] != s[i])
            k = next[k];
        if(p[k+1] == s[i])
            ++k;
        /*  ----------- 1
        if(k == lenp)
        {
            ret ++;
            k = next[k];
        }
        */
        /*  ----------- 2
        if(k == lenp)
            return true;
        */
    }
    //return ret;  --- 1
    //return false; --- 2
}


其中1代表的是求能匹配多少次,2代表能否匹配,根据题目要求而定。While循环表示的就是II中匹配的移动过程。

IV.next[]数组的进一步应用:循环节

比如,ababab是由三个ab组成的,ab就是循环节。

根据next[]的性质,设模式串为p,长度为len,则当(len%(len-next[len-1]-1)==0时,len-next[len-1]-1就是循环节的长度。举个例子,abababnex[]{-1,-1,0,1,2,3}len=6next[len-1]=3len-next[len-1]-1= 2。为什么呢?考虑next[n]= k,即如下所示:

s[0]s[1] s[2] ….. s[k] …...

x

...s[m] s[m+1] …. …. s[n]

设某个位置x,使得n-k== k-x,由于s(0~k)==s(m~n)n-k==k-x,所以s(0~k-x-1)==s(m,n-k-1),即

s[0]s[1] s[2] …. s[k-x-1]....

......s[m]s[m+1] …. s[n-k-1]

得到的子问题是一样的。所以可以得到循环节就是len-next[len-1]-1,因为模式串的下标是从0开始的,故有-1

还可以根据next[]求出模式串中,既是前缀又是后缀的子串。其实就是从串的末尾len-1开始算起,不断求出next[]next[]>-1的都是答案(需要+1,即next[]+1)。为什么呢?比如abababnext[]{-1,-1,0,1,2,3}3+1=4是答案,因为next[5]之所以为3,是因为p[2],p[3],p[4]都在前缀中出现过,根据getNext()不难知道这个结论,因为只有p[i]=p[k+1]时,k才进行自增。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值