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算法的主要内容,如果未能解决你的问题,请参考以下文章