KMP算法解析
前些日子,我因为面试需要,温习了下KMP算法,结果瞪着书看了3个小时愣是没有怎么看懂,不得已,不得不
求助于网络,在网络上搜索了半天,大概上是能理解,可是对于程序中getnext函数中有条语句k=next[k], 我怎么都没有找着是怎么来的,而且网络上的解答都没有说明为什么是这样的,没有办法,我自己做下来,好好研究了下,终于知道了是怎么回事。下面就是我对这个算法的一些理解没,如果有什么错误,希望大家能一起讨论。也可以给我发电子邮件或QQ联系:
E-MAIL:
playing0816@163.com
QQ:574860234
KMP算法是对传统的字符串模式匹配算法的一个改进,传统的字符串匹配其时间复杂度是O(m*n),而KMP算法则是O(m+n)。
首先看下传统的字符串模式匹配函数
int PatternMathch(char * str1,char * pat)
{
int i=0,j=0;
while(i < strlen(str1) && j < strlen(pat))
{
if(str[i] == pat[j])
{
i++;
j++;
}
else
{
i = i – j + 1;
j = 0;
}
}
if(j == strlen(pat))
return i– j;
return -1;
}
从上面的函数中我们可以知道,每当模式串和主串中的某个字符匹配不成功的时候,都需要将主串回溯到该次匹配的下个字符处,而模式串都需要回溯到第一个字符处。如下图所示
下标:0
1 2 3 4 5 6 7 8 9 10
主串:a
b a b a c c d e a a
模式:a
b a c
当第3个字符进行匹配的时候,发现主串[3] != 模式串[3],
这时候,传统的模式匹配算法需要进行回溯。回溯后的结果如下所示:
下标:0
1 2 3 4 5 6 7 8 9 10
主串:a
b a b a c c d e a a
模式:
a 。。。。
即用主串的第二个字符与模式的第一个字符进行匹配,这时候,主串从第一个匹配失败的地方(下标为3)回溯到下标为1的地方,而漠视串也从下标为3的地方回溯到下标为0的地方重新开始下次匹配。因此,才会导致该算法的匹配最坏情况下为O(m*n),当然在一般的情况下,模式串并不是每次都匹配到最后一个字符的时候才发现不匹配的。但是下面这种情况将导致传统的字符串模式匹配达到最坏的情况。
主串: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
模式串:aaaab
在上面这种情况下,每次的匹配都是在模式串的最后一个字符进行匹配的时候,发现不匹配,然后每次都在这个时候才进行回溯。
其实,从我们对前面的模式匹配过程可以发现,当第一次匹配不成功时,主串并不需要回溯到该次匹配的下个位置重新开始匹配。因为模式串[0]=模式串[2],当模式串[3]!=主串[3]的时候,前面的字符串子川p[0]。。。p[2] == s[0]。。。s[1]。这已经是匹配成功的。
所以此时主串不需要回溯到下个位置开始重新匹配,而可以直接用模式串的[1] 去和主串的[3]进行比较,而进行匹配。
下标:0
1 2 3 4 5 6 7 8 9 10
主串:a
b a b a c c d e a a
模式:
a b。。。。
现在我们的任务就是要对一个模式串,得到其当某个字符匹配失败的时候,主串不动,而模式串向进行右移后下个开始和主串进行匹配的字符的下标位置。
下面我们完成对这个数组的定义,
其中next[j]中的j表示当模式串中的第j个字符和主串不匹配的时候,下次匹配,模式串下个开始和主串当前位置的字符进行比较的位置(下标)。
next[]的定义:
next[j]=
=-1 当 j=0 时
=max{k|1<k<j且'P(1)...P(k-1)'='P(j-k+1)...P(j-1)'}当此集合非空时
=0 其它情况
next[j]=
=-1 当 j=0 时
=max{k|1<k<j且'P(1)...P(k-1)'='P(j-k+1)...P(j-1)'}当此集合非空时
=0 其它情况
根据定义。我们可以得到下面的求
next数组函数
//pat表示模式串,next表示数组,长度和pat的长度一样。
int getnext(char * pat,int * next)
{
int j=0,k = -1;
next[0] = -1;
while(j < strlen(pat))
{
if(k == -1 || pat[k] == pat[j])
{
j++;
k++;
next[j] = k;
}
else
k = next[k];
}
}
算法解释:
根据定义,
next[0] = -1.
而假设当前
next[j]=k已经求得,则求next[j+1]??
由于
next[j]=k,表示
p(0)p(1)…p(k-1)p(k)…
p(j-k)p(j-k+1)…p(j-1)p(j)中有
红色部分的相等,即
p(0)p(1)…p(k-1) = p(j-k)p(j-k+1)…p(j-1)。
此时,如果有
p(k)==p(j)
则
next[j+1]=next[j]+1;
如果
p(k) != p(j)
则剩下的任务就是要在
p(0)…p(k-1)中找到一个子串p(0)…p(m) == p(j-m)…p(j-1)
而且
p(m) == p(j)
因为之前已经有
p(0)p(1)…p(k-1) = p(j-k)p(j-k+1)…p(j-1)。
所以要求子串
p(0)…p(m) == p(j-m)…p(j-1),只要在p(0)…p(k-1)中找这个子串就可以了(这步想法很重要)。
因为假设存在
m,满足p(0)…p(m) == p(j-m)…p(j-1)
而
p(j-m)…p(j-1)一定与p(k-m)…p(k-1)相等的。
所以相当于
p(0)…p(k-m)…p(k-1)串中求next[k],
假设
next[k]=m(这个是已经求出来的了。),表示
p(0)p(1)…p(m-1) = p(k-m)…p(k-1)
这个时候除了满足这点之外,还需要满足
p(m) == p(j)
只有这两个条件都满足的情况下,这个时候
next[j+1]才能等于m,
假设还是不能满足
p(m) == p(j)这个条件。
又由上面的结果已经知道
p(0)p(1)…p(m-1) = p(k-m)…p(k-1)
这个是成立的。
即在
p(0)…p(m-1)p(m)…p(k-1)已经有m了(因为next[k]=m),
p(0)p(1)…p(m-1) = p(k-m)…p(k-1)
那么现在再在
p(0)…p(m-1)中找个子串p(0)…p(n-1)与p(k-n)…p(k-1)相等
假设找到了
n,
p(0)…p(n-1) == p(k-n)…p(k-1)
那么由前面的
next[j]=k
可以知道
p(j-n)…p(j-1) == p(j-n)…p(j-1)
如果这个时候又
p[n] = p[j]的话,则满足条件了,否则可以继续递归下去。