KMP

KMP 主要用于求解以下问题:

求字符串 \(t\) 在字符串 \(s\) 中出现的所有位置。

如果存在 \(s[i \dots j]\)\(t\) 完全相同,则称 \(t\) 在位置 \(i\) 出现了。

\(s[l \dots r]\) 表示字符串 \(s\) 的第 \(l\) 个字符到第 \(r-1\) 个字符所组成的字符串,下标从 \(1\) 开始。用 \(\operatorname{len}(s)\) 表示字符串 \(s\) 的长度。

\(fail_i\) 表示 \(s2[1 \dots i+1]\) 的前 \(fail_i\) 个字符与 \(s2[1 \dots i+1]\) 的后 \(fail_i\) 个字符完全相同。特别的,\(fail_1=0\)。如 \(s2=\)1234123,那么 \(fail=\{0,0,0,0,1,2,3\}\)。这样在匹配到 \(s2\) 串的第 \(i\) 位的时候,如果匹配失败,就继续用 \(s2\) 的第 \(fail_{i-1}\) 位匹配。时间复杂度接近 \(\mathcal{O}(N+M)\)

那么如何计算 \(fail\) 数组呢?

如果已经处理到第 \(i\) 位,先让 \(j=fail_{i-1}\),表示 \(s2[1 \dots j+1]=s2[i-j+1 \dots i-1]\)

如果 \(s2_i \ne s2_{j+1}\),就让 \(j=fail_j\),因为这个子串的前 \(fail_j\) 个字符和后 \(fail_j\) 个字符完全相同。然后一直这样判断。直到 \(s2_i=s2_{j+1}\)\(j=0\) 就结束。接下来判断现在这一位是否匹配,也就是判断 \(fail_i=fail_{j+1}\)。如果是,就让 \(j\) 加一。

\(fail_i=j\),这一位就计算完了。

如字符串 abacabacd。初始时 \(j=0\)。步骤如下。

  • \(i=2\)

\(s_i \ne s_{j+1}\),所以 \(fail_2 \leftarrow j=0\)

  • \(i=3\)

\(s_i=s_{j+1}\),所以 \(j \leftarrow j+1=1,fail_3 \leftarrow j=1\)

  • \(i=4\)

\(j \leftarrow fail_j=0\)

\(s_i \ne s_{j+1}\),所以 \(fail_4 \leftarrow j=0\)

  • \(i=5\)

\(s_i=s_{j+1}\),所以 \(j \leftarrow j+1=1,fail_5 \leftarrow j=1\)

  • \(i=6\)

\(s_i=s_{j+1}\),所以 \(j \leftarrow j+1=2,fail_6 \leftarrow j=2\)

  • \(i=7\)

\(s_i=s_{j+1}\),所以 \(j \leftarrow j+1=3,fail_7 \leftarrow j=3\)

  • \(i=8\)

\(j \leftarrow fail_j=1\)

\(j \leftarrow fail_j=0\)

\(s_i \ne s_{j+1}\),所以 \(fail_8 \leftarrow j=0\)

此时 \(fail\) 数组就计算出来了。

接着要进行匹配。匹配和处理的代码很像,如果第 \(i\) 位匹配失败了,就继续用第 \(fail_i\) 位进行匹配。如果 \(j=\operatorname{len}(s2)\),就说明 \(s2\)\(s1\) 的第 \(i-j+1\) 位出现了。

这就是 KMP 算法。时间复杂度约为 \(\mathcal{O}(N+M)\)

例1 洛谷-P3375

本题为模板题。

代码

posted @ 2024-02-08 20:02  lrx139  阅读(4)  评论(0编辑  收藏  举报