KMP算法(详细解释了next[]数组的构造原理)

1.为什么要用 KMP算法?

在每一趟匹配过程中出现比较字符不等时,不需要回溯指针,而是利用已经得到的“部分匹配”的结果将模式串向右“滑动
尽可能远的距离后,继续比较。

先放代码:

 int Kmp (sq s,sq p,int next[]){
    
    
 	int c = 0;       //主串移动变量 
 	int d =  0;
 	while(c < s.lenght && d < p.lenght) //合法长度之内 
 	{
    
    
 		if(d == -1 || s.string[c] == p.string[d])
		 {
    
    
		 	/* d == -1 或者匹配成功就累加继续比较下一个*/
		 	c++;
			d++; 
		 }
 		else 
 			d = next[d];  //利用next数组进行移动,通过移动最长真前缀,避免不必要的回溯 
	 }
	 if (d == p.lenght)
	 	return c-p.lenght;//匹配成功,返回和模式串中第一个字符等的字符在主串的序号
	 else
	 	return -1;    //匹配失败	  
 }

2.next[]是什么?

当模式中第j个字符与主串中相应字符“失配”时,在模式中需重新和主串中该字符进行比较的字符的位置。怎么确定该位置呢?就是要用的next[]数组,进行位置的确定。

3.构造next数组的过程

先放代码:

 void GetNext (sq q,int next[]){
    
    
 	int i = 0,j = -1;
 	/*next数组,默认为-1,避免长串与短串首字符不匹配时,短串首字符为,next[0]=0,造成长短串匹配位置不更新,进入死循环*/
 	netx[0] = -1;
	while (i < q.lenght){
    
    
		/*自动匹配过程*/
		if (j == -1 || q.string[i] == q.string[j]){
    
           //(与首字符进行匹配 || 两个字符相等) 
		/*当与首字符匹配时,因为首字符的next[j]=-1,此时i++;j++; 主串后移一位,模式串也刚好从首字符开始进行匹配 */ 
			i++;
			j++;
			next [i] = j;
		} 
		else
			 j = next[j];		 
	} 
 } 

构造next数组,可以理解为模式串自己与自己比较,寻找最大真前缀,让主串与最大真前缀的下一位进行匹配。原理解释如下:

在这里插入图片描述

这里我们把模式串标记成两个,方便表述。我们拿T2去与T1匹配。我们发现在T2的0号位与T1的3号位匹配成功,因为T2与T1是同一个串,那么T1的0号位一定和T2的0号位一样。所以,我们可以利用这个原理,来计算每一个位置的最大真前缀。
再如:
在这里插入图片描述
当我们发现T2的0号位和1号位分别和T1的3、4号位匹配,那么请问T1的1号位是什么颜色?
答案很简单,是蓝色,因为本身就是一个串。

我们看到,每匹配成功一项,最长真前缀就+1
若当前最长真前缀为next[i],匹配成功后next[i+1]=next[i]+1

如果模式串如下图所示
在这里插入图片描述
发生不匹配,T2回溯, T1不动
在这里插入图片描述
回溯多少呢?我们看到T2需要从4号位回溯到1号位。这里next[4]=1,说明最长的真前缀长度是1, 我们刚好要比较最长真前缀后面的字符,想回溯到标号为1的位置。由于字符串的下标从0开始,也就相当于回溯到了next[4]的位置,然后重新比较j = next[j]

扫描二维码关注公众号,回复: 14025755 查看本文章

简单来说,当T2的j号位与T1的i号位匹配不成功时,T2需要回溯到j号的最大真前缀的下一个位置,让其与T1的i号位进行比较。匹配成功,则各自后移,匹配不成功,继续寻找最大真前缀,进行匹配。

下面再深入分析匹配成功的情况
在这里插入图片描述
如图,我们在进行T1的9号位与T2的3号位匹配时,实际在计算T1的10号位的next值。
next[10] = next[9]+1
因为next[9]的值既然比较过了,就已知是3,我们观察到,此时正是T2的3号位与之进行匹配
即:next[i+1] = next[i] + 1= j + 1;

再次简单来说,计算当T1的i+1号位 的不匹配时下次进行匹配的位置时,若 i号位与T2的j号位进行匹配成功,说明已经至少有j长度的真前缀了,这里只需进行+1操纵,使其变的更长。

我们再看一下匹配成功的代码部分:

if (j = = -1 || q.string[i] == q.string[j]){
    
           //(与首字符进行匹配 || 两个字符相等) 
			i++;
			j++;
			next [i] = j;
		} 

我们发现在代码中,再给next[i]赋值之前,i 和 j 已经完成了自加1,那就直接写出

next [i] = j;

4.为什么设置next[0]=-1?

当回溯到0号位,依然匹配不成功,我们把0号位是next[j]赋值给j,使得下一次进入if语句满足条件,主串和模式串分别后移,继续匹配。若按照寻找next[j]的原理,把next[0]=0,那么会出现一直匹配不成功,形成死循环。

5.字符串从1的位置进行存储,代码有影响吗?

答案肯定是没有。
在我们手动书写next[j]的时候要注意,此时的next[j]的值不仅仅是最长前缀的长度,还得+1
两种区别如下:
在这里插入图片描述
在发生不匹配进行回溯时,我们说因为串从0开始标记,所以回溯到最大真前缀的位置继续进行匹配。代码为:i = next[j]。当我们把串从1开始标记,i,j的起始位置都从下标开始进行存储,所以在计算机里,代码实现不变,只需在人工计算时注意就好。

猜你喜欢

转载自blog.csdn.net/qq_30336973/article/details/115485263