关闭

关于KMP算法的一点个人理解

标签: 算法网络任务面试cqq
535人阅读 评论(1) 收藏 举报
 
KMP算法解析
前些日子,我因为面试需要,温习了下KMP算法,结果瞪着书看了3个小时愣是没有怎么看懂,不得已,不得不
求助于网络,在网络上搜索了半天,大概上是能理解,可是对于程序中getnext函数中有条语句k=next[k], 我怎么都没有找着是怎么来的,而且网络上的解答都没有说明为什么是这样的,没有办法,我自己做下来,好好研究了下,终于知道了是怎么回事。下面就是我对这个算法的一些理解没,如果有什么错误,希望大家能一起讨论。也可以给我发电子邮件或QQ联系:
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数组函数
//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]的话,则满足条件了,否则可以继续递归下去。
 
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:4128次
    • 积分:38
    • 等级:
    • 排名:千里之外
    • 原创:4篇
    • 转载:0篇
    • 译文:0篇
    • 评论:1条
    文章存档
    最新评论