KMP算法的特点是主串指针不回溯,匹配串会在与主串当前元素不匹配时向后移动多位。算法的关键在于找出匹配串的next数组。
若next[n]=j;(next数组也可以有不同的存储方法,思想都是一样的)
本解法中next数组的下标n代表着已匹配的前缀长度为n(即假设str2[0~(n-1)]和主串是匹配的,而str2[n]和主串不同),因此next[0]和next[1]的值始终都为0,因为str[0],str[1]并没有公共前后缀。
j表示在找到了最长公共前后缀之后,用最长前缀子串的下一位str2[j](j等于最长公共子串长度,str2[j]恰恰就代表着最长子串的下一位)去与主串当前元素继续作比较。
在寻找next数组时,从next[2]开始为了找出之后每个next[i]的值并采用简洁的代码,对每个next[i]并不需要从小到大逐次增加前后缀的长度去找最长公共子串,这样会像暴力匹配。其实为了找出每个数组下标的最长公共子串,也利用了KMP算法的思想,从代码就可以看出来主体算法和next算法两部分基本也是一样的。这时候是匹配串自己充当主串并和自身匹配去寻找最长匹配子串的一个过程,此时随着i的增加在一开始就累加j,j的值代表着当前i下最长公共字串的长度,随着i增加一直匹配时每次将该next[i]置为j+1;如果i增加了1位后,dest[j]!=dest[i],那么令j=next[j],往前缩几位继续看是否匹配,如果一直不匹配,那会到j=0,如果此时还是dest[0]!=dest[i],没有公共串,则记该next[i]为0。
具体细节看代码。
kmp解释视频:https://www.bilibili.com/video/BV1jb411V78H?t=288
https://baijiahao.baidu.com/s?id=1659735837100760934&wfr=spider&for=pc 形象解释了求next数组的取巧地方。
public class KMPAlgorithm {
public static void main(String[] args) {
String str1 = "GGGTTGTGTGCGTGTGCFTTTGG";
String str2 = "GTGTGCF";
// String str1 = "aaaabcdef";
// String str2 = "abc";
// int[] ints = kmpNext("AAAB");
// int[] ints = kmpNext("AAABA");
int[] ints = kmpNext(str2);
System.out.println(Arrays.toString(ints));//[0, 0, 0, 1, 2, 3, 0]
int index = kmpSearch(str1, str2, kmpNext(str2));
System.out.println(index);
}
/**
* @param str1 主串
* @param str2 匹配串
* @param next next数组,数组的下标代表了“已匹配前缀的下一个位置”,元素的值则是“最长可匹配前缀子串的下一个位置”。
* @return 第一次出现的索引位置
*/
public static int kmpSearch(String str1, String str2, int[] next) {
// 不能加这个条件!!i是可以比较完恰好到最后的
// for (int i = 0, j = 0; i < str1.length() - str2.length() + 1; i++) {
for (int i = 0, j = 0; i < str1.length() ; i++) {
while (str1.charAt(i) != str2.charAt(j) && j > 0) {
j = next[j];
}
if (str1.charAt(i) == str2.charAt(j)) {
j++;
}
if (j == str2.length()) {
return i - j+1;
}
}
return -1;
}
public static int[] kmpNext(String dest) {
int[] next = new int[dest.length()];
next[0] = 0;
next[1] = 0;
for (int i = 2, j = 0; i < dest.length(); i++) {
//回溯
while (dest.charAt(i - 1) != dest.charAt(j) && j > 0) {
j = next[j];
}
if (dest.charAt(i - 1) == dest.charAt(j)) {
j++;
}
next[i] = j;
}
return next;
}
}