KMP算法的概述

一:KMP算法是一个模式匹配算法,他最原始的方法就是从主串进行一个个的进行匹配,然后返回主串中模式串的第一个字母在主串中的位置,依次进行返回就能查出有多少子串。然后对于有些模式串返回时会有一些无必要的比较,所以要用算法进行优化处理,使其算法的时间复杂度由O(n*m)简化为O(n+M);

对于O(n*m)这个算法只能承受10的5次方以下的题。

1:next数组。

假设有一个字符串s(下标从0开始),那么他的第i号位作为结尾的子串就是s【0……i】。对该子串来说,长度为k+1的前缀与后缀分别是s【0,……k】和s【i-k……i】。现在定义一个int型数组next,其中next表示 使子串s【0……i】的前缀s【0……k】等于后缀s【i-k……i】的最大k(注意前缀和后缀可以部分重叠,但不能是s【0……i】本身);如果找不到相等的前缀和后缀,那么令next【i】=-1.显然,next【i】就是所求最长相等的前后缀中前缀最后一位的下标。

举一个例子:对于模式串ababaab,next数组计算过程是:

i=0时,子串s[0……i]为‘a’,所以找不到相等的前后缀,所以next【0】=-1;

i=1时,子串s【0……i】为‘ab’,next【1】=-1;

i=2时,子串s【0……i】为‘aba’,所以当k=0时,s【0,k】=‘a’,s【i-k,i】=‘a’;k=1时,s【0,k】=‘ab’,s【i-k,i】=‘ba’;所以next【2】=0;

i=3时,子串s【0……i】为‘abab’,所以k=1时,s【0,k】=‘ab’,s【k,i】=‘ab’,所以next【3】=1;

i=4时,子串s【0……i】为“ababa”,所以k=0时,s【0,0】=‘a’,s【4,4】=‘a’;k=1时,s【0,1】=‘ab’,s【3,4】=‘ba’,不成立;k=2时,s【0,2】=‘aba’

s【2,4】=‘aba’;k=3时,s【0,3】=‘abab’,s【1,4】=‘baba’,不成立。所以next【4】=2;

i=5时,子串s【0……i】为‘ababaa’,所以当k=3时,s【0,3】=‘abab’,s【2,5】=‘abaa’,不成立等等,所以next【5】=0;

i=6时,子串s【0……i】为‘ababaab’,所以当k=1时,s【0,1】=‘ab’,s【5,6】=‘ab’,所以next【6】=1;

注意:next数组中的k的值是最后一位的前缀的最后一个位置,要注意前缀加后缀可能不相等。

 对于这个例子,我们求解next数组时用暴力进行计算时是非常耗时的,下面就是用递推的方法进行计算。

作为举例,假设我们求出next【0】=-1,next【1】=-1,next【2】=0,next【3】=1,现在求解 next【4】。当我们已经的到next【3】=1,最长后缀为

‘ab’,由于s【4】=‘a’=s【next【3】+1】所以next【4】=next【3】+1,最长后缀为‘aba’;并让j指向next【4】;

接着在此基础上求解next【5】,当知道next【4】=2时,由于s【5】!=s【next【4】+1】,所以不能扩展最长相等的前后缀。次失败,要用别的方法

进行处理了。此时若找到一个j使得s【5】=s【j+1】。然后怎样找到这个j呢,你就需要不断的用j=next【j】进行返回只要找到就使next【5】=j+1;

具体的实现代码:

void next(char s[],int len){
int j=-1;
next[0]=-1;
for(int i=1;i<len;i++){
while(j!=-1&&s[i]!=s[j+1]){
j=next[j];//如果不相等的话,就不断返回j=next【j】 
}
if(s[i]==s[j+1]){//如果相等的话 ,就是next【i】=j+1; 
j++;
}
next[i]=j;//令next【i】等于j 
}
}

这段代码每次只求一个next值,然后就是对于这个理解最难。对于每次求解next【i】时,总是用j指向next【i】,以方便求解next【i+1】之用。

还有一个从1开始的代码;

#include<stdio.h>
#include<string.h>
int next[25];
/*
//这个next函数是用来求从1开始的next值 
void get(char t[]){
int i=1;
next[1]=0;
int j=0;
while(i<strlen(t)){
if(j==0||t[i]==t[j]){
i++;
j++;next[i]=j;
//printf("i=%d,j=%d,ne[%d]=%d\n",i,j,i,next[i]); 
}
else{
j=next[j];
//printf("nex=%d\n",j);
}
}
for(int i=1;i<strlen(t);i++){
printf("%d",next[i]);
}
}*/

//这个next函数时更简便的求一个函数的值
void  nextnal(char t[]){
int i=1;
next[1]=0;
int j=0;
while(i<strlen(t)){
if(t[i]==t[j]||j==0){
i++,j++;
if(t[i]!=t[j]){
next[i]=j;
}
else{
next[i]=next[j];
}
}
else{
j=next[j];
}
}
for(int i = 1;i<strlen(t);i++){
printf("%d",next[i]);
}
}
int main(){
char t[20];
scanf("%s",t+1);
// get(t);
    nextnal(t);
return 0;
}

然后就是KMP算法:

//计算a中是否存在b 
bool kmp(char a[],char b[]){
int j=-1;
int n=strlen(a),m=strlen(b);
next(b,m);
for(int i=0;i<n;i++){
while(j!=-1&&a[i]!=b[j+1]){
j=next[j];
}
if(a[i]==b[j+1]){
j++;
}
if(j==m-1){
return true;
}

return false;
}

对于这个KMP算法就发现求解next数组就是求解KMP算法的实现。

求解next数组的过程其实就是模式串pattern进行自我匹配的过程。

//计算a中存在b的数量 
bool kmp(char a[],char b[]){
int j=-1,ans=0;
int n=strlen(a),m=strlen(b);
next(b,m);
for(int i=0;i<n;i++){
while(j!=-1&&a[i]!=b[j+1]){
j=next[j];
}
if(a[i]==b[j+1]){
j++;
}
if(j==m-1){
ans++;
j=next[j];
}

return ans;
}对于求解next数组时,有些特殊例子:如aaaab,用next数组求就是01234,但我们发现这有些不必要的操作。所以还有更优的方法。

对于这个函数代码只有最后一步与前面不同。

void next(char s[],int len){
int j=-1;
next[0]=-1;
for(int i=1;i<len;i++){
while(j!=-1&&s[i]!=s[j+1]){
j=next[j];//如果不相等的话,就不断返回j=next【j】 
}
if(s[i]==s[j+1]){//如果相等的话 ,就是next【i】=j+1; 
j++;
}
if(j==-1||s[i+1]!=s[j+1]){
next[i]=j;

else{
next[i]=next[j];
}
}
}

从代码中可以看出,只是对next【i】=j做了补充而已。

二:从有限状态自动机的角度看待KMP算法

事实上,有限状态自动机可以看做一个有向图,其中顶点表示不同的状态,边表示状态之间的转移。

猜你喜欢

转载自blog.csdn.net/shi201619616/article/details/78458311