Loading

KMP 算法

KMP 算法

KMP 是一个字符串匹配算法, 最终是找到字符串 \(S\) 中子串 \(W\) 的起始索引地址, 下面我们假设:

string S = "ABCAABCDABCABCDABDABDE";
string W = "ABCDABD";
KMP 的核心, 部分匹配表 (Partial Match Table, PMT)

为什么要提出 PMT 呢? 其目的是为了不让 \(S\) 中的字符匹配超过一次, 也就是说 \(S\) 中的字符在整个算法过程中只会进行一次比较. 我们在了解 PMT 之前要先知道两个概念, Proper suffixesProper prefixes (这两种字符串既不是空串, 也不是原字符串)

Proper prefix: 不包括字符串尾部一个或多个字符的子串, 我们叫前缀集合,
Proper suffix: 不包括字符串前面一个或多个字符的子串, 我们叫后缀集合.

PMT 的定义就是, PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度.\(W\) 举例, 则有:

子串 前缀集合 后缀集合 PMT 值
A 0
AB A B 0
ABC A, AB BC, C 0
ABCD A, AB, ABC BCD, CD, D 0
ABCDA A, AB, ABC, ABCD BCDA, CDA, DA, A 1
ABCDAB A, AB, ABC, ABCD, ABCDA BCDAB, CDAB, DAB, AB, B 2
ABCDABD A, AB, ABC, ABCD, ABCDA, ABCDAB BCDABD, CDABD, DABD, ABD, BD, D 0

我们得到 pmt[W.length()] = { 0, 0, 0, 0, 1, 2, 0 }.

如何使用 PMT ?

我们使用 PMT 来避免多余的匹配操作. 如果我们前面已经匹配了 partial_match_length (下面省略为 pml)长度了, 下面我们开始匹配 SW, 始终要注意的是主串的索引 i 只要匹配成功就会有 i = i + 1,

ABCAABCDABCABCDABDABDE
|||+
ABCDABD

在比较到第四个字符时发生失配, 这是 pml 是 3, 3 - pmt[pml - 1]3 - pmt[2] 是 0, 则,

ABCAABCDABCABCDABDABDE
   |-
   ABCDABD

pml 为 1 后失配, pml[pml - 1]pmt[0] 是 0, 则,

ABCAABCDABCABCDABDABDE
    ||||||-
    ABCDABD

这次匹配到 W 的最后一个字符时失配了, pml = 6, pmt[pml - 1] = pmt[5] = 2

ABCAABCDABCABCDABDABDE
          |-
        ABCDABD

此时, pml = 1, pmt[pml - 1] = pmt[0] = 0

ABCAABCDABCABCDABDABDE
           |||||||
           ABCDABD

匹配结束.

在 Ref2 中解释说 " pmt[pml] > 1, 我们可以跳过 pml - pmt[pml - 1] 个字符", 其实没有必要, 我们从 pmt 中得到的值就是下一次开始比较的 W 起始索引. 如果为 1 则必定会从 W 的起始部分开始比较.

vector<int> getNext(string p) {
    int len = static_cast<int>(p.length());
    size_t next_len = p.length() + 1;
    
    vector<int> next(next_len, 0);
    next.reserve(next_len);
    
    next[0] = -1; // 初始值为 -1.
    int i = 0, j = -1;
    
    while (i < len) {
        if (j == -1 || p[i] == p[j]) {
            ++i, ++j;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
    
    next[len] = len; // 最后一位记录长度.
    return next;
}

int kmp(string t, string p) {
    int i = 0, j = 0;
    vector<int> next = getNext(p);
    
    while(i < t.length() && j < p.length()) {
        if (-1 == j || t[i] == p[j]) {
            ++i, ++j;
        } else {
            j = next[j];
        }
    }
    
    return j == p.length() ? i - j : -1;
}

References:

  1. https://en.wikipedia.org/wiki/Substring
  2. http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/
posted @ 2019-10-19 16:38  sad1e  阅读(161)  评论(0)    收藏  举报