- 问题
给定一个模式串p,和一个长文本t,求p是否为t的一个子串,如果是则返回子串的首地址 - 暴力解法
逐位对比模式串p和长文本t,如果不匹配,则回溯指向t和指向p的指针,再从头开始比对t和p。时间复杂度为O(nm) - KMP算法
KMP是一种时间复杂度为O(n)的算法。他的核心思想是当p[j]和t[i]失配时,我们不回溯指针i,只回溯指针j,然后再重新开始比对。KMP的核心思想是确定模式串中p[j]如果失配则j应该移动的位置next[j] - next[j]的定义
next[j]=k max(p[0...k−1]==p[j−k...j−1])next[j]=k \ max(p[0...k-1]==p[j-k...j-1])next[j]=k max(p[0...k−1]==p[j−k...j−1])
我们一般称k位字符串p[0...j-1]的最长公共前后缀,即p[0.. .j-1]中最多有最后k个字符和最前k个字符是一一对应的 - 数学证明
当p[j] !=t[i]时
显然有p[0...j-1]==t[i-j...i-1]
那么对于p[0...j-1]的最大公共前后缀p[0...k-1]==p[j-k...j-1]
由于p[0...j-1]==t[i-j...i-1]
所以有p[0...k-1]==t[i-k...i-1]
考虑到如果模式串p在t中存在,那么p要不包含t[i-1]要不在t[i-1]的右边,
所以当k==0时说明不存在最长公共前后缀,那么p肯定在t[i-1]右边,此时j
回退到0,继续比较t[i]和p[j]。如果k>0说明存在最长公共前后缀,
即在t[i-k...i-1]与p[0...k-1]匹配,此时由于k是最大的,所以不会有遗漏的
匹配情况,之后我们令j=k,继续匹配p[j]=t[i]
- 核心代码
KMP的核心代码是求解每个p[j]失配时的回退位置next[j],即p[0…j-1]的最长公共前后缀
vector<int> nextDp(1000000,0);
void initNext(string &p){
nextDp[0]=-1;
int j=0;
int k=-1;
while(j<p.size()){
if(k==-1||p[j]==p[k]){
nextDp[++j]=++k;
}else{
k=nextDp[k];
}
}
}
int searchStr(string &t,string &p){
int i=0;
int j=0;
while(i<n){
if(t[i]==p[j]||j==-1){
i++;
j++;
}else{
j=nextDp[j];
}
if(j==p.size()){
return i-m;
}
}
}
#include <iostream>
#include <algorithm>
#include <bits/stdc++.h>
#include <map>
#include <queue>
using namespace std;
string s1;
string s2;
int n;
int m;
vector<int> nextDp(1000008,0);
class KMP{
public:
void initNext(string &p){
nextDp[0]=-1;
int j=0;
int k=-1;
while(j<p.size()){
if(k==-1||p[j]==p[k]){
nextDp[++j]=++k;
}else{
k=nextDp[k];
}
}
}
int searchStr(string &t,string &p){
int i=0;
int j=0;
while(i<n){
if(t[i]==p[j]||j==-1){
i++;
j++;
}else{
j=nextDp[j];
}
if(j==p.size()){
return i-m;
}
}
}
};
int main(){
cin>>s1>>s2;
n=s1.size();
m=s2.size();
KMP kmp=KMP();
kmp.initNext(s2);
int res=kmp.searchStr(s1,s2);
cout<<res<<endl;
return 0;
}