1.KMP算法介绍
KMP算法是由D.E.Knuth、J.H.Morris和V.R.Pratt研究出来的算法。可以大大避免重复遍历的情况也称为克努特——莫里斯——普拉特算法,简称KMP算法。在我们进行字符串的模式匹配中,在主串中寻找相同的子串是非常常用的一种方法。
对于字符串匹配问题,我们最容易想到的是利用暴力求解的方法,将子串中的字符逐一与主串相匹配,如果全部相同则匹配成功,如果子串中有一个字符与主串不匹配,子串就移动一个位置,重新与主串匹配。简单来看,这种暴力求解的子串匹配时间复杂度是O(n^2),算法效率并不好。KMP算法源于暴力求解。但是避免了一些重复的比较。
例如:主串S="abcdefabc" 子串T="abcdex"。在第一次匹配时,发现子串与主串有共同的"abcde"串,且"abcde"中每个字符都不相等。如果按照暴力求解的方法,在第六个字符‘f’与‘x’不匹配的时候,子串整体需要向右挪动一个单位,然后重新与主串匹配。此时子串a与主串b,c,d,e匹配都是没有必要的。因为本身匹配前abcde就各不相同,a与主串之前匹配过的b,c,d,e再去匹配就显得重复。KMP算法就是解决这种重复问题。KMP算法的核心思想就是快速找到已匹配的字符中与子串串首相同的位置。并迅速跳转。但是对于没有任何相同的字符串,该算法效率与暴力算法相同。
2.如何实现
KMP算法借助一个nextval[]表来预先描述主串的具体位置信息。根据信息具体匹配子串。
3.设计思想
3.1理解公共前后缀:
我们看到在一个字符串中,从前向后数的不包含该字符串的所有子字符串构成一个前缀集合。同理,从后向前数也能获得一个后缀集合。
"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,
- "A"的前缀和后缀都为空集,共有元素的长度为0;
- "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
- "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
- "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
- "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
理解公共前后缀的目的是什么呢?为了快速跳转。
3.2思路
以上述字符主串ABCDABD为例,假如子串为ABCDAC。我们发现前五个字符完全匹配。匹配字符ABCDA的公共前后缀是A。因为字符已经匹配,且前后公共部分只有A字符,说明主串匹配过的BCD字符没有必要在与子串首字符A匹配。现在需要跳过哪些不需要匹配的字符,只需要将前缀部分跳到后缀部分即可。因为他们是公共前后缀,所以一定是匹配的。为了方便获得跳转步数,我们需要一个nextval存储公共前后缀长度。用匹配的字符数减去公共前后缀长度就是我们的跳转步长。
仍然以ABCDABD为例,假设子串ABCDAC与主串进行匹配,已匹配长度为5,公共前后缀长度为1。所以下次跳转步长为5-1=4。即ABCDAC与ABCDABD中的ABD匹配。
跳转前:
跳转后:
4.构造nextval[]
我们发现只与要主串的每个字符有关与子串无关,只需设置主串每个字符对应的公共前后缀长度即可。
对于主串ABCDABD,它的nextval值为0000012
对于子串字符C与主串不匹配,查nextval[]发现公共前缀长度为1,,所以跳转4个位置。
void get_nextval(string T,int* next)
{
int i,j;
i=1;
j=0;
nextval[1]=0;//第一个字符一定没有公共前后缀
while(i<T[0])//T[0]存放string长度
{
if(j==0||T[i]==T[j])
{
i++;
j++;
nextval[i]=j;
}
else
{
j=next[j];/*若字符不相同,则j回溯*/
}
}
}
int Index_KMP(string S,String T,int pos)
{
int i=pos;
int j=1;
int nextval[255];
get_nextval(T,nextval);
while(i<=S[0]&&j<=T[0])
{
if(j==0||S[i]==T[j])
{
i++;
j++;
}
else
{
j=nextval[j];
}
}
if(j>T[0])
{
return i-T[0];
}
else
return 0;
}