如果我们要找到一个子串在一个字符串第一次出现的位置,可以使用BF算法或者KMP。
其中,BF算法的原理就是将子串字符与主串字符挨个比较,一旦不同,就返回到首个相同的字符的下一个在进行比较,直到全部相同位置。
如:子串abac,主串ababacdac,其步骤如下:
ababacdac
a
ababacdac
ab
ababacdac
aba
ababacdac
a
ababacdac
a
ababacdac
abac
但是,这里我们发现,在第4步时,不需要将子串的a同主串的b进行比较,因为他们一定不会相等。如果是很多位的子串势必会造成很多不要的浪费,有没有办法避免这种情况?
答案是肯定的,KMP这是因为BF存在这种缺陷而诞生的。
按照我们的逻辑,第4步应该是这样的:
ababacdac
ab
而我们比较的是主串的第四位上的b和子串的第二位上的b。
这里我们引入一个概念,最长前缀后缀。如aba的最长前后缀为1,aabaa的最长前后缀为2,aabaab最长前后缀为3 。。。
如果要达到我们想要的效果,势必要记录子串的前缀在主串中的位置,这样才能快速匹配。
如本例中的子串abac,它的前缀有a,ab,aba。同时我们需要定义一个next数组,用来记录最长前后缀,每一个下标对应之前的最长前后缀。默认数组第一位为-1,如本例中的abac,对应的数组为 -1 0 0 1。
每次检查子串和主串对应字符是否相同:
如果相同,则都往后移一位继续比较,直到走到子串尽头
如果不同,则子串后移 j-next[j]位,即到达本例中的b,可以看到,子串此时的b之前的字符主串也有,即本例中的a。因为我们是将最长前后缀作为数组的值,next[j]则表示已经匹配到的位置的下一位,那么子串的前部分就意味着已经匹配了,这样就做到了不必回到原来的位置了!!!!
下面给出完整源码:
#include<bits/stdc++.h>
using namespace std;
int next[100100];
void get_next(string &str1)
{
int i=0,j=-1;
next[0]=-1;
while(i<str1.length()){
if(j==-1 || str1[i]==str1[j]){ //每次会回到起点,目的在于计算i之前的最长前后缀(必须理解!!)
i++,j++;
if(str1[i]!=str1[j] || i>j+1) next[i]=j; //将前面的最长前后缀赋值给ext[i]
else next[i]=next[j]; //如果相邻两个字符相同,则直接赋值
}
else j=next[j]; //多次执行后回到起点
}
}
int index_KMP(string &str,string &str1,int &pos)
{
int i=pos,j=0,m=str1.length();
while(i<str.length()){
if(j==-1 || str[i]==str1[j]){ i++; j++; } //主子串后移一位
else j=next[j]; //子串后移到匹配位置
if(m<=j) return i-j+1;
}
return -1;
}
int main(){
string str,str1;
getline(cin,str); //主串
getline(cin,str1); //子串
get_next(str1);
int pos=0; //表示开始查找的位置
int index=index_KMP(str,str1,pos);
cout<<index<<endl;
return 0;
}
KMP的重点在于理解next数组,理解next数组在于每次到了一个新位置,j 会回到起点往后查找 i 的最长前后缀!理解了这一点KMP的核心思想也就掌握了!