KMP算法简解:两张图彻底看懂

网上有很多关于讲解KMP算法的文章,很多都用了具体的例子,但本文只需要两张抽象图,即可快速理解KMP算法。

在理解了BF算法之后,我们发现因为模式串指针的每次复位,都可能造成不必要的某段匹配,这就需要某种策略,来使模式串指针跳过这段匹配,KMP算法应运而生。以下以一张图来解释KMP算法的基本原理(改自“数据结构(C++语言版),邓俊辉著”):

KMP算法简解:两张图彻底看懂_第1张图片

如图,KMP基本策略是当文本串T与模式串P在i位置处失配,则指针j用t来替代,即失配字符P[j]用P[t]来替代, z继续与x比较,比较结果目前未知,但可以确定的是,z之前的字符串是不必比较的,也即S1=S3。此策略,相当于指针i不动,P向右滑动了一段距离,使z与x对齐,而滑动距离显然是j-t。显然,S3和S2都是P的前t个字符组成的串,则S1=S2=S3,于是S2可作为S4的前缀,S3可作为S4的后缀,而指针j已知,于是滑动距离j-t只取决于S4相等的前后缀的长度t,而与T无关。

S4可能有多个相等的前后缀,也即S4可以有多个t,如何在S4中选取最佳的t呢?实验表明,t取最大时最佳,也就是滑动距离j-t最小时,指针i不可能回溯,也不可能遗漏任何可能的匹配。于是我们将在P每次失配时的子串S4中取最佳的t组成一张表,即用一个称作next的数组来存储,当P的j位置失配,j就可用next[j]来替代,继续与T[i]比较,于是KMP算法可描述如下:

int KMP(char* P,char* T){
	int n=strlen(T),i;//文本串,指针 
	int m=strlen(P),j;//模式串,指针 
	int* next=bulidNext(P);//获得next表 
	while(i

相较于BF算法,i无需回溯,j需要判定是否为负,后者正是基于next数组的特性,当P在j=0处失配时,P[0,j),不妨沿用之前的名字S4,S4之前已没有任何字符可选作新的比较字符,故可假想地在S4之前设置一个哨兵P[-1],该字符与文本串任一字符都匹配,于是j=p[j]=-1,P[j]与文本串中的失配字符匹配,于是i和j指针统一向前移动一个单位。换句话说,当P在j=0处失配说明已经匹配到头了,需要在失配字符的下一位置重新开始比较。

既然next[0]=-1,那么next数组的其他值又如何计算呢?首先,next数组的值是P每次失配时S4的相等前后缀的最大长度,这个求相等前后缀的过程,其实就是S4自身的前后缀自匹配的过程,若匹配成功则前后缀相等,而S4是P的一个子串,故求next数组,就是求不断地对P进行自匹配的过程,于是可画出下图:

KMP算法简解:两张图彻底看懂_第2张图片

如上图,当P在j处失配,则可像P与T匹配时那样通过next[j]来获取下一次匹配位置,以下前后缀最大长度简称最大长度。获得next[j]后,对比第1张图,它就是S的最大长度t,而若P在j+1处失配,则需从S再加一个字符x组成的字符串中取最大长度next[j+1],而此长度最多是S的最大长度next[j]再+1,也就是说,next[j+1]<=next[j]+1。当此式取等号时,next[j+1]也就是S的最大长度t+1。此时P[j]与P[t]相等,即z归入S2。

当P[j]与P[t]不等时,如何求next[j+1]?显然,此时相当于t失配,则再次通过next数组获得next[t],若next[t]==t,则next[j+1]=next[t]+1=next[next[j]]+1,若next[t]!=t,则按上述方法以此类推,即next[j+1]=next[…next[j]…]+1,直到P[j]==P[t]的情况出现,包括t=-1的情况。

总的来说,对于位置P[j+1]之前的串中的最大前后缀长度会因j位置的失配不断减少,过程中不断令j=next[j],直到有一个字符与P[j]匹配,那么next[j+1]随即确定,这样便确定了一个位置上的next值,而要确定所有位置上的next值,需让j从0开始一直判定到m-2,m-2的next值确定,m-1的next值也随即确定,构造next数组的算法可描述如下:

int* bulidNext(char*P){
	int m=strlen(P),j=0;//j指示模式串位置 
	int *N=new int[m],t=-1;//t指示next数组位置,也可理解为指示复制模式串位置 
	N[0]=-1;//初始化,N[0]=-1
	while(j

上述算法看似完美,然而还有待改进的地方,因实验表明,当p[j]==p[t]时,若p[j+1]==p[t+1],会增加多次注定失败的比对,故可在p[j]==p[t]时,在j和t自增后,再次判定p[j]!=p[t],若成立,则正常赋值,若不成立,再令t=next[t]。具体做法是将原语句“N[j]=t”改为“N[j]=(P[j]!=P[t]?t:N[t])”

你可能感兴趣的:(算法心得)