KMP算法的实现在网站上随处可见。我进行也进行了练习。但是对于其时间复杂的详细说明却少见。而且多数说的很是抽象。所以今天我便写下这篇博文,来梳理一下其时间复杂度。
1. 首先,贴出代码,此处使用的是C语言编写
#include <stdio.h>
#include <string.h>
void getNext(char* aim, int* next);
int getIndex(char *base, char *aim);
int main (){
char base [] = "afunction";
char *aim = "function";
int next[strlen(aim)];
int index = getIndex(base, aim);
printf("%d", index);
return 0;
}
int getIndex(char *base, char *aim){
int a = strlen(base);
int b = strlen(aim);
if (a < b) {
return -1;
}
int i=0,j=0;
int next[b];
getNext(aim, next);
while (i < a && j < b){
if (base[i] == aim[j]) {
i++;
j++;
}else if (j > 0){
j = next[j-1] + 1;
}else {
i++;
}
}
return j == b? (i - b) : - 1;
}
void getNext(char* aim, int* next){
int a = strlen(aim);
next[0] = -1;
int j = 0;
int i;
for (i = 1; i < a; i++) {
j = next[i-1];
while(j > 0 & aim[i] != aim[j + 1]){//循环找到最大的符合条件的字从头开始的符串
j = next[j];
}
if(aim[i] == aim[j + 1]){
next[i] = next[i-1] + 1;
}else {
next[i] = -1;
}
}
}
- 对于代码来说主要有两处的时间复杂度没有那么明显。
1)第一处
while (i < a && j < b){
if (base[i] == aim[j]) { // i是base的索引,j是aim的索引
i++;
j++;
}else if (j > 0){ //回滚
j = next[j-1] + 1;
}else {
i++;
}
}
分析:
- 乍一看去,对于每次循环,最多可以回滚j次;所有时间复杂度应该是
O(ij);但是这种分析实在粗糙,没有考虑i,j之间的关联 - 可以用一种更精细化的想法来刻画,该处的时间复杂度;这种分析是一种摊还分析。
假设:base[i - 1] = aim[j - 1];且base[i] != aim[j];
那么便有:
对于base : [i - j -2, i -1] 处每个字符只判断过一次;
对于base:i处字符串最多判断j次;
所以有:
base j + 1长度字符最多判断 j + j - 1 = 2j -1 次;
那么对于base的这些字符长度的平均判断次数为:
(2j-1)/(j+1); 它小于2;
可以看出其中的i、j值都是变量;
所以可以下这样的结论:
base的字符的平均比较次数小于2;即base所以字符比较次数小于2a;
这个算法的实际复杂度为O(a);
2)第二处:
for (i = 1; i < a; i++) {
j = next[i-1];
while(j > 0 & aim[i] != aim[j + 1]){//循环找到最大的符合条件的字从头开始的符串
j = next[j];
}
if(aim[i] == aim[j + 1]){
next[i] = next[i-1] + 1;
}else {
next[i] = -1;
}
}
分析:
- 可以想象代码:
while(j > 0 & aim[i] != aim[j + 1]){
j = next[j];
} 每循环一次都会将j值减少;最多减少为0;所以该回滚的最大次数为j; - 那么在前一次回滚最大次数的前提下,i+1索引处的回滚最多为几次呢?
答案显然是0次 - 我想你应该发现了相关性了,那就是对于回滚来说,它的最大全部回滚次数不会超过next[]增加的次数;你可以想象成一个栈;回滚就是取栈中元素;next[i] = next[i-1] + 1就是在栈中存一个元素;所有回滚之和(也就是while循环的总次数)不会超过next[i] = next[i-1] + 1语句执行的总次数
- 而next[i] = next[i-1] + 1语句执行的总次数对多为a次
- 综上,全部执行次数 < 2a;时间复杂度就是O(a)
总结:
- 由上,易得时间复杂度为O(a+b);其中a是base的长度、b是aim的长度。