专栏文章

扩展kmp

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mipwczbk
此快照首次捕获于
2025/12/03 19:01
3 个月前
此快照最后确认于
2025/12/03 19:01
3 个月前
查看原文

扩展kmp(Z Algorithm)

引入

移除字符串的一部分,加上一部分,问多少步能变回原样(移除几个加上几个)

定义

是一种字符串算法。
核心:z数组
可以在O(n + m)的时间复杂度内,在给定的文本串T和一个模式串P中,找到所有匹配的位置。

Z函数

核心是z(i)
定义为:字符串s与s[i, ....... , n - 1]的最长公共前缀(LCP)
Z(i)是满足s[0, ....., x - 1]与s[i, ....., i + x - 1]

令Z(0) = 0

例子:

Z-box

这个过程就像啃老,老人死了就啃不了。
---------林老师

构建Z数组

通过维护一个窗口 ZboxZ-box ,利用已知信息高效计算Z函数
ZboxZ-box 的左右边界为 llrr ,表示当前已知的匹配区间
对于每个位置 ii
  • ii 在已知的匹配区间内,即 iri \le r ,则可以利用Z值的对称性来初始化 ZiZ_i
  • 否则,从位置 ii 开始扩展,计算最长的公共前缀长度

快速求Z函数

  • z(i)0 z(i) \ne 0,可以定义区间[i, i + z[i] - 1]为一个Z-Box, 显然,Z-box 对应的子串一定是整个字符串的前缀
  • 从左往右枚举下标i,同时维护l和r,用[l, r]表示满足 lil \le i且r最大的Z-Box。
  • 综上,有3种情况,要分别考虑。

情况1 i>ri > r

  • 说明已经完全超过上一个Z-Box,任何一个新的Z-Box的r都会更大。
  • 直接暴力比较,暴力计算出Z[i]
  • 计算完成后,若Z(i) 0 \ne 0 更新l和r。
当你做不了富二代,啃不了老,你就努力做个富一代,让别人啃你。 -------林老师

代码

CPP
if(i > r){
	while(i + z[i] < n && s[z[i]] == s[i + z[i]])
		z[i]++;
	l = i;
	r = i + z[i] - 1;
}

情况2 ir i \le rz[il]<ri+1 z[i - l] < r - i + 1

  • 已知s[0,....,r1]s[l,.....,r]s[0, ...., r - 1]与s[l, ....., r]相等,故s[il,.....,rl]s[i,......,r]s[i - l, ....., r - l]与s[i, ......, r]也相等。
  • 所以,如果 Z[i1]<ri+1Z[i - 1] < r - i + 1,说明从i开始匹配和从i - l开始匹配是一样的。
  • 所以这个匹配一定会在离开ZBoxZ-Box前停止
  • Z[i]=Z[il]Z[i] = Z[i - l]
CPP
else if(z[i - l] < r - i + 1)
	z[i] = z[i - l];

情况3 iri \le rz[il]ri+1 z [i - l] \ge r - i + 1

  • 说明整个ZBoxZ-Box剩余部分都可以匹配,但后面的情况不确定,所以不能直接认定Z[i]=Z[i1]Z[i] = Z[i - 1]
  • 但知道Z[i]Z[i]至少有 ri+1r - i + 1, 后面进行暴力扩展ZBoxZ-Box尽量延长。
CPP
else{
		z[i] = r - i + 1;
		while(i + z[n] < n && s[z[i] == s[i + z[i]]])
			z[i] ++;
		l = i;
		r = i + z[i] - 1;
	}
其实
情况1和3可以合并
CPP
for(int i = 1, l = 0, r = 0; i < n; i ++){
	if(z[i - l] < r - i + 1)
		z[i] = z[i - l];
	else{
		z[i] = max(r - i + 1, 0);
		while(i + z[i] < n && s[z[i] == s[i + z[i]]])
			z[i] ++;
		l = i;
		r = i + z[i] - 1;
	}
}
复杂度是O(n),因为内层循环每执行一次,都会使r加1,执行次数不会超过n次。

评论

0 条评论,欢迎与作者交流。

正在加载评论...