KMP字符串匹配算法
KMP算法(Knuth-Morris-Pratt Algorithm)是一种高效的字符串匹配算法,用于在一个较长的文本串中查找特定的子串。KMP算法的时间复杂度为O(n+m),其中n为文本串的长度,m为子串的长度。相比于朴素的字符串匹配算法,KMP算法更加高效。
原理
KMP算法的核心思想是在匹配过程中,利用已经匹配过的字符信息,避免在文本串中进行不必要的回溯。KMP算法通过预处理子串,得到一个称为“部分匹配表”的数组(也称为“失败函数”或“next数组”),用于记录子串中的前缀和后缀信息。
部分匹配表(next数组)
部分匹配表是一个长度为m的数组,用于存储子串的每个字符对应的最长相同前后缀长度。例如,给定子串ABCDABD
,其部分匹配表如下:
字符 A B C D A B D
next 0 0 0 0 1 2 0
构建部分匹配表的方法是遍历子串,利用动态规划的思想计算每个字符对应的最长相同前后缀长度。
KMP算法步骤
- 构建子串的部分匹配表。
- 在文本串中进行匹配。遍历文本串,从左到右与子串进行匹配。
- 当文本串和子串的字符相等时,将文本串和子串的指针向右移动。
- 当文本串和子串的字符不相等时,将子串的指针向左移动至next数组对应的位置,而文本串的指针不回溯。
- 当子串的指针移动至最后一个字符,说明找到了匹配的子串。记录匹配位置,并将子串的指针重置至初始位置。
例题
【模板】KMP字符串匹配
题目描述
给出两个字符串
s
1
s_1
s1 和
s
2
s_2
s2,若
s
1
s_1
s1 的区间
[
l
,
r
]
[l, r]
[l,r] 子串与
s
2
s_2
s2 完全相同,则称
s
2
s_2
s2 在
s
1
s_1
s1 中出现了,其出现位置为
l
l
l。
现在请你求出
s
2
s_2
s2 在
s
1
s_1
s1 中所有出现的位置。
定义一个字符串
s
s
s 的 border 为
s
s
s 的一个非
s
s
s 本身的子串
t
t
t,满足
t
t
t 既是
s
s
s 的前缀,又是
s
s
s 的后缀。
对于
s
2
s_2
s2,你还需要求出对于其每个前缀
s
′
s'
s′ 的最长 border
t
′
t'
t′ 的长度。
输入格式
第一行为一个字符串,即为
s
1
s_1
s1。
第二行为一个字符串,即为
s
2
s_2
s2。
输出格式
首先输出若干行,每行一个整数,按从小到大的顺序输出
s
2
s_2
s2 在
s
1
s_1
s1 中出现的位置。
最后一行输出
∣
s
2
∣
|s_2|
∣s2∣ 个整数,第
i
i
i 个整数表示
s
2
s_2
s2 的长度为
i
i
i 的前缀的最长 border 长度。
样例 #1
样例输入 #1
ABABABC
ABA
样例输出 #1
1
3
0 0 1
提示
样例 1 解释
。
对于
s
2
s_2
s2 长度为
3
3
3 的前缀 ABA
,字符串 A
既是其后缀也是其前缀,且是最长的,因此最长 border 长度为
1
1
1。
数据规模与约定
本题采用多测试点捆绑测试,共有 3 个子任务。
- Subtask 1(30 points): ∣ s 1 ∣ ≤ 15 |s_1| \leq 15 ∣s1∣≤15, ∣ s 2 ∣ ≤ 5 |s_2| \leq 5 ∣s2∣≤5。
- Subtask 2(40 points): ∣ s 1 ∣ ≤ 1 0 4 |s_1| \leq 10^4 ∣s1∣≤104, ∣ s 2 ∣ ≤ 1 0 2 |s_2| \leq 10^2 ∣s2∣≤102。
- Subtask 3(30 points):无特殊约定。
对于全部的测试点,保证 1 ≤ ∣ s 1 ∣ , ∣ s 2 ∣ ≤ 1 0 6 1 \leq |s_1|,|s_2| \leq 10^6 1≤∣s1∣,∣s2∣≤106, s 1 , s 2 s_1, s_2 s1,s2 中均只含大写英文字母。
#include <iostream>
#include <string>
#include <string.h>
#include <vector>
using namespace std;
const int N=1e6+10;
char s[N],p[N];
int ne[N];
int main() {
cin.tie(0);
ios::sync_with_stdio(false);
cin>>s>>p;
int n=strlen(s);
int m=strlen(p);
vector<int> ans;
//计算ne数组
ne[0]=-1;
for(int i=1,j=-1;i<m;i++){
while(j!=-1&&p[i]!=p[j+1])j=ne[j];
if(p[i]==p[j+1])j++;
ne[i]=j;
}
//匹配
for(int i=0,j=-1;i<n;i++){
while(j!=-1&&s[i]!=p[j+1])j=ne[j];
if(s[i]==p[j+1])j++;
if(j==m-1){
j=ne[j];
ans.push_back(i-m+1);
}
}
for(auto t:ans){
cout<<t+1<<endl;
}
for(int i=0;i<m;i++)cout<<ne[i]+1<<" ";
return 0;
}