数据结构——KMP之next执行过程解析

在复习到KMP的时候,发现KMP虽然是字符串匹配问题,但是核心不是模板串和目标串的匹配,而是求出模板串自己的每一位对应的前面公共前后缀的长度。关于求next的代码网上很多,书上也写有,但是运行的过程不太直观。以下是手推过程(关于next的思想我是菜鸡就不解释了,以下只推执行过程,通过过程来理解求next数组的思想吧):

运行截图(此例子选自天勤数据结构)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

让我们一步一步地进行理解:

初始化:next[0]和next[1]=0(第一个字符前哪来的公共前后缀的说法,肯定是0)

参数说明:
i是模板串的下标;j指的是在第i个位置之前的最长公共前后缀长度

模板串(为了数字和书上一致,字符串数组的下标也和书上一样从1开始算):

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0

正在比较的字符:
A 此时的i=1,j=0
j=0
i:2 j:1 next[2]:1

说明j = 0说明模板串在当前位置(1)号位置之前没有公共前后缀,当模板串的2(i+1=2)号位与目标串对应的字符不匹配时,可以拿模板串的1(j=j+1=0+1=1)号位置与目标串进行匹配,所以next[2]=j=1。毕竟你第一个位置都不匹配了肯定还是只能拿模板串第一个位置和目标串下一位匹配了

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1

正在比较的字符:
B A此时的i=2,j=1
不相等且j!=0
i:2 j:1 next[1]:0
j变为:0

说明j!=0说明当前位置(2)号位置之前存在公共前后缀,长度为1(j)。一旦(2)号位置与目标串对应字符不匹配,则让模板串的1号位顶上来进行匹配,如果使用1号位进行匹配失败,就得看1号位前的公共前后缀是多长(这一步分解就是把当前i=2,j=1换成原来的i=1,j=0来看待,因为要考虑到替换后还是不成功的情况,所以一定要保证模板串移动后当前匹配位置前方的元素都与目标串匹配)。查表发现,next[1]=0,说明如果1号位顶上来还是不匹配的话,从当前位置模板串和目标串之间第一个字符就不匹配了,模板串只能整体下移一位进行观察。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1

正在比较的字符:
B 此时的i=2,j=0
j=0
i:3 j:1 next[3]:1

说明j = 0说明模板串在当前位置(2)号位置之前没有公共前后缀,当3(i=i+1=3)号位与目标串对应的字符不匹配时,只能拿模板串的1(j=j+1=0+1=1)号位置与目标串进行匹配,所以next[3]=j=1。因为 i 没变 j 变了,下一步就是看在第3号位置前有没有最长公共前后缀

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1

正在比较的字符:
A A此时的i=3,j=1
字符相等
i:4 j:2 next[4]:2
说明字符相等说明从开头(1号位)到当前位置(3号位)存在最长公共前后缀;长度为1(j=1),当4(i=i+1=3+1=4)号位与目标串对应的字符不匹配时,可以拿模板串的2(j=j+1=1+1=2)号位置与目标串进行匹配(因为4号位的前面1个元素和模板串的第1个元素是一样的,就可以把1号位置顶到3号位置去比较,那么下一位4号位匹配不成功的话就可以直接把2号位的元素拿过来进行匹配了),所以next[4]=j=2。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2

正在比较的字符:
B B此时的i=4,j=2
字符相等
i:5 j:3 next[5]:3
说明字符相等说明从开头(1号位)到当前位置(4号位)存在最长公共前后缀;长度为2(j=2),当5(i=i+1=4+1=5)号位与目标串对应的字符不匹配时,可以拿模板串的3(j=j+1=2+1=3)号位置与目标串进行匹配(因为5号位的前面2个元素和模板串的第12个元素是一样的,就可以把12号位置顶到3~4号位置去比较,那么下一位5号位匹配不成功的话就可以直接把3号位的元素拿过来进行匹配了),所以next[5]=j=3。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3

正在比较的字符:
A A此时的i=5,j=3
字符相等
i:6 j:4 next[6]:4
说明字符相等说明从开头到当前位置(5)存在最长长度为3(j=3)的公共前后缀;当6(i=i+1=5+1=6)号位与目标串对应的字符不匹配时,可以拿模板串的4(j=j+1=3+1=4)号位置与目标串进行匹配(因为6号位的前面3个元素和模板串的第13个元素是一样的,就可以把13号位置顶到3~5号位置去比较,那么下一位6号位匹配不成功的话就可以直接把4号位的元素拿过来进行匹配了),所以next[6]=j=4。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4

