KMP算法以及next函数值、nextval函数值的计算方法
数据结构中串涉及的内容即串的模式匹配,比较难理解的KMP算法,难在next函数值和nextval函数值的求解
一、问题描述
给定一个主串S及一个模式串P,判断模式串是否为主串的子串;若是,返回匹配的第一个元素的位置(序号从1开始),否则返回0;
先了解一下最简单直观的模式匹配算法BF(Brute-Force)算法,对理解KMP算法做个铺垫!
1、BF算法
假设S=“BBC ABCDAB ABCDABCDBADE”,P=“ABCDABD”。
- 分别利用计数指针i和j指示主串S和模式串P中当前正在比较的字符位置,i的值可以指定,j初值为1;
- S.ch[i]和P.ch[j]比较,若相等,则i和j分别指示串的下个位置,继续比较后续字符;
- 若不相等,指针后退重新开始匹配,从主串的下一个字符(i=i-j+2)起再新和模式串的第一个字符(j=1)比较。
1、当S=“BBC ABCDAB ABCDABCDBADE”
与P="ABCDABD"进行匹配时:
串S BBC ABCDAB ABCDABCDBADE
串P ABCDABD
因为B与A不匹配,模式串后移一位。
直到文本串有一个字符与模式串中的第一个字符相同为止,然后逐一比较下一个字符,发现“空格”与“D”不匹配。
BBC ABCDAB ABCDABCDBADE ABCDABD
3.重复第1第2步,能找到文本串中有“ABCDABD”与模式串相匹配,但效率很慢。
下面我们来了解另一种改进的模式匹配算法。
2、KMP算法
KMP算法,其改进在于,每当一趟匹配过程中出现字符比较不等时,不需回溯i指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续进行比较。
KMP算法是在已知模式串的next函数值的基础上执行的,那么,如何求得模式串的next函数值呢?(模式串根据next函数值向右“滑动”距离此函数值仅取决于模式串本身)
3、计算next函数值
【举例】
串“ababaaababaa”的next数组为
- next[1]=0
- next[2]=1
(这两个值是约定的)
- next[3]:"ab"没有相同的前缀或后缀,所以模式串又得从头开始匹配,因此next[3]=1;
- next[4]:“aba"的最长公共串是"a”,前三位匹配上了,并且第一位和第三位相同,因此可以将模式串整体向右移动,直到将原来的第一位移到原来的第三位上,继续进行匹配,而原来模式串指针指着的第四位现在指向第二位了,因此next[4]=2;
- next[5]:“abab"的最长公共串是"ab”,用同样的方法得出next[5]=2;
以此类推······
- next数组为:
0 1 1 2 3 4 2 2 3 4 5 6
4、计算nextval函数值
【举例】
串“ababaabab”的nextval为
总结如下:
1、nextval[1]和next[1]的值一样,为0;
2、如果s[i]==s[j],则nextval[i]=nextval[j];反之,nextval[i]=next[i].
代码附上
通过递推求得next数组
Void GetNext (char *p, int next[ ])
{
Int plen=strlen(p);
Next [0]=-1;
Int k=-1;
Int j =0;
While(j<plen-1);
{
If(k==-1||p[j]==p[k])
{
++k;
++j;
Next[j]=k;
}
Else
{
K=next[k];
}
}
}
KMP算法:
Int KmpSearch(char *s,char *p)
{
Int i=0;
Int j=0;
Int slen = strlen(s);
Int plen = strlen(p);
While (i<slen && j<plen)
{
//如果j=-1,或者当前字符匹配成功,都令i++,j++
If (j==-1 || s[i]==p[j])
{
I++;
J++;
}
Else
{
//如果j!=-1,且当前匹配失配,则令i不变,j=next[j]
//next[j]即为j所对应的next值
J=next[j];
}
}
If (j==plen)
Return i-j;
Else
Return -1;
}
此时,我们得到修改后的求next数组的代码
Void GetNext (char *p, int next[ ])
{
Int plen = strlen(p);
Next [0]=-1;
Int k=-1;
Int j =0;
While(j<plen-1);
{
//p[k]表示前缀,p[j]表示后缀
If (k==-1 || p[j]==p[k])
{
++j;
++k;
If(p[j]!=p[k])
Next[j]=k;
Else
//因为不能出现p[j]=p[next[j]]所以当出现需要递 //推时,k=next[k]=next[next[k]]
Next[j]=next[k];
}
Else
{
K=next[k];
}
}
}