KMP 学习笔记

把之前留存在 dp 学习记录里的扒下来了,也方便寻找。

发现 KMP 重要的实际不在 KMP 本体,而是前缀函数的处理,知道怎么处理前缀函数就知道怎么写 KMP 了。

前缀函数在 OIWIKI 上有详解,因为个人看第二个优化感觉有点糊,所以重点写一下第二个优化。

令前缀函数为 \(\pi_i\)

优化一:

注意到对于所有的 \(i\)\(\pi_i\) 最大也只等于 \(\pi_{i-1}+1\)

所以可以限定上界,优化枚举次数。

优化二:

我们可以发现,实际上有效的转移只会是 \(\pi_{i-1}\) 的不断迭代,即类似于这种形式 \(\pi(\pi(\pi(i-1)-1)-1)\),直到迭代到零。

采用反证法。

我们有字符串 \(s\)

\(s_0,s_1,s_2,s_3,......,s_{i-3},s_{i-2},s_{i-1},s_{i},s_{i+1}\)

\(s_{i,j}\) 表示从 \(s\) 中的第 \(i\) 个字符到第 \(j\) 个字符。

以上字符串满足 \(\pi_i=4,\pi_3=2\)

\(s_{0,3}=s_{i-3,i},s_{0,1}=s_{2,3}\)

由上可以推得:

\(s_{0,1}=s_{i-1,i}\)

假设 \(\pi_{i+1}=4\)

$\Downarrow $

\(s_{0,3}=s_{i-2,i+1}\)

$\Downarrow $

\(s_{0,2}=s_{i-2,i}\)

$\Downarrow $

\(s_{1,3}=s_{i-2,i}\)

\(\pi_{3}=3\),与题目条件不符,故假设不成立。

所以 \(\pi_{i+1}\ne 4\)

因为更短的转移,会更劣,所以不考虑。(都更短了还能不更劣吗)

有点粗糙,但是应该能看懂,以后每层迭代都可以套用以上证明。

至于字符串匹配,考虑使用一个分隔符(既不在字符串中也不在文本中)将字符串与文本连接起来,然后直接求前缀函数,看哪一位的前缀函数等与字符串长度就代表匹配到了。这部分还是挺简单易懂的,难搞的还是前缀函数。

实现代码如下:

vector<int> pre_func(string s)
{
	int n = s.size(), x;
	vector<int> pre(n);
	for (int i = 1; i < n; i++)
	{
		x = pre[i - 1];
		while (x && s[i] != s[x])
			x = pre[x - 1];
		if (s[i] == s[x])
			++x;
		pre[i] = x;
	}
	return pre;
}
void find_func(string text, string s)
{
	string s0 = s + '#' + text;
	vector<int> pre = pre_func(s0);
	int n = s.size();
	for (int i = n + 1; i < s0.size(); i++)
		if (pre[i] == n)
			cout << i - (n << 1) + 1 << "\n";
	for (int i = 0; i < n; i++)
		cout << pre[i] << " ";
}

可以根据需求改变文本和字符串的类型。

posted @ 2024-08-08 19:42  -wryyy-  阅读(9)  评论(0编辑  收藏  举报