字符串匹配中经常会用到KMP算法。它求解的问题类型是:字符串匹配。给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置。 

我们一般的做法是:将一个字符串(长度为n,模式串)放在另一个字符串(长度为m,主串)开始的位置,然后依次比较,如果有不匹配的字符,就将字符串往后移一位,然后依旧从头开始比较。直到找到完全匹配的位置,返回。遍历顺序从0到m-n,每次遍历,都要比较n(最多)次,所以复杂度近似为O(m*n);

而我们今天说的这个KMP算法,可以达到O(m+n)的复杂度。

算法基本思想:每次当匹配失败时,没有必要就移动一位,因为前面可能有已经匹配的字符了。如果模式串当前的字符存在公共前后缀,那么我可以对于每一个模式串事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数。

注意最长前缀:是说以第一个字符开始,但是不包含最后一个字符

KMP有个next数组,用来存放模式串中内部的匹配信息。举个例子,比如模式串为ababaca,长度是7,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6]分别计算的是 aababaababababaababacababaca的相同的最长前缀和最长后缀的长度。由于aababaababababaababacababaca的相同的最长前缀和最长后缀是“”,“”,“a”,“ab”,“aba”,“”,“a”,所以next数组的值是[-1,-1,0,1,2,-1,0],这里-1表示不存在,0表示存在长度为1,2表示存在长度为3。这是为了和代码相对应。

当求出模式串的next数组之后,我们就要进行KMP算法的匹配了。

#include <iostream>
#include <string>
#include <vector>
using namespace std;
void cal_next(string str,vector<int>&next)
{
    int len=str.size();
    next[0]=-1;
    int k=-1;
    for(int i=1;i<len;i++)
    {
        while(k>-1 && str[k+1]!=str[i])
        {
            k=next[k];
        }
        if(str[k+1]==str[i])
        {
            k++;
        }
        next[i]=k;
    }
}
int KMP(string str,string ptr,vector<int>&next)
{
    int s=0,p=0;
    while(s<str.size() && p<ptr.size())
    {
        if(str[s]==ptr[p])
        {
            s++;
            p++;
        }
        else
        {
            if(p==0)
                s++;
            else
                p=next[p-1]+1;
        }
    }
    return (p==ptr.size())?(s-ptr.size()):-1;
}
int main()
{
    string str,ptr;
    cin>>str>>ptr;
    vector<int> next;
    next.resize(ptr.size(),0);
    cal_next(ptr,next);
    cout<<KMP(str,ptr,next)<<endl;
    return 0;
}

程序的关键在于如何理解next的作用,以及如何维护它。

posted on 2018-08-25 17:44  Mini_Coconut  阅读(139)  评论(0编辑  收藏  举报