KMP算法实践与简单分析

Posted Qcer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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实现如下

 1 package agstring;
 2 
 3 public class KMP {
 4     public static int[] getNextAry(String sub){
 5         int subLenght = sub.length();
 6         int[] next = new int[subLenght];
 7         int t = next[0] = -1,j = 0;
 8         while(j < subLenght-1){
 9                 if(t < 0 || sub.charAt(t) == sub.charAt(j)){
10                     t++;
11                     j++;
12                     next[j] = t;//可优化
13                 }else {
14                     t = next[t];
15                 }
16         }
17         return next;
18     }
19     public static int[] getNextAryExt(String sub){
20         int subLenght = sub.length();
21         int[] next = new int[subLenght];
22         int t = next[0] = -1,j = 0;
23         while(j < subLenght-1){
24                 if(t < 0 || sub.charAt(t) == sub.charAt(j)){
25                     t++;
26                     j++;
27                     next[j] = sub.charAt(t) != sub.charAt(j)?t:next[t];
28                 }else {
29                     t = next[t];
30                 }
31         }
32         return next;
33     }
34 
35     /*
36      *i为主串位置指针,j为sub串位置指针
37      *j<0的情况为sub串的位置指针为0,且sub[0] != base[i]
38      *匹配能够成功的情况必为j==subLength
39      * */
40     public static int  matchOfKMP(String base,String sub){
41         int baseLength = base.length();
42         int subLength = sub.length();
43         int i = 0,j = 0;
44         int[] next = getNextAryExt(sub);
45         while(i < baseLength && j < subLength){
46             if(j < 0 || base.charAt(i) == sub.charAt(j)){
47                 i++;
48                 j++;
49             }else {
50                 j = next[j];
51             }
52         }
53         int result = j == subLength?i-j:-1;
54         return result;
55     }
56     
57     public static void main(String[] args) {
58         try {
59             String base = "ababghababa";
60             String sub = "ababap";//chinchilla,ababaaaba,
61             int result = matchOfKMP(base, sub);
62             System.out.println(result);
63         } catch (Exception e) {
64             // TODO: handle exception
65             e.printStackTrace();
66         }
67     }
68 }

 

以上是关于KMP算法实践与简单分析的主要内容,如果未能解决你的问题,请参考以下文章

kmp算法详解

编程实践使用golang 解析json字符串代码 / 使用 golang 实现一个HashSet / 使用C语言实现KMP算法,并加上非常详尽的注释。

前缀函数与KMP算法

图解KMP算法原理及其代码分析

kmp算法的个人理解

kmp算法原理与应用(简单易懂)