正在比较的字符:
A B此时的i=6,j=4
不相等且j!=0
i:6 j:4 next[4]:2
j变为:2

说明:字符不等而且j!=0,说明模板串从开始到当前位置(6)号位不存在长度为4的公共前后缀。那就看长度为2时的最长公共前后缀是多少(这一步分解就是把当前i=6,j=4换成原来的i=4,j=2来看待)?查表发现,next[4]=2,说明如果4号位顶上来还是不匹配的话,可以拿2号位顶上来进行匹配(因为要考虑不匹配的情况,所以不能简单地拿4号顶上来就完事儿,思考如果4号不匹配会如何,不匹配的情况必须考虑进去,这个情况在上面已经发生过,所以要将上面的结果代到后面的情形进行处理,j的结果一定是模板串滑动后当前位置之前的 j位是与目标串匹配的情况)。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4

正在比较的字符:
A B此时的i=6,j=2
不相等且j!=0
i:6 j:2 next[2]:1
j变为:1
说明字符不等而且 j!=0,说明模板串从开始到当前位置(6)号位的不存在长度为2的公共前后缀。那就看长度为2时的最长公共前后缀是多少?查表发现,next[2]=1,(这一步分解就是把当前i=6,j=2换成原来的i=2,j=1来看待)。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4

正在比较的字符:
A A此时的i=6,j=1
字符相等
i:7 j:2 next[7]:2
说明字符相等,说明模板串从开始到当前位置(6)号位存在最长长度为1的公共前后缀,当7(i+1=7)号位与目标串对应的字符不匹配时,可以拿模板串的2(j=j+1=1+1=2)号位置与目标串进行匹配(因为7号位的前面1个元素和模板串的第1个元素是一样的,就可以把1号位置顶到6号位置去比较,那么下一位7号位如果匹配不成功就可以直接把2号位的元素拿过来进行匹配了),所以next[7]=j=2。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4 2

正在比较的字符:
A B此时的i=7,j=2
不相等且j!=0
i:7 j:2 next[2]:1
j变为:1

说明字符不相等且 j!=0,说明模板串从开始到当前位置(7)号位的不存在长度为2的公共前后缀,那就看长度为2时的最长公共前后缀是多少?查表发现,next[2]=1(这一步分解就是把当前i=7,j=2换成原来的i=2,j=1来看待)。说明如果2号位顶上来还是不匹配的话,可以拿1号位顶上来进行匹配),所以j变为1。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4 2

正在比较的字符:
A A此时的i=7,j=1
字符相等
i:8 j:2 next[8]:2
说明字符相等,说明模板串从开始到当前位置(7)号位存在长度为1的公共前后缀,当8(i+1=8)号位与目标串对应的字符不匹配时,可以拿模板串的2(j=j+1=1+1=2)号位置与目标串进行匹配,所以next[8]=j=2。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4 2 2

正在比较的字符:
B B此时的i=8,j=2
字符相等
i:9 j:3 next[9]:3
说明字符相等,说明模板串从开始到当前位置(8)号位存在长度为2的公共前后缀,当9(i+1=9)号位与目标串对应的字符不匹配时,可以拿模板串的3(j=j+1=2+1=3)号位置与目标串进行匹配,所以next[9]=j=3。

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4 2 2 3

正在比较的字符:
A A此时的i=9,j=3
字符相等
i:10 j:4 next[10]:4
说明字符相等,说明模板串从开始到当前位置(9)号位存在长度为3的公共前后缀,当10(i+1=10)号位与目标串对应的字符不匹配时,可以拿模板串的4(j=j+1=3+1=4)号位置与目标串进行匹配,所以next[10]=j=4

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4 2 2 3 4

正在比较的字符:
B B此时的i=10,j=4
字符相等
i:11 j:5 next[11]:5
说明字符相等,说明模板串从开始到当前位置(10)号位存在长度为4的公共前后缀,当11(i+1=11)号位与目标串对应的字符不匹配时,可以拿模板串的5(j=j+1=4+1=5)号位置与目标串进行匹配,所以next[11]=j=5

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4 2 2 3 4 5

正在比较的字符:
A A此时的i=11,j=5
字符相等
i:12 j:6 next[12]:6

