参考:https://www.bilibili.com/video/BV1i64y1M7zv?from=search&seid=4229951256989443915
用途:在主串(长度n)中快速匹配到模式串位置(长度m)
比起暴力法(复杂度O(n*m)),KMP的复杂度可以是O(n+m)
关键在于发生不匹配时如何更好地选择子串的下一个比较字符的位置(暴力法是从子串的初始位置重新开始,而KMP利用了已匹配的信息)
所以问题的关键在于如何求解当发生不匹配时,如何选取子串的下一个比较字符的位置,而这个位置实际上就是截止到上一个字符子串的最长公共前后缀的长度,这些信息存放在next数组中,因此问题转换成求解next数组。
子串最长公共前后缀长度,要小于选取长度,因为本身肯定是公共前后缀, 但是这没有意义
求解next数组:
如果暴力求法复杂度是O(m*m),但是可以通过动态规划让复杂度下降到O(m),获取next数组的代码如下:
next[i]:截止上一个索引i-1的最长公共前后缀长度,也是如果在索引i位置出现不匹配时,下一个需要去匹配的子串的位置
特别注意不要拿string::size_t和int类型数据比较,会导致一些问题,比如int为负数时
void getNext(const string &s,vector<int> &next){
int j=0,k=-1;
next[0] = -1;
int sz = s.size();
while(j<sz-1){
if(k==-1 || s[j]==s[k]) next[++j] = ++k;
else k=next[k]; // 当前字符出现不匹配,那么退而求其次,再去比较稍微短一点的前后缀,也是就当前匹配前后缀的公共前后缀长度
}
}
完整程序如下:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void getNext(const string &s,vector<int> &next){
int j=0,k=-1;
next[0] = -1; //初始值为-1是为了使后面处理方便
int sz = s.size();
while(j<sz-1){
if(k==-1 || s[j]==s[k]) next[++j] = ++k;
else k=next[k];
}
}
int kmp(const string &s1,const string &s2){
int i=0,j=0;
int sz1 = s1.size(),sz2 = s2.size(); //注意不要拿string::size_t和int类型数据比较,会导致一些问题,比如int为负数时
vector<int> next(s2.size());
getNext(s2,next);
while(i<sz1 && j<sz2){
if(j==-1 || s1[i]==s2[j]){
++i;
++j;
}else
j = next[j];
}
if(j>=s2.size()) return(i-sz2);
else return -1;
}
int main() {
string s1("abdbabde");
string s2("babd");
cout << kmp(s1,s2) << endl;
return 0;
}