KMP算法理解
来源:互联网 发布:mac 启动 磁盘工具 编辑:程序博客网 时间:2024/05/29 15:47
作者:高城
链接:https://www.zhihu.com/question/36149122/answer/66867065
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
面试官:
现在请你把我当做完全不了解KMP算法的人,向我解释一下KMP算法的原理。
面霸:
KMP算法俗称“看(K)毛(M)片(P)算法”,用于快速文本匹配。试想你有两条字符串src和pat,需要判断pat是否在src中出现,如果出现就给出出现的具体位置。假设src的长度为N,pat的长度为M,首先我们来讨论一下一个平凡的蛮力解。
如果你想判断两个字符串是否相等,你会写一个这样的循环(在纸上写下):
for(int i = 0; i < M; i++) if(src[ i ] != dst[ i ]) return false;
return true;
这里你最多比较了M次。回到原来的问题,有了这个子函数,你只要依次判断(一边在纸上写符号)src[i, i + M - 1], i = 0, 1, 2, … 与dst是否相等就行了。复杂度是N乘以M。
那么如何优化呢?我换一种说法,为什么能够优化呢?因为你做了重复的事情。我们换一种写法,就能更直观地发现重复在哪里。
void bruteMatch(char* src, char* pat, vector<int> &ans){ int N = strlen(src), M = strlen(pat); if(M == 0 || N < M) return; int i = 0, j = 0; while(i <= N - M) { j = 0; while(j < M && src[i] == pat[j]) { i++; j++; } if( j == M ) ans.push_back(i - M); i = i - j + 1; }}
你看这个变量i(一边用手指),它每次比较完一轮,就退回到原来的位置的下一个位置重新开始匹配。难道我刚刚做了那么多次比较,得到的信息就白白扔掉了吗?KMP三人找到了一种简单的利用这信息的方法,只要花点力气对模式串pat做一下预处理,就能使这匹配程序里的循环变量i只进不退,从而达到N+M的复杂度。
我们把这个匹配过程想象成,模式串依附着源串向后移动。你看(一边画图),i在这个位置,j在这个位置,走了这么一段路才发生失配了,意味着这两条(src[i - j, i - 1]和pat[0, j-1])是公共子串,也就是说此时src的这条子串的信息完全包含于pat的前缀子串之中,原则上我如果对pat做了充分的了解,就可以保持i不变,而单单令pat往后移动。假如我可以令pat移动一步而i不变,那说明这两大段是相等的;假如这两大段不相等,那么我至少可以令pat移动两步。观察发现,我应该令pat移动多少步,取决于pat[0, j - 1]的最长的相等{前缀、后缀}的长度。
KMP的精髓就在于,用了一个线性的算法,得到了每次在pat[ j ]发生失配时,应该让pat往后移动多少步,这个值对应于pat[0, j - 1]的最长相等{前缀、后缀}的长度。这些值所存的数组叫做next数组。
我需要写出计算next数组的函数,才能具体解释这个算法。
面试官:
可以了,说到最长相等前后缀这点就足够了。接下来请你手写一个归并排序的代码……
附一套高效的KMP代码:
void get_next(const string &pat, vector<int> &next){ // 通过该函数得到next数组之后,当在src[i]和pat[j]处发生失配时,保持i不动, // j变更为next[j],就相当于把pat向后移动了(j - next[j])步 int i = 0, j = -1, p_len = pat.length(); next[0] = -1; while (i < p_len) { if (j == -1 || pat[i] == pat[j]) { i++; j++; if (pat[i] != pat[j]) next[i] = j; else next[i] = next[j]; } else { j = next[j]; } }}
这个函数的过程可以看作将pat和自身做匹配的过程。第13行至第16行的判断比较令人费解,其实那是一个加速优化。语句j = next[ j ]也可以看作用动态规划做的优化。get_next可以写成下面的非优化形式,并且它的复杂度也是O(M):
void get_next(const string &pat, vector<int> &next){ // 通过该函数得到next数组之后,当在src[i]和pat[j]处发生失配时,保持i不动, // j变更为next[j],就相当于把pat向后移动了(j - next[j])步 int i = 0, j = -1, p_len = pat.length(); next[0] = -1; while (i < p_len) { if (j == -1 || pat[i] == pat[j]) { i++; j++; next[i] = j; } else { j--; } }}
然后是利用next数组进行匹配的代码:
//add the positions to a vectorvoid indice_kmp(const string &str, const string &pat, vector<int> &ans){ int i = -1, j = -1; int len1 = str.length(), len2 = pat.length(); vector<int> next(len2 + 2, 0); get_next(pat, next); while(i < len1) { if (j == -1 || str[i] == pat[j]) { i++; j++; if(j == len2){ ans.push_back(i - len2); } } else { // (j - next[j])就是pat向后移动的步数 j = next[j]; } }}
关于复杂度:无论是get_next还是indice_kmp,在每个while循环单体中,存在一个严格增量:i + (模式串往后移动的步数),因此该算法的复杂度是O(N + M).
最后说一句:看毛片有害身心健康。
有首歌是这么唱的:
You VOTE ME UP~~~~ so I can stand on moutains ~~~~
- KMP算法理解
- 深入理解KMP算法
- KMP算法的理解
- 理解KMP算法
- KMP算法初步理解
- 从头到尾理解KMP算法
- 从头到尾理解KMP算法
- KMP算法的理解
- KMP算法理解
- KMP算法---理解
- 从头到尾理解KMP算法
- 对KMP算法理解
- KMP算法理解
- 【算法】KMP的理解
- KMP算法的理解
- 理解KMP算法
- KMP算法初步理解
- 深入理解KMP算法
- Android可收缩/扩展的TextView【1】
- HTML5 canvas--可动的时钟
- 代码布局relativeLayout的位置设置
- SDUT OJ 2139 图结构练习——BFS——从起始点到目标点的最短步数
- SOCKET简介
- KMP算法理解
- OpenCV Surf特征匹配
- Linux c编写用数据库写的通讯录
- python连接mysql数据库
- iOS多线程入门
- iOS开发进阶-UIAlertController使用
- 缓存陷阱
- 使用rdtsc指令,测量程序的运行速度
- POJ 2406 Power Strings(KMP)