我不认为以我乏力的描述能讲清楚KMP算法,所以我在这里推荐两个大佬写的博客,以供我自己遗忘时再次复习。
NEXT数组的理解:
https://blog.csdn.net/lee18254290736/article/details/77278769
作者:JensLee
KMP算法总体思想:
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
作者:阮一峰老师
编程思路:
https://blog.csdn.net/ebowtang/article/details/49129363
作者:EbowTang
什么时候对KMP的理解火候到了,我也写一篇有关KMP的详解,哼?
想了想,虽然有些地方我确实没法讲,但是还是尽量把我自己理解的放上去吧,下次忘了也好快速复习。
先放我的next数组:
vector makeNext(string P) //构造NEXT数组
{
int k = 0;
int size = P.size();
vector Next(size);
Next[0] = 0; //给第一个元素赋值为0
for (int i = 1; i < size; i++)
{
while (k > 0 && P[i] != P[k]) //精髓所在,判断是否继续相等
{
k = Next[k-1];
}
if (P[i] == P[k]) 如果相等则最长相等子串长度加1
{
k++;
}
Next[i] = k;
}
return Next;
}
Next数组的求解是KMP算法中最难理解的地方也是最巧妙的地方, 虽然在大二学数据结构的时候为了应付考试学会了一个计算Next数组的小技巧,但是却完全没有理解它的精髓,当然,现在也仅仅是一只脚迈入了KMP算法的台阶,现在我就来解释一波Next数组的求解过程。
首先需要知道一个概念,那就是前缀串和后缀串。什么是前缀?前缀就是除去最后一个元素以外的串。
如“abcd”,前缀即为“abc”或“ab”或“a”,要注意的是前缀串必须从第一个元素开始,但长度不一定需要最长,同理可以理解一下后缀串。
在这里先一句话概括Next数组里的元素都是些什么东西,不说明白这个后面讲它的算法估计也很难理解。一句话概括:Next数组里的元素如Next[i]的值的含义是前i+1个元素前缀串和后缀串最大的相等长度。举个栗子:
如模式串P="abcdabd",则前缀串一共有如下可能,''a","ab","abc","abcd","abcda","abcdab" 共六种。
后缀串也是六种可能,结果如下,"bcdabd","cdabd","dabd","abd","bd","d"。
此时求Next[6]的值即为求串P的前7个元素前缀串和后缀串相等的最大长度,在这里没有一个相等的,所以是0,Next[6]=0;
Next[5]的值为前5+1=6个元素前缀串和后缀串相等的最大长度,前缀串如下''a","ab","abc","abcd","abcda"。后缀串如下:
"bcdab","cdab","dab","ab","b",相等的最大前后缀串为"ab",则长度为2,Next[5]=2。依此类推,Next[0]需要我们自己赋值为0。
问题在于,电脑不是人,必须要将规律抽象出来程序才能正常运行,同时时间复杂度还不能太大。。导致上述的求Next数组的函数我理解了挺久的,特别是下面这几行代码。
while (k > 0 && P[i] != P[k]) //精髓所在,判断是否继续相等
{
k = Next[k-1];
}
if (P[i] == P[k])
{
k++;
}
这个地方我真解释的很乏力,所以我索性还是不解释了吧。大概就是对两种情况的分别计算,一种是继续相等,一种是不再相等。
然后放我的kmp的函数:
void KMP(string S, string P) //KMP算法
{
vector Next = makeNext(P);
int k = 0; //模式串下标
for (int i = 0; i < S.size();) //
{
if (S[i] == P[k]) //如果比较结果相等各往后移一位
{
if (k == P.size()-1) //判断是否找到子串
{
cout <<"Index:"<< i - k<<" ";
k = 0;
continue;
}
k++;
i++;
}
else if(S[i] != P[k]&&k>0) //如果比较结果不等且比较的不是第0位模式串,将k移到Next[k-1]的位置
{
k =Next[k-1];
}
else if(S[i] != P[k] && k==0) //比较结果不等且比较的为第0位模式串,各向后移动一位
{
k=0;
i++;
}
}
}
我的程序如下:
#include
#include
#include
#include
using namespace std;
vector makeNext(string P) //构造NEXT数组
{
int k = 0;
int size = P.size();
vector Next(size);
Next[0] = 0;
for (int i = 1; i < size; i++)
{
while (k > 0 && P[i] != P[k]) //精髓所在,判断是否继续相等
{
k = Next[k-1];
}
if (P[i] == P[k])
{
k++;
}
Next[i] = k;
}
return Next;
}
void KMP(string S, string P) //KMP算法
{
vector Next = makeNext(P);
int k = 0; //模式串下标
for (int i = 0; i < S.size();) //
{
if (S[i] == P[k]) //如果比较结果相等各往后移一位
{
if (k == P.size()-1) //判断是否找到子串
{
cout <<"Index:"<< i - k<<" ";
k = 0;
continue;
}
k++;
i++;
}
else if(S[i] != P[k]&&k>0) //如果比较结果不等且比较的不是第0位模式串,将k移到Next[k-1]的位置
{
k =Next[k-1];
}
else if(S[i] != P[k] && k==0) //比较结果不等且比较的为第0位模式串,各向后移动一位
{
k=0;
i++;
}
}
}
int main()
{
string S;
string P;
cout << "输入S串:";
cin >> S;
cout << "输入P串:";
cin >> P;
cout << "KMP index:";
KMP(S, P);
cout << endl;
return 0;
}
写到这发现我解释的可真的乏力( ̄_ ̄|||),什么时候有补充我再更吧(不存在的)
尴尬,在刷leedcode 28实现strstr()的时候,本来想看看我自己写的这篇文章加快回忆KMP算法的,但是却很尴尬地发现我给的代码里有一个bug,难怪之前测试这个kmp算法的时候偶尔会很诡异地出问题,我现在改完之后应该是没有问题了。
然而我还是无法彻底理解求Next数组的代码。。枯了