KMP算法核心思想就是跳过已经对比过的字符,不进行重复对比,在代码中比较难以理解的其实就是下面这段代码:
//源字符串:str1 匹配字符串:str2
while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
j = next[j - 1];
}
上面这段代码是借助了匹配表(next数组)所实现的,同时这也是最难以理解的地方,但它也是KMP实现的核心;按照我的理解就是:当匹配字符串str2的 j 个位置的值与str1在 i 的位置的值匹配失败时,那么此时应该在next数组中找到 j 位置的前一个位置元素的值,也就是找到next数组中 j - 1这个位置的值,该匹配值告诉我们下一个与 str1.charAt(i) 需要对比的str2中的字符的下标,这就是它的核心所在,我也是通过画图推演,花了几天才弄明白,匹配值就是为了让我们忽略已经对比的过的字符,减少重复对比。
完整KMP代码实现以及思路:
package com.zc.study;
import java.util.Arrays;
/**
* @author zc
* 模拟KMP算法实现
*/
public class KMPDemo {
public static void main(String[] args) {
String str1 = "AAB AABAA BAABABCDABDA AB";
String str2 = "ABCDABD";
String str3 = "ABCDABAABAA BAABABCDABDA AB";
String str4 = "ABCDABD";
String str8 = "ABAB";
/**
* 思路:
* 假设源字符串中的指针为i,匹配字符串中的指针为j;
* 当匹配字符串和源字符串出现字符匹配失败时,
* 获取匹配字符串中当前字符的前一个字符所在的next数组中的值,也就是next[j-1],让它等于j
* j此时所代表的是当前需要对比的元素下标(这里比较难以理解)
*/
String str6 = "ABABABCDABAA BAABABABABDA AB";
String str5 = "ABABABD";
System.out.println(Arrays.toString(matchTable(str8)));
System.out.println(kmpSearch(str6, str5));
}
/**
* @param str1:源字符串
* @param str2:匹配字符串
* @return 返回匹配字符串在源字符串中的开始索引
* */
public static int kmpSearch(String str1,String str2){
//计算得出匹配表
int[] next = matchTable(str2);
//i是str1中的字符指针,j是str2中的字符指针,字符串的对比是通过两个指针来进行的
for (int i = 0,j = 0; i < str1.length(); i++) {
//当源字符串与匹配字符串不相同时,通过匹配表的规则,获取到当前指针的上一个位置:next[j - 1],然后将源字符串的当前字符与str2.charAt(next[j - 1])做对比,如果一直不同,就一直重复进行对比,直到j=0,也就是指针指向了匹配字符串的第一个位置为止
while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
j = next[j - 1];
}
//对比成功,指针j后移,继续比较两个字符串中的下一个字符
if(str1.charAt(i) == str2.charAt(j)){
j++;
}
//证明匹配成功,str1中包含了str2,返回下标
if(j == str2.length()){
//为什么索引位置是i + 1 - j,因为数据元素一共有 i+1 个,下标为0的数据元素也是其中之一,如果直接i-j就会少计算一个数据元素的个数
return i + 1 - j;
}
}
//没有找到指定字符串
return -1;
}
/**计算目标字符串匹配表
* 匹配表中每个元素的值实际代表的是匹配字符串每个字符的索引
* */
public static int[] matchTable(String str){
int[] next = new int[str.length()];
//当字符串只有一个字符时,公共前后缀的长度必定为0,所以第一个元素的值设定为0
next[0] = 0;
for (int i = 1 , j = 0; i < next.length; i++) {
//当字符不相同时,将当前next[j]的上一个位置的值赋值给j,即 j = next[j - 1],此时j的值正是匹配字符串所需要移动的步长,要就是为了防止重复对比,下次直接需要对比的匹配字符串元素下标;然后比较str.charAt(i) 与 str.charAt(j)是否相等
while (j > 0 && str.charAt(i) != str.charAt(j)) {
j = next[j - 1];
}
//字符相同时,匹配值j+1
if(str.charAt(i) == str.charAt(j)){
j++;
}
//将位置i的匹配值设定为j
next[i] = j;
}
return next;
}
}