在串的模式匹配中,最简单的BF(Brute-Force,布鲁特-福斯)算法,由于需要回溯,最坏的情况下时间复杂度为O(m*n).其中m,n分别表示主串和模式串的长度。
由D.E.Knuth, J.H.Morris和V.R.Pratt三人提出的KMP算法,由于不需要回溯,其时间复杂度可以降到O(m+n)。其中m,n意义同上。
下面给出KMP算法的一个证明。
讨论一般情形,设主串S=“S0 S1 S2 ... Sn-1”, 模式T=“T0 T1 T2 ... Tm-1”.在进行第 i 趟匹配时,出现以下情况:
S: S0 S1 ... Si-j Si-j+1 ... Si-1 Si Si+1 ... Sn-1
|| || || `|
T0 T1 ... Tj-1 Tj Tj+1 ... Tm-1
即: T0 T1 ... Tj-1 == Si-j Si-j+1 ... Si-1, 且 Si != Tj. ---(1)
如果在模式T中, 有: T0 T1 ... Tj-2 != T1 T2 ... Tj-1, ---(2)
则由(1)得:
T1 T2 ... Tj-1 == Si-j+1 Si-j+2 ... Si-1. ---(3)
将(3)代入(2),则马上有:
T0 T1 ... Tj-2 != Si-j+1 Si-j+2 ... Si-1.
则显然有:
T0 T1 ... Tj-2 Tj-1 != Si-j+1 Si-j+1 ... Si-1 Si.
由上述证明可知:回溯到Si-j+1开始进行匹配必然失败。 也就是说,回溯到Si-j+1进行匹配可以不做。那么,考虑回溯到Si-j+2的情况:
从前面的推理可知,如果有
T0 T1 ... Tj-2 != T2 T3 ... Tj
则必然有:
T0 T1 ... Tj-2 != Si-j+2 Si-j+3 ... Si .
这样的比较仍然会失败。依此类推,直到对于某一个值k,使得:
T0 T1 ... Tk-2 != Tj-k+1 Tj-k+2 ... Tj-1 且 T0 T1 ... Tk-1 == Tj-k Tj-k+1 ... Tj-1,
才会有:
Tj-k Tj-k+1 ... Tj-1 == Si-k Si-k+1 ... Si-1 == T0 T1 ... Tk-1.
这说明下一次可直接比较 Si 和 Tk,这样,可以直接把第 i 趟比较失败时的模式 T 从当前位置右移 j-k 位。而这里的 k 即为next(j)。
关于next(j)的求法不再证明。求next(j)的算法其实就是上述算法的又一次运用,只不过这时主串和模式串相同。
下面是KMP算法的C实现。假设串的下标从0开始。
const int MAXSIZE = 100;
void getNext(char *T, int next[]){ //求next
int j = 0, k = -1;
next[0] = -1;
while(j < strlen(T)){
if( (j == -1) || (T[j] == T[k]) ){
j++; k++;
next[j] = k;
}else
k = next[k];
}
}
int KMP(cahr *S, char *T){
int next[MAXSIZE], i = 0, j = 0, v;
getNext(T, next);
while( (i < strlen(S)) && (j < strlen(T)) ){
if( j == -1 || S[i] == T[j]){
i++;
j++;
}else
j = next[j];
}
if(j > strlen(T))
return (i - strlen(T));
else
return -1;
}
由D.E.Knuth, J.H.Morris和V.R.Pratt三人提出的KMP算法,由于不需要回溯,其时间复杂度可以降到O(m+n)。其中m,n意义同上。
下面给出KMP算法的一个证明。
讨论一般情形,设主串S=“S0 S1 S2 ... Sn-1”, 模式T=“T0 T1 T2 ... Tm-1”.在进行第 i 趟匹配时,出现以下情况:
S: S0 S1 ... Si-j Si-j+1 ... Si-1 Si Si+1 ... Sn-1
|| || || `|
T0 T1 ... Tj-1 Tj Tj+1 ... Tm-1
即: T0 T1 ... Tj-1 == Si-j Si-j+1 ... Si-1, 且 Si != Tj. ---(1)
如果在模式T中, 有: T0 T1 ... Tj-2 != T1 T2 ... Tj-1, ---(2)
则由(1)得:
T1 T2 ... Tj-1 == Si-j+1 Si-j+2 ... Si-1. ---(3)
将(3)代入(2),则马上有:
T0 T1 ... Tj-2 != Si-j+1 Si-j+2 ... Si-1.
则显然有:
T0 T1 ... Tj-2 Tj-1 != Si-j+1 Si-j+1 ... Si-1 Si.
由上述证明可知:回溯到Si-j+1开始进行匹配必然失败。 也就是说,回溯到Si-j+1进行匹配可以不做。那么,考虑回溯到Si-j+2的情况:
从前面的推理可知,如果有
T0 T1 ... Tj-2 != T2 T3 ... Tj
则必然有:
T0 T1 ... Tj-2 != Si-j+2 Si-j+3 ... Si .
这样的比较仍然会失败。依此类推,直到对于某一个值k,使得:
T0 T1 ... Tk-2 != Tj-k+1 Tj-k+2 ... Tj-1 且 T0 T1 ... Tk-1 == Tj-k Tj-k+1 ... Tj-1,
才会有:
Tj-k Tj-k+1 ... Tj-1 == Si-k Si-k+1 ... Si-1 == T0 T1 ... Tk-1.
这说明下一次可直接比较 Si 和 Tk,这样,可以直接把第 i 趟比较失败时的模式 T 从当前位置右移 j-k 位。而这里的 k 即为next(j)。
关于next(j)的求法不再证明。求next(j)的算法其实就是上述算法的又一次运用,只不过这时主串和模式串相同。
下面是KMP算法的C实现。假设串的下标从0开始。
const int MAXSIZE = 100;
void getNext(char *T, int next[]){ //求next
int j = 0, k = -1;
next[0] = -1;
while(j < strlen(T)){
if( (j == -1) || (T[j] == T[k]) ){
j++; k++;
next[j] = k;
}else
k = next[k];
}
}
int KMP(cahr *S, char *T){
int next[MAXSIZE], i = 0, j = 0, v;
getNext(T, next);
while( (i < strlen(S)) && (j < strlen(T)) ){
if( j == -1 || S[i] == T[j]){
i++;
j++;
}else
j = next[j];
}
if(j > strlen(T))
return (i - strlen(T));
else
return -1;
}