KMP算法

来源:互联网 发布:淘宝盗图怎么修改 编辑:程序博客网 时间:2024/06/11 06:46

1.字符串匹配:给定两个字符串S1, S2,我们需要确定S1里面是否包含S2,换句话说就是判断S2是否是S1的子串。
我们很容易想到的方法就是暴力搜索:


从第一个字符开始匹配,如果不相等则顺序后移一位,再进行比较,伪代码为:

n = s1.sizem = s2.sizefor i = 0 to n - m    if s2[0 ... m-1] == s1[s ... s+m-1]        return s

这种方法的时间复杂度为O(n*m),很显然,这种方法不适用来解决大规模的匹配问题。

KMP算法:(时间复杂度为O(n + m))
现在来介绍KMP算法,这种算法能够省略那些重复的匹配,使用一个数组来保存相关的信息,并且利用这个数组就可以实现跳跃重复的匹配。
KMP算法的重点就是对这个数组进行初始化,在说明如何初始化这个数组之前,我们需要知道什么叫前缀,什么叫后缀
举个例子:在字符串ABABCAB中
所有的前缀为:

A AB ABA ABAB ABABC ABABCA

所有的后缀为:

B AB CAB BCAB ABCAB BABCAB

所以前缀就是除了最后一个字符的连续子串,后缀就是除了第一个字符的连续子串。

接着我们来说这个关键数组,我们把这个数组命名为next,那么对于字符串ABABCAB来说,这个数组为:

index 0 1 2 3 4 5 6value 0 0 1 2 0 1 2

为什么能得到这个数组:
其中index代表数组的索引,也代表这个字符串的前 index + 1 个单词组成的子串:比如 index = 3,那么子串为ABAB,index = 4,那么子串为ABABC.

我们从index = 0 开始分析 :
index = 0,子串为 A

没有前缀,没有后缀

那么 value = 0;

index = 1,子串为 AB

前缀 : A  后缀 :B

没有相同的前缀和后缀,那么value = 0;

index = 2,子串为 ABA

前缀 : A AB 后缀 :A BA

前后缀相等的子串最大长度为1,那么value = 1;

index = 3,子串为 ABAB

前缀 : A AB ABA 后缀 :B AB BAB

前后缀相等的子串最大长度为2,那么value = 2;

index = 4,子串为 ABABC

前缀 : A AB ABA ABAB 后缀 :C BC ABC BABC

没有相同的前缀和后缀,那么value = 0;

index = 5,子串为 ABABCA

前缀 : A AB ABA ABAB ABABC 后缀 :A CA BCA ABCA BABCA

前后缀相同的最大子串长度为1,那么value = 1;

index = 6,子串为 ABABCAB

前缀 : A AB ABA ABAB ABABC ABABCA 后缀 :B AB CAB BCAB ABCAB BABCAB

前后缀相同的最大子串长度为2,那么value = 2;

这样就可以把数组初始化好了。

其中value代表着:如果我们匹配字符串时发生了不匹配,那么我们可以把字符串向前跳跃的步数。跳跃相当于把前缀代替成为后缀:

target : BCABABABCAB   -->   BCABABABCABpattern:   ABABCAB     -->       ABABCAB

上面这个例子中:在C这里发生了不匹配,那么我们向后跳跃next[4] == 2步,为什么index是4?因为前面已经有4个字符完全匹配了。

现在知道了大致的匹配流程了,接下来就是需要书写代码来初始化这个数组,伪代码为:

size = pattern.sizenext[size]next[0] = 0p = 0for i = 1 to size    while p > 0 && pattern[i] != pattern[p]        p = next[p-1]    if pattern[i] == pattern[p]        p++    next[i] = p

上面最难理解的就是 p = next[p-1]了,这一句代表着当前面已经有着匹配时,下一个字符不匹配,那么模式自身就要退到上一次的状态,重新匹配上一次的状态的下一个字符。

初始化完毕数组之后就是kmp算法本身了, 伪代码为:

t = target.sizep = pattern.sizek = 0for i = 0 to t    while k > 0 && target[i] != pattern[k]        k = next[k-1]    if pattern[k] == target[i]        k++    if k == p        return i-p+1;    return -1

两段代码很相似,因为两段代码都是对字符串进行匹配,只不过对数组初始化的时候是对模式本身进行匹配(寻找最大前后缀相同子串)

最后我们可以得到以下的c++代码:

kmp.hpp

#include <iostream>#include <string>#include <vector>using namespace std;class Kmp {public:    Kmp(){}    ~Kmp(){}    int getSolution(const string& total, const string& pattern);private:    vector<size_t> next;    void initNext(const string& pattern);};int Kmp::getSolution(const string& total, const string& pattern){    initNext(pattern);    size_t t = total.size();    size_t size = pattern.size();    size_t p = 0;    for (size_t i = 0; i < t; ++i) {        while (p > 0 && total[i] != pattern[p]) {  //当下一个字符不匹配时            p = next[p - 1];        }        if (total[i] == pattern[p]) {            p++;        }        if (p == size) {            return i - p + 1;        }    }    return -1;}void Kmp::initNext(const string& pattern){    size_t p = pattern.size();    next.resize(p, 0);    size_t k = 0;    for (size_t i = 1; i < p; ++i) {        while (k > 0 && pattern[i] != pattern[k]) { //当下一个字符不匹配时            k = next[k - 1];            }        if (pattern[i] == pattern[k]) {            k++;        }        next[i] = k;    }}

main.cpp

#include "./kmp.hpp"int main(int argc, char** argv) {    if (argc < 3) {        cout << "Usage: ./a.out total pattern" << endl;        return 0;    }    Kmp solution;    string total(argv[1]);    string pattern(argv[2]);    int result = solution.getSolution(total, pattern);    cout << result << endl;    return 0;}

Mac 下面编译 : g++ -std=c++11 kmp.hpp main.cpp 通过。函数会输出第一次出现完全匹配时的target数组索引,如果没有匹配,则返回-1。

原创粉丝点击