朴素算法在对比过程中如果发现失配则会将模板串右移一位然后从第一个字符开始重新匹配。
而KMP算法认为匹配过的位置就不应该再匹配一次。
对于模板串abcde,文本串abcdd
当e位置失配时我们已经知道了文本串当前位置的前4位是abcd此时就没有必要再一次匹配,而快速移动到下一次可能适配位置则是kmp的核心要点。
而实现这一方案首先需要做自匹配,这也是为什么kmp要先自己匹配自己,通过自匹配算法可以得到一个失配状态转移图,
设f[i]表示i失配后下一次要匹配的位置,在计算时我们要使用f[0],f[1],...f[i-1]来递推f[i]
如何从旧状态递推新状态:
此处要引入一个前缀后缀的概念。 设想对于两个串{文本串:abcabd... , 模板串:abcabe... }比较,在位置 i 处不同则意味着他们有一个相同的长度为 i 的前缀。那么下一个可能匹配文本串 i 位置字符的位置在哪呢? 分析此时的模板串{例子中模板串e位置失配,我们知道文本串对应位置不可能是e且其前五{已匹配五个字符}个字符是a,b,c,a,b。其后缀为b, ab, cab, bcab。而对于模板串此时有前缀a, ab, abc, abca。而模板串e位置若想重新和文本串匹配,则已匹配子串的后缀必须要等于,模板串此时对应子串的前缀,其实两个子串完全匹配可以都认为是模板串中已匹配子串,而为了造成能匹配串的丢失,我们要在可移动集合里选择最短步长。要让步长最短,需要已匹配串长 - 可能匹配子串长最小, 即可能匹配的最长子串。则为在模板串里开始位置长度为[i - 1]的子串的完全相等的最大前缀,后缀长度}
对于模板串s : {abcabe} ,i 位置的最长匹配前缀后缀长度递推。易知f[0] = 0, f[1] = 0(长度为1的串没有前后缀);
| i | 前缀 | 后缀 |
| 0 | ||
| 1 | ||
| 2 | a | b |
| 3 | a, ab | c, bc |
| 4 | a, ab, abc | a, ca, bca |
| 5 | a, ab, abc, abca | b, ab, cab, bcab |
| 6 | a, ab, abc, abcab | e, be, abe, cabe, bcabe |
状态图递推的过程中类似于自匹配的递推使用,加入上一次已匹配ab,则有串ab_**ab,下一位置会加入字符@,构成串 ab_**ab@ , 可以发现若_与@匹配则最大长度在上一次基础上加1,若不匹配易知,串前缀为ab_前缀ab,ab@前缀ab,他们相同,则退化为子串ab@上的自匹配问题。
void calcuate(char* c, int* f) {
f[0] = f[1] = 0;
for(int i = 1; i < strlen(c); i++) {
int j = f[i];
while(j && c[i] != c[j]) j = f[j];
f[i + 1] = c[i] == c[j] ? j + 1 : 0;
}
}
本文深入解析KMP算法的核心思想及实现过程,强调了KMP算法在字符串匹配中的优势,即避免重复匹配,通过构建失配状态转移图提高匹配效率。
1万+

被折叠的 条评论
为什么被折叠?



