今天在图书馆,整整一个下午就学会了一个kmp算法。
我是根据李春葆数据结构进行学习的,期间有点看得头晕,然后在Bilibili看了某个大佬的视频才算是勉强理解了KMP算法。
首先需要一些前置知识
-
前缀,后缀
例:aabaaf的前缀分别是
a
aa
aab
aaba
aaaba
前缀是包含首个字符的字串例:aabaaf的后缀分别是
f
af
aaf
baaf
abaaf
后缀是包含末尾字符的字串
KMP算法
KMP算法高效率的原因:通过在模式串中找到每个位置前的字符串的最大相等前后缀,然后将其存在next数组中。在做模式匹配时,就可以根据next数组跳过一个最大相等前后缀的长度从而提高效率
难点:
- 如何理解next数组的建立
- 如何理解next数组中k的回退
- 如何理解匹配过程中j的回退
#include <stdio.h>
#include <stdbool.h>
#define MaxSize 1000
typedef struct{
char data[MaxSize];
int length;
}SqString;
//生成串
void StrAssign(SqString *s,char str[]){
int i;
for(i=0;str[i]!='\0';i++){
s->data[i]=str[i];
}
s->length=i;
}
void Out(SqString s){
int i;
for(i=0;i<s.length;i++){
printf("%c",s.data[i]);
}
printf("\n");
}
void GetNext(SqString t,int next[]){
int j=0,k=-1;
next[0]=-1;
while(j<t.length){
if(k==-1 || t.data[j]==t.data[k]){
j++;k++;
next[j]=k;}
// if(t.data[j]!=t.data[k])
// next[j]=k;
// else
// next[j]=next[k]; //先理解未改进的方法
// }
else
k=next[k]; //既然K和j不匹配,那么K回退到next[k]
//next[k]代表了 k这个位置前面的最大相等前后缀
//找一个较短的前后缀,看看是否和j位置的匹配
//直到找不到为止
//理解了哪个当tk=tj时,他们的相等前后缀多加了一位字符
}
}
int KMP(SqString s,SqString t){
int next[MaxSize],i=0,j=0;
GetNext(t,next);
while(i<s.length && j<t.length){
if(j==-1 || s.data[i]==t.data[j])
{
i++;
j++;
} else
j=next[j];
//next[j]代表此字符前的最大相等前后缀长度
//经过测试,这为止就是忽略掉最大相等前后缀长度后,应该比较的位置
}
if(j>=t.length)
return(i-t.length);
else
return(-1);
}
int main(){
SqString s,t;
printf("生成字符串s,t\n");
char a[]="aabaabaaf";
char b[]="aabaaf";
StrAssign(&s,a);
StrAssign(&t,b);
printf("输出s:");
Out(s);
printf("输出t:");
Out(t);
printf("%d",KMP(s,t));
}
上述为完成代码,我们先来理解next数组的建立
例:aabaaf
next[ ]:-1 0 1 0 1 2
void GetNext(SqString t,int next[]){
int j=0,k=-1;
next[0]=-1;
//0号位置前面无字符,无字符无前后缀
//1号位置前面1个字符,1个字符无前后缀,这种情况在下面被包括了
while(j<t.length){
if(k==-1 || t.data[j]==t.data[k]){
j++;k++;
next[j]=k;
}
//第一轮可以算做初始化,让j=1,k=0,代表1号位置前,无相等前后缀
//随后每一轮,j++ k++,既慢慢试探其相等前后缀
//next[j]=k
//这一句其实很好理解,k是从0开始 == 没有相等前后缀
//如果j=k,则可以用k++保存最大相等前后缀长度
else
k=next[k];
//难点1:如果不相等,则j需要回退,那要回退到最开始吗?
//不需要,k这个位置(j)的最大相等前后缀长度
//所以回退到上一步即可
}
}