KMP next数组优化的思路彻底解析

首先我们假设各位都已经了解了next数组的作用,在这里我们的next数组记录的是该位置之前的子模式串的最长公共前后缀

非常浅显易懂的KMP算法讲解

那么我们优化的思路就来了:

这点优化的思路在于假设模式串中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;
} 


阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ltyqljhwcm/article/details/52199581
文章标签: kmp 算法 函数 优化
个人分类: 算法杂论 字符串
上一篇KMP算法总结(纯算法,为优化,没有学应用)
下一篇微软面试百题008——后序遍历找BST
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