KMP算法(3个人的名字的头字母,Knuth与Pratt和Morris),主要就是应用有限自动机的原理。
KMP算法的核心思想是利用已经得到的部分匹配信息来进行后面的匹配过程。
在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,当第一次搜索到S[5] 和T[5]不等后,S下标不是回溯到1,T下标也不是回溯到开始,而是根据T中T[5]==’d’的模式函数值(next[5]=2),直接比较S[5] 和T[2]是否相等(换到状态2中去),因为相等,S和T的下标同时增加。
一. 模式值next[n]的计算,也就是状态转移
一定要想成状态转移,eg: str数组为: ababaa
a | -1 |
ab | -1 |
aba | 0 |
abab | 1 |
ababa | 2 |
ababaa | 0 |
next[n]的值也就是假如它的下一个错了,它可以回溯的状态(只能向上)。next[n]的下标代表状态。状态的增加最多加一(依赖字符的增加)。
*第一个状态只有一个可回溯的,那就是-1状态。
*当str[i]==str[j+1] (j为上一个状态可回溯的状态),此时符合上一个状态的增加。所以next[i]=j+1。
*当str[i]!=str[j+1],此时i状态不符合上一个状态的增加,所以反复回溯再上一个状态,直至i状态符合j状态的增加或者状态-1。
二. 字符串的匹配
失配后,回溯,和生成next的时候一样,反复回溯再上一个状态,直至成功或者状态-1。
相关解释在代码中
1 /** 2 * KMP字符串匹配 3 * @author jslee 4 * 5 */ 6 public class kmp { 7 8 public static void main(String[] args) { 9 String str1 = "ababababbabababa"; 10 String str2 = "ababa"; 11 System.out.println(kmpMatch(str1,str2)); 12 } 13 14 public static int[] calcuNext(String string){ 15 if (string == null) 16 return null; 17 char[] str = string.toCharArray(); 18 int[] next = new int[str.length]; 19 int j; 20 next[0] = -1;//第一个状态只有一个可回溯的,那就是-1状态 21 for (int i = 1; i < next.length; i++) { 22 j = next[i-1]; //上一个状态 23 //上一个状态为-1时,不用再回溯 24 //str[i]!=str[j+1] 需要往上回溯 25 while (j>=0 && str[i]!=str[j+1]) { 26 j = next[j]; 27 } 28 //符合上一个状态的增加 29 if (str[i]==str[j+1]) 30 next[i] = j+1; 31 //回溯后也不符合,那就只有原始状态-1了 32 else 33 next[i] = -1; 34 } 35 return next; 36 } 37 public static int kmpMatch(String bigString, String string) { 38 if (bigString == null && string == null) 39 return -1; 40 int returnInt=0; //返回的值,整体匹配成功的个数 41 char[] bigStr = bigString.toCharArray(); 42 char[] str = string.toCharArray(); 43 int[] next = calcuNext(string); //next状态数组 44 int matchNum=0; //字符匹配的个数 45 for (int i = 0; i < bigStr.length; i++) { //遍历大数组 46 if (bigStr[i] == str[matchNum]) { //字符匹配成功,下一个字符 47 matchNum++; 48 }else {//失配,反复回溯再上一个状态,直至成功或者状态-1。 49 if (matchNum!=0) { //回溯 50 matchNum = next[matchNum-1]+1; 51 i--; //!!一定要记住i不变,再次判断此字符 52 }//matchNum为0时,不用回溯 53 } 54 if (matchNum == str.length) { //当整个匹配成功时,记录并回溯 55 matchNum = next[matchNum-1]+1; 56 returnInt++; 57 } 58 } 59 return returnInt; 60 } 61 }
三.变种算法
1.求字符串的最短循环节,对于每个i,求一个最大的整数K,使得字符串的前i个字符组成的前缀是某个字符子串重复K次得到。输出i和K
eg:aabaabaabaab -> i=2,6,9,12 时,K为 2,2,3,4
1 public static void main(String[] args) { 2 String str1 = "aabaabaabaab"; 3 int[] next = calcuNext(str1); 4 for (int i = 0; i < next.length; i++) { 5 //此处i-next[i] = (i+1)(当前位置) - (next[i]+1)(回溯的位置) 6 //当前位置 如果是 当前位置和回溯的位置之差 的倍数 那就代表这个“错位”刚好是一个循环节 7 if(next[i]!=-1 && (i+1)%(i-next[i])==0) 8 System.out.println("数组下标为"+i+" 找到了,K值为:"+(i+1)/(i-next[i])); 9 } 10 }