KMP算法

C++

Posted by HustDsy on September 12, 2020

KMP算法是一种字符串匹配算法,可以再$O(n+m)$的时间复杂度内实现两个字符串的匹配。这篇主要记录一下自己对KMP算法的理解。

字符串匹配

​ 字符串匹配问题就是:字符串P是否为字符串S的子串?如果是它出现在s的哪些位置。其中S称为模式串,P称为子串。

暴力解(Brute-Force)

​ 对于A和B两个字符串是否相等,我们一般是首先比较长度$len(A)==len(B)$,如果长度相等的话,接着遍历$A,B$判断$A[i]==B[i]$,如果都相等话返回$true$,否则返回$false$.

bool is_equal(string A,string B){
    int lenA=(int)A.length();
    int lenB=(int)B.length();
    if(lenA!=lenB) return false;
    for(int i=0;i<lenA;i++){
        if(A[i]!=B[i]) return false;
    }
    return  true;
}

​ 我们知道判断两个字符串是否相等,那么最暴力的字符串匹配算法氛围两步骤:

  1. 枚举i=0,1,2…….len(S)-len(P)
  2. 判断S[i,i+len(P)-1]和P作比较,如果一致,那么就找到了一个匹配

下图展示了一个例子:

下面给出c++代码:

bool bruteForce_find(string P,string S){
    int lenP=(int)P.length();
    int lenS=(int)S.length();
    if(lenS<lenP) return false;
    for(int i=0;i<=lenS-lenP;i++){
        bool flag=true;
        for(int j=0;j<lenP;j++){
            if(S[i+j]!=P[j]){
                flag=false;
                break;
            }
        }
        if(flag) return true;
    }
    return false;
}

可以看出,时间复杂度为$O(nm)$

KMP算法

​ 下面看个很简单的例子,模式串P=”abcabd“,和主串进行比较,在P[5]处失配。

​ 按照暴力解法,那肯定就是从S[1]再开始和P做比较,但是我们发现为什么不能从S[3]开始进行匹配呢,因为S[1]和S[2]肯定失配,一个开头为b,一个开头为c,肯定和P[0]也就是和a不一样。假设有这样一个next数组,可以记录在某个位置失配后下一个S开始的位置的话,那肯定可以节省一大部分时间。

next数组

​ next数组记录的是一个字符串的最长公共前缀和后缀。比如字符串abcab。它的前缀集合为{$a,ab,abc,acca$},它的后缀集合为{$bcab,cab,ab,b$}。那么它在next数组中对应的值为2。

那么如何求next数组呢,这里采用动态规划的方法。$next[i]$表示$ P[0]~P[x]$ 这一段字符串,使得k-前缀恰等于k-后缀的最大的k。那么我们知道$next[0],next[1]……next[x-1]$如何求$next[x]$.来分情况讨论,我们将$next[x-1]$的值称为$now$。

  1. 如果$P[now]==P[next]$的话,那么$next[x]=next[x-1]+1$

  2. 如果$P[now]!=P[next]$的话,我们需要缩小now,那么now缩小到什么时候比较合适呢

我们可以看到子串A等于子串B,那么A的后缀和B的后缀是相同的,这就意味着子串A的最长公共前后缀肯定包括了next[c]所代表的字符串。我们把now变为next[now-1]。逐渐缩小,直到为0。c++代码如下:

void getNext(string p){
    int len=(int)p.length();
     vector<int>next(len,0);
    next[0]=0;
    int i=1;
    int now=0;
    while(i<=len-1){
        //如果相等,加一
        if(p[now]==p[i]){
            now=now+1;
            next[i]=now;
            i+=1;
        }else if(now!=0){ //不相等,缩小now
            now=next[now-1];
        }else{ //如果now=0的话,并且P[0]不等于P[i]的话
            next[i]=0;
            i+=1;
        }
    }
}

利用next数组匹配

下面直接上代码

bool isIn(string p,string s){
    //首先求next数组n
    if(s==""){
        return false;
    }
    int len=(int)s.length();
    vector<int>next(len,0);
    //next为第一个数字的时候就是next就是0,这个代表长度
    next[0]=0;
    int i=1;
    int now=0;
    while(i<=len-1){
        if(s[now]==s[i]){
            now=now+1;
            next[i]=now;
               i+=1;
        }else if(now!=0){
            now=next[now-1];
        }else{
            next[i]=0;
            i+=1;
        }
    }
    //接下来 kmp
    int si=0;
    int sj=0;
    while(si<p.length()){
        if(p[si]==s[sj]){
            si++;
            sj++;
        }else if(sj!=0){
            sj=next[sj-1];
        }else{
            si++;
        }
        if(sj==len){
            return true;
        }
    }
    return false;
}