您的位置:首页 > 其它

[置顶] 解析KMP模式匹配算法

2017-08-11 19:38 225 查看
    程杰《大话数据结构》那本书中,“串”那一章的最后有个特别厉害的算法——KMP模式匹配算法。模式匹配算法其实就是,在一个主字符串当中寻找某子字符串,并返回子字符串在主字符串中的位置。

   
这个KMP模式匹配算法可真的是狂拽酷炫吊炸天,核心代码仅仅四五行,而我看了两个多小时才看明白(允许我在这里卖弄一下我的“笨”)。KMP算法的核心思想很好理解,但其中有个求next[]数组的处理技巧,比较令人费解。下面将自己的理解和分析进行说明,重点说“求next[]数组的处理技巧”的那部分,希望对其他人理解KMP算法有所帮助。

注:我将书中代码进行了修改,去除了字符串中第一个字符为字符串长度的情况,因为string的字符串长度可以通过size()求出。

    求next[]数组的问题抽象:给定一个字符串,求前子字符串和后子字符串匹配的最大位数,再+1,例如“abc[/u]exyzabc[/u]”的前子字符串“abc”==后子字符串“abc”,即该字符串前后子字符串匹配的最大位数==3,+1==4。next[]数组中的每个元素next[
j ]是指给定字符串的前 j 个字符(0~j-1)构成的字符串,的前后子字符串匹配的最大位数+1。例如字符串“ababaaaba”的next[]={011234223},其中next[4]==3,前4个字符“ab[/u] ab”的前后子字符串匹配的最大位数==2,+1==3。令人费解的数学公式表达如下:



先上代码(next[]数组的处理技巧代码),再解释。

// 算法核心部分
void get_next(const string& str, vector<int>& maxK)
{
int i = 0, ki = 0;
// i 表示
b41c
前 i 个字符构成的字符串
// ki 表示前 i 个字符构成的字符串中,前后缀匹配最大的个数,再 +1
maxK[i] = ki;// maxK[0] = 0;
while (i + 1 < str.size())
{
// 前 ++i 个字符 的最后一位字符是 str[i],已经匹配的个数是 ki-1,即已经匹配的位置是 str[i-1]==str[ki-2]
// 判断 str[i] 与 str[ki - 1] 是否匹配
if (ki == 0 || str[i] == str[ki - 1])
maxK[++i] = ++ki;// 前 ++i 个字符,前后缀匹配最大的个数为 ki,再 +1
// 这里符合“不能突增原理”,即 maxK[i+1] 比 maxK[i] 最多大 1
else
ki = maxK[ki - 1];
// 回溯,当 str[i] 与 str[ki - 1] 无法匹配时,寻找前 i 个字符构成的字符串中 前后缀匹配 “次大”的个数(再+1),再重新判断
// 这个 “次大”个数对应的子字符串一定在 最大个数 ki-1 对应的子字符串当中
// 即前 ki-1 个字符构成的字符串的 前后匹配的最大个数,是目前 前 i 个字符构成的字符串的 前后匹配的 “次大”个数
}
}

    解释两点:
    1.“不能突增原理”,即maxK[i+1]比maxK[i]最多大1。假设存在maxK[i+1]比maxK[i]大,且>1,成立。因为前i个字符的前后缀匹配最大个数+1==maxK[i],前
i+1 个字符的前后缀匹配最大个数+1==maxK[i+1]。我们从前 i+1 个字符的情况倒着往前 i 的情况进行推导,令k=maxK[i+1]
-1,当maxK[i+1]>maxK[i]时,那么前 i+1 个字符有k个匹配,此时前 i 个字符有k-1个匹配一定成立。则前 i 个字符的前后缀匹配最大个数k-1
再+1==k == maxK[i+1]-1 > maxK[i],与“最大”为maxK[i]矛盾。所以,不能突增,maxK[i+1]比maxK[i]最多大1。

 
  2.回溯,前 ki-1 个字符构成的字符串的 前后匹配的最大个数,是目前 前 i 个字符构成的字符串的 前后匹配的 “次大”个数。同样假设“次大”个数  >  前 ki-1 最大个数,会得到与 “前 ki-1 个字符构成的字符串的 前后匹配的最大个数” 相矛盾的结果。

 
  “求next[]数组的处理技巧”的亮点在于,在求当前 前 i 个字符构成的字符串的 前后最大匹配的情况时,它利用之前已经计算过的信息来计算当前能够匹配的最大可能,减少了很多单独求该字符串的最大匹配情况
的重复的工作,所以“很高效”。另外,在KMP算法使用next[]数组时,也减少了很多重复性的工作,所以KMP算法“很高效”。

KMP模式匹配算法的其它部分代码:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

void get_next(const string& str, vector<int>& vec);
void get_nextval(const string& str, vector<int>& vec);
int Index_KMP(string s, string t, int pos);
int main()
{
string str1 = "abcdexabcdel", str2 = "exa";

int pos = Index_KMP(str1, str2, 0);
cout << str1 << endl;
for (int i = 0; i < pos; i++)
cout << " ";
cout << str2 << endl;

return 0;
}

// 算法核心部分,改进版
void get_nextval(const string& str, vector<int>& maxK)
{
int i = 0, ki = 0;
maxK[i] = ki;
while (i + 1 < str.size())
{
if (ki == 0 || str[i] == str[ki - 1])
{
++i;
++ki;
// 利用并查集思想,“认爹”
if (str[i] != str[ki - 1])
maxK[i] = ki;
else
maxK[i] = maxK[ki - 1];
}
else
ki = maxK[ki - 1];
}
}

int Index_KMP(string s, string t, int pos)
{
int i = pos;
int j = 0;// 从 0 开始
vector<int> next(t.size(), 0);
//get_next(t, next);
get_nextval(t, next);
while (i < s.size() && j + 1 < t.size() + 1)// 不知道为什么非得 +1,否则出错
{
if (j == -1 || s[i] == t[j])// 改了 -1
{
++i;
++j;
}
else
j = next[j] - 1;// 改了 -1
}
if (j >= t.size())// 改了 >=
return i - t.size();
else
return -1;// 改了 -1
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: