KMP算法的思想在于,让每一次匹配都尽可能使用先前匹配产生的信息(部分匹配表)。
KMP算法的难点在于,部分匹配表的构造也在尽可能使用之前构造过程中的信息(动态规划?)。
暴力:失配,字串向后1位,成功匹配字符数归0。
KMP:失配,字串向后n位,成功匹配数m位。n,m的计算即KMP算法的核心。
?? ??
abcdabcy abcdabcya? a?
abcdabcy abcdabcyab? ab?
abcdabcy abcdabcyabc? abc?
abcdabcy abcdabcyabcd? abcd?
abcdabcy abcdabcyabcda abcda?
abcdabcy abcdabcyabcdab? abcdab?
abcdabcy abcdabcyabcdabc? abcdabc?
abcdabcy abcdabcyabcdabcy
abcdabcy
观察得知,移动的位数分别需要考察 a ab abc abcd abcda abcdab abcdabc的前缀和后缀是否一样。
same[index]可以理解为pattern[index]之前的前后缀匹配最大长度
为了使得move[index] = index - same[index],规定same[0] = -1
| index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| pattern[index] | a | b | c | d | a | b | c | y |
| same[index] | -1 | 0 | 0 | 0 | 0 | 1 | 2 | 3 |
| move[index] | 1 | 1 | 2 | 3 | 4 | 4 | 4 | 4 |
上一部分中最后的表格能极大的帮助我们完成字符串的匹配,它也就是所谓的部分匹配表。使用该表的时机即pattern[index]无法匹配,所以该表也称失配函数
以 aabaabaaa为例子,构造部分匹配表
| index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| pattern[index] | a | a | b | a | a | b | a | a | a |
| same[index] | -1 | 0 | 1 | 0 | 1 | 2 | 3 | 4 | 5 |
| move[index] | 1 | 1 | 1 | 3 | 3 | 3 | 3 | 3 | 3 |
类似动态规划问题,如果我们已知same[index-1] = k,那么pattern[0...(index-2)]的前后缀匹配最大长度已知,即pattern[0...k-1] == pattern[(index-k-1)...(index-2)]。
在求same[index]时我们需要考虑新的字符pattern[index - 1],也就增加了1个后缀需要考虑的字符。
pattern[k] == pattern[index-1],那么pattern[0...k] == pattern[(index-k-1)...(index-1)],那么same[index] = same[index - 1] + 1pattern[k] != pattern[index-1],则需要回溯到更小的匹配长度,一定小于k。我们考虑same[k]的定义,即pattern[k]之前的前后缀最大匹配长度,是能回溯到的最好的匹配长度。 pattern[same[k]]和pattern[index - 1]的大小关系,无法得出结果则继续回溯。same[0] == -1,则same[index] = 0public static int[] getSame(String modelString) {int[] same = new int[100];same[0] = -1;int i = 0;int j = same[i];while (i < modelString.length()) {if (j == -1 || modelString.charAt(i) == modelString.charAt(j)) {// same[i+1] = same[i] + 1i++;j++;same[i] = j;} else {// backtrackj = same[j];}}return same;
}
same可能就是其他文章中提到的next数组
KMP字符串匹配算法【双语字幕】
wiki百科