KMP算法避免了朴素算法中指针的完全回溯,而是利用起了字符串失配后算法已知的信息,实现了待匹配串指针不回退,模式串指针回归由下文提及的next数组计算得出。

Reference

http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

https://blog.csdn.net/v_july_v/article/details/7041827

KMP的简略步骤:

  • 求next数组(同一个pattern对应一组next数组)
  • 利用next数组匹配

next数组的含义

部分匹配表(PMT, Partial Match Table)整体右移,首位补-1。

部分匹配表中存储了:对应元素前的子字符串的前后缀最大公共元素长度。

求解next数组的方法

    void GetNext(){
        next[0] = -1;
        int k = -1, j = 0;
        while(j < pat.length()){
            if(k == -1 || pat[j] == pat[k]){
                ++k;
                ++j;
                next[j] = k;
            }else{
                k = next[k];
            }
        }
    }

求解next数组的分析

我们利用递归思想,假设我们已知next[0]...next[j],如何求取next[j+1]呢?

  • 如果p[j] == p[k],显然next[j+1] = next[j] + 1;
  • 如果p[j] == p[k],此时没有长度为k+1的前后缀公共子字符串,需要考虑能否找到更短的公共子字符串。我们这里运用k = next[k]递归求解k,这里的递归需要借助以下思路理解:

拿前缀$p_0, \cdots, p_{k-1}, p_k$和后缀$p_{j-k}, \cdots, p_{j-1}, p_j$匹配,如果$p_k,p_j$不匹配,下一步我们就要拿p[next[k]]与p[j]继续匹配,这个过程相当于字符串的自我匹配,可以通过k = next[k]的递归过程实现。

如何利用next数组匹配?

int KMPSearch(string src, string pat){
    int i = 0, j = 0;
    KMPPat p = KMPPat(pat);
    int sLen = src.size();
    int pLen = pat.size();
    while(i < sLen && j < pLen){
        if(j == -1 || src[i] == pat[j]){
            ++i;
            ++j;
        }else{
            j = p.next[j]; // 对应pat串右移j-next[j]
        }
    }
    if (j == pat.length()) return i-j;
    else return -1;
}

next数组决定了:当模式串中的某个字符跟文本串中的某个字符匹配失配时,模式串下一步应该跳到哪个位置。

如模式串中在j 处的字符跟文本串在i 处的字符匹配失配时,下一步用next [j] 处的字符继续跟文本串i 处的字符匹配,相当于模式串向右移动 j - next[j] 位

有关next数组的一些修正

上文中提到的求解next数组的办法尽管可用,但上一步过程中不应该出现p[j] = p[next[j]],理由是:当p[j] != s[i] 时,下次匹配必然是p[ next [j]] 跟s[i]匹配,如果p[j] = p[ next[j] ],必然导致后一步匹配失败(因为p[j]已经跟s[i]失配,然后你还用跟p[j]等同的值p[next[j]]去跟s[i]匹配,很显然,必然失配)

当出现这种情况时,需要在再次递归,即next[j] = next[next[j]]

需要修改的代码段:

while(j < pat.length()){
            if(k == -1 || pat[j] == pat[k]){
                ++k;
                ++j;
                if(p[j] != p[k])  next[j] = k;
                else  next[j] = next[k];
             }else{
                k = next[k];
            }
        }

Tips: 如何利用KMP进行多次匹配

当pat指针j指向pLen时,模式串遍历完毕,证明匹配上了一串字符,此时存下索引$i-j$,然后继续利用j = next[j]递归即可。

Last modification:October 21st, 2019 at 08:04 pm
If you think my article is useful to you, please feel free to appreciate