相比于暴力方法,KMP算法进行字符串搜索时可以根据子串自己的特性,以确定当前是否匹配成功。简单来讲,就是使用了子串的每个字符的集合作为一个状态,创建一个有限状态机(DFA)。
比如要查找的子串为 ABABAC,那么对于每个位置的状态只有三个,即A、B、C,所以可以建立如下表格:
j | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
A | B | A | B | A | C | |
A | 1 | 1 | 3 | 1 | 5 | 1 |
B | 0 | 2 | 0 | 4 | 0 | 4 |
C | 0 | 0 | 0 | 0 | 0 | 6 |
用 i 表示现在文本中的位置, j 表示子串的位置。上面的矩阵dfa[i][j] 即为子串第j个位置时,如果出现的字符为表的第i行对应的字符( A、B、C中的一个),那么它此时j应该跳转到的位置即为 dfa[i][j]。不管怎样, i 都只是增长而不回退。《算法》第四版的500页有清晰的图示,可以参考。下面的代码也是摘抄自此书。
package kmp;
import java.util.Scanner;
public class KMP {
private String pat;
private int[][] dfa;
public KMP(String pat) {
this.pat = pat;
int M = pat.length();
int R = 256;
dfa = new int[R][M];
dfa[pat.charAt(0)][0] = 1;
for (int X = 0, j = 1; j < M; ++j) {
for(int c = 0; c < R; ++c){
dfa[c][j] = dfa[c][X];
}
dfa[pat.charAt(j)][j] = j + 1;
X = dfa[pat.charAt(j)][X];
}
}
public int search(String txt){
int i, j, N = txt.length(), M = pat.length();
for(i = 0, j = 0; i < N && j < M; ++i){
j = dfa[txt.charAt(i)][j];
}
if(j == M) return i - M;
else return N;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String txt = scanner.nextLine();
String pat = scanner.nextLine();
scanner.close();
KMP kmp = new KMP(pat);
System.out.print("text: ");
System.out.println(txt);
int offset = kmp.search(txt);
System.out.print("pattern: ");
for(int i = 0; i < offset; ++i)
System.out.print(" ");
System.out.println(pat);
}
}