应用:有一个文本串S,和一个模式串P,要查找P在S中的位置(kmp算法的时间复杂度为O(n))
暴力做法思想:字串整体往右一格一格移动,最开始子串的第一个字符与文本串的第一个字符对齐,字串整体与文本串和它对齐的那部分比对,然后子串的第一个字符与文本串的第二个字符对齐,比对,以此类推......
而实际上,子串不一定非要一格一格移动,可能移动的这一格又一格根本就没必要去移动,根本就不可能比对成功
子串在第一个位置时最后一个字符’B‘与文本串的'D'不匹配,于是需要将子串整体往右移动,我们要算的是我们最少往右移动多少格能使得文本串刚才不匹配的字符‘D’能继续有机会去和与它对齐的子串的那一字符匹配,而要想有这个机会,至少也得保证文本串的字符'D'所对应的子串的那一字符的前面所有部分与对齐的文本串的部分完全相同,即移到子串第五行那个状态,这样字符'D’才能安心去匹配。这就涉及到了子串的最大长度的相同前缀和后缀,这样移动对齐之后才会相同,我们只需要算子串不匹配的那个字符前面的部分的相同前缀和后缀的最大长度,用next数组来记录,而运动是相对的,子串向右移就相当于指针j向左移,j=next[j],用p[j+1]继续去和s[i]匹配
那么如何求next值呢?next[i]=j,i表示子串不匹配的那一字符的前面部分的长度,j表示该部分的前缀和后缀最大相同长度,next[1]肯定等于0,不用算了,i从2开始遍历,依次算出next[2],next[3],一直到next[n],j=ne[j]指针j是在往左移,因此需要条件while(j)保证j!=0即j不在子串的开头,毕竟已经在最左边了,也左移不了了,另一条件是p[i]和p[j+1]不匹配,这样就需要将指针左移,j=next[j]用以维持不匹配的字符有基础去进行匹配,然后再if判断能否匹配最后记录next值
for(int i=2,j=0;i<=n;i++){
while(j&&p[i]!=p[j+1]) j=ne[j];
if(p[i]==p[j+1]) j++;
ne[i]=j;
}
而文本串与子串的匹配也是相同的道理,最后如果j=n,则说明找到了,那么记录下匹配成功的起始坐标并继续维持有基础继续进行后面的匹配
for(int i=1,j=0;i<=m;i++){
while(j&&s[i]!=p[j+1]) j=ne[j];
if(s[i]==p[j+1]) j++;
if(j==n){
cout<<i-n<<' ';
j=ne[j];
}
}
与双指针算法类似,指针i遍历文本串s,一个一个移动,指针j则用以维持文本串不匹配的那个字符继续有基础去匹配
例:
给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。模式串 P在字符串 S中多次作为子串出现。求出模式串 P在字符串 S中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P的长度。第二行输入字符串 P。第三行输入整数 M,表示字符串 S的长度。第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0开始计数),整数之间用空格隔开。
数据范围
1≤N≤105
1≤M≤106
#include<iostream>
using namespace std;
const int N=10010,M=100010;
int n,m,ne[N];
char p[N],s[M];
int main()
{
cin>>n>>p+1>>m>>s+1;
for(int i=2,j=0;i<=n;i++){
while(j&&p[i]!=p[j+1]) j=ne[j];
if(p[i]==p[j+1]) j++;
ne[i]=j;
}
for(int i=1,j=0;i<=m;i++){
while(j&&s[i]!=p[j+1]) j=ne[j];
if(s[i]==p[j+1]) j++;
if(j==n){
cout<<i-n<<' ';
j=ne[j];
}
}
return 0;
}