问题 给定一个模式串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]的定义 n e x t [ j ] = k m a x ( p [ 0... k − 1 ] = = p [ j − k . . . j − 1 ] ) next[j]=k \ max(p[0...k-1]==p[j-k...j-1]) n e x t [ j ] = k ma x ( 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 ;
}