最近通过网上的资料学习了KMP算法~KMP算法是用于匹配两个字符串的一种算法,相较于最初始的逐个匹配的办法其优势就在于,KMP算法匹配字符串出现字符比较不相等时,尽可能的利用已经得到的“部分匹配”的结果,对子串“滑动”尽肯能多的距离。
KMP算法是在已求得子串(模式串)的next数组的基础上进行匹配的。因此next数组的求解对于KMP算法来说是至关重要的。
例如:有一个子串str1[n]={a,b,a,b,b,c}需要求解next数组,那么我们先要了解字符串的前后缀是什么。
所谓前缀,就是对应的字符串剔除最后一个字符所得的字符串。“abcca”的前缀就是“abcc”。后缀则相反,是对应的字符串剔除第一个字符所得的字符串。“abcca”的后缀为“bcca”。
了解完前后缀的概念,我们就可以正式开始求解next数组了。当数组下标为0时,对应的字符串字符个数为1,所以一般都是将next[0]进行初始化,这里我习惯将next[0]初始化为“-1”(“-1”代表着这个字符串没有相同的最大前缀和最大后缀,也可以初始化为其他数值)。
当数组str1[n]={a,b,a,b,b,c}下标为1时,对应的字符串为“ab”,其前缀为“a”,后缀为“b”,不相等,因此为“-1”;
当数组str1[n]={a,b,a,b,b,c}下标为2时,对应的字符串为“aba”,其前缀为“ab”,后缀为“ba”,不相等。将前缀向左移一位,后缀向右移一位。分别为“a”,“a”,相等,则在-1的基础上+1,next[1]为“0”;
所以next数组求解为
0 1 2 3 4 5
a b a b b c
下标为0,字符串为“a”,无前后缀,next[0]=-1;
下标为1,字符串为“ab”,前缀“a”,后缀“b”,next[1]=-1;
下标为2,字符串为“aba”,前缀“ab”,“a”,后缀“ba”,“a”,(一个相等)next[2]=0;
下标为3,字符串为“abab”,前缀“aba”,“ab”,后缀“bab”,“ab”,(两个相等)next[3]=1;
下标为4,字符串为“ababb”,前缀“abab”,“aba”,“ab”,“a”,后缀“babb”,“abb”,“bb”,“b”,next[4]=-1;
下标为5,字符串为“ababbc”,前缀“ababb”,“abab”,“aba”,“ab”,“a”,后缀“babbc”,“abbc”,“bbc”,“bc”,“c”,next[5]=-1;
所以求得next数组为{-1,-1,0,-1,-1}。
代码实现:
/**计算next数组长度**/
void get_next(char str1[],int next[])///str1为子串(模式串)
{
int i=1,j=-1;
next[0]=-1;///next[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀
for(i=1;i<strlen(str1)-1;i++)
{
while (j>-1&&str1[j+1]!= str1[i])///如果下一个不同,那么j就变成next[j]
j=next[j];///往前回溯
if (str1[j+1]==str1[i])///如果相同,j++
{
j=j+1;
}
next[i]=j;///没有相同,next[i]=-1
}
}
求解完next数组接下来就可以利用next数组对主串进行匹配了。
匹配的思想与next数组求解类似,i表示当主字符串的下标从0开始,j表示模式串的下标,从-1开始表示前后缀无重复。i与j+1所指字符不相同时,对j进行回溯,即当出现失配时,将j进行回溯到next[j],继续进行比较。移动模式串,直到j指到模式串的末尾。
代码为:
/*KMP算法*/
void KMP_A(char str1[],char str[])///str1为子串,str为主串
{
int next[MAXN];
int L1,L2;
int i=0,j=-1;///将j初始化为-1
L2=strlen(str1);///求取str1的数组长度
L1=strlen(str);///求取str的数组长度
get_next(str1,next);///求取next数组
for(i=0;i<L1;i++)
{
while(j>-1&&str[i]!=str1[j+1])///str和str1不匹配,且j>-1(表示str和str1有部分匹配)
j=next[j];///往前回溯
if(str[i] ==str1[j+1])
j=j+1;
if (j==L2-1)///说明 j 移动到str1的最末端
{
j=-1;///重新初始化,寻找下一个
i=i-L2+1;///i定位到该位置,外层for循环i++可以继续找下一个
}
}
}