首先我们假设各位都已经了解了next数组的作用,在这里我们的next数组记录的是该位置之前的子模式串的最长公共前后缀
那么我们优化的思路就来了:
这点优化的思路在于假设模式串中q号位置失配了,那么我们下一次要跳转到next[q-1]号位置进行匹配,但是如果pattern[q]==pattern[next[q-1]]的话,那么我们这一次的匹配还是失败的,我们需要继续我们的next迭代过程,知道找到开始匹配的为止
那么我们为什么不能再next数组的求解过程中就提前优化好呢,这一点就是优化的思路,我们既然还有再次调用next数组,还不如让next[q]=next[next[q-1]],这就是优化的思路
但是我们可以很明显的发现,在我们的KMP函数中,我们的语句while(j>0&&pattern[i]!=pattern[j]) j=next[j-1];我们突然发现这句话的思路和KMP的next数组上述优化的思路是一致的,但是我们会发现,如果我们提前将next数组的优化确定下来的话,我们对于这种多次的迭代(被优化的部分)的过程只用进行一次,下一次就是常数时间的O(1)时间的调用,但是如果我们不对next数组进行优化,而只是决定依靠我们KMP函数中的那句话来进行我们的迭代过程,我们碰见一次就要迭代一次,原本我们可以一次求出直接用的方式就变成每次用都要求一遍,这样子的话,KMP 的速度就会大大降低,所以说,我们决定直接对next数组进行优化,反而还节省了大量的时间,并且我们在这样优化next数组的过程中时间复杂度并没有上升,时间复杂度是没有变的,所以说,但是不优化,频繁调用迭代的next会使我们的复杂度大幅度上升
作为实例,下面是我封装的代码,在代码中还有更详细的解释:
#include"iostream"
#include"cstdio"
#include"cstdlib"
#include"cstring"
#define N 100
using namespace std;
template<typename T> class kmp;
template<typename T> istream& operator>>(istream&,kmp<T>&);
template<typename T> ostream& operator<<(ostream&,kmp<T>&);
template<typename T>
class kmp
{
public:
kmp()
{
memset(next,0,sizeof(next));
memset(pattern,0,sizeof(pattern));
memset(mother,0,sizeof(mother));
num=plength=mlength=fpos=0;
}
friend istream& operator>><>(istream&,kmp<T>&);
friend ostream& operator<<<>(ostream&,kmp<T>&);
void getnextone(); //未优化的
void getnexttwo(); //优化过的
void find();
private:
T pattern[N];
int plength;
T mother[N];
int mlength;
int next[N];
int fpos;
};
template<typename T>
istream& operator>>(istream& in,kmp<T>& k)
{
cout<<"请输入母串的长度"<<endl;
cin>>k.mlength;
cout<<"请输入母串"<<endl;
for(int i=0;i<k.mlength;i++) cin>>k.mother[i];
cout<<"请输入模式串的长度"<<endl;
cin>>k.plength;
cout<<"请输入模式串"<<endl;
for(int i=0;i<k.plength;i++) cin>>k.pattern[i];
return in;
}
template<typename T>
ostream& operator<<(ostream& out,kmp<T>& k)
{
cout<<"next数组的内容如下,以供查错"<<endl;
for(int i=0;i<k.plength;i++) cout<<k.next[i]<<' ';
cout<<endl;
cout<<"母串中包含的传的个数是"<<k.num<<endl;
cout<<"第一次出现模式串的位置是"<<k.fpos<<endl;
return out;
}
template<typename T>
void kmp<T>::getnextone()
{
//next[0]=0,因为0号位置没有前缀和后缀
int k=0; //目前最长公共前后缀的长度
int q=1; //q记录目前扫描的的位置
for(;q<plength;q++) //永远记住,k代表的是长度,实际上的区间位置是0--k-1适合和额前缀
{
while(k>0&&pattern[k]!=pattern[q]) k=next[k-1]; //算法中描述的部分
if(pattern[k]==pattern[q]) k++; //再次匹配,我们扩充最长公共前后缀
next[q]=k;
}
}
template<typename T>
void kmp<T>::getnexttwo()
{
int k=0;
int q=1;
for(;q<plength;q++)
{
while(k>0&&pattern[q]!=pattern[k]) k=next[k];
if(pattern[k]==pattern[q]) k++; //再次匹配,我们扩充最长公共前后缀
if(pattern[q]==pattern[next[q-1]]) next[q]=next[next[q-1]]; //优化的原理,当第q位置失配的时候,我们会跳转到next[q-1]来判断,如果pattern[q]==pattern[next[q-1]],书名这两个字符是一样的,前者失配了,后者必然也失配,所以说我们就直接再进一步,,实际上,如果不优化,我们在跳转到pattern[next[q-1]]的时候还是要在进行next的迭代一次操作,所以说next数组还不如初始就优化一下
else next[q]=k;
}
}
template<typename T>
void kmp<T>::find()
{
int i=0;
int j=0;
getnexttwo();
for(;i<mlength;i++)
{
while(j>0&&pattern[j]!=mother[i]) j=next[j-1]; //其实这一句话和next数组的优化思路是一致的
if(pattern[j]==mother[i]) j++;
if(j==plength)
{
fpos=i-plength+1; //j-fpos+1=k.plength
cout<<"我们找到了匹配的模式串,第一次出现的位置在"<<fpos<<endl;
return ;
}
}
cout<<"母串中不存在匹配的模式串"<<endl;
return ;
}
int main()
{
kmp<int> my;
cin>>my;
my.find();
cout<<my;
return 0;
}