对KMP的理解,以及kmp算法java版本实现

来源:互联网 发布:java程序设计课后答案 编辑:程序博客网 时间:2024/05/16 11:23
        在看了网上一系列关于KMP算法的讲解以及《算法导论》32章字符串匹配的内容之后,自己也心血来潮想写一篇关于KMP的文章,本文纯属个人理解,有些地方或许和实际内容有所出入,望大家指出,并在文章最后给出kmp java版本的实现.
首先看看下面这个现象:
                   1  2  3  4  5  6  7  8  9  10  ....
源字符串:a  b  c  a  b  c  a  d  a   b   ....
        模式:a  b  c  a  d

观察到在第五个位置源字符串和模式是不匹配的,这时候简单而且容易想到的就是重新开始匹配,也就是将模式中的第一个字符与源字符串的第二个字符进行比较,而kmp算法的思想是这样子的:

我们可以注意到在模式中前四个字符a b c a,第一个字符和最后一个字符是一样的,而在上述现象中前四个位置是匹配的,第五个位置才是不匹配的,但是我们已经知道了前四个字符中,第一个字符和最后一个字符是一样的,这样我们就能得出模式的第一个字符和源字符串的第四个字符是匹配的,所以只需要比较源字符串的第五个位置是否和模式的第二个字符匹配即可.于是我们就得到这样一个状态,在模式前四个字符都匹配但第五个字符不匹配的时候可以过渡到模式的第一个字符与源字符串之前的内容,下面进行模式的第二个字符的匹配

说到这里,引入最大真前缀的概念吧.对于一个字符串s,长度为n,
若索引从1开始:
1<=i<=n,令prefixLen[i]=p,p满足max{k:0<=k<i且s[1]s[2]...s[k] = s[n-k+1]s[n-k+2]...s[n],称p为字符串在长度为i的位置最大真前缀的长度
若索引从0开始:
0<=i<=n-1,令prefixLen[i]=p,p满足max{k:0<=k<=i且s[0]s[2]...s[k-1] = s[n-k]s[n-k+1]...s[n-1],称p为字符串在长度为i+1的位置最大真前缀的长度
实例:
s: a b c d a b c d
p: 0 0 0 0 1 2 3 4

下面是如果求解prefixLen[i]的过程,以下内容均是针对索引从0开始,位置是从1开始(也就是下面出现的"第几个位置",这个几是从1开始数的)而讨论的,目的是为了最后程序的实现.
从上面对最大真前缀的解释,可以得出对于任意不为空的字符串,prefixLen[0]=0,问题是我们能不能从目前唯一能得到的字符串第一个位置的最大真前缀长度出发,计算其他位置的最大真前缀长度呢?
首先设模式字符串为s,长度为n,
假设我们要计算prefixLen[i],0<i<n,即s第i+1个位置的最大真前缀长度;并且我们已经得到了prefixLen[0]一直到prefixLen[i-1]的值.
a.令tmp=prefixLen[i-1],
进行下面步骤:
b.若tmp大于0,否则进入步骤c:
(1)判断s的第tmp+1个位置是否和s的第i+1个位置相等,也就是判断s[tmp]和s[i]是否相等,若相等进入步骤c,否则进入步骤b.(2)
(2)令tmp等于s的第tmp个位置的最大真前缀的长度,也就是tmp=prefixLen[tmp-1],回到步骤b.(1)
c.判断s的第tmp+1个位置和s的第i+1个位置是否相等,也就是判断s[tmp]和s[i]是否相等(大家可能会奇怪为什么这里还要判断,在步骤b.(1)中不是也有这个判断吗?这是因为进入步骤c有2种情况,一种是s[tmp]和s[i]相等,另一种是x等于0),如果相等,prefixLen[i]=tmp+1,否则prefixLen[i]=0
通过a,b,c这三个步骤就可以计算得到prefixLen[i],当然这是在我们已经得到了prefixLen[0]一直到prefixLen[i-1]的值的情况下,因为我们知道了prefixLen[0],所以我们可以用上述方法得到prefixLen[1],然后依次得到后面的值.

至此关于最大真前缀相对重要的内容已经阐述完了,下面给出具体java程序实现计算prefixLen以及匹配过程,更详细的证明的过程大家可以看《算法导论》

    private static int[] prefixLen(String pattern) {        int len = pattern.length();        int[] prefixLen = new int[len];        prefixLen[0] = 0;        int tmp = 0;        for (int i = 1; i < len; i++) {            while (tmp > 0 && pattern.charAt(tmp) != pattern.charAt(i))                tmp = prefixLen[tmp - 1];            if (pattern.charAt(tmp) == pattern.charAt(i))                tmp++;            prefixLen[i] = tmp;        }        return prefixLen;    }    public static void kmpMatcher(String src,String pattern){        int srcLen = src.length();        int patLen = pattern.length();        int[] prefixLen = prefixLen(pattern);        int matched_len = 0; //已经匹配的长度        for(int index = 0;index+patLen-matched_len<=srcLen;index++){            //如果已经匹配的长度大于0,但是在第matched_len+1的位置不匹配,计算下一个可能的匹配点(这里得靠自己的理解啦,我表达能力有限,抱歉啦!)            while(matched_len>0&&src.charAt(index)!=pattern.charAt(matched_len))                matched_len=prefixLen[matched_len-1];            if(src.charAt(index)==pattern.charAt(matched_len))                matched_len++;            //如果匹配完成,输出匹配位置的索引,并计算下一个可能的匹配点            if(matched_len==patLen){                System.out.println(index-patLen+1);                matched_len=prefixLen[matched_len-1];            }        }    }



0 0