KMP算法

朴素的字符串匹配算法想必大家懂得(串下标全部从0计)

模式串P与文本串T匹配,假设扫描到串T的i+1处匹配,串P的j+1处失配,那么下次串T又将回溯到i-j+1处试图与P重新匹配。如下图的前缀蛮力匹配算法,T[5]与P[5]失配后,下次又从i=1处开始和串P试图匹配。

朴素的模式串匹配算法中,在失配之前模式串P与文本串T的一段匹配信息在下一次匹配中直接被忽略,这无疑是一种信息资源上的浪费。如上图,在文本串T[0...4]与模式串P匹配后,我们便知道从T[1]开始的串是无法和P匹配的。因为文本串T[1]和模式串的P[1]是匹配的,而P[1]和P[0]是不匹配的!此时,朴素算法盲目的再去从T[1]开始匹配 ,增加了时间复杂度。

于是,我们考虑如何更有效的使用匹配信息。

一个正确的思路是,在让串P滑动能正确匹配到当前T所扫描到的位置的基础上,保证一个最小滑动确保不会丢失匹配……

如上图,在T[5]和P[5]失配后,显然模式串向后滑动2、4……都可能匹配成功,因为就我们目前扫描串T所到的位置来说它们暂时是匹配的。当然,我们要选取最小的滑动距离,不然可能丢失匹配。本例中,若模式串向后滑动4与T[4]去匹配,那么由于我们并不知道T[5]之后的信息而有可能丢失可以从T[2]匹配成功的子串T[2...](当然本例中是不可能了)。

现在,我们考虑如何去获取这种最小滑动距离。如上图,显然最小滑动后T[2...4]和P[0...2]会正确匹配(前提条件),而由于之前的匹配信息T[2...4]和P[2...4]是匹配的!现在一下子可以看出,滑动时需要的信息是失配之前的模式串P的子串的可以和其前缀匹配的最大后缀长度!如上例,P[0...4]子串的最大匹配长度是3,前缀aba和后缀aba!

接下来就是如何计算模式串P的每个子串P[0...j]的最大匹配后缀长度的问题了,我们把结果存入一个Next数组中。如上例模式串P预处理过后的Next数组将是这样:

P:       a  b  a  b  a  c  b                                                                                                        Next: 0  0  1  2  3  0  0

首先,初始化 Next[0]=0,这个很容易理解。

后,假设当前计算模式串P的子串P[0...j]的最大后缀匹配长度Next[j]的问题。

已知Next[j-1],令k=Next[j-1]。那么k将表示当前试图继续匹配的前一次成功匹配的前缀串的后一个字符,当然也表示前一次成功匹配的长度。

如上例,假设当前要计算P[4],令k=Next[3]=2,即前一次成功匹配的前缀为P[0...1](与P[2...3]),本次扫描是试图继续匹配P[0...2]与P[2...4]!显然上一次成功匹配长度为k=2!

现在需要分两种情况讨论:

1).如果P[j]==P[k],那么在前一次成功匹配的基础上本次匹配尝试成功!显然成功匹配度加1,即Next[j]=k+1!前缀指针与后缀指针向后移动,继续尝试匹配!

2).如果P[j]!=P[k],那么基于前一次成功匹配的基础再继续匹配必然是失败的!现在一个这正确的思路就是,在前一次成功匹配的前缀串中去寻找可匹配的子串!此时修正k指向上一次成功匹配的前缀串中,最长的与后缀匹配的前缀的后一个字符,即令k=Next[k-1]! 然后重复前面的过程即可!

例如下面这个情况:

P:       a  b  a  b  a  c  b                                                                                                        Next: 0  0  1  2  3  0  0

当计算子串P[0...4]最大前缀后缀匹配长度时,j=4,k=Next[3]=2。因为当前扫描位置P[4]与前一次匹配的前缀串的下一个字符P[2]匹配成功,那么子串P[0...4]的最大前后缀匹配长度就是前一次子串P[0...3]的匹配长度加1,也即k+1=3!

当计算子串P[0...5]的最大前后缀匹配长度是,j=5,k=Next[4]=3。因为当前扫描位置P[5]与前一次成功匹配前缀串的下一个字符P[3]匹配失败,那么在前一次匹配基础上试图继续匹配将是不可能的!现在考虑这样的处理方式,因为前一次成功匹配后缀串P[2...4]和P[0...2]匹配,那么由于P[2...4]中的后缀将和P[0...2]中的后缀匹配,可以考虑用P[2...4]中的后缀在P[0...2]子串前后缀成功匹配的基础上在P[0...2]前缀后继续试图匹配!本例中,即P[4]和P[0]成功匹配,现在试图用P[5]从P[1]开始继续匹配!(虽然本例并不成功)此时修正k(k=3)值为k=Next[k-1]=1,然后重复前面的过程即可。

注意,此时需要处理一种特殊情况。当去试图匹配第一个字符(k=0)但失配时,也就意味着当前匹配不能建立在任何一个匹配基础上,并且不能和首字符匹配,那么直接将Next[j]置0。如本例中的Next[5]。

预处理的Next数组计算代码如下:

while(j<m)    //j指向当前需要处理的最大前后缀匹配长度的位置,m为模式串P的长度
    {            //k指向与当前j前的后缀子串匹配的前缀子串的下一个字符,也是当前试图匹配的前一次匹配的最大长度
        if(P[j]==P[k]||k==0)    //后缀子串的下一个字符与前缀子串的下一个字符继续匹配
                                //或者匹配到串首,无前一次匹配基础
        {    
            if(P[j]!=P[k]) Next.push_back(0);    //当前字符与第一个字符不匹配,后缀前缀匹配长度置0
            else Next.push_back(++k);    //当前后缀串后的字符与试图匹配的前缀串后的字符匹配,匹配长度为前缀串长度+1,即k+1
            j++;    //向下扫描
        }
        else k=Next[k-1];    //当前后缀串后的字符与前缀串后的字符失配,在前缀串中寻找前一个可匹配的前缀子串
                            //修正前一次匹配长度和试图继续匹配位置
    }    

字符串匹配过程的代码如下:

while(1)
    {
        while(i<n&&j<m&&T[i]==P[j]) {i++;j++;}    //当前字符匹配,向后扫描
        if(j==m) cout<<i-m<<endl;    //模式串匹配成功,输出开始匹配子串起始的位置
        if(i==n) break;    //串扫描结束
        if(T[i]!=P[j])    //当前位置失配
        {
            if(j==0) i++;    //文本串模式串首字符失配,与目标串当前所试图匹配子串不能匹配,扫描下一个子串
            else j=Next[j-1];    //模式串向后滑动,试图匹配下一个可匹配的子串
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值