KMP算法

特点

假定文本串长度为n, 模式串长度为m, 朴素字符串比较时间复杂度为O(n*m),KMP算法时间复杂度为O(n+m)

朴素算法在匹配失败后重新从首部开始匹配,而KMP则利用模式串本身的特点,在匹配失败时不用每次都从模式串首部开始匹配,
关键就在于利用模式串的前缀和后缀相同的部分
比如char[] p = "abdabe", 当p[5] = e匹配失败时,朴素算法是重新从p[0]开始匹配,但通过观察,我们发现p[0,1]和p[3,4]相同,可以从p[3]开始匹配
这正是KMP的思想,next数组就对应着前面需要的关键观察信息(知道p[0,1] = p[3,4]相同),在这里next[4] = 1;

计算next数组

令next[i-1] = k 表示[0, i-1]的最长前缀后缀的索引k(不包括p[0, i]),即p[0, k] = p[i-k-1, i-1]相同
如何求p[0, i]的最长前缀后缀呢?

  • p[i] == p[k+1], 显然next[i] = k+1
  • p[i] != p[k+1], next[i] < k+1, 这时仍然有p[0, k] = p[i-k-1, i-1]相同, 我们再次在p[0, k]中找到最长前缀后缀,k = next[k], 然后在其后的位置检验

证明:

  • 若next[i] = k+1, 则p[i] == p[k+1],与题设不符
  • 若next[i] > k+1, 则next[i-1] > k,与题设不符

还是以上面的例子来说明,根据定义最后一个b位置next[4]=1,

  • 如果e位置能够和d位置匹配,则next[5]=1+1=2很合理
  • 如果e位置不能喝d位置匹配,则问题演化成d之前ab的前缀最长有多少和e之前的ab的后缀重合,这也就是next[2]

例题

28. Implement strStr()

class Solution {
private:
    vector<int> next;
    void ComputeNext(const string& p){
        int n = p.size();
        next.resize(n);
        
        int k = -1;
        next[0] = k;
        for(int i = 1; i < n; i++){
            while(k > -1 && p[k+1] != p[i]){
                k = next[k];
            }
            
            if(p[k+1] == p[i]) k++;
            next[i] = k;
        }
    }
    
    
    int KMP(const string &t, const string &p){
        ComputeNext(p);
        int k = -1;
        for(int i = 0; i < t.size(); i++){
            while(k > -1 && p[k+1] != t[i]){
                k = next[k];
            }
            
            if(p[k+1] == t[i]) k++;
            if(k == p.size() - 1) return i - p.size() + 1;
        }
        
        return -1;
    }
public:
    int strStr(string haystack, string needle) {
        return needle == "" ? 0 : KMP(haystack, needle);
    }
};
原文地址:https://www.cnblogs.com/qbits/p/10986489.html