本文共 3637 字,大约阅读时间需要 12 分钟。
一、KMP解决的问题
一个字符串s1=“BBCABCDABABCDABCDABCDABDE”,和一个模式串s2=“ABCDABD”;现在要求s2于s1匹配的开端位置。如本串的位置就是 “BBCABCDABABCDABCDABCDABDE”,开端位置就是A,下标为17二、KMP的过程
KMP主要分为两个过程。一个是求next数组。另一个是根据next数组移动。
(一)求next数组 (1)求解next数组,next数组是对于s2模式串来说的。 求解next数组首先要明白前缀和后缀的概念,这边不再重复造轮子,关于这个概念看其他的博客,其实说白了 前缀就是:ABCDABD 后缀就是:ABCDABD 当这两部分能相等,且前缀最长不能到达最后一个字符,后缀不能到达第一个字符的最长串,就是最长前后缀。 next数组保存的就是从第一个字符到当前字符之前的这个串的最长前后缀: 比如:对于ABCDABD中的B来说,它的最长前后缀就是ABCDA这部分的值。因为只有前后缀都为A时才相等。所以B这个位置的最长前后缀就是1。 (2)如何快速求得next数组呢,其实求这个数组有一个规律。 首先next[0]和next[1]认为规定为: next[0] = -1; next[1] = 0; 接下来从i = 2开始都遵循这样的一个规律: 如果s2[i-1]等于(i-1)这个位置的最长前后缀中的前缀的下一个字符;如果不相等,则以这个字符,判断其最长前后缀的前缀字符是否等于,直到找到相等的,则最长前缀就是这个最长前缀+1,否则为0; 所以可以求得上面这个串的next={-1,0,0,0,0,1,2}private static int[] getNextArray(char[] str2) { int[] next = new int[str2.length]; next[0] = -1; next[1] = 0;//前面这两个数是认为规定的 int i = 2; int cn = 0;//代表分界指标,这边有一个隐含的条件,最长前缀和最长后缀的长度==最长前缀的最后一个字符下标 while(i < str2.length) { if(str2[i - 1] == str2[cn])//判断最长前后缀的前缀的下一个字符是否等于当前判断的位置的前一个字符 { next[i++] = ++cn;//存在则值为最长前后缀的前缀的长度+1 } else if(cn > 0)//前一个字符和最长前缀的下一个字符不相等,则以最长前缀下一个字符为切分点继续切分 { cn = next[cn]; } else { next[i++] = 0;//cn来到了没有最长前缀,则i这个位置的next值为0 } } return next; }
(二)移动求解过程
根据我们求得的KMP就可以快速找到是否相等的匹配串。 主要由三部分组成: (1)判断字符是否相等,如果相等,匹配下一个字符 (2)如果不相等,从查找当前不相等字符的next值,模式串移动next值对应的最长前缀。其中的原理可以参考其他博客。 比如 s1 = “BBCABCDABABCDABCDABCDABDE” s2 = “ABCDABD” 当前匹配到s1的A和s2的D不相等,也就是当前的指向i1来到了A位置,i2来到了D位置。则查找D这个位置的next数组中的值。根据上面生成的next数组可以知道next[D]=2,则i2指向s2的C位置,因为前面的是最长前缀,一定跟s1的那部分相等,所以只需要从c位置开始验证,如果next[D]==-1,也就是没有最长前缀,从我们的规定next数组可以看出,这个时候其实已经来到了s2的首字母,则从新开始匹配。public static int getIndexOf(String s1, String s2) { char[] str1 = s1.toCharArray(); char[] str2 = s2.toCharArray(); int[] next = getNextArray(str2); int i1 = 0;//主子串下标 int i2 = 0;//匹配子串下标 while(i1 < str1.length && i2 < str2.length) { if(str1[i1] == str2[i2])//如果字符相等,则继续往前匹配 { i1++; i2++; } else if(next[i2] == -1)//如果不相等,且当前字符已经没有最长前缀和最长后缀的匹配 { i1++;//顺序匹配下一个字符 } else { i2 = next[i2];//存在最长前缀和最长后缀相等大于0的,则从最长前缀的下一个字符开始匹配,最长前缀那一部分不用匹配是因为 //一定是相等的 } } return i2 == str2.length? i1 - i2:-1;//如果跳出循环的是因为匹配到最后一个字符正确跳出的,则存在子串,否则不存在返回-1 }
总的代码如下:
package lc.Top.Inte;public class KEMAlgorithm { /** * 根据next移动,找到匹配串的开端下标 * @param s1 * @param s2 * @return */ public static int getIndexOf(String s1, String s2) { char[] str1 = s1.toCharArray(); char[] str2 = s2.toCharArray(); int[] next = getNextArray(str2); int i1 = 0;//主子串下标 int i2 = 0;//匹配子串下标 while(i1 < str1.length && i2 < str2.length) { if(str1[i1] == str2[i2])//如果字符相等,则继续往前匹配 { i1++; i2++; } else if(next[i2] == -1)//如果不相等,且当前字符已经没有最长前缀和最长后缀的匹配 { i1++;//顺序匹配下一个字符 } else { i2 = next[i2];//存在最长前缀和最长后缀相等大于0的,则从最长前缀的下一个字符开始匹配,最长前缀那一部分不用匹配是因为 //一定是相等的 } } return i2 == str2.length? i1 - i2:-1;//如果跳出循环的是因为匹配到最后一个字符正确跳出的,则存在子串,否则不存在返回-1 } /** * 计算next数组 * @param str2 * @return */ private static int[] getNextArray(char[] str2) { int[] next = new int[str2.length]; next[0] = -1; next[1] = 0;//前面这两个数是认为规定的 int i = 2; int cn = 0;//代表分界指标,这边有一个隐含的条件,最长前缀和最长后缀的长度==最长前缀的最后一个字符下标 while(i < str2.length) { if(str2[i - 1] == str2[cn]) { next[i++] = ++cn; } else if(cn > 0)//前一个字符和最长前缀的下一个字符不相等,则以最长前缀下一个字符为切分点继续切分 { cn = next[cn]; } else { next[i++] = 0;//cn来到了没有最长前缀,则i这个位置的next值为0 } } return next; } public static void main(String[] args) { String s1 = "BBCABCDABABCDABCDABCDABDE"; String s2 = "ABCDABD"; int indexOf = getIndexOf(s1, s2); System.out.println(indexOf); }}
转载地址:http://txrai.baihongyu.com/