不同于bf算法,kmp算法不回溯主串的元素,回溯模式串中的元素,并且尽量的利用上一次匹配的结果。kmp算法有两个部分,一个是计算临时数组(用于模式串的回溯),一个是字符串匹配的部分,详细代码如下:
public class Kmp {
/*
第一个方法利用模式串求一个数组,这个数组存储的信息是该模式串在匹配失败时,下一次应该
从模式串的何处开始匹配,这个下一次匹配处的索引就是匹配出错的字符以前的字符串的相同的
前后缀的最大值。该数组存储的就是这个值。
例如:
字符串ABCDABD,若在第一个B处(index:1)失配,则看A,它的相同的前后缀的最大值为0,
再如,若在D处(index:7)失配,则看ABCDAB,其相同的最大的前后缀为“AB”,长度为2。
*/
public static int[] kmpNext(String dest) {
int[] next = new int[dest.length()];
next[0] = 0;
/*用i来计算是否遍历到所有的情况,并且i提供的索引可以用于存储j的值,这个值就意味
着:当模式串在某个地方失配的时候,直接找到失配处字符前面一个字符所处的位置的j值就
好了。
例如:AAAB在B失配,那么就看AAA,他们个字符所对应的j值分别为:0,1,2,所以下一次
从模式串索引为2的地方开始匹配
*/
for (int i = 1, j = 0; i < dest.length(); i++) {
/*不相等且j不为0,就将j-1,意味着缩小最大前后缀,看看是否可以匹配,还不能
就接着缩小,直到j为0(不用缩小了,没有相同的前后缀)或者找到匹配的位置了,进入
下方的if语句再把j加回来。
*/
while (dest.charAt(i) != dest.charAt(j) && j > 0){
j = next[j-1];
}
//如果相等,就把j+1,意味着j已经把最大前缀(或说后缀)扩大一个字符了
if (dest.charAt(i) == dest.charAt(j)) {
j++;
}
/*将相应的j值存到对应的i位置,意味着:dest[0:i+1]的字符串,只用知道i的值
就可以找到最长公共前后缀的长度值j了。
*/
next[i] = j;
}
return next;
}
/*
该方法时kmp匹配的主体部分
*/
public static int kmpMatch(String str, String dest) {
//首先计算出部分匹配表
int[] next = kmpNext(dest);
/*
从主串和模式串的第一个字符开始匹配,若匹配成功,则双双加一,反之,如果匹配失败
就将模式串根据前后缀回溯,不移动主串.设置推出条件为:1.匹配成功返回位置。2.匹配
不成功,跳出循环,返回负一。对前后缀在模式串匹配中的应用解释如下:
主串:ABCABFGT...
模式串:ABCABD 如下列文字所示,模式串在第六个位置失配
ABCABFGT
ABCABD
此时看的就是ABCAB,很容易可以看出,最大公共前后缀的值为2,所以下一次模式串从2处开始匹配
主串字符位置不变
ABCABFGT
ABCABD
就相当于,将ABCAB分为前和后两个最大的相同的部分,主串利用后部分,模式串用前部分和它匹配
在一起了,值一样。
*/
for(int i = 0, j = 0; i < str.length(); i++){
while(j > 0 && str.charAt(i) != dest.charAt(j)){
j = next[j-1];
}
if(str.charAt(i) == dest.charAt(j)){
j++;
}
if(j == dest.length()){
return i-j+1;
}
}
//没有匹配到字符串返回-1
return -1;
}
public static void main(String args[]) {
System.out.println(kmpMatch("BBC ABACABACABAD ABCDABDE", "ABACABAD"));
}
}