深入理解KMP算法上篇

来源:互联网 发布:mac配置java环境 编辑:程序博客网 时间:2024/05/18 06:10

1.引言

网上已经有很多关于KMP算法的博客,有的讲的不错有的讲的不是很清楚。所以自己想写一篇关于KMP算法的博客。以来记录下自己的理解,另外希望对大家理解KMP有点帮助。参考了两篇大牛的博客。大家也可以一起看看。

所谓KMP算法就是Knuth-Morris-Pratt算法的缩写,它是以三个科学家的名字来进行命名的。其中打头的科学家就是计算机领域的经典书籍《计算机程序设计艺术》的作者Donald Knuth。

问题的提出:有一个待匹配的字符串str,现在要求在str字符串中找到需要匹配的子字符串d,并且返回其在str中的位置,如何来实现?

通常的思路是使用暴力匹配法,基本思路是这样的:

1.加入现在我们在str字符串中匹配到i位置,而需要匹配的子字符串的匹配到j位置;

2.如果说当下匹配成功也就是说str.CharAt(i)==d.CharAt(j),那么i++、j++,同时继续向后匹配字符串;

3.如果说匹配失败了也就是说str[i]!=d[j],那么i=i-(j-1),j=0,每次匹配失败的时候i都将回溯,同时j被置为0,重新开始匹配。

暴力匹配的代码如下:

public int ForceMatch(String str,String p)
{
int i=j=0;
int length1=str.length();
int length2=p.length();
        while(i<length1 && j<length2)
{
           if(str.CharAt(i)==p.CharAt(j))
 {
             i++;
             j++;
           }
           else
           {
               i=i-j+1;
               j=0;
            }
}
if(j==length1)
{
 return i-j;  
}
        else
 return -1;
}

我们假设待匹配的字符串为str=“BBC ABCDAB ABCDABCDABDE”,匹配的字符串p="ABCDABD"。下面为大致的匹配过程。参考了几个大牛的内容。str.CharAt(0)='B',而p.CharAt(0)='A',发现匹配不成功。i=i-j+1,j=0。str.CharAt(1)和p.CharAt(0)继续进行匹配

 

 


       

                      

str.CharAt(1)跟p.CharAt(0)还是不匹配,以此类推。继续执行else后面的指令:i = i - (j - 1),j = 0。str.CharAt(2)跟p.CharAt(0)匹配(i=2,j=0),从而待匹配的字符串不断的向右移动(不断的执行“i = i - (j - 1),j = 0”,i从2变到4,j一直为0)。


    str.CharAt(5)跟p.CharAt(1)再次匹配成功,将执行i++,j++,得到str.CharAt(6)跟p.CharAt(2)匹配(i=6,j=2),一步一步这样进行下去。


           直到str.CharAt(10)为空格字符,p.CharAt(6)为字符D(i=10,j=6),因为不匹配,执行i = i - (j - 1),j = 0,相当于S[5]跟P[0]匹配(i=5,j=0)

到这一步我们可以看的出来暴力匹配算法的不足之处了,我们尽管之前已经分别匹配到了str.CharAt(9)、p.CharAt(5),只是因为str.CharAt(10)跟p.CharAt(6)不匹配,我们就要把待匹配字符串回溯到str.CharAt(5),子字符串重新置为p.CharAt(0),使得str.CharAt(5)和p.CharAt(0)继续进行匹配工作。也就是说之前的一些匹配工作白做了,我们现在又要做之前做过的匹配工作。

我们可以清楚的知道str.CharAt(5)肯定跟p.CharAt(0)是不匹配的。在之前的字符串匹配中,我们已经得知str.CharAt(5)=p.CharAt(1)= B,而p.CharAt(0)='A',所以str.CharAt(5)必定不匹配p.CharAt(0),所以这种回溯是没有意义的,返回会增加计算量,导致运算效率的底下和很多重复的计算。

而KMP算法就是为了避免这种回溯情况的发生,改进的一种字符串匹配算法。

2.KMP算法

现在我们知道了KMP算法较之暴力匹配最重要的改进之处就在于努力避免不必要的回溯和重复匹配来提高字符串匹配的速度和效率。下面先结合上面的字符串匹配来进行介绍,而后再给出KMP算法的程序基本思路。

      

当我们的字符串匹配到这一步的时候,当空格与D匹配不成功时,我们其实已经知道前面六个字符是"ABCDABD"。KMP算法的想法就是,设法利用这个已知信息,不要把"搜索位置"回溯到已经比较过的位置,而是继续把它向后移,这样就提高了效率。

那么现在的问题就是在上述的情况下如何来实现不回溯的后移以避免重复匹配。我们可以这样来实现,就是针对搜索词的内容,计算出一张部分匹配表来。通过这张表中的字符对应的位数来对待匹配的搜索词来进行移位工作。

  下面我们先介绍这张部分匹配表是怎么计算出来的。我们先来介绍两个概念,即为“前缀”和“后缀”。所谓“前缀”就是指除了最后一个字符以外,前面所有字符的顺序组合。所谓“后缀”就是指除了第一个字符之外,后面所有字符的顺序组合。那么部分匹配值就是说“前缀”和“后缀”的最长共有元素的的长度。下图以“ABCDABD”为例子:  

 

那么我们得到的部分匹配表为:


至此,匹配表已经计算出来,我们接着上面的字符串匹配过程。当最后一个字符‘D’与空格发生不匹配的时候。我们不回溯此时的i。而是将待匹配的字符串搜索词向后移动。移动的位数=已匹配的字符数-对应的部分匹配值。对于此时的搜索词来说,已匹配的字符数为6,对应的部分匹配值为2.则知移动的位数为4。则知道此时搜索词的移动位数为4位。那么移动后的位置为:

 

由于C和空格不能匹配,此时的待匹配的搜索词还是要继续往后移动。移动的位数=2(已经匹配的"AB")-0(对应的部分匹配值),移动后:

 

     首部的“A”与空格不匹配,则继续执行i++,j++。

  

    一步一步进行匹配,直到C与D无法匹配。那么移动的位数=6-2。移动后:

    

    

 

      发现完全匹配了,返回此时的i-j+1。

      现在问题又来了,就是搜索词的移动位数如何用程序来实现呢?这部分内容将在KMP算法的下一篇进行介绍。水平有限不足之处望批评指正。

参考:http://blog.csdn.net/hguisu/article/details/7676786

 


0 0
原创粉丝点击