怎么说呢,KMP算法确实有难度。我一开始就是搞不明白,半懂不懂的,后来看了大神的帖子才有点小明白。特此写个博客,加深一下印象,并且希望能够理解的更透彻一些。
大神博客:http://www.cnblogs.com/tangzhengyue/p/4315393.html
要了解KMP算法,首先需要了解BF算法。
一、BF算法——最简单最直观的模式匹配算法
首先,要说明的是模式匹配不一定是从主串的第一个位置开始的,所以要指定主串查找的其实位置pos.下面是以顺序结构存储的字符串为例介绍。
算法步骤:
1、分别利用指针i,j指示主串S和模式串T中当前正待比较的的字符位置,i初值为pos,j初值为1.(字符串不用0位置)
2、如果两个串都没有比较到各自的最后(就是指针i,j分别小于等于S和T字符串的长度),则循环执行以下操作:
(1)两个字符串当前位置(S.ch[i]和T.ch[j])若果相等,那么就各自向后移一位,继续比较。
(2)两个字符串当前位置(S.ch[i]和T.ch[j])若果不相等,那么就需要回溯,显而易见模式串需要回到1(j=1),而主串则需要 回溯到上一次开始匹配的下一个位置(i=i-j+2)。
3、跳出循环后,有两种可能方式跳出循环,所以需要判断是那种方式跳出循环的来判定字符串是否匹配成功。若j当前指向的位置大于模式串的长度(j>T.length),则说明是模式串匹配成功了,那么返回和模式串T中第一个字符相等的字符在主串S中的序号(i-T.length);否则就是主串超过了末尾跳出的循环,说明匹配不成功,返回0.
上面算法虽然写的很多,但是总的来说思路很简单,主串每一个字符都和模式串的第一个字符开始向后比较,直到寻找到匹配或者主串遍历完之后匹配失败。
代码:
#include<stdio.h>
#define MAXSIZE 255
typedef struct{
char ch[MAXSIZE];
int length;
}SString;
int Index_BF(SString S,SString T,int pos);
int main(){
SString S,T;
S.length=0;
T.length=0;
char c;
for(int i=1;i<255;i++){
scanf("%c",&c);
if(c=='\n')
break;
S.ch[i]=c;
S.length++;
}
for(int i=1;i<255;i++){
scanf("%c",&c);
if(c=='\n')
break;
T.ch[i]=c;
T.length++;
}
int i= Index_BF(S,T,1);
printf("%d",i);
return 0;
}
int Index_BF(SString S,SString T,int pos){
int j=1;
int i=pos;
while(i<=S.length&&j<=T.length){
if(S.ch[i]==T.ch[j]){
i++;
j++;
}
else{
i=i-j+2;
j=1;
}
}
if(j>T.length){
printf("OK");
return i-T.length;
}
else{
printf("NO");
return 0;
}
}
算法部分:
int Index_BF(SString S,SString T,int pos){
int j=1;
int i=pos; //初始化i,j.
while(i<=S.length&&j<=T.length){ //如果两个指针都小于各自的长度,则进入循环
if(S.ch[i]==T.ch[j]){//若当前位置相等就各自向后移动一位
i++;
j++;
}
else{ //若不相等则回溯
i=i-j+2; //先将主串回溯的本次开始匹配的下一个位置
j=1; 模式串回到1
}
}
if(j>T.length){ //判断,如果模式串指针大于字符数组的长度那么就说明匹配成功
printf("OK");
return i-T.length; //返回位置
}
else{
printf("NO");
return 0;
}
}
二、KMP算法——让人刚开始学脑子嗡嗡的算法(大神请忽略 )
KMP 算法是在BF算法的基础上改进来的,它可以在O(n+m)的时间数量级上完成串的匹配。
他与BF算法的不同之处就是主串不用回溯。
它的思想刚开始时和BF算法一样,如果两个指针都小于数组长度,那么就进入循环,如果当前位置相等,都想后移一位;如果不等,主串不动,模式串滑动到该位置对应的next[j]位置,继续进行比较。
它的思想是:
记录先模式串当前位置的前缀字符串和后缀字符串的重复情况,并且记录重复的前缀字符串的最后一个字符的位置K,next[j]=k。
这样做当当前位置S.ch[i]不等于T.ch[j](就是书上的失配)时,就说明当前位置前面的已经匹配了,那么当前位置的前缀字符串和后缀字符串相当,就可以把模式串向后滑动,j=next[j],这样前缀字符串和滑动到后缀字符串的位置,继续进行比较。
那么问题又来了,如果j=next[j]之后进行比较S.ch[i]仍不等于T.ch[j]怎么办,那就继续让j=next[j]。这里是最难理解的。
由此可见,KMP算法与主串关系不大,最主要的是模式串的next数组,下面就着重解析一下如何求next数组:
下面借助大神的图进行分析
1.由"next[j] == k;"这个条件,我们可以得到A1子串 == A2子串(根据next数组的定义,前后缀那个)。
2.由"next[k] == 绿色色块所在的索引;"这个条件,我们可以得到B1子串 == B2子串。
3.由"next[绿色色块所在的索引] == 黄色色块所在的索引;"这个条件,我们可以得到C1子串 == C2子串。
4.由1和2(A1 == A2,B1 == B2)可以得到B1 == B2 == B3。
5.由2和3(B1 == B2, C1 == C2)可以得到C1 == C2 == C3。
6.B2 == B3可以得到C3 == C4 == C1 == C2
由上面各种推理可以知道,2个A串相等,而A串里的B1和B2相等,所以A1里的B1也等于2里的B3.B1里面有C1和C2,那么B1里面的C1也一定等于B3里面的C4.
总结来说就是当前前缀字符串里面的前缀字符串,一定与当前后缀字符串的后缀字符串相等。
所以这样当仍然不匹配的时候,就可以继续让j=next[j],从下一个前缀入手。
由next的得:next[1]=0;
next[j]=k,说明前面有k-1个前缀字符串(T1~Tk-1)和K-1个后缀字符串(Tj-k+1~Tj-1)相等。
那么如何得到j+1的位置next[j+1]的值呢?
由上面的next[j]=k可以直到,当前j位置记录的是他前面前缀和后缀的相等情况,和j本身无关,所以得到next[j+1]的值是有前面字符串决定的。
如果ch[j]=k(即ch[j]=next[j],一位next[j]已经在j-1时确定了),那么next[j+1]=k+1(即next[j+1]=next[j]+1).意思就是如果j位置等于next[j]位置(next[j]记录的是前缀字符串的下一位),那么j+1的前缀就是k个字符了,也就是说next[j+1]=k+1.
如果ch[j]!=k,那么只能从前缀的前缀下手了,k=next[k],然后继续比较ch[i]与k.
代码:
void get_next(SString T,int next[]){
next[1]=0;
int i=1;
int j=0;
while(i<T.length){
if(j==0||T.ch[i]==T.ch[j]){
next[++i]=++j;
}
else
j=next[j];
}
}
KMP代码:
int Index_KMP(SString S,SString T,int pos,int next[]){
int i=pos;
int j=1;
while(i<=S.length&&j<=T.length){
if(j==0||S.ch[i]==T.ch[j]){
++i;
++j;
}
else
j=next[j];
}
if(j>T.length){
printf("OK");
return i-T.length;
}
else{
printf("NO");
return 0;
}
}