标签:
关于KMP模式匹配算法
在处理字符串中,我们总是需要判断一个主串S中,是否包含子串T,那么我们怎么能高效率地去做呢?
① 、朴素的模式匹配算法,所谓朴素,就是不讲技巧,暴力枚举,我们先看个例子,例如有个主串
S=”ABCDEFGGGQ”,我们需要去找其中其否含有子串T=”GGGQ”,如果用朴素的模式匹配,我们应该怎么弄呢?答案是:枚举s中的每一位
for (i=0;i<strlen(s);i++)
其中,枚举每一位后,去枚举子串T的每一位
for (j=0;j<strlen(t);j++)
然后,每位判断即可,不难看出,时间复杂度是O(n*m),很大。
#include <stdio.h> #include <stdlib.h> #include <string.h> int main () { char s[100]; char t[100]; scanf ("%s%s",s,t); int len_s=strlen(s); int len_t=strlen(t); int i,j; for (i=0;i<len_s ;i++ ) { for (j=0;j<len_t ;j++ ) { if (s[i+j]!=t[j]) { break; } } if (j==len_t) { printf ("%d--%d\n",i+1,i+len_t); break; } } if (i==len_s) { printf ("no respon\n"); } return 0; }
但是很容易发现,有些比较是可以省略的。考虑一下这个串S=”0000000001”和T=”0001”就是每次不成功的匹配都是在T的最后一个字符,但是,真的要这样嘛?我们开始判断的前3位(大家都是0),匹配成功,那么,以后我不能少判断一些吗?当然是可以得,这要看我们的KMP模式匹配了。
① 、KMP模式匹配:再看一个例子:s=”abcdefgab”和t=”abcdeL”,在这里,子串t中的每个元素都不相同,然而在匹配时,当t循环到’L’的时候判断不匹配,那么我就问了,既然t中的前5个”abcde”和s中的前5个相等,那么s中的i向下移动一位有用吗?就是,现在要比较s中的第二个字符b和t中第一个字符a是否相等,其实不用比较我就知道是不相等的了,为什么,你想啊,我们已经知道t中的各个字符都不相等,而s和t的前5位分别相等,说明什么?t中的第一个字符a肯定与s的2--5位不相等啊。!所以,这里可以省点时间。我们希望判断一次之后,i的值不回溯(就是i别让他变成2再去一位一位比较,直接跳跃),我们只移动子串t去比较。用上面的例子说吧,就是比较完“abcde”后,s中的f与t中的L不等,那么我就直接去判断s中的f与t中的a是否相等就可以了,前面那些比较是多余的。这里就有个问题,怎么确定j值的变化呢?i值好办,就是
for (i=0;i<strlen(s);i++)//因为它不回溯,就是说不会减少啦。
那么j值怎么办?如果我能预处理一个数组,使得每次我匹配不成功后,直接用j=next[j];就能够得到j值该去哪就好了。我也想啊,怎么得到啊?
Case:关于next数组的推导:
在这之前先要知道next数组的意义,next数组的作用是:帮助我们减少重复或者没必要的比较。何为没必要?就是:如果s=”000001”和t=”0001”,前面比较3个0相同,第四个失败了,然后,我让s中的第四个0与t中的那个比较呢?答案是第三个0,为什么?因为我们之前的0比较过 啊,相等啊,还用比较么?这里是减少了重复比较。而上面的s=”abcdefghi”和t=”abcdeL”中,i值不回溯,是为了减少没必要的比较。
知道了个大概之后,我们来看看next[]的数学定义
0, if(j==1)
next[j] = max(k|”a1a2a3a4….a(k-1)”==”a(j-k+1)a(j-1)”);
1,其他情况
需要注意的是,这个匹配是比较(k-1)位的,就是,如果有个串t=”AAAAL”,当j=1时,比较的为空串,所以next[1]=0;当j=2时,比较的只有A一个,所以属于其他情况next[2]=1;当j=3时,比较的是串”AA”,这个时候,前缀A和后缀A相等,请注意看上面公式,这个时候,k=2;所以,next[3]=2;当j=4的时候,注意啦,比较的串是”AAA”,那么,它的前缀和后缀是怎样相等的呢?很明显k=3的时候,前缀”AA”和后缀”AA”是匹配的,所以next[4]=3;注意啦,这里他们是公用了中间的一个A值的,同理next[5]=4;而next[6],没有与他匹配的,属于其他情况,所以next[6]=1;
此时的next数组是,下标从1开始的、
0 |
1 |
2 |
3 |
4 |
1 |
这里有个经验,如果得到前后缀一个字符相等,k=2;两个字符k=3;n个相等就是n+1;
next[]数组的意思是什么?根据上面的你能知道吗?就是,当第j位匹配不成功时,返回第next[j]为开始比较,为什么?就是能够减少重复比较和无用的比较啊。完整代码如下:
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> void get_next(char *t,int *next) { int i=1,j=0; next[1]=0;//固定的 int len_t=strlen(t+1); while (i<len_t) { if (j==0||t[i]==t[j]) { next[++i]=++j; } else j=next[j]; } return ; } void work () { char s[1000]={0}; char t[1000]={0}; scanf ("%s%s",s+1,t+1); int next[1002]={0}; get_next(t,next);//得到next数组 int i; int len_s=strlen(s+1),len_t=strlen(t+1); for (i=1;i<=len_t ;i++ ) { printf ("%d ",next[i]);//先看看next数组的值 } printf ("\n"); int j; i=1;j=1; while (i<=len_s&&j<=len_t) { if (j==0||s[i]==t[j]) { i++; j++;//比较下一位 } else //否则的话,j去到最优的位置 { j=next[j]; } } if (j==len_t+1) { printf ("%d---%d\n",i-len_t,i-1); } else printf ("no\n"); return ; } int main () { work (); return 0; }
这篇文章写的很不详细,建议读者自己多推导next数组的值,理解好next数组的意思,就是较少重复比较和无用的比较,其中,推导next数组的值的时候,也用了next数组。
如果读者发现有错误,请帮忙指出,本人感激不尽。
标签:
原文地址:http://www.cnblogs.com/liuweimingcprogram/p/5175041.html