浅谈KMP算法

KMP算法

KMP算法是一种改进的字符串匹配算法,其核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)

整个KMP的重点就在于当某一个字符与主串不匹配时,我们应该知道j指针要移动到哪?
接下来我们自己来发现j的移动规律:
T为主串,P为模式串
浅谈KMP算法_第1张图片
如图:C和D不匹配了,我们要把j移动到哪?显然是第1位。为什么?因为前面有一个A相同啊:
浅谈KMP算法_第2张图片
如下图也是一样的情况:
浅谈KMP算法_第3张图片可以把j指针移动到第2位,因为前面有两个字母是一样的:
浅谈KMP算法_第4张图片至此我们可以大概看出一点端倪,当匹配失败时,j要移动的下一个位置k。存在着这样的性质:最前面的k个字符和j之前的最后k个字符是一样的。
如果用数学公式来表示是这样的P[0 ~ k-1] == P[j-k ~ j-1]
KMP算法就是在不断寻找最长公共前后缀,然后移动模式串。

接下来就是重点了,怎么求这些k呢?因为在P的每一个位置都可能发生不匹配,也就是说我们要计算每一个位置j对应的k,所以用一个数组next来保存,next[j] = k,表示当T[i] != P[j]时,j指针的下一个位置。另一个非常有用且恒等的定义,因为下标从0开始的,k值实际是j位前的子串的最大重复子串的长度。请时刻牢记next数组的定义,下面的解释是死死地围绕着这个定义来解释的。

public static int[] getNext(String ps) {

    char[] p = ps.toCharArray();
    int[] next = new int[p.length];
    next[0] = -1;
//模式串位置
    int j = 0;
//最大重复子串长度
    int k = -1;
    while (j < p.length - 1) {
//当最大重复子串还可以增加长度
       if (k == -1 || p[j] == p[k]) {
           next[++j] = ++k;
       } else {
//不可增加
           k = next[k];
       }
    }
    return next;
}

这个版本的求next数组的算法应该是流传最广泛的,代码是很简洁。
求next数组的时候一共有四种情况。
1.当j为0时,如果这时候不匹配,怎么办?
浅谈KMP算法_第5张图片
像上图这种情况,j已经在最左边了,不可能再移动了,这时候要应该是i指针后移。所以在代码中才会有next[0] = -1;这个初始化。
2.当j为1的时候
浅谈KMP算法_第6张图片
显然,j指针一定是后移到0位置的。因为它前面也就只有这一个位置了。
3.当P[k] == P[j]时,
浅谈KMP算法_第7张图片
有next[j+1] == next[j] + 1

其实这个是可以证明的:

因为在P[j]之前已经有P[0 ~ k-1] == p[j-k ~ j-1]。(next[j] == k)

这时候现有P[k] == P[j],我们是不是可以得到P[0 ~ k-1] + P[k] == p[j-k ~ j-1] + P[j]。

即:P[0 ~ k] == P[j-k ~ j],即next[j+1] == k + 1 == next[j] + 1。
4.P[k] != P[j]
浅谈KMP算法_第8张图片
从代码上看应该是这一句:k = next[k]。看了很多文章都说这里很难理解。我个人觉得就是当P[k] != P[j]的时候,k = next[k]的意思就是重新确定最大重复子串的长度,而这个长度就是next[k]的值,换个角度看k值就是当j值不匹配的时候模式串应该移动的位置。

本文章多有借鉴KMP算法,此篇文章写得更加详细,有兴趣的人可以多看看。

你可能感兴趣的:(算法)