KMP算法实践与简单分析

时间:2023-03-09 08:52:54
KMP算法实践与简单分析

一、理解next数组

1、约定next[0]=-1,
同时可以假想在sub串的最前面有一个通配符“*”,能够任意匹配。对应实际的代码t<0时的处理情况。

2、next[j]可以有如下的几种理解思路:
1)next[j]为sub[j]前面的字符串的前后缀字符串匹配的最大匹配长度
例如sub=“ababap”
next[5]=3,前后追匹配字符串为“aba”
2)在sub[j]位置匹配失败后,next[j]为为sub串的位置指针j能够先前回溯到的位置。
3)next[j]为最长前缀匹配串的下一个字符位置(这就是为什么求next数组时需要有t=next[t]这一步。)

由此不难发现:
next[j]的值越大,在base[i]与sub[j]处匹配失败时,sub串的位置指针j需要回溯的跨越长度越小。
反之,
next[j]的值越小,在base[i]与sub[j]处匹配失败时,sub串的位置指针j需要回溯的跨越长度越大。
极端情况下,next[j]为0,sub串的位置指针j直接回溯到sub串起始位置。

二、理解KMP主算法

1、base串的位置指针i在匹配的过程中始终不会向前回溯,这也是KMP算法较蛮力匹配算法高效的原因。
2、当base[i]和sub[j]匹配失败时,sub串的位置指针j回溯,j变小,等效于将sub串向右移动。

j回溯到next[j]的位置。

三、理解改进的next数组

改进的next数组的取值优化算法:

if (sub.charAt(t) != sub.charAt(j)) {
next[j] = t;
}else{
next[j] = next[t];
}

考虑对于base主串和sub串如下:
String base = "aaaabcde";
String sub = "aaaaax";
用改进的next数组取值为[-1,-1,-1,-1,-1,4]
当b=base[4] != sub[4]=x时,j=next[j]=-1,直接跳到sub串的哨兵“*”位置,然后进入j<0,进而i++,j++,中间省略了层层回溯的步骤。

其原理相当于简化了将KMP主算法中的sub位置指针j的跳转条件t = next[t];的负担。
因为在KMP主算法中base[i] != sub[j]时,j经过第一次回溯之后,如果出现sub[[next[j]]]=sub[j]的话,不难推断sub[[next[j]]]=sub[j]!=base[i],那么这一次回溯是没有实际效果的,j必将还要向前回溯。。。基于这样的考虑,直接对next数组做优化处理,避免了主算法中这样的层层回溯,能够减少主算法中while循环的次数。

改进的next数组能够避免sub串的位置指针j层层向前回溯,保证每次j的回溯都是有效的。

四、java实现如下

 package agstring;

 public class KMP {
public static int[] getNextAry(String sub){
int subLenght = sub.length();
int[] next = new int[subLenght];
int t = next[0] = -1,j = 0;
while(j < subLenght-1){
if(t < 0 || sub.charAt(t) == sub.charAt(j)){
t++;
j++;
next[j] = t;//可优化
}else {
t = next[t];
}
}
return next;
}
public static int[] getNextAryExt(String sub){
int subLenght = sub.length();
int[] next = new int[subLenght];
int t = next[0] = -1,j = 0;
while(j < subLenght-1){
if(t < 0 || sub.charAt(t) == sub.charAt(j)){
t++;
j++;
next[j] = sub.charAt(t) != sub.charAt(j)?t:next[t];
}else {
t = next[t];
}
}
return next;
} /*
*i为主串位置指针,j为sub串位置指针
*j<0的情况为sub串的位置指针为0,且sub[0] != base[i]
*匹配能够成功的情况必为j==subLength
* */
public static int matchOfKMP(String base,String sub){
int baseLength = base.length();
int subLength = sub.length();
int i = 0,j = 0;
int[] next = getNextAryExt(sub);
while(i < baseLength && j < subLength){
if(j < 0 || base.charAt(i) == sub.charAt(j)){
i++;
j++;
}else {
j = next[j];
}
}
int result = j == subLength?i-j:-1;
return result;
} public static void main(String[] args) {
try {
String base = "ababghababa";
String sub = "ababap";//chinchilla,ababaaaba,
int result = matchOfKMP(base, sub);
System.out.println(result);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}