KMP网上已经有很多资料了,详细的模拟过程,这里就不展开讲了。这篇文章主要是探讨如何构造next[]数组、匹配复杂度的证明、KMP的缺陷以及该如何改进算法使得性能更优。
一、KMP匹配代码
int match (char* P, char* T) {
int* next = buildNext(P);
int n = (int) strlen(T), i = 0;
int m = (int) strlen(P), j = 0;
while ( j < m && i < n) {
if ( j < 0 || T[i] == P[j]) {
i++;
j++;
}
else {
j = next[j]
}
}
delete [] next;
return i - j
}
二、next[j]含义
next[j]表示j前面前缀跟后缀相等的最长长度
三、如何构造next[]
思路:已知next[j] = t ==> 求next[j+1] = ?
- 若P[j] == P[next[j]],则next[j+1] = t+1
- 若P[j] != P[next [ j ]],则在j前面找前缀跟后缀相等的次长长度,即t前面的最长长度:next[t],即next[next[j]] ==>两步走:若P [j] == P [next [ next[ j ] ] ],则next[j+1] = next[ next[ j ] ] + 1;若P [j] != P [next [ next[ j ] ] ],则在j前面找次次长,即next[ next[ next[j] ] ]...直到找到next[ next[ .... ] ] = -1为止
例子:如下图,j = 7,已知next[7] = 3,求next[8]
int* buildNext( char* P){
int m = (int) strlen(P), j = 0;
int* N = new int[m];
int t = N[0] = -1;
while( j < m-1 ){
if( t < 0 || P[j] == P[t]){
j++; t++;
N[j] = t;
}
else{
t = N[t]; //失配
}
}
return N;
}
四、匹配复杂度证明
i:T串实际匹配位置
i':P串的头对应的T的位置
- 若T[i] == P[j],则i++,j++ ==> i 往前移动
- 若T[i] != P[j],则j = next[j] ==> i' 往前移动
综上:每一次匹配,总有 i 往前移动,或者 i' 往前移动
0 <= i < n,0 <= i' < n-m <= n-1(m至少为1)
所以复杂度为:O(2n-1),即O(n)
五、KMP的缺陷
- 若T[i] == P[j] ==> i++,j++ ==> 没问题
- 若T[i] != P[j] ==> j = next[j] ==> T[i] ? P[j]
针对第二种情况,若P[j] == P[next[j]],则会多出一次无效的比较,若P[j] == P[next[next[j]]],则会多出两次无效的比较...
显然,在构造next[]的时候就可以避免掉这种问题,直接跳过那些与P[j]相等的点。
六、如何改进KMP
int* buildNext( char* P){
int m = (int) strlen(P), j = 0;
int* N = new int[m];
int t = N[0] = -1;
while( j < m-1 ){
if( t < 0 || P[j] == P[t]){
j++; t++;
N[j] = ( P[j] != P[t] ? t: N[t]); // 在找N[t]的时候已经满足P[t]!=P[N[t]]
}
else{
t = N[t]; //失配
}
}
return N;
}