提示:请结合上一篇文章一起阅读。
子串的移动:
现在我们用一个int类型的数组来记录当前字符前面最多有多少个子字符与字符串一开始是相同的。
前两个字符的next数组对应的值都是0,第一个是0,是因为他前面没有字符,第二个字符前面就一个字符与字符串一开始的字符是同一个字符,如果算上的话,那么从相同的字符的下一个字符开始比较,还是失配点与第二个字符开始比较,没有意义,所以前两个字符对应的值都是0.
Next数组的得出:当前字符的前一个字符与前一个字符的next数组的值对应的字符是否相等,如果相等,当前字符的next数组的值就是前一个字符的next的值+1,如果不相同,就比较前一个字符与前一字符的next数组的值对应的字符的next数组的值对应的字符是否相等,相等当前字符的值就是那个相等字符的next值+1,不相等就继续往前移动,知道移动到前两个字符都不相同,那个当前字符的next数组的值就是0了。
上面的那段话可能太抽象了,现在再给出一组例子:
下标:0 1 2 3 4 5 6 7 8 9 0 1 2 3
Next:0 0 0 0 1 2 3 4 5
子串:a b c a b c a b a b a b c d
下标为0 – 8与上一个例子是一样的,我就不再去跟踪了,不懂的,再去跟踪下上一个例子,从下标为9的字符开始:
下标为9的字符b的前一个字符下标为8的a与此a的next数组的值5对应的字符c去比较不相同,那么用下标为8的字符a与 下标为next[8] = 5 的字符c对应的next数组的值next[5] = 2对应的字符c比较还是不相同,再次用下标为8的字符a与next[2] = 0 的字符去比较,此时下标为8的字符a与下标为next[2] = 0的字符相同,next[8] = next[0] + 1 = 1.
下标:0 1 2 3 4 5 6 7 8 9 0 1 2 3
Next:0 0 0 0 1 2 3 4 5 1
子串:a b c a b c a b a b a b c d
下标为10的前一个字符为下标为9的b,此b与next[9] = 1为下标的字符是相同的,所以next[10] = next[9] + 1.
下标:0 1 2 3 4 5 6 7 8 9 0 1 2 3
Next:0 0 0 0 1 2 3 4 5 1 2
子串:a b c a b c a b a b a b c d
….
最终的结果为:
下标:0 1 2 3 4 5 6 7 8 9 0 1 2 3
Next:0 0 0 0 1 2 3 4 5 1 2 1 2 3
子串:a b c a b c a b a b a b c d
手工过程到这里基本就结束了,现在该编程了,建议没有弄懂我的手工过程的人可以多跟踪几遍,再去看编程:
package com.zhangyike.Kmp;
import java.util.Arrays;
import java.util.Scanner;
public class StringKmp {
public static void main(String[] args) {
Scanner can = new Scanner(System.in);
try{
String str = can.nextLine();
String subStr = can.nextLine();
if (filter(str,subStr)) {
if (str.length() < subStr.length()) {
System.out.println(-1);
}else{
int index = findString(str.trim(),subStr.trim());
System.out.println(index);
}
}else{
System.out.println("字符串输入不合法,请重新输入!");
}
}finally{
can.close();
}
}
private static int findString(String str, String subStr) {
int[] next = new int[subStr.length()];
kmpNext(next,subStr);
System.out.println(Arrays.toString(next));
return findSubString(str,subStr,next);
}
private static int findSubString(String str, String subStr, int[] next) {
int i = 0;//控制源字符串的下标
int j = 0;//控制子串的下标
int len = str.length() - subStr.length();//i的最大下标
//i小于len并且j < subStr.length()(j没有比较完),循环继续
while (i < len && j < subStr.length()) {
/*每次子字符串与源字符串开始比较的下标是从next[j]开始比较的,不是从子字符串的开头开始比较的
* subStr没有比较完,并且subStr[j] == str[i] 的时候,
* 执行i++,j++,继续比较下一个字符
* 直到j比较完了,或者遇到subStr[j] != str[i] 的时候,跳出for循环
* */
for(j = next[j]; j < subStr.length() && subStr.charAt(j) == str.charAt(i); i++, j++)
;
//j没有比较完,而且j 的值为0,说明子字符串与源字符串失配点的下一个下标必须从头开始的。
if (j < subStr.length() && j == 0) {
i++;
}
}
return j >= subStr.length() ? -1 : i - j;
}
private static void kmpNext(int[] next, String subStr) {
//因为next函数下标为0 和 1 的元素的值都为 0,为其他值没有意义。
if (subStr.length() < 3) {
return;
}
int index = 0;//控制next函数下标值得变化。
//for循环从下标为2的第三个元素开始依次为next函数的元素赋值
for (int i = 2; i < next.length; i++) {
/*
* 因为当前下标i对应的next[i]的值是他前面最多有多少个字符与从字符串一开始是相等的,所以是i - 1。
* index = 0 的时候是next数组的第0或者1的元素,直接让他为0.跳出循环去处理
* subStr.charAt(i - 1) != subStr.charAt(next[index]:当前元素i的前一个字符与他前一个字符的next[index]
* 对应的字符不相等,执行步长:index = next[index],继续下一次的比较。
*/
for (index = i - 1;index != 0 && subStr.charAt(i - 1) != subStr.charAt(next[index]);
index = next[index])
;
next[i] = index == 0 ? 0 : next[index] + 1;
}
}
private static boolean filter(String str, String subStr) {
if (str != null && str.length() > 1 && subStr != null && subStr.length() > 1) {
return true;
}
return false;
}
}