【字符串】KMP

Algorithm

Task

给定一个文本串 S 和一个模式串 T,求 TS 中出现的所有位置。

Limitations

要求时空复杂度均为线性。

Solution

回头重新学一遍看毛片 KMP 算法。

X 是一个字符串,则以下表述中,Xu 代表 X 的第 u 个字符,Xuv 代表 X 的从 u 起到 v 结束的字串。

首先定义一个字符串的公共前后缀为这个字符串的一个 border,最长公共前后缀称为最长 border。特别的,不认为字符串本身是自身的 border

性质:字符串 Sborderborder 一定是 Sborder,正确性显然。因此不断地跳最长 border 可以遍历字符串的所有 border

例如,对于字符串 abaab 来说,其唯一的 borderab

暴力匹配两个字符串,时间复杂度为 O(|S||T|),考虑优化这个算法。

假设当前匹配时 S 扫描到了第 i 位, T 扫描到了第 j 位,且 Si 向前 j 位组成的字符串与 T 的前 j 位相同,而 Si+1Tj+1,我们称为发生了失配。

考虑失配时,指针 i 不变,只有将指针 j 前移,才可能令下一位成功匹配。由于 i 不变,所以下一个可能发生匹配的字符串一定是 T1j 的某个前缀 T1k 满足

T1k=Sik+1i

其中由于 T1kT1j 的字串,一定有 k<j。由于 S1i 的后 j 位与 T 的前 j 位匹配,又有 k<j,因此 T1j 的后 k 位一定与 S1i 的后 k 位即 Sik+1i 匹配。得出

Tjk+1j=Sik+1i

上面两个式子等量代换得到

T1k=Tjk+1j

border 的定义,我们发现 T1k 一定是 T1jborder。根据 border 的性质,我们只需要不断的跳 T1j 的最长 border 即可找到一个最长的可以与 S1i 的后几位匹配的字串。因此问题转化为了如何求一个字符串 T 的所有前缀的最长 border

显然 border1=0。从第 2 位开始,我们发现问题等价于用 T(模式串) 的一个前缀去匹配 T1i (文本串)的一个后缀,求这个后缀最长是多少,而这个问题的解决方法与上面那个问题的方法 完 全 一 致,都是不断跳 border 即可。在 ij 成功匹配时,记录 borderi=j。而在这个问题中,由于 j 恒小于 i,正向扫描 i 时,所用到的 border 值都已经被计算出,因此可以得出正确的结果。

考虑时间复杂度:一个显然的事实是每次跳 border 模式串指针 j 都会至少减少 1,而当且仅当第 Si+1 与第 Tj+1 匹配时,j 才会自增,因此 j 仅增加了 O(|S|),因此 j 只可能减少 O(|S|) 次,所以跳 border 的总次数不超过 O(|S|),而扫描整个文本串需要 O(|S|) 的时间,因此总时间复杂度 O(|S|)

Example

P3375 【模板】KMP字符串匹配

Description

给定一个文本串 S 和一个模式串 T,求 TS 中出现的所有位置,同时要求输出 T 的每个前缀的 border 长度。

Limitations

字符串长度不超过 106

Solution

板板题

Code

#include <cstdio>
#include <cstring>

const int maxn = 1000006;

char S[maxn], T[maxn];
int nxt[maxn];

void KMP(char *A, char *B, int x, int y, const bool pt);

int main() {
  freopen("1.in", "r", stdin);
  scanf("%s\n%s", S + 1, T + 1);
  int x = strlen(S + 1), y = strlen(T + 1);
  KMP(T, T, y, y, false); KMP(S, T, x, y, true);
  for (int i = 1; i <= y; ++i) {
    qw(nxt[i], i == y ? '\n' : ' ', true);
  }
  return 0;
}

void KMP(char *A, char *B, int x, int y, const bool pt) {
  for (int j = 0, i = pt ? 1 : 2; i <= x; ++i) {
    while (j && (B[j+1] != A[i])) j = nxt[j];
    if (B[j+1] == A[i]) ++j;
    if (!pt) nxt[i] = j;
    if (j == y) {
      qw(i - y + 1, '\n', true);
    }
  }
}
posted @   一扶苏一  阅读(350)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2018-09-04 【数据结构】树链剖分
点击右上角即可分享
微信分享提示