KMP中几种Next数组的求法

今天复习到 KMP 算法时候发现考研教材上的 Next 数组和我之前学的不一样,所以特地拎出来比对一下,顺便总结。
因为我之前掌握的 KMP 是李煜东的《算法竞赛进阶指南》上介绍的,而考研教材似乎更多用的是严蔚敏的《数据结构(C语言版)》,所以就对这两种实现方式做整理。

竞赛版

设 P[i , j] 为模式串 P 从 i 到 j 位置上元素构成的子串,t 为当前子串中真前缀与真后缀的最长匹配长度。
假设字符串从下标 1 开始存放,即 P[1 , n] 为模式串。

nex[i] 表示“P 中以 i 结尾的真后缀”与“P 的真前缀”能够匹配的最长长度,即:nex[i] = max{ t },其中 t < i 并且 P[i-t+1, i] = P[1 , t] 。

亦即,在 P[ 1 , j ] 中长度为 t 的真前缀,应与长度为 t 的真后缀完全匹配,故 t 必来自集合: Next(P, j) = { 0 <= t < j | P[1 , t] = P[j - t + 1 , j] }

nex 数组的求法

  1. 初始化 nex[1] = t = 0,假设nex[1, i-1] 已求出,下面求nex[i]。
  2. 不断尝试拓展匹配长度 t,如果拓展失败(下一个字符不相等),令 t 变为nex[t],直至 t 为 0(应该从头开始匹配)。
  3. 如果能够拓展成功,匹配长度 t 就增加1。nex[i] 的值就是 t 。

代码块

void getNex(const char *s){
	/*更新模式串s的nex数组*/
	int len = strlen(s);
	memset(nex,0,sizeof nex);
	for(int i = 2,j = 0;i < len;i++){
		while(j > 0 && s[i] != s[j+1]) j = nex[j];
		if(s[i] == s[j+1]) j++;
		nex[i] = j;
	}
}

需要注意的是,该代码无需优化,在算法竞赛中该效率已足够优秀,为O(M+N)。

举例:

PABABABAA
Next00123451

可以使用任意字符串,并调用上述代码求对应的 Next 数组。

严版

设 P[i , j) 为模式串 P 从 i 到 j-1 位置上元素构成的子串,t 为当前子串中真前缀与真后缀的最长匹配长度。
假设字符串从下标 1 开始存放,即 P[1 , n] 为模式串。

nex[i] 表示“P 中以 i 结尾的真后缀”与“P 的真前缀”能够匹配的最长长度再加 1,即:nex[i] = max{ t } +1,其中 t < i 并且 P[i-t, i) = P[1 , t] 。特别的,nex[1] = 0,因为 P[1,1) 是个空串。

亦即,在 P[ 1 , j ] 中长度为 t 的真前缀,应与长度为 t 的真后缀完全匹配,故 t 必来自集合: Next(P, j) = { 0 <= t < j | P[1 , t] = P[j - t , j) }

nex 数组的求法

  1. 初始化 nex[1] = t = 0,假设nex[1, i-1] 已求出,下面求nex[i]。
  2. 不断尝试拓展匹配长度 t,如果拓展失败(下一个字符不相等),令 t 变为nex[t],直至 t 为 0(应该从头开始匹配)。
  3. 如果能够拓展成功,匹配长度 t 就增加1。nex[i] 的值就是 t + 1。

代码块

void getNex(const char *s){
	/*更新模式串s的nex数组*/
	int len = strlen(s);
	memset(nex,0,sizeof nex);
	for(int i = 2,j = 0;i < len;i++){
		while(j > 0 && s[i-1] != s[j]) j = nex[j];
		j++; nex[i] = j;
	}
}

举例与练习
下面通过一个例子表述如何手工写严版 next 表:

例如模式串为:AAAABAA
那么列表就有:

			j = 1 为空串,此时定义nex[j] = 0;
A 			j = 2 有1个字符,此时真前缀与真后缀最长匹配长度为0,故nex[j] = 0+1;
AA 			j = 3 有2个字符,此时真前缀与真后缀最长匹配长度为1,故nex[j] = 1+1;
AAA 		j = 4 有3个字符,此时真前缀与真后缀最长匹配长度为2,故nex[j] = 2+1;
AAAA 		j = 5 有4个字符,此时真前缀与真后缀最长匹配长度为3,故nex[j] = 3+1;
AAAAB 		j = 6 有5个字符,此时真前缀与真后缀最长匹配长度为0,故nex[j] = 0+1;
AAAABA 		j = 7 有6个字符,此时真前缀与真后缀最长匹配长度为1,故nex[j] = 1+1;

所以如果理解了真前缀与真后缀,写next表就很简单。

next表的优化

所谓的优化就是为了避免移动后仍然失配的情况,因为每次 T[i] 和 P[j] 失配后,我们是令 j = nex[j],然后再继续比较 T[i] 和 P[j] 。因此一旦 T[i] 和 P[j] 失配时我们是一定知道 T[i] 不等于 P[j] 的, 此时如果 P[nex[j] ] = P[j] ,那显而易见还是会失配,于是我们就要重复 j = nex[j] 直至匹配成功或算法结束,所以我们就可以在计算 next 表的时候顺便判断一下转移前后 P 的字符变不变。

具体做法是:

void getNex(const char *s){
	/*更新模式串s的nex数组*/
	int len = strlen(s);
	memset(nex,0,sizeof nex);
	for(int i = 2,j = 0;i < len;i++){
		while(j > 0 && s[i-1] != s[j]) j = nex[j];
		j++; 
		if(s[j] != s[i]) nex[i] = j;
		else nex[i] = nex[j];
	}
}

求优化后的 next 表,也叫 nextval 表,操作只需要在求普通的 next 表上加一步,即在求出真前缀与真后缀的最长匹配长度 t 后,判断 P[t+1] 是否等于 P[i] ,如果相等就令 nextval[i] = nextval[t+1] ,否则就 nextval = t + 1。

例如:求AAAABAA 的 nextval(0 0 0 0 4 0 0 )

P:AAAABAA
		j = 1 为空串,此时定义nex[j] = 0;
A 		j = 2 有1个字符,t = 0,且P[t+1] == P[j],故nex[j] = nex[1] = 0;
AA 		j = 3 有2个字符,t = 1,且P[t+1] == P[j],故nex[j] = nex[2] = 0;
AAA 	j = 4 有3个字符,t = 2,且P[t+1] == P[j],故nex[j] = nex[3] = 0;
AAAA 	j = 5 有4个字符,t = 3,且P[t+1] != P[j],故nex[j] = 3+1;
AAAAB 	j = 6 有5个字符,t = 0,且P[t+1] == P[j],故nex[j] = nex[1] = 0;
AAAABA 	j = 7 有6个字符,t = 1,且P[t+1] != P[j],故nex[j] = nex[2] = 0;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷亭1213

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值