找工作练手--KMP算法理解

来源:互联网 发布:手机淘宝官网找回密码 编辑:程序博客网 时间:2024/05/17 07:28

KMP算法讲解的实在太多了,各位作者为了它的严谨性,不惜花大量篇幅证明算法的正确性,也就用了很多公式去推导。曾经看了严蔚敏和唐宁九两位老师的,写的不错,花了不少时间。找工作在即,又花了点时间温习此算法,感觉比之前有了更多的感悟。

此算法的精髓在于运用匹配字符串P的右移省却了源字符串S的回溯过程,为什么右移以及右移多少位,这是该算法最关键的地方。为什么要右移,这个应该是显而易见的,匹配的过程就是P不断右移的过程;右移多少位,这应该是所有初学KMP算法的同学最困扰的地方了。

我的感悟就是:假设P=P1 P2 P3 .... Pi.....Pn在Pi处与S不匹配,此时P1 P2 P3 ... Pi-1都是已经匹配的,要算P要右移多少位,就是要算出P1 P2  P3 .... Pi-1中最大的K,把P1 P2  P3 ... Pi-1分成了两个字符串:P1 P2 .... Pk-1 和 Pi-k+1 Pi-k+2 ... Pi-1,使得这两个字符串正好匹配。

举例说明:

S = “acabaabaabcacx”  P = "abaabcac"

第一趟:

             S               a       c        a        b       a        a       b        a        a         b        c        a         c          x

             P               a      b       

此时在i = 2时 pi = b != c。此时按照上面我的所述,P1...Pi = a。而此时a仅仅是一个字符的字符串,无法拆分成两个,所以对于此时的右移比较特殊,直接右移一位即可。

第二趟:

             S               a       c        a        b       a        a       b        a        a         b        c        a         c          x

             P                         a     

此时在i = 1处就不匹配了,i之前没有匹配字符串,所以特殊情况,直接右移一位。

第三趟:

             S               a       c        a        b       a        a       b        a        a         b        c        a         c          x

             P                                  a         b       a        a       b        c 

此时在i = 6时不匹配,i之前有“abaab”是已经匹配的。这时的右移按照我上面所说要找到一个最大的K值把“abaab”划分成匹配的两个字符串。很容易看到此时K = 3,划分成的两个字符串分别是: P1 P2   =  “ab” 和 P4P5 = “ab” 。

第四趟:

             S               a       c        a        b       a        a       b        a        a         b        c        a         c          x

             P                                                                a        b       a        a          b        c 

第四趟即为第三趟右移三位后的匹配过程,很清楚地看到此时匹配成功了。

右移多少位就是求next[i]的值,相信大家已经很清楚的知道为什么右移以及右移多少位是如何来的,也就是最大值K是怎么产生的。

详细的证明部分还请大家去看相关书籍吧,理解了上面说的这些,再去看严,唐二人的书,应该就很快了。

下面附上代码:

#include "string.h"// 串类// KMP匹配算法void getNext(const String &P, int next[])// 操作结果: 求模式串P的next数组的元素值{next[0] = -1;// 由next[0] = -1开始进行递推int j = 0, k = -1;// next[j] = k成立的初始情况while (j < P.Length() - 1){// 数组next的下标范围为0 ~ P.Length() - 1, 通过递推方式求得next[j+1]的值if (k == -1){// k == -1只在j == 0时发生next[j+1]=0;// next[j+1]=next[1] = 0j=1; k = 0;// 由于已求得next[1] = 0,所以j = 1, k = 0}else if (P[k] == P[j]){// 此时next[j+1] = next[j]+1next[j+1]=k+1;// 由于P[k] == P[j],所以next[j+1] = next[j]+1 = k + 1j++; k++;// 由于已求得next[j+1]=k+1,所以j更新为++j,k更新为++k}else{// P[k]与P[j]不匹配k = next[k];// 寻求新的匹配字符}}}int KMPIndexHelp(const String &T, const String &P, int pos, int next[])// 操作结果: 通过next数组查找模式串P第一次在目标串T中从第pos个字符开始出现的位置{int i = pos, j = 0;// i为目标串T中的当前字符位置,j为模式串P的当前字符位置while (i < T.Length() && j < P.Length()){if (j == -1){// 此时表明P中任何字符都不再与T[i]进行比较,下次P[0]与T[i+1]开始进行比较i++; j = 0;}else if (P[j] == T[i]){// P[j]与T[i]匹配i++; j++;// 模式串P与目标串T的当前位置向后移}else{// P[j]与T[i]不匹配j = next[j];// 寻找新的模式串P的匹配字符位置}}if (j < P.Length()) return -1;// 匹配失败else return i - j;// 匹配成功}int KMPIndex(const String &T, const String &P, int pos = 0)// 操作结果: 查找模式串P第一次在目标串T中从第pos个字符开始出现的位置{int *next = new int[P.Length()];// 为数组next分配空间getNext(P, next);// 求模式串P的next数组的元素值int result = KMPIndexHelp(T, P, pos, next);// 返回模式串P第一次在目标串T中从第pos个字符开始出现的位置delete []next;// 释next所占用的存储空间return result;}

原创粉丝点击