KMP算法

133 阅读2分钟

KMP算法是一种在给定文本串T中查找是否包含特定模式子串P的一种较高效的算法。

BM算法

一般地,我们会想到用两个指针:ti指向文本串T的头部,pi指向模式串P的头部;如T[ti] == P[pi],则ti , pi 同时向右移动;否则pi回撤到0,ti回撤到TP对齐位置的后一位;最终如果pi到达P的末尾,则T中存在子串P;如果ti到达T的末尾而pi未到达P的末尾,则T中不存在子串P。这种暴力查找算法被称作BM算法。

KMP算法

KMP算法和BM算法的主体骨架是一样的,都是两个指针比较并向后移动;区别在于当两个指针下标的元素不相同时,BM算法tipi都需要回撤,而KMP算法中,ti是不回撤的,pi也不需要回撤到0,而是根据一个next数组,确定要回撤到哪个位置。

next数组

next数组的作用是,当 T[ti] != P[pi] 时确定pi应该回撤到哪个位置。规定 next[0] = -1next[1] = 0next[pi]的值为P[0:pi-1]的最长公共真前、后缀(注意:next[pi]对应的是P[0:pi-1]的真前后缀,也就是不包括P[pi])的长度(pi>0)。

next数组的求法

  1. 首先 next[0] = -1, next[1] = 0
  2. 对于next[i] = 2,...,lenP-1, 令j指向前缀字符串末尾后一位,初始化时没有前缀字符串,其值为0。
  3. 当P[i-1] == P[j],说明P[0:j]和P[i-1-j:i-1]是公共最长真前后缀,故而next[i] = j+1;
  4. 当P[i-1] != P[j], 则j 需要回撤,回撤到next[j] 如果P[i-1]还是不等于P[j]且j != 0 ,则j继续回撤(为什么需要回撤:回撤的过程和实际匹配的过程是一样的,实际匹配时也是不断对pi进行回撤)

最终实现

void bulidNextArray(const string& p, vector<int>& next) {
    const int N = p.size();
    next.resize(N);
    next[0] = -1;
    if(N == 1)
        return;
    next[1] = 0;
    for(int i = 2, j = 0; i < N; i++) {
        while(j != 0 && p[i-1] != p[j]) {
            j = next[j];
        }
        if(p[i-1] == p[j]) {
            j++;
        }
        next[i] = j;
    }
}
int KMP(const string& t, const string& p) {
    const int M = t.size(), N = p.size();
    if(M * N == 0) {
        return -1;
    }
    vector<int> next(N);
    bulidNextArray(p, next);
    int ti = 0, pi = 0;
    while(ti != M && pi != N) {
        if(t[ti] == p[pi]) {
            ti++;
            pi++;
        } else {
            pi = next[pi];
            if(pi == -1) {
                pi = 0;
                ti++;
            }
        }
    }
    if(ti == M && pi != N) {
        return -1;
    } else {
        return ti - N;
    }
}