next数组
kmp算法的关键在于next数组的求解,next的值仅取决于子串本身而和主串无关。我们可以从定义分析出发用一种递推思想求得next的函数值。这里以数组下标从0开始为例。
这里又有一种将子串看成"主串"的思想,利用前面的next信息,递推构造后面的值
关键代码为:
while(j<len2-1){
if(k==-1||t[j]==t[k])//如果k为-1,即到了递推前部边界,由下面的语句可得next[++j]=0
next[++j]=++k; //如果t[j]==t[k],则相当于递推地直接获得了j+1的"最大满足"k
else k=next[k];//递推,降低k看前后是否匹配
}
代码示例
#include<bits/stdc++.h>
#define max_size 200
using namespace std;
char p[max_size];
char t[max_size];
int next[max_size+5];
int len1,len2,pos;
void get_next(char *t,int *next)
{
int j=0;
int k=-1;
next[j]=k;
while(j<len2-1){
if(k==-1||t[j]==t[k]) next[++j]=++k;
else k=next[k];
}
}
int index_kmp(char *p,char *t,int *next)
{
int i=pos-1;
int j=-1;
get_next(t,next);
while(i<len1&&j<len2)
{
if(j==-1||p[i]==t[j]){
++i;
++j;
}
else j=next[j];
}
if(j>=len2) return i-len2+1;
else return 0;
}
int main()
{
cin>>p>>t>>pos;
len1=strlen(p);
len2=strlen(t);
int ans=index_kmp(p,t,next);
cout<<ans<<endl;
return 0;
}
对于get_next函数来说,若t的长度是m,因只涉及到简单的单循环,其时间复杂度为O(m)。而由于i的值不回溯,使得index_kmp()算法效率得到了提高,其时间复杂度为O(n+m)。相较于朴素模式匹配算法的O((n-m+1)*m)来说,是要好一些。
需要强调的是,kmp算法仅当子串与主串之间存在许多“部分匹配”的情况下才能体现出它的优势,否则两者差异并不明显。
get_next函数的改进
有一类情况:例如主串为aaaabcde 子串为aaaaax ,在匹配的过程中会发现有些步骤是多余的。
因为对于主串和子串大面积相同的情况,next数组的移动并不明显,于是我们将代码改进
void get_next(char *t,int *next)
{
int j=0;
int k=-1;
next[j]=k;
while(j<len2-1){
if(k==-1||t[j]==t[k]){
++j;
++k;
if(t[j]!=t[k])//若当前字符与前缀字符不同,则当前的k为next在j位置的值 (这部分同上)
next[j]=k;
else //如果与前缀字符相同,则将前缀字符的next值直接赋给next在j位置的值(改进部分)
next[j]=next[k];
}
else k=next[k];//如果与前缀字符相同,
}
}
e.g
总结
改进过的kmp算法,它是在计算出next值的同时,如果a位字符与它next值指向的b位字符相等,则该a位字符的next就指向b位的next值;如果不等,则该a位的next(改进)的值就是它自己a位的next(原)的值。