开头:之前已经看过好几遍 KMP 了,但每次一想就又忘了,可恶!!!
所以今天我到处学习,就写一个 炒 鸡 详 细 的 KMP 的思路过程,这我要再能忘真就白痴了(FLAG高高立起)。
废话不多说,开始我的表演:
两个字符串,text,pattern,
要求:在text中查找pattern。
思路:
KMP算法的精髓和text是没有关系的,精髓在于处理pattern之中。
用i遍历text,j遍历pattern。
我们知道,常规的暴力方法每匹配失败的时候,i总会回到原来的地方+1,于是时间复杂度就变成了O(MN)之高。
而KMP算法则不同,KMP算法的i只会往前进,这都得益于我们对pattern数组的绝妙处理。
重点来了!!
对pattern数组的处理本质上便是找到每个位置对应的相等的最大前后缀。
很绕口,我们来举个栗子!
加入pattern是 a b a b c a b a a
我们分解为
1、a(len = 0)
2、a b(len = 0)
3、a b a(len = 1)
4、a b a b(len = 2)
5、a b a b c(len = 0)
6、a b a b c a(len = 1)
7、a b a b c a b(len = 2)
8、a b a b c a b a (len = 3)
9、a b a b c a b a a(len = 1)
每一段括号里的数字就是该段对应的最大公共前后缀。
我们发现在第七行到第八行,如果我们要让最大公共前后缀加一,我们必须使得新添加的元素等于pattern[len]。
我们只需要用一个prefix数组将它存起来,就快大功告成了。
那么到这里可能就有人问了,可恶,前缀怎么求啊??!
代码配注释:
int i = 1;
int len = 0;//记录当前比较位置之前的字符串的长度
while(i<pattern.length()){
if(pattern.charAt(i) == pattern.charAt(len)){//好理解
len++;
prefix[i] = len;
i++;
}else{
if(len>0){
len = prefix[len-1];//重难点在此,为什么要这样
//相当于prefix[len]和pattern[i]匹配失败,但是说不定pattern[prefix[len-1]]可能会匹配成功
}else{
prefix[i] = 0;
i++;
}
}
}
}
而这里有个小技巧:
001201231
-100120123
将数组所有元素右移一位,最左边补-1,这将有利于我们后续的操作。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 0 1 2 3 4 5 6 7 8 9 10 11 12 13
例如我们要在 text:a b a b a b a b c a b a a b
;;;;;;;;;;查找pattern:a b a b c a b a a
我们建立的prefix:-1 0 0 1 2 0 1 2 3
用 i 遍历text,j 遍历pattern.
上代码
int j = 0;
int i = 0;
while(i<text.length()){
if(text.charAt(i) == pattern.charAt(j)){
i++;
j++;
if(j == pattern.length()) return i-j;
}else{
if(j>0){
j = prefix[j];
}else{
j = 0;
i++;
}
}
}
return -1;
}