简介
KMP算法是一种经典的字符串匹配算法,它的核心思想是利用已匹配的部分信息,尽可能减少比较次数,从而提高匹配效率。具体来说,KMP算法维护一个next数组,用于存储匹配失败时应该回溯到哪个位置重新开始匹配。这个数组的计算过程可以使用动态规划思想来理解。
前缀和后缀
首先,我们需要了解前缀和后缀概念。对于一个字符串S,它的前缀是指从开头开始的任意一个子串,比如S的前缀有S本身、S的第一个字符、S的前两个字符等等;而S的后缀是指从结尾开始的任意一个子串,比如S的后缀有S本身、S的最后一个字符、S的后两个字符等等。
实现过程
接下来,我们以字符串“ABCDABD”和“ABCABC”的匹配为例,来具体说明KMP算法的实现过程。
计算next数组
我们以主串ABCDABD和模式串ABCABC为例,来计算next数组。
首先,我们将模式串的前两个字符“AB”作为第一个前缀,后两个字符“BC”作为第一个后缀,它们没有公共部分,因此next[2]的值为0。接下来,我们将模式串的前三个字符“ABC”作为第二个前缀,后三个字符“ABC”作为第二个后缀,它们的公共部分是“ABC”,因此next[3]的值为1。
同理,我们可以得到next数组的其他值:
next[1] = 0
next[2] = 0
next[3] = 1
next[4] = 2
next[5] = 0
next[6] = 1
匹配过程
接下来,我们以主串“ABCDABD”和模式串“ABCABC”为例,来说明KMP算法的匹配过程。
假设我们已经匹配到主串的第i个字符和模式串的第j个字符,如果S[i]等于P[j],那么我们继续往下匹配;否则,我们需要根据next数组回溯到某个位置重新开始匹配。
具体来说,我们将模式串的前j-1个字符中最长的相等的前缀和后缀的长度记为len,那么我们就可以将模式串向右移动j-len个位置,从而避免重复比较已经匹配过的字符。具体的匹配过程如下:
i = 1, j = 1: A != A, j = next[1] = 0, j = 0
i = 1, j = 0: A != B, j = next[0] = -1, j = 0
i = 1, j = 0: A == A, j = j + 1 = 1
i = 2, j = 1: B != B, j = next[1] = 0, j = 0
i = 2, j = 0: B != A, j = next[0] = -1, j = 0
i = 2, j = 0: B == A, j = j + 1 = 1
i = 3, j = 1: C != B, j = next[1] = 0, j = 0
i = 3, j = 0: C != A, j = next[0] = -1, j = 0
i = 3, j = 0: C == A, j = j + 1 = 1
i = 4, j = 1: D != B, j = next[1] = 0, j = 0
i = 4, j = 0: D != A, j = next[0] = -1, j = 0
i = 4, j = 0: D == A, j = j + 1 = 1
i = 5, j = 1: A == A, j = j + 1 = 2
i = 6, j = 2: B == B, j = j + 1 = 3
i = 7, j = 3: D != C, j = next[3] = 1, j = 1
i = 7, j = 1: D != B, j = next[1] = 0, j = 0
i = 7, j = 0: D != A, j = next[0] = -1, j = 0
匹配失败
从上面的匹配过程可以看出,KMP算法避免了重复比较已经匹配过的字符,因此可以大大提高匹配效率。同时,由于next数组的计算只需要一次,因此KMP算法的时间复杂度为O(m+n),其中m和n分别是主串和模式串的长度。
代码实现
以下是 Typescript 实现 KMP 算法的代码:
function kmp(str: string, pattern: string): number {
const next: number[] = getNext(pattern);
let i: number = 0, j: number = 0;
while (i < str.length && j < pattern.length) {
if (j === -1 || str[i] === pattern[j]) {
i++;
j++;
} else {
j = next[j];
}
}
if (j === pattern.length) {
return i - j;
} else {
return -1;
}
}
function getNext(pattern: string): number[] {
const next: number[] = [-1];
let i: number = 0, j: number = -1;
while (i < pattern.length - 1) {
if (j === -1 || pattern[i] === pattern[j]) {
i++;
j++;
next[i] = j;
} else {
j = next[j];
}
}
return next;
}
同样,kmp函数接受两个参数,分别是主串str和模式串pattern,返回模式串在主串中第一次出现的位置;如果模式串没有在主串中出现,则返回-1。getNext函数用于计算模式串的next数组。