KMP算法主要用于字符串的匹配问题,能够节省时间快速找到匹配项的一种算法。这种算法与暴力匹配算法有所不同。我们可以从下面这个例题观察两者之间的区别。
首先我们来看一下暴力解法:
int search(String a, String b) {
int M = a.length; //记录主字符串的长度
int N = b.length; //记录子字符串的长度
for (int i = 0; i <= N - M; i++) {
for (int j = 0; j < M; j++) {
if (b[j] != a[i+j]){
break;
}
}
if (j == M){return i;} //b字符串全部匹配成功
}
return -1; //b没有匹配成功
}
这种暴力解法的逻辑简单,但是耗时长,问题就在于每次有一个字符匹配不成功时,子字符串都要从头开始重新匹配,并且主字符串也会向后走一位。
再来看看KMP算法:
public int search(String a, String b) {
int M = a.length(), N = b.length();
// 主字符串和子字符串前面都加空格,使其下标从 1 开始
a = " " + a;
b = " " + b;
char[] s = a.toCharArray();
char[] p = b.toCharArray();
int[] next = new int[N+1]; // 构建 next 数组,数组长度为子字符串的长度
// 构造过程 i = 2,j = 0 开始,i 小于等于子字符串串长度
for (int i = 2, j = 0; i <= N; i++) {
while (j > 0 && p[i] != p[j + 1]) { j = next[j]; }
if (p[i] == p[j + 1]) { j++; }
next[i] = j;
}
// 匹配过程,i = 1,j = 0 开始,i 小于等于主字符串长度
for (int i = 1, j = 0; i <= M; i++) {
while (j > 0 && s[i] != p[j + 1]) { j = next[j]; }
if (s[i] == p[j + 1]) { j++; }
if (j == N) { return i - N; } // 匹配成功,直接返回下标
}
return -1;
}
其中toCharArray()方法的作用是:将字符串对象中的字符转换为一个字符数组。
也就是说把字符串中的每一个字符转换成一个字符数组中的元素。
在实际编码时,通常会往主字符串和子字符串首字符前面加一个空格。
目的是让 j
下标从 0
开始,从而省去 j
从 -1
开始的麻烦。
KMP算法在这个代码中的优点就是不需要让子字符串在遇到不同字符的情况下重新重头开始匹配,而是通过新构建的next数组来记录子字符串中字符的位置,通过next数组可以快速的跳到该字符的位置。