KMP算法是一种改进的字符串匹配算法,KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的
本文KMP算法可能与课堂中课本有所不同,但其中思想是一致的,均适用模式匹配,且理解难度低。那么我们就开始吧!
例子:
将BBC ABCDAB ABCDABCDABDE与ABCDABD匹配
要学会KMP算法,我们就要知道KMP算法的匹配过程是什么?我们先看一下KMP算法的匹配过程:
第一步:先从开头开始匹配
直到匹配到
下一步:直接跳跃匹配
又依次匹配
下一步
直接跳跃匹配,此时匹配成功。
这就是KMP算法的精妙之处,在依次匹配的基础下,有时可以直接跳跃匹配。其实学会KMP算法,也就是要学会什么条件下要跳跃匹配。
我们再回顾上述的匹配过程,其中要跳跃的步骤已经用带红框的图标出。我们先看第一次跳跃时,有什么特殊之处。
我们发现被红框框住的字符串是AB,疑?为什么是AB呢?是不是一脸懵?
我们不难发现模式串(ABCDABD)的首字符串也有AB,那么你想一想,我这次匹配虽然失败了,只有最后一个字符D没匹配成功,但前面都成功了。但我后面的AB匹配成功了。
而我们在进行下一次匹配时,一定是先找到与模式串开头的AB匹配时,再依次向后进行匹配。但是我上一次匹配发现,我后面的AB已经成功匹配了,而我要找的就是以AB开头的字符串来进行比较,
何曾不好好利用呢?我们这时就可以直接跳过。同理第二次跳跃也是如此
看到这里,你应该了解了KMP的匹配过程了。现在问题就是如何实现依次跳多少位了。下面直接列出next()数组,next()数组就是跳跃位数的判断依据:
next数组由来:从模式串第二位B开始与模式串匹配,匹配不上,则是0,连续匹配上则加一,中断则从0开始。
再举一个例子:
这时我们在转向这张图:
我们知道这个父串一次要跳4位,并且模式串已经匹配到达了D,下标j为6。所以根据这一规律。我们可得出每次跳跃的长度为j-next[j-1](即6-2=4)。但有时候next[j-1]可能为0,为了尽可能的提高效率,我们还要向前遍历,尽可能找到第一个next不为0的那位,找到时j也要发生相应变化。如
此时next[j-1]=0,,但我们前面的AB已经匹配成功了,此时不能浪费,我们要把下标j移到C,即4。然后在将父串移动j-next[j-1]位,即移动2位。
代码实现:
package Arithmetic;
public class KMP {
public static void main(String[] args){
String str1 = "BBC ABCDABABCDABCDABDE";
String str2 = "ABCDABD";
int next[]=kmpNext(str2);
int index=kmpCompare(str1,str2,next);
System.out.println(index);
}
//1.获取匹配值
public static int[] kmpNext(String dest) {
//创建一个next[]数组储存部分匹配值
int next[] = new int[dest.length()];
next[0] = 0;//第一位一定为0
for (int i = 1, j = 0; i < dest.length(); i++) {
//不相等就要从头开始比较
if (dest.charAt(i) != dest.charAt(j)) {
j = 0;
}
//当dest.charAt(i) == dest.charAt(j)时,部分匹配值就+1
if (dest.charAt(i) == dest.charAt(j)) {
j++;
}
next[i] = j;
}
return next;
}
// 2.KMP比较
public static int kmpCompare(String str1, String str2, int next[]) {
int index = 0;//记录匹配成功时的起始位置
for (int i = 0, j = 0; i < str1.length(); i++) {
if (str1.charAt(i) == str2.charAt(j)) {
index = i;
//逐个比较
while (i<str1.length() && j<str2.length()&&str1.charAt(i)==str2.charAt(j)) {
i++;
j++;
}
//如果j等于str2的长度,说明此时已经成功匹配
if (j == str2.length()) {
return index;
}
//如果此时匹配不成功,我们要移动子串
else {
//此时我们只要从next[j]往前找到第一个不为0的数,然后
// 把父串往后移(j-next[k]-1)位,
// 减一是因为for循环后会加一
int k = j;
while (k > 0 && next[k] == 0) {
k--;
}
//此时我们已经找到第一个不为0的匹配数
//把父串往后移(j-next[k]-1)位
i = index + j - next[k] - 1;
//此时要从头匹配
j = 0;
}
}
}
//匹配不成功,返回-1
return -1;
}
}
片尾一言:
若你决定灿烂,山无遮,海无拦。