说明字符相等,说明模板串从开始到当前位置(11)号位存在长度为5的公共前后缀,当12(i+1=11)号位与目标串对应的字符不匹配时,可以拿模板串的6(j=j+1=5+1=6)号位置与目标串进行匹配,所以next[12]=j=6

1 2 3 4 5 6 7 8 9 10 11 12
A B A B A A A B A B A A
0 1 1 2 3 4 2 2 3 4 5 6

有点长?换短一点的例子模板串

说明:**

1 2 3 4 5 6 7
A B C D A B D
0

正在比较的字符:
A 此时的i=1,j=0
j=0
i:2 j:1 next[2]:1

说明:* j = 0说明模板串在当前位置(1)号位置之前没有公共前后缀,当模板串的2(i+1=2)号位与目标串对应的字符不匹配时,可以拿模板串的1(j=j+1=0+1=1)号位置与目标串进行匹配,所以next[2]=j=1(毕竟你第一个位置都不匹配了肯定还是只能拿模板串第一个位置和目标串下一位匹配了)。*

1 2 3 4 5 6 7
A B C D A B D
0 1

正在比较的字符:
B A此时的i=2,j=1
不相等且j!=0
i:2 j:1 next[1]:0
j变为:0

说明字符不相等且 j!=0,说明模板串从开始到当前位置(2)号位的不存在长度为1的公共前后缀,那就看长度为1时的最长公共前后缀是多少?查表发现,next[1]=0。说明如果2号位顶上来还是不匹配的话,只能拿模板串第一个位置和目标串下一位匹配了),所以j变为1。

1 2 3 4 5 6 7
A B C D A B D
0 1

正在比较的字符:
B 此时的i=2,j=0
j=0
i:3 j:1 next[3]:1
说明j = 0说明模板串在当前位置(2)号位置之前没有公共前后缀,当3(i=i+1=2+1=3)号位与目标串对应的字符不匹配时,只能拿模板串的1(j=j+1=0+1=1)号位置与目标串进行匹配,所以next[3]=j=1。因为 i 没变 而 j 变了,下一步就是看在第3号位置前有没有最长公共前后缀

1 2 3 4 5 6 7
A B C D A B D
0 1 1

正在比较的字符:
C A此时的i=3,j=1
不相等且j!=0
i:3 j:1 next[1]:0
j变为:0
说明:字符不等而且j!=0,说明模板串从开始到当前位置(3)号位不存在长度为1的公共前后缀。那就看长度为1时的最长公共前后缀是多少?查表发现,next[1]=0,说明只能把模板串挪上来从第1个位置开始和进行匹配了。

1 2 3 4 5 6 7
A B C D A B D
0 1 1

正在比较的字符:
C 此时的i=3,j=0
j=0
i:4 j:1 next[4]:1

说明j = 0说明模板串在当前位置(3)号位置之前没有公共前后缀,当4(i=i+1=4)号位与目标串对应的字符不匹配时,只能拿模板串的1(j=j+1=0+1=1)号位置与目标串进行匹配,所以next[4]=j=1。因为 i 没变 而 j 变了,下一步就是看在第4号位置前有没有最长公共前后缀

1 2 3 4 5 6 7
A B C D A B D
0 1 1 1

正在比较的字符:
D A此时的i=4,j=1
不相等且j!=0
i:4 j:1 next[1]:0
j变为:0
说明:字符不等而且j!=0,说明模板串从开始到当前位置(4)号位不存在长度为1的公共前后缀。那就看长度为1时的最长公共前后缀是多少?查表发现,next[1]=0,只能把模板串挪上来从第1个位置开始和进行匹配了。

1 2 3 4 5 6 7
A B C D A B D
0 1 1 1

正在比较的字符:
D 此时的i=4,j=0
j=0
i:5 j:1 next[5]:1
说明j = 0说明模板串在当前位置(4)号位置之前没有公共前后缀,当5(i=i+1=5)号位与目标串对应的字符不匹配时,只能拿模板串的1(j=j+1=0+1=1)号位置与目标串进行匹配,所以next[5]=j=1。下一步就是看在第5号位置前有没有最长公共前后缀

1 2 3 4 5 6 7
A B C D A B D
0 1 1 1 1

正在比较的字符:
A A此时的i=5,j=1
字符相等
i:6 j:2 next[6]:2
说明字符相等,说明模板串从开始到当前位置(5)号位存在长度为1的公共前后缀,当6(i+1=6)号位与目标串对应的字符不匹配时,可以拿模板串的2(j=j+1=1+1=2)号位置与目标串进行匹配,所以next[6]=j=2

