KMP算法简单分析
2016-04-09 21:31
274 查看
定义问题
字符串匹配是这样一个问题: 对于两个包含且仅包含字母表∑中的字母的串P,T,计算出所有有效的**移进**s使得P[1..|P|] = T[s+1..s+|P|]。(|P|为P的长度)。
或者说:求出在什么位置P被T完全包含。
为了表达方便,定义m = |P|, n = |T|。P称为模式串,T称为匹配串。
朴素算法
朴素算法是一种显然的方法。直接给出伪代码:Naive-Match (P, T) m = |P|, n = |T| for i = 1..n do if P[1..m] == T then print i" "
朴素算法可以看成模式串紧贴匹配串滑动,尝试移进s = 1..n时能否匹配。大多数情况下,朴素算法已经可以解决问题。但是当数据极大(例如在很长的基因串中寻找一组基因)时,朴素算法的效率就显得差了。因此,科学家寻找到许多种优秀的匹配算法。这是一个常用算法时间对照表。
算法 | 预处理 | 匹配 |
---|---|---|
朴素算法 | 0 | O((n-m+1)m) |
Rabin-Karp | Θ(m) | O((n-m+1)m) |
有限自动机 | Θ(m∑) | Θ(n) |
Knuth-Morris-Pratt | Θ(m) | Θ(n) |
KMP算法
Quote:来自 zrO matrix67 Orz
假如,A=”abababaababacb”,B=”ababacb”,我们来看看KMP是怎么工作的。我们用两个指针i和j分别表示,A[i-j+ 1..i]与B[1..j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符(j当然越大越好),现在需要检验A[i+1]和B[j+1]的关系。- 当A[i+1]=B[j+1]时,i和j各加一;什么时候j=m了,我们就说B是A的子串(B串已经整完了),并且可以根据这时的i值算出匹配的位置。
- 当A[i+1]<>B[j+1],KMP的策略是调整j的位置(减小j值)使得A[i-j+1..i]与B[1..j]保持匹配且新的B[j+1]恰好与A[i+1]匹配(从而使得i和j能继续增加)。我们看一看当 i=j=5时的情况。
详细内容参见 http://www.matrix67.com/blog/archives/115
个人理解
我是自己推导之后才看到上面大牛的解释,真的非常通俗。所以看不懂的同学可以去哪里膜拜一下。kmp算法实在比较恶心,虽然代码秘制煎蛋,不习惯推导的童鞋直接背下来就可以了。:(语言表达能力捉急):。ps:这里并没有使用图形辅助理解,个人认为这样更有利于理解kmp匹配原理。
kmp基于一个函数π,π表示有最大的t < i使P[1..t] = P,则t = π。或者形式化地:
π[i] = max{t | P[1..t] = P 且 t < i}
证明一个结论,对于任意T[k-i+1..k] = P[1..i],有:
π[i] = max{t | P[1..t] = T[k-t+1..k]} 用反证法,假设有π < x < i使得P[1..x] = T[k-x+1..k] ∵ P[1..i] = T[k-i+1..k] ∴ T[k-x+1..k] = P 又 P[1..t] = T[k-t+1..k] ∴ P[1..x] = P ∵ x > π[i], 根据定义,矛盾 原命题得证。
这个结论将说明kmp不会错过正确解。
以及:
如果有s使T[k-s+1..k] = P[1..s], 那么有T[k-π[s]+1..k] = P[1..π[s]]。 证明很简单,根据定义等量代换即可。
这个结论将说明kmp不会找到错误解。
这些结论并不足以证明kmp的正确性,但是基本可以看出主要思想了。事实上,通过π可以省略许多无用的比较(基于第二个结论)。kmp匹配算法代码如下:
void kmp_match(int l) { // l是T的长度,pL是P的长度 int q = 0; // 匹配的长度 for (int i=1; i<=l; i++) { while (q > 0 && P[q+1] != T[i]) q = pie[q]; // 无法匹配下一位,找到可以部分匹配的最大部分,或者没有可以匹配 if (P[q+1] == T[i]) q++; // 下一位可以匹配 if (q == pL) { // 找到 printf("Shift %d >>> ", i-pL); q = pie[q]; // 找下一个匹配位置 } } }
计算匹配函数π的方法:
void kmp_init() { int k = 0; pie[1] = pie[0] = 0; // 第一位不可能找到匹配 for (int i=2; i<=pL; i++) { while (k > 0 && P[k+1] != P[i]) k = pie[k]; // 同上,自己匹配自己罢了 if (P[k+1] == P[i]) k++; pie[i] = k; // 记录最长匹配 } }
所谓自己匹配自己,就是π就是找到一对最大且相等的前缀和后缀,记录前缀出现位置。(基于定义)
kmp大概就是这样了,多思考就可以想通。。
kmp时间复杂度分析
kmp的复杂度为Θ(n)-Θ(m),这里用摊还分析中的聚合分析法给出一个kmp_init复杂度分析例子。我们试图证明while循环的执行次数为O(n)。k的初值为0,而k的值增长有且只有一个途径:10行的k++。由于for循环一次k最多加一,n-1次循环之后k最多为n-1呢。由于π < i,因此while循环只会使k减少,且一次至少减少1。而k < n-1,所以while的循环次数为O(n)。不难得出kmp_init的复杂度为Θ(n)。用这种方法也可以得出kmp_match的复杂度为Θ(m)。
linux下装逼代码
装逼专用,仅售998,到linux上看看效果吧。#include <iostream> #include <cstdio> #include <cctype> using namespace std; char P[10005], T[10005]; int pL; int pie[10005]; int readfln(char *str) { char c; int i = 0; str[0] = '\"'; while (c = getchar()) { if (c!= '\n') str[++i] = c; else break; } return i; } void printfln(int shift,int l) { int beg = shift-5; if (shift <= 5) beg = 0; else printf("..."); for (int i=beg+1; i<=shift; i++) putchar(T[i]); printf("\033[33m"); printf("%s", P+1); printf("\033[0m"); int end = shift+pL+5; if (shift+pL+5 > l) end = l; for (int i=shift+pL+1; i<=end; i++) putchar(T[i]); if (shift+pL+5 < l) printf("..."); printf("\n"); } void kmp_init() { int k = 0; pie[1] = pie[0] = 0; for (int i=2; i<=pL; i++) { while (k > 0 && P[k+1] != P[i]) k = pie[k]; if (P[k+1] == P[i]) k++; pie[i] = k; } } void kmp_match(int l) { int q = 0; for (int i=1; i<=l; i++) { while (q > 0 && P[q+1] != T[i]) q = pie[q]; if (P[q+1] == T[i]) q++; if (q == pL) { printf("Shift %d >>> ", i-pL); printfln(i-pL,l); q = pie[q]; } } } int main() { pL = readfln(P); kmp_init(); int l; while (l = readfln(T)) kmp_match(l); return 0; }
参考资料:《算法导论》
相关文章推荐
- lintcode: Maximum Subarray
- 软件工程个人作业04
- 【VS开发】COM组件技术概述
- androidStudio异常:Couldn't load memtrack module (No such file or directory)
- 【步兵 cocos-js】prototype在开发中的妙处
- 《Effective STL》学习笔记(第三部分)
- Java泛型机制
- Socket.io 400 (Bad Request)
- PHP入门学习笔记之—— PHP支持的数据类型
- x86上的那些UNIX——BSD篇+主流桌面
- 排序问题-插入排序
- 阿里云人工智能小Ai是比深度学习更高阶的算法
- 使用 PuTTY 安全复制客户端将文件传输到您的 Linux 实例
- find ith smallest element in an array
- 《Effective STL》学习笔记(第二部分)
- 返回N的二进制表示中1的个数
- 近期遇到的好几个创业团队和创业题材
- 【leetcode】20. Valid Parentheses
- ## stm32库函数初探 ##
- Android性能优化