什么是KMP呢?
我们有一个string1,又有一个string2,这个时候我们想知道string2是不是string1的子串。这就是KMP
首先我们说经典算法,其实时间复杂度是比较大的,一般就是O(n*m)
其实KMP的主要思路和经典暴力方法还是一样的,但是KMP通过了一定的策略实现了加速。
首先我们要知道前缀和和后缀和
这个字符前面字符串中前后缀的最大匹配长度。记得不要取到整体
我们要这个干什么呢?是要给string2求的,string2的每一个字符都需要这个信息。我们把这个数据存储在next arr数组中。这里怎么求我们先不说,我们知道了这个简单求法先用着,看KMP过程有了这个数组之后怎么加速
开始执行KMP算法
两个string比较,我们到了string1的第n个位置和string2的i位置,我们发现没有对上,我们是不是就要从string的n+1位置和string2的0位置继续比,两个string双双回跳。
而现在我们知道了i之前的匹配情况,我们把string2的比较位置回跳到后缀和的位置。
这里特别抽象。我们细说。
我们之前是string1的0位置(比如说)开始比的,而此时到了n发现了匹配不上,但是我们此时不用叫string2从头去比,因为前后缀有一样的部分,这个时候我们将后缀当做前缀,我们从前缀后面开始去比,因为后缀和前缀一样,并且string1已经匹配好前缀了,后缀不用比也知道一样。
以此为例,经典过程我们最后发现没对上怎么办,是不是要从string1的b开始,再去和string2的a比。
而此时我们知道string2前缀后缀有一定的一样的位置abbstk,那么此时我们是要从string1的第二个abbstk之后和string2前缀之后的位置开始比。而我们知道这个时候string1之前是已经指定配不出来了,所以我们可以大胆把string2挪过去。
但是为什么呢?为啥一定配不出来了呢?
我们假设能配出来,是不是在错误之前的部分,至少得和string2的前半截得能配上,然后我们发现假设不成立,因为这样就意味着string2要找到一个更长的后缀。和我们之前next存在冲突。
核心思路
KMP的核心思路就可以说是string1死活不回跳。String2只要能往前跳就往前,不能跳了,说明前后缀直接用完也没匹配上,也可以理解为string2在不断的右移,此时下一轮比较节省的越来越少。string1就往下走走一个我们重新一轮再比。
KMP看来真的是挺麻烦的对吧。。不过代码确实是异常的简单。
我们就是要找i2越界,只有一路对比一路加走完越界才说明找到了。
一般来讲时间不超过2n次,所以也可以说就是常数级别的。
最后我们来看next数组怎么求就好了。
首先我们知道0,1位置分别是-1,0.
其次我们知道n号位置是需要前面n-1个next数组共同计算的就可以。就是当a[next[n-1]+1]=a[n-1]的时候,next[n]=next[n-1]+1,如果没有对上就要往前看了,这里往前看下一次比的就是a[next[next[n-1]]],这样一次次知道不能再往前了。那就0,往后走。
实现代码:
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
vector<int> getnextArray(string s)
{
vector<int> nextarray;
if(s.length()==1){
nextarray.push_back(-1);
return nextarray;
}
nextarray.push_back(-1);
nextarray.push_back(0);
/*
我们知道n号位置是需要前面n-1个next数组共同计算的就可以。
就是当a[next[n-1]+1]=a[n-1]的时候,next[n]=next[n-1]+1,
如果没有对上就要往前看了,这里往前看下一次比的就是a[next[next[n-1]]],
这样一次次知道不能再往前了。那就0,往后走。
*/
//这个变量的意义就是遍历到的字符要去和谁去比,顺便可以记录之前的前后缀重合程度
int biao=0;
for(int i=2;i<s.length();){
//这里要说明的一点就是我们要求的next数组是这个字符之前的部分的前后缀
//所以是i-1
if(s[i-1]==s[biao]){
//这里就是成功借助了之前的积累
nextarray.push_back(++biao);
i++;
}
//这个地方就是因为我们是第biao个没有匹配上,但是因为biao之前的部分也有自己的前后缀
//所以这一次我们放低标准,缩短了前后缀匹配长度,再去比
else if(biao!=0){
biao=nextarray[biao];
}
//这个意思就是biao=0了,就是两个比一下都没对上,那就没救了,下一个了。
else{
nextarray.push_back(0);
i++;
}
}
return nextarray;
}
int getindexof(string a,string b)
{
//这里的意思就是说必不能找到子串的几种可能。
if(a.length()==0||b.length()==0||a.length()<b.length()){
return -1;
}
//也算是双指针吧
int biao1=0;
int biao2=0;
vector<int> nextay=getnextArray(a);
//这里其实如果只是为了遍历的话,前面的条件就够了,不过这里附加了一个找到跳出的条件
//这里也是KMP算法最精髓的地方
while(biao1<a.length()&&biao2<b.length()){
//能匹配上,自然是皆大欢喜,我们继续往下走
if(a[biao1]==b[biao2]){
biao1++;
biao2++;
}
//接下来看一下如果没有匹配上,按照我们之前的老思路,就是biao1++,biao2=0了
//但是这样是浪费了不少时间的
//所以这里我们利用之前得到的前缀和进行了一下调整。
/*
KMP的核心思路就可以说是string1死活不回跳。
String2只要能往前跳就往前,不能跳了,说明前后缀直接用完也没匹配上,
也可以理解为string2在不断的右移,此时下一轮比较节省的越来越少。
string1就往下走走一个我们重新一轮再比。
*/
//这里就是说明已经没救了,这个点位是绝对匹配不上了,换下一个点
else if(nextay[biao2]==-1){
biao1++;
}
//这里就是虽然我们之前匹配了半截之后断了,但是我们还是有一小截能匹配上的
//所以就还是可以少匹配一些
else{
biao2=nextay[biao2];
}
}
//这里就是看一下从循环是怎么出来的,如果是找到了出来的,返回找到的起始点,反之-1
return biao2==b.length()?biao1-biao2:-1;
}
int main()
{
string s;
cin>>s;
string t;
cin>>t;
/*
vector<int> nextay=getnextArray(s);
for(int i=0;i<nextay.size();i++){
cout<<nextay[i]<<endl;
}
*/
cout<<getindexof(s,t)<<endl;
return 0;
}