[置顶] 解析KMP模式匹配算法
2017-08-11 19:38
204 查看
程杰《大话数据结构》那本书中,“串”那一章的最后有个特别厉害的算法——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。令人费解的数学公式表达如下:
![](https://img-blog.csdn.net/20170811210028940?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMzY4MzAyMQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
先上代码(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
}
这个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
}
相关文章推荐
- [置顶] 解析KMP模式匹配算法
- [置顶] 解析KMP模式匹配算法
- [置顶] 解析KMP模式匹配算法
- KMP模式匹配算法之next数组解析
- KMP模式匹配算法 分类: C/C++ 2015-07-01 16:41 150人阅读 评论(0) 收藏
- KMP模式匹配算法实现
- KMP字符串模式匹配算法Java实现
- KMP模式匹配算法
- KMP 字符串模式匹配算法
- 算法-KMP模式匹配算法学习
- 9.KMP模式匹配算法实现o(n)复杂度的匹配
- KMP模式匹配算法
- 串的模式匹配(基于KMP的匹配算法)
- 模式匹配 -- KMP 算法原理与实现
- KMP字符串模式匹配算法
- 模式匹配算法kmp
- KMP模式匹配算法(1)
- KMP模式匹配算法中next,nextval的分别实现
- KMP模式匹配算法
- KMP模式匹配算法