假设m为模式串strM的长度,n为待匹配的字符串strN的长度。
- 求模式串strM的next数组
- 遍历比较待匹配的字符串strN(过程=遍历strN+遍历时出现strM[j]的回跳)
比较strN[i]、strM[j]时可能出现的情况为:
2.1 当前字符匹配时,同时移动 i++,j++
2.2 当前字符不匹配,且j=0时,只移动 i++,j=0不动
2.3 当前字符不匹配,且j!=0时,i不变,strM[j]回跳,当前strN[i]时最多回跳j次
基本的过程参考下面博文就可以:
https://blog.csdn.net/sinat_37537673/article/details/73331135 (忽略里面的代码部分)
https://blog.csdn.net/christ1750/article/details/51259425
https://www.cnblogs.com/imzhr/p/9613963.html
假设模式串为:strM[11] = ABCDABCDABE
对应的next数组为: next[11] = 0,0,0,0,1,2,3,4,5,6,0
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
strM[j] | A | B | C | D | A | B | C | D | A | B | E |
next[j] | 0 | 0 | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 0 |
例如到strN[i] -> H, strM[j=10] -> E不匹配,可能的回跳为:
第一次最大匹配回跳:next[10-1]=6 -> ABCDAB*
如果第一次回跳后strN[i], strM[j=6] 不match,第二次最大匹配回跳:next[6-1]=2 – AB*
如果第二次回跳后strN[i], strM[j=2] 不match,第三次最大匹配回跳:next[2-1]=0 – *
所以实际上该过程可以抽象为:
当前比较strN[i],strM[j];
如果strN[i],strM[j] 匹配时, i++,j++;
如果strN[i],strM[j] 不匹配,且j=0时, i++;
如果strN[i],strM[j] 不匹配,且j!=0时,基于strM[0-j]串,寻找前一个最大匹配串strM[0-k],k
如果还是不匹配且j!=0,再基于strM[0-j]串,寻找前一个最大匹配串strM[0-h],h<(j=k),赋值j=h+1,看strN[i]和strM[j=h+1]是否匹配了;
…
例如ABCDABCDAB*(*号为当前比较的字符), 后缀ABCDABCDAB
*就是ABCDAB
CDAB*里的前缀包含的关系,那就可以再以ABCDAB*(*号为当前比较的字符,当前模式串里就是’C’)为基础,再进行比较…
可以直接看代码的实现
假设m为模式串strM的长度,n为待匹配的字符串strN的长度。
O(m+n)=O( [m,2m]+ [n,2n] ) = 计算next数组的时间复杂度+遍历比较的复杂度。
也就是:
计算next数组时的比较次数介于[m,2m]。
遍历比较的比较次数介于[n,2n],最坏情形形如T=“aaaabaaaab”,P=“aaaaa”。
所以算法时间复杂度时O(m+n).
这里分析下[n,2n]的最坏情况是怎么得出的,可以抽象下这样理解,遍历待匹配字符串strN时,比较strN[i]、strM[j]时可能的情况为:
1.当前字符匹配时,同时移动 i++,j++
2.当前字符不匹配,且j=0时,只移动 i++,j=0不动
3.当前字符不匹配,且j!=0时,i不变,strM[j]回跳,最多跳j次,但j由前面匹配的情况1确定,而情况1总共不可能出现超过n次,所以总回跳不会超过n次
所以最坏情况,i++次数(情况一+情况二)+ j回跳(情况3)= n + 最坏n = 2n
[m,2m]也可以类似证明。
这里也可以换个更简单的方式和思路去证明。
参考文档:
KMP时间复杂度分析
java代码实现为:
package classic;
public class KMP {
public static void main(String[] args){
KMP kmp=new KMP();
System.out.println(kmp.kmp("BBCWABCDABWABCDABCDABDE","ABCDABD"));
}
private int kmp(String originStr, String subString) {
if (originStr == null || subString == null || originStr.length() == 0 || subString.length() == 0
|| originStr.length() < subString.length()) {
return -1;
}
int[] next = getNext(subString);
for(int i = 0,j=0; i < originStr.length(); i++){
while(j > 0 && originStr.charAt(i) != subString.charAt(j)){
j = next[j - 1];
}
if(originStr.charAt(i) == subString.charAt(j)){
j++;
}
if(j == subString.length()){
return i-j+1;
}
}
return -1;
}
private int[] getNext(String str) {
int[] next = new int[str.length()];
next[0] = 0;
int j;
for(int i = 1; i < str.length(); i++){
j=next[i-1];
while(j > 0 && str.charAt(j) != str.charAt(i)){
j = next[j - 1];
}
if(str.charAt(i) == str.charAt(j)){
next[i]=j+1;
}
else
next[i]=0;
}
return next;
}
}