关于KMP算法的个人理解

最近准备接下来的校招面试,一直没学过数据结构的我,也不得不准备准备这方面的知识。对于字符串匹配这个课题,现在有很多的方法,然KMP确实其中经典的一种方法,这两天找了很多的相关资料,却发现理解很简单,而实现却有点小困难,为了永久解决这个问题,苦心钻研了下,现把我的理解记录如下,供有相同疑惑的童鞋一起学习,探讨。

1.KMP算法的引入

问题:有一个目标文本串T,和一个模式串P,现在要查找P在T中的位置,怎么查找呢?

首先我们还是看下最基本的暴力求解(相信这个方法大家都了解了):

假设现在目标文本串T匹配到 i 位置,模式串P匹配到 j 位置;

1.如果当前字符匹配成功(即T[i]== P[j]),则i++,j++,继续匹配下一个字符;

2.如果当前字符不匹配(即T[i]!=P[j] ),则令i=i - (j - 1)j = 0即每次失配时,模式串都要从第一位开始重新匹配,i需要回溯,j被置为0;

C++代码如下:

//暴力求解字符串匹配B-F算法
//szTarget表示目标字符串,szPattern表示模式字符串(即最终需要查找到的字符串)
void match(const string& szTarget,const string& szPattern)
{
	int iTagetLength=szTarget.size();
	int iPatternLength=szPattern.size();
	for (int i=0;i<iTagetLength-iPatternLength+1;i++)
	{
		int j=0;
		while (j<iPatternLength)
		{
			if(szTarget[i+j]==szPattern[j])
				j++;
			else
				break;
		}
		if (j==iPatternLength)
			cout<<i<<endl;
	}
}


举个例子,理解下暴力匹配的过程如下所示:

①.


首先,目标文本串"BBC_ABCDAB_ABCDABCDABDE"(其中_表示空格)的第一个字符与模式串"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以模式串后移一位。

②.


因为B与A不匹配,模式串再往后移。

③.


就这样,直到字符串有一个字符,与模式串的第一个字符相同为止。

④.


接着比较目标文本串和模式串的下一个字符,还是相同。

⑤.


直到目标文本串有一个字符,与模式串对应的字符不相同为止。

⑥.


这时,最自然的反应是,将模式串整个后移一位,再从头逐个比较。这时,我们发现,这样比较是多余的,因为在第4步时,我们已经知道T[5] = P[1] = B,而P[0] = A,即P[1] != P[0],故T[5]必定不等于P[0],所以回溯过去必然会导致失配。

KMP算法就是去除这些多余步骤,提高效率的。

2.KMP算法

对于上述所阐述的问题,我们该怎么解决呢?

上述第步时,我们发现T[10]为空格字符,P[6]为字符D,所以不匹配,而我们也看到P[0]==P[4]==T[8],P[1]==P[5]==T[9],我们接下来的一步是不是直接可以拿P[2]与T[10]比较,我们是不是可以建立一个数组:存放目标文本串第i位与模式串第j位字符失配时,下一次与第i位比较的模式串中的字符位置。这就是大部分文献中的Next数组(本文中会利用vector)。

那么我们该怎么求next呢?首先我们上代码:

//计算Next数组
//Next[i]表示模式串的第i位与目标串不匹配时(前面i-1位都匹配),下一步i的值
void GetNext(const string& szPattern,vector<int>& viNext)
{
	int iLen = szPattern.size();
	viNext.resize(iLen);
	viNext[0] = -1;
	int iIndex = 0;
	while (iIndex < iLen-1)
	{
		if (-1 == viNext[iIndex] || szPattern[iIndex] == szPattern[viNext[iIndex]] )
			viNext[iIndex+1] = viNext[iIndex] + 1;			
		else
			viNext [iIndex+1] = 0;
		++iIndex;
	}
}

或者如下:

//计算Next数组
//Next[i]表示模式串的第i位与目标串不匹配时(前面i-1位都匹配),下一步i的值
void GetNext(const string& szPattern,vector<int>& viNext)
{
	int iPatternLen = szPattern.size();
	viNext.resize(iPatternLen);
	viNext[0] = -1;
	int iIndex = 0;
	int iTemp = -1;
	while(iIndex<iPatternLen-1)
	{
		if (-1==iTemp || szPattern[iTemp]==szPattern[iIndex])
		{
			++iIndex;
			++iTemp;
			viNext[iIndex] = iTemp;
		}
		else
			iTemp = viNext[iTemp];
	}
}

现详细叙述:

a.对于模式串“ABCDABD”,若第0位’A’与目标文本串中的第j位字符不匹配,那么j+1,i=0;此时我们令Next[0]=-1;(表示j+1,i=0;)

b.若第1位’B’与目标文本串中的第j位字符不匹配,此时表明第0位与第j-1位已经匹配,那么我们就要将整个模式串后移一位,用模式串中的第0位与目标字符串中第j位比较,即Next[1]=0;

.若第2位’C’与目标文本串中的第j位字符不匹配,此时表明第0、1位分别与第j-2、j-1位已经匹配,由于前两位组成的字符串没有前缀子串和后缀子串相等,下一步直接用第0位与目标字符串中第j位比较,即Next[2]=0;

