一篇博文让你理解KMP算法

提到串的模式匹配算法,不得不提两种思想方法,一种是我们常用的BF算法一种就是我们将要介绍的KMP算法。

什么是模式核匹配呢?即子串的定位操作,求子串在主串中的位置。

最简单的思路就是,分别利用两计数指针指向主串S和子串T中的当前正待比较的字符位置,然后从主串中的第pos个字符开始和子串的第一个字符比较,

若相等,则继续比较,否则从主串的下一个字符起和子串的第一个比较,直至子串中的每一个字符都和主串中的某一段相同,则匹配成功,计算匹配串在

主串中的起始字符位置。这也就是我们常说的BF算法,下面给出一种实现。

#include<iostream>
#include<string>
using namespace std;
int index(string s,string t){
//s主串,t子串
        int i=0,j=0;
        while(i<s.length()&&j<t.length()){
            if(s[i]==t[j]){i++;j++;}
            else{
                  i=i-j+1; //i移回到匹配前位置,然后下移一位
                  j=0;
                }
            }
       if(j==t.length()) return i-t.length();
       else return 0;
        }
int test(){
        string s="aaabaaaab";
        string t="aaaab";
        int res=index(s,t);
        cout<<res<<endl;
        }
int main(){
        test();
        return 0;
        }
运行结果:4

上面程序思路其它比较简单,当然也有些改进方法,比如主串剩余字符数少于子串数,可提前终止。

但思想方法是硬伤,对于大量数据的匹配,如果一位一位的比较,十分的低效。

举个最坏情况的例子,比如一个字符串:00000000000000000000000001和0000001,几乎每一次都要比到最后才能确定不匹配。其时间复杂度为O(m*n)。

BF其最主要的缺点就是每一次的比较如果出现不匹配,则前面的匹配结果全部舍弃了,造成了大量不必要的比较。

下面开始介绍一个改进算法--KMP算法,

是由D.E.Knuth,V.R.Pratt和J.H.Morris同时发现的,因此称克努特-莫里斯-普拉特操作(简称KMP算法),此算法可以在O(m+n)的数量级上完成串的模式匹配。

其改进在于:每当一躺匹配过程中出现字符比较不相等时,不需要回溯i指针,而是利用已经匹配的结果,向右移一段尽可能远的距离,继续比较。

我们举个例子先:

s串:ababcabcdabcded

t串: abcd

第一次:ababcabcdabcded   c不匹配,子串右移两位,将a对齐

                abcd

第二次: ababcabcdabcded  d不匹配,子串右移三,将a对齐

                      abcd

第三次:ababcabcdabcded    匹配

                           abcd

总共三躺解决。

这里可能有疑问了,为什么有时候移两位,而有时候又移三位呢?其它这和字串有关。

想想,如果子串是aaab,匹配到b时发现不匹配了,那么,子串可以直接右移四位。因为前面不可能有三个a。

又如果子串是ababc如果在第二个ab处不匹配,则知道前面有个ab,右移两位即可,因为子串的头上的两个ab还是和主串中的后两个匹配的。

所以移动的位数与子串中的重复数有关,在这里我们将子串中每一位不匹配移动对齐的情况存在next[] 数组中。

对于abaabcac

我们如何求next数组呢?

我们将第一个数即next[0]计为-1.

如果第一个字符a就不匹配,则移右一位。

如果b不匹配向右移1,此时子串仍中没有重复字符,所以称动数为1.

如果到第三个字符(a)不匹配,此时a与第一个字符相同,那么如果与第一个字符比肯定也不相同,故右移两位。

当遇到第四个字符(a)不匹配时,此时a的前一个字符a与第一个相同,此a与b对齐,想象一下,b前的a和此字符前的a是一样的。

当遇到第五个字符(b)时不匹配,由前面next得,其与第一个a匹配,将右移将第一个a与b前一个a对齐。

。。。。

我们从上可以发现以下规律,如果子串中没一个字符是相同的,那么有几个不匹配,就可以移动几位。

如果有相同的,寻找当前不匹配处前k个字符,与子串首部字符有多少相同的,则移动多少位。将相等的对齐。而不是从头比较。

    j       | 0  1  2  3  4  5  6  7 |

 模式   | a  b  a  a  b  c   a  c |

next[j] | -1 0  0   1  1  2   0  1|

注意,这里的next[j]是要对齐字符的下标,不是称动位数。

为什么c是2呢,因为c前是ab,与子中头两个是一样的,所以如果c不匹配,可将c与第2位对齐,即右移3位。

下面给出求next数组的算法:

void getNext(const string T,int next[]){
        int i=0,j=-1;
        next[0]=-1;
        while(i<T.length()-1){
            if(j==-1||T[i]==T[j]){
                i++;
                j++;
                next[i]=j;    //每次匹配求的是下一个字符不匹配时的对齐位数
                }else{
                    j=next[j];
                    }
            }
        
        }



这里定义next数组第一个数为-1,如果第一个字符就不匹配,那么i+1,主串右移,j+1,子串从头匹配。

每次求得如果该位置不匹配,应该移至的位置。

最后就是KMP算法的实现了:

#include<iostream>
#include<string>
using namespace std;

void getNext(const string T,int next[]){
        int i=0,j=-1;
        next[0]=-1;
        while(i<T.length()-1){
            if(j==-1||T[i]==T[j]){
                i++;
                j++;
                next[i]=j;    //每次匹配求的是下一个字符不匹配时的对齐位数
                }else{
                    j=next[j];
                    }
            }
        
        }

int indexKMP(string S,string T){
        int next[10]={0};
        getNext(T,next);
        int i=0,j=0;
        int lens=S.length();
        int lent=T.length();
        while(i<lens&&j<lent){
        if(-1==j||S[i]==T[j] ){
            i++;
            j++;
            }else{
                j=next[j];
                }
            }
        if(j==T.length()) return i-T.length();
        else return 0;
        }
void text(){
        string t="abaabcac";
        int next[8]={0};
        getNext(t,next);
        int i=0;
        for(;i<8;i++){
            cout<<next[i]<<endl;
            }
        }
void test2(){
        string s="acabaabaabcacaabc";
        string t="abaabcac";
        int res=indexKMP(s,t);
        cout<<res<<endl;
        }
int main(){
        test2();
        return 0;
        }
运行结果:5
知道了不匹配要移的值,也就知道了每次不匹配要移的数,实现就方便了。


最后还想补充一点就是关于KMP算法的优化,比如对于如下的例子

S串是:aaabaaaab

T串是:aaaab

由上面的算法我们发现,其next数组为{-1,0,1,2,3},在数组第三位不匹配,那么只向左移了一位。因为前面的字母是一致的。

这里我们给出优化当子串前出现连续相同字符时,更改移动位数。

void getNextVal(string T,int nextval[]){
 int i=0,j=-1;
 nextval[0]=-1;
while(i<T.length()-1){
   i++;
   j++;
 if(T[i]!=T[j]) nextval[i]=j;  
  else nextval[i]=nextval[j];//如果子串连续相等的话,next值和前一个值的next相等
}else{
   j=nextval[j];
}
}

这样求出的nextval是{0,0,0,0,3};

第三个不匹配,一下子就移了四位。

最后,说的比较罗嗦,有什么问题可以留言,看到我会尽快回复,欢迎交流。。。。




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值