在介绍kmp算法之前,我想先简单介绍一下Brute-Force算法,这是一个回溯的字符串模式匹配算法,是一个简单暴力狂!
由上图的匹配思想,主要代码如下:
public static int indexOf(String target, String pattern){
int i = 0,j = 0;
while(i < target.length() - pattern.length()){
if(target.charAt(i+j) == pattern.charAt(j)){
j++;
}else{
i++;
j = 0;
}
if(j == pattern.length()){
return i;
}
}
return -1;
}
很明显,当匹配不成功时,目标串每次向前移动一位,模式串每次从P0开始重新匹配,双方都存在回溯现象。在上图中可以看出最差的情况: 每次匹配比较M次,比较了N - M + 1 次,比较总数是 M*(N - M + 1),由于M << N,所以时间复杂度O(M * N) 。
下面分析下Brute-Force算法的缺点:
下面就着重讲一下KMP算法:
-
算法描述:
核心是找到模式串中的k( 0 <=k < j) 满足"P0...Pk-1" = "Pj-k...Pj-1" = "Ti-k...Ti-1" (当Pj != Ti时),则模式串从Pk开始遍历。
所以问题就转化为:找到串"P0...Pj-1"相同的前缀子串和后缀子串的长度k。
- 求next数组
根据上图的模式串"abcabc",可以得到next数组(注意在"P0...Pj-1"中寻找)
j | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
模式串 | a | b | c | a | b | c |
next[j] | -1 | 0 | 0 | 0 | 1 | 2 |
- 计算next数组的算法
由next数组的定义可以:
- next[0] = -1;
- 2.对于任意的j (0<=j <m ) next[j] < j;
- 若next[j] = k 说明在子串"P0...Pj-1"中存在相同的前缀子串和后缀子串,"P0...Pk-1" = "Pj-k...Pj-1" (0=<k<j,k取最大值);
- 对于next[j+1]而言,判断"P0...Pj"子串中,是否存在"P0...Pk" = "Pj-k...Pj-1Pj"的问题,可以分解为 :
- 是否存在"P0...Pk-1" = "Pj-k...Pj-1"的问题;
- Pk == Pj的问题;
- 对于next[j+1]而言,判断"P0...Pj"子串中,是否存在"P0...Pk" = "Pj-k...Pj-1Pj"的问题,可以分解为 :
很符合动态规划(Dynamic Programming)的问题,把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,很像分治法。
同时个人认为很像高考数列的相关问题 ---- 数学归纳法。
某个数列 An
- An = K (常数);
- 假设前N项满足: An = f(n) (某个函数式);
- 如果第N+1项 满足 An+1 = f(n+1),则An = f(n) 等式成立。
综上所述该动态规划的递推公式:
D(j+1) = max(D(j)) + (Pk == Pj ? 1 : 0)
- 改进next数组
在上图1-3中 Pk = Pj , Ti != Pj => Pk != Ti 则完全没有必要回溯到Pk开始比较,直接从P0开始,所以假如next[j] = k 且 Pk = Pj 则 next[j] = next[k];
- KMP核心代码
private static int[] getNext(String pattern){
int j = 0,k = -1;
int[] next = new int[pattern.length()];
next[0] = -1;
while(j < pattern.length() - 1){
if(k == -1 || pattern.charAt(j) == pattern.charAt(k)){
j++;
k++;
//改进next数组
if(pattern.charAt(j) != pattern.charAt(k)){
next[j] = k;
}else{
next[j] = next[k];
}
}else{
k = next[k];
}
}
return next;
}
public static int indexOf(String target, String pattern){
int i = 0,j = 0;
int[] next = getNext(pattern);
while(i < target.length()){
if(j == -1 || target.charAt(i) == pattern.charAt(j)){
i++;
j++;
}else{
j = next[j];
}
if(j == pattern.length()){
return i - j;
}
}
return -1;
}
- KMP算法分析
时间复杂度O(M + N) ,当M<<N或者比较次数少(匹配的成功率比较高),时间复杂度O(N)。
以上是本人对KMP算法的理解,欢迎大家指正和交流,谢谢!