d. 若第3位’D’与目标文本串中的第j位字符不匹配,此时表明第0、1、2位分别与第j-3、j-2、j-1位已经匹配,由于前三位组成的字符串中没有前缀子串和后缀子串相等,下一步直接用第0位与目标字符串中第j位比较,Next[3]=0;

e. 若第4位’A’与目标文本串中的第j位字符不匹配,此时表明第0、1、2、3位分别与第j-4、j-3、j-2、j-1位已经匹配,由于前四位组成的字符串中没有前缀子串和后缀子串相等,下一步直接用第0位与目标字符串中第j位比较,Next[4]=0;

f. 若第5位’B’与目标文本串中的第j位字符不匹配,此时表明第0、1、2、3 、4位分别与第j-5、j-4、j-3、j-2、j-1位已经匹配,由于前五位组成的字符串中有前缀子串”A”(第0位)和后缀子串”A”(第4位)相等(P[m]==P[i-1],此时m=0,i=5),故第j-1位和模式串中的第0位就不需比较了,肯定相等,下一步只需将模式串中的第1位与目标文本串中的第j位比较就可以了,即Next[5]=1;

g. 若第6位’D’与目标文本串中的第j位字符不匹配,此时表明第0、1、2、3 、4、5位分别与第j-6、j-5、j-4、j-3、j-2、j-1位已经匹配,由于前六位组成的字符串中有前缀子串”AB”(第0、1位)和后缀子串”AB”(第4、5位)相等(P[m+1]==P[i],m、i与上一步相同),故第j-2、j-1位和模式串中的第0、1位就不需比较了,肯定相等,下一步只需将模式串中的第2位与目标文本串中的第j位比较就可以了,即Next[6]=2;

故综上所求的Next[]={-1,0,0,0,0,1,2}。

同理可得模式串“ababcabaa”的Next[]={-1,0,0,1,2,0,1,2,3};

 

接下来我们根据Next数组,我们求解文章开始的问题,首先上C++程序:

void KMPMatcher(const string& szTarget,const string& szPattern,vector<int>& viNext)
{
	int iTargetLen = szTarget.size();
	int iPatternLen = szPattern.size();
//首先从第一位开始匹配
	int iPatternIndex = 0;
	int iTargetIndex = 0;
	while(iTargetIndex<iTargetLen)
	{
//若目标文本串第iTargetIndex位和模式串第iPatternIndex位
//匹配,则比较下一位,条件A
		if(szTarget[iTargetIndex] == szPattern[iPatternIndex])
		{
			++iTargetIndex;
			++iPatternIndex;
		}
		//若目标文本串第iTargetIndex位和模式串第iPatternIndex位
//不匹配时,且当前Next数组值为-1时,将用文本串
//第iTargetIndex+1位和模式串第0位匹配,条件B
		else if(-1 == viNext[iPatternIndex])
		{
			++iTargetIndex;
			iPatternIndex = 0;
		}
		//若目标文本串第iTargetIndex位和模式串第iPatternIndex位
//不匹配时,且当前Next数组值不为-1时,将用文本串
//第iTargetIndex位和模式串第viNext[iPatternIndex]位匹配
		else
			iPatternIndex = viNext[iPatternIndex];
		if (iPatternIndex == iPatternLen)
		{
			cout<<iTargetIndex-iPatternLen<<endl;
			iPatternIndex = 0;
		}
	}
}
从上述程序中,可以发现条件A和条件B可以合并为:

if (-1==iPatternIndex || szTarget[iTargetIndex] == szPattern[iPatternIndex])

3.KMP改进算法

         然而,上述求解模式串“ABABCABAA”的Next[]={-1,0,0,1,2,0,1,2,3}时,若第j=2位’A’与目标文本串第i位不匹配时,下一步将会用第j=0位’A’与目标文本串第i位比较,但此时明显是匹配的,那我们是不是也可以更改Next的值:令Next[2]= Next[0]=-1 表示呢?显然是可以的,

按照此更改后我们可得新的Next[]={-1,0-1,0,2,-1,0,-1,3}.

那我们怎样更改程序呢?如下:
//计算Next数组
//Next[i]表示模式串的第i位与目标串不匹配时(前面i-1位都匹配),下一步i的值
void GetNext(const string& szPattern,vector<int>& viNext)
{
	int iPatternLen = szPattern.size();
	viNext.resize(iPatternLen);
	viNext[0] = -1;
	int iIndex = 0;
	int iTemp = -1;
	while(iIndex<iPatternLen-1)
	{
		if (-1==iTemp || szPattern[iTemp]==szPattern[iIndex])
		{
			++iIndex;
			++iTemp;
			if(szPattern[iTemp] != szPattern[iIndex])
				viNext[iIndex] = iTemp;
 			else
 				viNext[iIndex] = viNext [iTemp];
		}
		else
			iTemp = viNext[iTemp];
	}
}

自此,KMP算法得以理解,不知各位网友可理解了?如有不理解,欢迎相互交流交流。

4.参考文献:

1.《数据结构》第二版,严蔚敏,吴伟明著;

2.《算法导论》第三版中文版第32章;

3.http://blog.csdn.net/v_july_v/article/details/7041827

4.http://kb.cnblogs.com/page/176818/



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值