KMP算法(时间复杂度O(m + n))最核心的是求next数组,然后再根据next数组来进行模式匹配。因此分为两部分:
1.求next数组
next数组记录的是模式串的特征,即最长相同前后缀
考虑如下模式串P:ABCDABC
它的前缀有:A,AB,ABC,ABCD,ABCDA,ABCDAB
它的后缀有:C,BC,ABC,DABC,CDABC,BCDABC
因此它的最长相同前后缀是ABC,长度是3.
next数组中p[i]中存的就是模式串p[0]…p[i]的最长相同前后缀长度
因此它的next数组就是如下形式:
Next数组:
模式串P | A | B | C | D | A | B | C |
---|---|---|---|---|---|---|---|
next数组 | 0 | 0 | 0 | 0 | 1 | 2 | 3 |
2.用求出的next数组进行字符串匹配
有了next数组后,我们就可以进行匹配了,考虑如下匹配串:
ABCABCDABDDABCDABC
ABCDABC
↑
当匹配到D时,发现不匹配了,普通的做法会把模式串p后移一位继续匹配,像这样:
ABCABCDABDDABCDABC
ABCDABC
↑
但我们发现,根据next数组,k-1位即第一个c对应的值为0,即前面的ABC在模式串后移的过程中不能再出现匹配的过程了,因此可以直接中模式串的第0位开始匹配,像这样:
ABCABCDABDDABCDABC
ABCDABC
↑
我们接着继续匹配:
ABCABCDABDDABCDABC
ABCDABC
↑
发现D和C不同,再根据next数组k-1位即第二个B的值2,我们可以接下从第二位(下标从0开始)开始匹配,像这样:
ABCABCDABDDABCDABC
ABCDABC
↑
继续按上述方法匹配直到找到匹配的串为止,找不到返回-1
ABCABCDABDDABCDABC
ABCDABC
↑
下面是对应的代码:
public class Solution {
public static void next(String p, int[] next) {
int pl = p.length();
for (int i = 1, k = 0; i < pl; i++) {// k表示p[0]…P[i - 1]的最长相同前后缀
while (k > 0 && p.charAt(i) != p.charAt(k)) {
k = next[k - 1];
}
if (p.charAt(i) == p.charAt(k)) {
k++;
}
next[i] = k;
}
}
public static int kmp(String t, String p) {
int tl = t.length();
int pl = p.length();
int[] next = new int[pl];
next(p, next);
for (int i = 0, k = 0; i < tl; i++) {
while (k > 0 && t.charAt(i) != p.charAt(k)) {
k = next[k - 1];
}
if (t.charAt(i) == p.charAt(k)) {
k++;
}
if (k == pl) {
return i - k + 1;//输出匹配的第一个位置
}
}
return -1;
}
public static void main(String[] args) {
String T = "ABCABCBABCDABC";
String P = "ABCDABC";
System.out.println(kmp(T, P));
}
}