KMP
KMP
首先有暴力算法:
int findPos(char S[], char T[]) {
int lens = strlen(S), lent = strlen(T), i = 0, j = 0;
while(i<lens && j<lent) {
if (S[i] == T[j])
i++,j++;
else
i = i -j + 1,j = 0; // i, j均移动
}
return (j >= lent ? i - lent : -1);
}
然后对暴力算法进行优化:
只移动j,而不移动i。
对于j的移动,j应该如何移动?
当前的\(s[i] \not ={ t[j]}\),如果对于t串,其t[0~j-1]有着相同的前后缀,那么,当\(s[i] \not ={ t[j]}\)时,j最佳的移动方法就是去与后缀相同的前缀的下一个字符处。
由此,我们想知道,对于每一次\(s[i] \not ={ t[j]}\),其前缀的下一处是什么。即nxt[j]。
对于nxt数组,求nxt[j]也就是对t[0~j-1]的最长相同前后缀的长度,那么问题就是,如何快速地得到最长相同前后缀的长度。
下面求nxt数组:
考虑我们已经得到了nxt[0~i],要求nxt[i+1]。
根据定义,nxt[i+1]就是t[0~i]的最长相同前后缀的长度。有下图:

求nxt代码:
void CalcNext(char T[],int next[]) {
next[0] = -1, next[1] = 0;
for (int i = 1;i < (int)(strlen(T) - 1);i++) {
int k = next[i];
while (k != -1 && T[i] != T[k])
k = next[k];
next[i + 1] = k + 1;
}
}
由此,我们已经得到了KMP:
int findPos_kmp(char S[], char T[], int next[]) {
int lens = strlen(S), lent = strlen(T), i = 0, j = 0;
while(i<lens && j<lent) {
if(j == -1 || S[i] == T[j])
i++,j++;
else
j = next[j]; // 只移动j,而不移动i.
}
return (j >= lent ? i - lent : -1);
}
可选优化:
我们知道,对于最大相同前后缀的后缀的后一个字符,如果匹配失败,显然会找到前缀的后一个字符,再进行判断,如果前缀的后一个字符和后缀的后一个字符相等的话,那从后缀的后一个字符到前缀的后一个字符的操作显然无意义,肯定不相等。于是,我们可以判断后缀的后一个字符和前缀的后一个字符是否相等,如果相等,就让后缀的后一个字符的nxt等于前缀的后一个字符的nxt。
void CalcNextVal(char T[],int next[],int nextVal[]) {
int len = strlen(T);
for (int i = 0;i < len;i++)
nextVal[i] = next[i];
for (int i = 1;i < len;i++) {
int k = nextVal[i]; // 前缀的后一个字符。
if (T[i] == T[k])
nextVal[i] = nextVal[k]; // 后缀的后一个字符的nxt等于前缀的后一个字符的nxt
}
}

浙公网安备 33010602011771号