KMP算法

Posted COOL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP算法相关的知识,希望对你有一定的参考价值。

                                  KMP算法---看了就明白

    笔者学习串的匹配时,就是在目标串(主串)中找到与模式串(子串)一样的部分,返回它的子串位置的操作,这叫串的模式匹配。

   一种效率低的算法,主串与子串从第一个字符进行比较,直到某一个不相等,然后主串退回到第二个字符重新开始,子串重新从首字符开始与主串进行匹配,一直循环进行比较,这样的话,就存在主串与子串前几个字符已经相等了,可是重新匹配时,又要重新进行比较,每次匹配没有利用前一次匹配的结果,使得算法中存在较多的重复比较,降低了算法效率。

    KMP算法,很好的利用了上次匹配的结果,目标串不会回溯,效率大大增强,在网上也看了很多博客,有了一个比较深的认识,现在尽量写的通俗易懂,希望可以帮助到各位。

   假设目标串target="t0 ...tn",模式串pattern=“p0...pm”,依次进行比较,如果遇到不相等的字符,目标串的下标i不回溯,而模式串有一个下标k来决定模式串跳到对应下标为k的字符,也就是目标串“ti...tn”与“pk...pm”进行依次匹配,所以计算k值变得非常重要,这个也是KMP算法的核心。

    模式串的每一个pj对应的k值都不同,所以定义了一个next数组来存储。这里就有一个前缀后缀的概念,前缀子串就是从第一个字符开始,除了最后一个字符,所组成的各种子串形式,后缀相反。比如字符串“abcad”的前缀子串为a,ab,abc,abca,后缀子串分别为d,ad,cad,bcad。

   next[j]的值就是k,也就是最大前缀后缀子串的长度。假定next[0]=-1。举个例子,模式串“abcabc”的j对应的k值,见下表:

j 0 1 2 3 4 5
模式串 a b c a b c
最长相同前后缀子串的长度k -1 0 0 0 1 2

     接下来就是如何计算next数组,

 

     KMP算法充分利用前一次计算的结果,由next[j]逐个递推计算得到next[j+1];

约定next[0]=-1,而且next[1]=0;对于next[j]=k知,"p0...pj-1"中有最长为k的前缀子串和后缀子串相等,也就是"p0...pk-1"="pj-k...pj-1",接下来就是next[j+1],则”p0...pj“中求最长前缀后缀子串,因为next[j]=k,所以只需求pk是否等于pj,如果等于,则next[j+1]=next[j]+1=k+1,如果不相等,在”p0...pj“中找较短的前缀后缀子串,只需求next[k]即可得到新的k值,即next[j+1]=k+1。

 

    还有一种改进了的计算next数组的方法,比上述方法的效率更高,就是在匹配pj不等于ti之后,比较一下Pj和pk(此时,k=next[j])的大小,如果不相等,就next[j]=k,如果恰好相等,就不用再比较ti和pk的值了,肯定不相等,下次匹配从pnext[k]开始比较,next[j]=next[k],比较的次数就减小了。

至此,KMP算法就说完了,代码附上加以理解。

 1 public class KMP {
 2     private static int[] next;
 3 
 4     public static int indexOf(String target, String pattern) {
 5         return indexOf(target, pattern, 0);
 6     }
 7 
 8     public static int indexOf(String target, String pattern, int begin) {
 9 
10         // 返回目标串从begin开始,模式串与目标传匹配的序号
11         int n = target.length(), m = pattern.length();
12         int i = begin, j = 0;
13 
14         next = getNext(pattern);
15         // next=getNexts(pattern);
16 
17         if (i < 0)
18             i = 0; // 对开始的位置begin进行容错处理
19         if (i >= n || n == 0 || m > n)
20             return -1;// 目标串为空或比模式串短或开始位置越界,不进行比较
21         while (i < n && j < m) {
22             if (j == -1 || target.charAt(i) == pattern.charAt(j)) {
23                 i++;
24                 j++;
25 
26             } else { // 若匹配不成功,目标串下标i不回溯
27                 j = next[j]; // 获得模式串下一趟开始匹配的下标
28 
29                 if (n - i + 1 < m - j + 1)
30                     break; // 目标串的长度不够模式串的长度,不进行比较
31             }
32         }
33         if (j == m) {
34 
35             return i - j;// 返回匹配的子串序号
36         }
37         return -1;
38     }
39 
40     // next数组的计算
41     public static int[] getNext(String pattern) {
42         int j = 0, k = -1, next[] = new int[pattern.length()];
43         next[0] = -1;
44         while (j < pattern.length() - 1) {
45             if (k == -1 || pattern.charAt(j) == pattern.charAt(k)) {
46                 j++;
47                 k++;
48                 next[j] = k; // 在“p0~pj-1”串中存在为k的相同前缀和后缀子串
49                 // next[j+1]求在“p0~pj”中找相同前缀和后缀子串,只需判断pk和pj的值是否相等
50                 // 相等,就next[j+1]=next[j]+1=k+1
51 
52             } else {
53                 k = next[k];// 不相等,则找较短的相同前缀和后缀子串,长度k为next[k],再比较,相等则next[j+1]=next[k]+1
54             }
55         }
56         return next;
57 
58     }
59 
60     // 改进后的next数组计算
61 
62     public static int[] getNexts(String pattern) {
63         int j = 0, k = -1, next[] = new int[pattern.length()];
64         next[0] = -1;
65         while (j < pattern.length() - 1) {
66             if (k == -1 || pattern.charAt(j) == pattern.charAt(k)) {
67                 j++;
68                 k++;
69                 if (pattern.charAt(j) != pattern.charAt(k))// pj!=pk,有k个相同前缀和后缀子串
70                     next[j] = k;
71                 else {
72                     next[j] = next[k];// pj=pk,则下次模式串从pnext[k]开始进行比较
73                 }
74 
75             } else {
76                 k = next[k];
77             }
78         }
79         return next;
80 
81     }
82 
83     public static void main(String[] args) {
84 
85         String target = "abcadbabcab", pattern = "abcab";
86         System.out.println(KMP.indexOf(target, pattern));
87 
88     }
89 }

 

如果还不能很深刻理解KMP,建议大家看一下 数据结构(Java版)(第四版)(叶核亚)这本书P79-P84,笔者很大部分参考了这本书。

或者访问这个博客,http://www.cnblogs.com/SYCstudio/p/7194315.html笔者觉得这个博客的作者画的动图更能加深认识。

   

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

数据结构—串KMP模式匹配算法

Python ---- KMP(博文推荐+代码)

KMP算法及Python代码

KMP算法及Python代码

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

Kmp算法Java代码实现