KMP算法是基本的字符串匹配算法,但是代码实现上有一些细节容易错。这篇随笔将认真总结一下。

KMP算法的核心是:

The KMP algorithm searches for occurrences of a "word" W within a main "text string" S by employing the observation that when a mismatch occurs, the word itself embodies sufficient information to determine where the next match could begin, thus bypassing re-examination of previously matched characters. (form Wikipedia)

首先定义几个概念

对于长为$L$的字符串$s[0..L-1]$, 我们定义$s$的一个前缀 (prefix) 为字符串$s[0..i], 0\le i<L$, 记作$s_i$; $s$的一个正规前缀 (proper prefix) 为$s[0..i], 0\le i<L-1$; 另外空串是任意串 (包括空串) 的正规前缀. 若$s$的某个正规前缀 $s[0..i] (i<L-1)$ 恰是$s$的后缀,则将此前缀称作$s$的一个关键前缀 (critical prefix)

另外我们定义: 空串是任意串 (包括空串) 的关键前缀。

对于模式串$w$, 预处理一个长为$|w|$的数组$next[0..|w|-1]$,$next[i]$表示$w$的前缀$w_i$的最长关键前缀的长度。

借助$next[]$数组,可以在$O(|s|)$时间内完成匹配。

具体实现以及复杂度分析略过,留下K. M. P. 三人论文的链接

Knuth, Donald; Morris, James H.; Pratt, Vaughan (1977). "Fast pattern matching in strings"SIAM Journal on Computing 6 (2): 323–350.doi:10.1137/0206024.

下面详细介绍一下next数组的求法. 显然我们有

\[next[i]=\begin{cases} 0, \text{if $i=0$; } \\ 1+\max\{i \mid next[i], &\text{if $s[next[i-1]]=s[i]$;} \\ \end{cases}\]


题目链接:hihocoder 1015

#include <bits/stdc++.h>
using namespace std;
const int N(1e4+), M(1e6+);
char s[N], t[M];
int nt[N];
int main(){
int n;
scanf("%d", &n);
for(int ls, k, ans;n--;){
scanf("%s%s", s, t);
k=nt[]=;
for(int i=ls=; s[i]; i++, ls++){
for(;k&&s[k]!=s[i];) k=nt[k];
nt[i]=s[i]==s[k]?++k:k;
}
ans=k=;
for(int i=; t[i]; i++){
//k:t[0..i-1]的匹配长度
for(;k&&s[k]!=t[i];) k=nt[k-]; //error-prone
if(t[i]==s[k]){
k++;
if(k==ls) ans++;
}
}
printf("%d\n", ans);
}
}

代码中注释的两处是容易写错的地方,典型错误是

for(;k&&s[k]!=s[i];) k=nt[k];
for(;k&&s[k]!=t[i];) k=nt[k]; 

这个错误坑在:往往可过样例,提交后不会WA而是会TLE。


还可以将next[i]定义成前缀w[0..i]的最长关键前缀的长度减一,这时可将next[i]的含义表述为前缀w[0..i]的最长关键前缀的结束位置。

代码只消稍作变动

#include<bits/stdc++.h>
using namespace std;
const int MAX_N=1e6+;
char s[MAX_N], t[MAX_N];
int nt[MAX_N];
void get_next(char *s){
nt[]=-;
int k=-;
for(int i=; s[i]; i++){
while(k!=-&&s[k+]!=s[i]) k=nt[k];
if(s[k+]==s[i]) k++;
nt[i]=k;
}
} int ans;
void match(char *s, char *t){
int ls=strlen(s), k=-;
for(int i=; t[i]; i++){
while(k!=-&&s[k+]!=t[i]) k=nt[k];
if(s[k+]==t[i]) k++;
if(k==ls-) ans++;
}
}
int main(){
int N;
scanf("%d", &N);
while(N--){
scanf("%s%s", s, t);
get_next(s);
ans=;
match(s, t);
printf("%d\n", ans);
}
return ;
}

最新文章

  1. 添加 Android Framework 到 Adt
  2. SharePoint API如何处理时区问题
  3. uitableview的空白处不能响应 touchesbegan 事件
  4. kvc/kvo复习
  5. 黑马程序员——Java高级应用(一)
  6. (原创)Python 自动化测试框架详解
  7. HTML+CSS画一朵向日葵
  8. VMware 安装 Mac OS 注意事项
  9. Tips_信息列表(手风琴)效果的多种实现方法
  10. 论文笔记:Decoders Matter for Semantic Segmentation: Data-Dependent Decoding Enables Flexible Feature Aggregation
  11. fatal error: No such file or directory
  12. C++ 文件大小格式化
  13. 关于 error C2001: 常量中有换行符
  14. angularJS中$apply()方法详解
  15. 软工网络15团队作业8——Beta阶段敏捷冲刺(用户使用调查报告)
  16. 开源中国上抓取的content-type
  17. CentOS 7如何开放其它的端口,比如8080
  18. MUI框架-13-使用百度地图 API(图文教程)
  19. spring cloud gateway 之限流篇
  20. Arduino上“Collect2.exe: error: ld returned 5 exit status”错误的解决方法

热门文章

  1. [Android Studio] Android studio 多渠道打包(超简洁版)
  2. 6月27日 OGDF不同的布局算法
  3. 通过imeMode禁用键盘只能输入数字
  4. 去他的效应(what-the-hell effect)与自我放纵
  5. 10个鲜为人知的WordPress函数
  6. TCP建立连接、断开连接以及正常报文的报头和报位的大小
  7. [CareerCup] 8.9 An In-memory File System 内存文件系统
  8. LeetCode:Jump Game I II
  9. Opencv step by step - 图像载入
  10. ANSI,UTF8等等这些格式