序:很久没做算法题了,为了回顾一下自己的算法知识,方便下次理解,特地记录自己一些对一些算法的理解。
约定:
模式串 ababcd
文本串 abababcd
用M代表模式串,W代表文本串
kmp算法包括两个部分,1.计算模式串的next数组。 2.kmp主程序,模式串与主串(即文本串)的匹配。
Next数组
next[i]表示字符串第i个字符可匹配的最近的下标(挺拗口的),作用是记录已经遍历过的字符内的信息。
先看计算next数组的程序,
private static int[] getNext(char[] str) {
int length = str.length;
int[] next = new int[length];
next[0] = -1;
int k = -1;
for(int i=1; i<length; i++) {
while(k > -1 && str[i] != str[k+1]){
k = next[k];
}
if(str[i] == str[k+1]) {
k++;
}
next[i] = k;
}
return next;
}
字母下方即是模式串的next数组
a b a b c d
-1 -1 0 1 -1 -1
从结果来看,我们可以看到next[3] = 1,含义是它的可匹配的最近的下标是1。其实从这时候来看还是觉得没什么作用,我们下面在匹配文本的时候具体分析。
kmp主程序
下面是kmp主算法:
1: public static int kmp(final char[] text, final char[] pattern) {
2: // 检查是否为空
3: if(isEmpty(text) || isEmpty(pattern)) {
4: return -1;
5: }
6:
7: int lengthText = text.length;
8: int lengthPattern = pattern.length;
9: int[] next = getNext(pattern); // 获取模式串的next数组
10:
11: int k = -1;
12: for(int i=0; i<lengthText; i++) {
13: while(k > -1 && text[i] != pattern[k+1]) {
14: k = next[k]; // 若不相等则找出与k可匹配的最近的下标
15: }
16: if(text[i] == pattern[k+1]) {
17: k++;
18: }
19: if(k == lengthPattern - 1) {
20: // 表示找到了,再赋值k,继续下次寻找
21: System.out.println("find position is " + (i - lengthPattern + 1));
22: k = next[k];
23: }
24: }
25:
26: return -1;
27: }
看了上面的代码,可以发现其实和计算next数组的代码差不多。其实他们的本质是一样的,计算next数组是模式串匹配自己,而kmp主程序是模式串匹配文本串。
匹配过程
从文本串的第一个字母开始匹配,文本串下标为int i,初始化为0。模式串的下标为k+1,k为int型,k初始化为-1。
下面是遍历到i = 4, k = 3,时的情况,当发现两者不相同时,k=next[k],所代表的意义是模式串的下标k位置的字符与前面的字符串哪一个字符最近可匹配,就是这个步骤可以省去和前面字符比较的时间(因为前面都相同)。所以当你发现文本串i位置和模式串j位置匹配不成功时,你可以比较模式串(next[j-1] +1)和文本串的i位置是否相等,如不相等如此循环查找(kmp主程序的13~15行)。所以此时的情况是k = next[k]之后k = 1,然后比较M(k+1)与W(i),我们发现此时是相等的,这一步就是kmp和普通模式串匹配的差别,它省去了再去从模式串的开头进行比较,而是直接从下标2开始比较。
0 1 2 3 4 5 6 7
a b a b a b c d
a b a b c d
kmp的算法复杂度(O(n))
设主串的长度为n,模式串为m,n>m。
我们可以这样计算,首先是对主串的遍历,复杂度是n,主串的循环内的while循环,我们可以追踪k的变化,k++的过程只能在每次循环时进行,k=next[k]是k的减少,k++最多发生n次,k=next[k]发生最多的次数是n/m *m = n,所以总的算法复杂度是3*n,就是O(n)的。
kmp的用处场景
1.查找模式串是否在目标串
2.计算字符串是由哪个或哪几个字符串循环而来
3.查找模式串在目标串的哪些位置
4.最长公共子串
kmp的优化
以上我对kmp的理解来自于《算法导论》这本书,其实kmp还可以优化。一位大牛在网上论述了他对kmp的优化,下面是他的链接http://www.if-yu.info/2010/11/23/kmp-complexity.html
他的kmp思想跟导论不同在于next数组--直接记录每个字符匹配失败后应该去匹配哪个字符,这样就可以优化重复匹配失败的字符,而导论检查的是前一个字符。
后记:转载请标明出处,谢谢。 ----- doubleHHHH
来源:https://www.cnblogs.com/hhuang2012/archive/2012/05/29/kmp.html