- 关于字符串s的next数组的求解,next数组本质是当j+1位匹配失败时j应当回退到的位置
1.初始化next数组,令j = next[0] = -1;
2.令 i = 1,2,... len-1(len为s的长度),对每个i,通过步骤3和4求解next[i]
3.s[i] == s[j+1] 成立 或者令j = next[j],直至j回退至-1
4.如果s[i]==s[j+1], 则令next[i]=j+1,否则next[i] = j.

KMP算法:
判断pattern串是否为text串的子串。

  1. 求出pattern串的next数组
  2. 初始化j = -1,j表示pattern串中已经匹配的最后位。
  3. 令 i=1,2,...n-1(n为text的长度),对每个i执行以下步骤4和5来匹配text[i]和pattern[j+1]
  4. text[i]==pattern[j+1],或者令j=next[j],直至j回退到-1
  5. 如果text[i]==pattern[j+1],则令j++,如果j==m-1(m为pattern的长度)则表示pattern是text的子串,返回true
    bool strStr(string text, string pattern) {
          // pattern串为空时 此处默认是text的子串
          if(!pattern.size()) return true;
          // 求解pattern串的next数组
          vector<int> next = getNext(pattern);
          int j=-1; //j指向pattern中已经匹配的最后一位
          // kmp算法
          int n = text.size();
          int m = pattern.size();
           // 遍历i 匹配text[i]与pattern[j+1]
          for(int i=0;i<n;++i)
          {
              while(j!=-1 && text[i]!=pattern[j+1])
                  j = next[j];
              // 匹配成功 令j+1
              if(text[i]==pattern[j+1])
                  ++j;
              // pattern完全匹配 说明是text的子串 返回true
              if(j==m-1) return true;
          }
          return false;     
      }
      // 获取pattern串的next数组
      vector<int> getNext(string s)
      {
          int len = s.size();
          vector<int>next(len);
          //初始化j=next[0]=-1
          int j = -1;
          next[0] = -1;
          // 求解next[1]~next[len-1]
          for(int i=1;i<len;++i)
          {
              while(j!=-1 && s[i]!=s[j+1])
              {
                  j = next[j]; // 反复令j = next[j]
              }
             // 直到j回退到-1 或是s[i]==s[j+1]
             // 如果是s[i]==s[j+1]  则next[i]=j+1,先令j指向这个位置
              if(s[i]==s[j+1])
                  ++j; 
              next[i] = j;
          }
          return next;
      }

其他变形问题。如求pattern串在text串中第一次出现的位置;pattern串在text中出现的次数(每成功匹配一次让计数器增1,j回退到next[j])等等。

从DFA的角度看待KMP问题

在文本串s中查找模式串p的过程就是在状态转移的过程。利用模式串p构造出有限状态自动机,要确定状态转移的行为关键点有两个:
1.当前所处状态
2.当前遇到的文本串s中的字符
KMP算法使得当匹配失败时,可以不回退文本串的指针,而将模式串的指针回退到适当位置(重启状态X)。

具体地,

构造一个二维的dp数组来描述状态的转移图:
dp[j][c] = next
0<=j<=M,代表当前的状态
0<=c<256,代表遇到的字符(ASCII码值)
0<=next=M,代表要转移到的下一个状态
例如 dp[4]['A'] = 3 表示当前是状态4,如果遇到字符'A',则模式串p应当转移到状态3
利用该状态转移过程,KMP算法的search函数可以写成:

int search(string txt, string pat)
{    
    //模式串的长度
    int M = pat.size();
    //文本串的长度
    int N = txt.size();
    //pat的初始状态为0
    int j = 0;
    //扫描文本串
    for(int i=0;i<N;++i){
        //当前状态是j,遇到字符txt[i]
        //pat应该转移到哪个状态?
        j = dp[j][txt[i]-'0'];
        //如果到达终止态 说明匹配成功 返回匹配开头的索引
        if(j==M) return i - M + 1;
    }
    //未到达终止态 匹配失败
    return -1;
}

状态转移数组dp如何构造的?(同样是利用dfa)

    //模式串长度
    int M = pat.size();
    int dp[M][256];
    //base case
    dp[0][pat[0]-'0'] = 1;
    //重启状态 当匹配失败就回退到重启状态
    int X = 0;
    //当前状态j从1开始
    for(int j=1;j<M;++j){
        for(int c=0;c<256;++c){
            //匹配成功进入下一个状态
            if(pat[j]-'0'==c)
                dp[j][c] = j+1;
            //否则回退到重启状态
            else dp[j][c] = dp[X][c];
        }
        //更新重启状态
        X = dp[X][pat[j]-'0'];
    }

给个例子测试一下:

#include<bits/stdc++.h>
using namespace std;

//构造状态转移dp数组
void helper(string& pat, vector<vector<int>>& dp)
{
    int M = pat.size();
    dp[0][pat[0]-'0'] = 1;
    int X = 0;
    for(int j=1;j<M;++j){
        for(int c=0;c<256;++c){
            if(pat[j]-'0'==c)
                dp[j][c] = j+1;
            else dp[j][c] = dp[X][c];
        }
        X = dp[X][pat[j]-'0'];
    }
}

//在文本串txt中查找模式串pat
int search(string& txt, string& pat, vector<vector<int>>& dp)
{
    int N = txt.size();
    int M = pat.size();
    int j = 0;
    for(int i=0;i<N;++i){
        j = dp[j][txt[i]-'0'];
        if(j==M) return i-M+1;
    }
    return -1;
}

int main()
{
    string txt = "AABRAACADABRAACAADABRA";
    string pat = "AACAA";
    int M = pat.size();
    vector<vector<int>>dp(M,vector<int>(256,0));
    helper(pat,dp);
    int index = search(txt,pat,dp);
    cout<<index<<endl;  //12
    return 0;
}