KMP算法概括

KMP算法有很多不错的解析,这里推荐两个:1、2

本文只是做一个简单的概括。

思想

KMP算法的思想用下面一张图就能说清楚:

在上图中,要检测T中是否包含P。

当匹配到某个位置发现两者不相等时(红框),肯定P要继续往后挪。普通的字符串匹配方法(也就是时间复杂度时m*n的那种),一般P只往后挪一个位置(也就是P'),然后从头开始和T比较。但是如图所示,当我们发现红框位置的不一样时,同时也代表P中红框前5个字母已经和T匹配上了,所以只有图中蓝色框部分的字母相同时,P'的比较才有意义,否则P'在前4个字母的比较时候就会匹配失败。

换句话说,当红框位置的匹配失败时,只有b之前的字母有相同的前后缀时,后移后的比较才有意义。再看下图

还是同一个字符串,其中b之前还真有相同的前后缀,也就是“ab”。此时我们可以断言,把P后挪3个位置时安全的,起码对于T中我们已经“看”过的字母,P和它匹配还好。

所以KMP算法的关键就是构建Failure Array(很多博客叫next数组)。next数组的长度和P长度相同,next[j]代表的含义是,在P[:j+1]中,最长的相同的前后缀长度是多少,如下图。next中的2表示P中截止到第5个字母为止的字串中,最长的前后缀是2。(注意,在具体实现时,有的人next的位置和我描述的刚好差1,如上面给的第2个参考文档中的实现)

LeetCode第28题就是实现字符串匹配,代码如下:

class Solution {
public:
    vector generate_next(string s){
        int i = 0, j = 1;
        vector res(s.size(), 0);
        while(j < s.size()){
            if(s[i] == s[j]){
                res[j++] = ++i;
            } else if(i > 0){
                i = res[i-1];
            } else{
                res[j++] = 0;
            }
        }
        return res;
    }
    
    int strStr(string haystack, string needle) {
        vector next = generate_next(needle);
        int i = 0, j = 0;
        while(j < needle.size() && i < haystack.size()){
            if(haystack[i] == needle[j]){
                ++i, ++j;
            } else if(j > 0){
                j = next[j-1];
            } else {
                ++i;
            }
        }
        if(j == needle.size()){
            return i - needle.size();
        } else{
            return -1;
        }
    }
};

假如P的长度是m,T的长度是n,KMP算法的时间复杂度是O(m+n),其中构建next数组复杂度是O(m),匹配复杂度是O(n)。有一个很有趣的证明,以上述的代码为例:

  1. 构建next时,考虑2*j - i的大小,会发现它初始是0,最终最大是2m,且三个if-else分支不论走哪个,2*j-i都是递增的,所以最多训练2m次,也就是O(m).
  2. 匹配时也同理,考虑2*i-j的大小,可以得到同样的结论。

你可能感兴趣的:(KMP算法概括)