从KMP到exKMP

KMP(Knuth-Morris-Pratt)

用途:

用于一个文本串S内查找一个模式串P 的出现位置,以及求一个字符串的最小循环元长度和最大循环次数。

思路:

\(kmp\)是对原始的在文本串S内查找一个模式串P的出现位置的一种优化。

原始做法

\(s\)的每一位都与\(p\)的第一位开始匹配。

(匹配到\(s\)的第\(i\)位和\(p\)的第\(j\)位。\(i\),\(j\)从0开始)

if(s[i]==p[j])i++,j++;//匹配成功,同时加1
else i=i-j+1,j=0;//j变成0,匹配的起始点向右移一位

\(kmp\)做法

匹配不成功,\(i\)不变,\(j=nxt[j]\)

\(nxt[j]\)表示\(j\)的最长前后缀,eg. \(abacaba\)\(aba\)是最长前后缀,\(a\)也是它的前后缀但不是最长的;

这样匹配,合理的运用了前面匹配的信息,只有前后缀才有可能成为答案,理由很显然,这样可以避免一些无用的枚举。

直接求\(nxt\)t的复杂度是\(n^2\),所以考虑优化。

i的前后缀一定是\(i-1\)的前后缀\(+1\),因为将\(i\)掉,剩下的一定满足前后缀。

所以只要不停跳\(i-1\)的前后缀,可以快速求\(nxt\).

void gnxt()
{
	int j=0;
	int len=strlen(p+1);
	for(int i=2;i<=len;i++)
		while(j&&p[i]!=p[j+1]) j=nxt[j];//找一个前后缀满足下一位相同
		if(p[i]==p[j+1])j++;//符合条件j匹配下一位
		nxt[i]=j;//记录
	}
	return ;
}

模式串与文本串匹配与求\(nxt\) 的过程相似,

	int s1=strlen(s+1);
	int s2=strlen(p+1);
	int i=0,j=0;
	for(int i=1;i<=s1;i++)
	{
		while(j&&s[i]!=p[j+1]) j=nxt[j];
		if(s[i]==p[j+1])j++;
		if(j==s2)printf("%d\n",i-j+1),j=nxt[j];
	}
	return ;

EXKMP

用途:

能在 \(O(|a|+|b|)\) 的时间内处理出文本串 \(a\)的所有后缀和模式串 \(b\) 的最长公共前缀。

流程(图片来自洛谷):

\(nxt[i]\)\(nxt[i]\)表示模式串\(b\)与模式串\(b\)\(b[i]\) 开头的后缀的最长公共前缀的长度)如下图

图片 0

假设求\(nxt[x]\)时所有\(nxt[j](0\le j<x)\) 都已经求出来了。

定义\(p\)\(max(i+nxt[i]-1)\),\(k\)是这个最大的\(i\).可知蓝色段相同

图片 1

加上当前点\(x\) ,可知绿色段相同

图片 2

因为绿色段相同,\(nxt[x-k]\) 对计算 \(nxt[x]\) 有帮助。

如果\(x-k+nxt[x-k]-1<nxt[k]-1\) 那么 \(nxt[x]=nxt[x-k]\) (显然灰色部分相同且\(l-1\)\(x-k\)的蓝色部分与剩下的绿色不同,不然\(nxt[x-k]\) 应该包括它们)

图片 3

如果比它大,显然 \(nxt[x]>=(nxt[k]-1-(x-k))\) 整个绿色都是公共前缀,\(p\) 的后面不一定相同所以,\(p\)\(l-1\)一个一个暴力比较计算\(nxt[x]\)

图片 4

正确性显然,时间复杂度参考\(manacher\) 算法。

void qnxt(char *c)
{
	int len = strlen(c);
	int p = 0, k = 1, l; //我们会在后面先逐位比较出 nxt[1] 的值,这里先设 k 为 1
	//如果 k = 0,p 就会锁定在 |c| 不会被更改,无法达成算法优化的效果啦
	nxt[0] = len; //以 c[0] 开始的后缀就是 c 本身,最长公共前缀自然为 |c|
	while(p + 1 < len && c[p] == c[p + 1]) p++;
	nxt[1] = p; //先逐位比较出 nxt[1] 的值
	for(int i = 2; i < len; i++)
	{
		p = k + nxt[k] - 1; //定义
		l = nxt[i - k]; //定义
		if(i + l <= p) nxt[i] = l; //如果灰方框小于初始的绿方框,直接确定 nxt[i] 的值
		else
		{
			int j = max(0, p - i + 1);
			while(i + j < len && c[i + j] == c[j]) j++; //否则进行逐位比较
			nxt[i] = j;
			k = i; //此时的 x + nxt[x] - 1 一定刷新了最大值,于是我们要重新赋值 k
		}
	}
}
  • \(ext[i]\)

我们用 \(ext[i]\) 表示“模式串 \(b\)” 与 “文本串 \(a\)\(a[i]\) 开头的后缀” 的 “最长公共前缀” 的长度。

与求\(nxt[i]\) 的方法相同,\(nxt[i]\)是自己与自己匹配而已

void exkmp(char *a, char *b)
{
	int la = strlen(a), lb = strlen(b);
	int p = 0, k = 0, l;//ext[0]!=|c|,所以预处理0,从一开始递推
	while(p < la && p < lb && a[p] == b[p]) p++; //先算出初值用于递推
	ext[0] = p;
	for(int i = 1; i < la; i++) //下面都是一样的逻辑啦
	{
		p = k + ext[k] - 1;
		l = nxt[i - k];
		if(i + l <= p) ext[i] = l;
		else
		{
			int j = max(0, p - i + 1);
			while(i + j < la && j < lb && a[i + j] == b[j]) j++;
			ext[i] = j;
			k = i;
		}
	}
}
posted @ 2024-08-13 20:08  storms11  阅读(13)  评论(0编辑  收藏  举报