1 2 3 4 5 6 7
A B C D A B D
0 1 1 1 1 2

正在比较的字符:
B B此时的i=6,j=2
字符相等
i:7 j:3 next[7]:3
说明字符相等,说明模板串从开始到当前位置(6)号位存在长度为2的公共前后缀,当7(i+1=7)号位与目标串对应的字符不匹配时,可以拿模板串的3(j=j+1=2+1=3)号位置与目标串进行匹配,所以next[7]=j=3

1 2 3 4 5 6 7
A B C D A B D
0 1 1 1 1 2 3

好了以上就是全部的手推过程。

本次测试代码

#include<iostream>
#include<stdio.h>
using namespace std;

typedef struct Str{
    
    
	char *ch;
	int length;	
}S; 

void showStr(S s){
    
    
	for(int i=0;i<s.length;i++){
    
    
		cout<<s.ch[i+1]<<" ";
	}
	cout<<endl;
}

int initial(Str &s,int length){
    
    
	if(s.ch){
    
    
		free(s.ch);//如果存在有字符串,先清空 
	}
	s.ch = (char*)malloc(sizeof (char)*(length+1));//多加一个位置给'\0' 
	if(s.ch==NULL){
    
    
		cout<<"初始化失败\n";
		return 0;
	}
	s.length = length;
	return 1;
}

int insertStr(S &s,char *ch){
    
    
	int length = 0;//记录字符串数组ch的长度 
	char *p = ch;//声明一个指针p指向字符串数组ch 
	while(*p){
    
    
		length++;//注意,这个length不会算入最后的'\0'进去 
		p++;
	}
	if(!length){
    
    //如果ch为空 
		s.ch == NULL;
		s.length = 0;
		return 0;
	}
	if(initial(s,length)){
    
    //初始化串 
		p = ch;//指针p重新指向字符串ch的起点
		for(int i = 0;i<=length;i++){
    
    //用=号是为了将末尾的'\0'复制出来 
			s.ch[i+1] = *p;//+1是想让字符从1开始存储。
			p++;
		}
		return 1;
	}
	return 0;
}

void getNext(S &s,int next[]){
    
    
	int i = 1;//字符串从下标1开始存储 
	int j = 0;//j指的是在第i个位置之前的最长公共前后缀长度 
	//如果j = 0则说明没有公共前后缀,只能简单粗暴地将模板串往下移一个位置了 
	next[1] = next[0] = 0;
	while(i<s.length){
    
    
		cout<<"正在比较的字符:\n"<<s.ch[i]<<" "<<s.ch[j]<<"此时的i="<<i<<",j="<<j<<endl;
	
		if(j==0||s.ch[i]==s.ch[j]){
    
    
			if(j==0){
    
    
				cout<<"j=0\n";
			}
			if(s.ch[i]==s.ch[j]){
    
    
				cout<<"字符相等\n";
			}
			i++;
			j++;
			next[i] = j;
			cout<<"i:"<<i<<" "<<"j:"<<j<<" "<<"next["<<i<<"]:"<<next[i]<<endl; 
		}else{
    
    
			cout<<"不相等且j!=0\n";
			cout<<"i:"<<i<<" "<<"j:"<<j<<" "<<"next["<<j<<"]:"<<next[j]<<"\n";  
			j = next[j];//
			cout<<"j变为:"<<j<<endl; 
		}
		cout<<endl;
	}
	
	for(int i = 1;i<=s.length;i++)
		cout<<i<<" ";
	cout<<endl; 
	
	for(int i = 1;i<=s.length;i++)
		cout<<next[i]<<" ";
		cout<<endl; 
	showStr(s);
}
int main(){
    
    
	Str s1;
	char ch1[100],ch[100];
	scanf("%s",&ch1);
	if(insertStr(s1,ch1)){
    
    
		for(int i = 1;i<=s1.length;i++)
			cout<<i<<" ";
		cout<<endl; 
		showStr(s1);
	}
	int next[100];
	getNext(s1,next);
	
} 

构造顺序串的过程可以看这里的解释:数据结构——顺序串(定义初始化、赋值、遍历、两串比较)

猜你喜欢

转载自blog.csdn.net/qq_51231048/article/details/125828404