给定两个字符串str1:ABCD ABD ACDABCDABCDABDED,str2:ABCDABD。问str1是否包含str2。
我们很容易就想到暴力求解该问题的方法,从str1的第一个字符开始与str2开始匹配,匹配成功则开始匹配后一个字符,不成功则拿str1的后一个字符重新开始匹配str2的第一个字符。代码写起来也非常简单,时间复杂度为o(mn)
public static boolean isMatch(String str,String pattern){
int strLength = str.length();
int patternLength = pattern.length();
if(strLength
举一个简单的例子str1:ABCDABCDABCD,str2:ABCDABD。(说明:i表示str1的index从1开始,j表示str2)用暴力求解来匹配时,str1与str2的前缀ABCDAB匹配成功后str1的下一个字母是C而str2下一个字母是D出现失配,
str1 | A | B | C | D | A | B | C | D | A | B | C | D |
---|---|---|---|---|---|---|---|---|---|---|---|---|
str2 | A | B | C | D | A | B | D |
然后从i=2即(B),j=1与str2重新匹配。
str1 | A | B | C | D | A | B | C | D | A | B | C | D |
---|---|---|---|---|---|---|---|---|---|---|---|---|
str2 | A |
B | C | D | A | B | D |
必然失配,且后续一系列匹配全都失败。显然这样浪费了很多时间,我们可以看出,失配时j=7(D)与i=7(C)拥有相同前缀AB,这时候将j设置为3而i不变,这是AB与j=5,j=6(AB)匹配,继续向下匹配即可。很明显这样只需要在失配时重置str2继续匹配即可,而不用重置i而浪费大量时间做必然失败的匹配。相当于是利用了前面匹配的结果而少走弯路。具体原因会在后文介绍。
str1 | A | B | C | D | A | B | C |
D | A | B | C | D |
---|---|---|---|---|---|---|---|---|---|---|---|---|
str2 | A | B | C |
D | A | B | D |
首先kmp算法会根据待匹配的字符串str2来生成一个数组,暂且叫它next数组(在后面进行详细讲解),该数组记录了当前失配后str2应该移动到什么位置继续匹配,就前一个例子str1:ABCDABCDABCD,str2:ABCDABD。此时的next数组为[-1,0,0,0,0,1,2]所以当下面失配情况发生时,
str1 | A | B | C | D | A | B | C | D | A | B | C | D |
---|---|---|---|---|---|---|---|---|---|---|---|---|
str2 | A | B | C | D | A | B | D |
j应该直接等于next[j]即2,而i不变继续进行匹配
str1 | A | B | C | D | A | B | C |
D | A | B | C | D |
---|---|---|---|---|---|---|---|---|---|---|---|---|
str2 | A | B | C |
D | A | B | D |
然后继续向后匹配,直到失败或者成功。
next数组内存储的是以该位置结尾的匹配串子串的部分匹配值(即对于str:ABCDABD,nex[3]指ABCD的部分匹配值),我理解的部分匹配值其实就是部分字符串的首尾是相同的,自己可与自己部分匹配。例如ABCDAB他们的开头和结尾都是AB则部分匹配值为2。官方定义是最长前缀与后缀匹配值,例如对于AACDAA,其前缀有A,AA,AAC,AACD,AACDA后缀有A,AA,DAA,CDAA,BCDAA有相同元素AA与A。注意这里取最长匹配,即AA长度为2。
接下来详细讲一下对于str2:ABCDABD next数组的详细求解过程。
next数组 | 数值求法 |
---|---|
next[0] | 子串A的部分匹配(0) |
next[1] | 子串AB的部分匹配(0) |
next[2] | 子串ABC的部分匹配(0) |
next[3] | 子串ABCD的部分匹配(0) |
next[4] | 子串ABCDA的部分匹配(1) |
next[5] | 子串ABCDAB的部分匹配(2) |
next[6] | 子串ABCDABD的部分匹配(0) |
得到[0,0,0,0,1,2,0]
而next数组就是讲全部数字右移一位,空位补上-1。得到
next[-1,0,0,0,0,1,2]。
设str1:ABCDABCDABDE,str2:ABCDABD然后开始进行匹配
str1 | A | B | C | D | A | B | C |
D | A | B | D | E |
---|---|---|---|---|---|---|---|---|---|---|---|---|
str2 | A | B | C | D | A | B | D |
匹配到上图所示情况后(i=6,j=6)查next[6]得到2则直接将j设为2,按照下图开始继续比较
str1 | A | B | C | D | A | B | C |
D | A | B | D | E |
---|---|---|---|---|---|---|---|---|---|---|---|---|
str2 | A | B | C |
D | A | B | D |
建议将代码调试一下,特别是生成next数组那部分。
package others;
/**
* KMP算法匹配字符串
*
* @Author dewey
* @Date 2018/10/16 20:18
*/
public class KMP {
public static void main(String[] args) {
String str = "BBC ABCDAB ABCDABCDABDE";
String pattern = "ABCDABD";
int[] result = getNext(pattern);
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
System.out.println(isMatch(str,pattern));
}
/**
* 判断字符串是否匹配
*
* @param str
* @param pattern
* @return
*/
public static boolean isMatch(String str,String pattern){
int[] next = getNext(pattern);
int i = 0;
int j = 0;
while(i