菜鸟都能理解的看毛片(KMP)算法

来源:互联网 发布:excel筛选后数据求和 编辑:程序博客网 时间:2024/04/19 08:28

首先,允许我标题党了,看毛片算法和毛片没啥关系,如果你不小心进来了,那么我只能说呵呵了,呵呵^ ^

KMP算法其实是一个O(n)的字符串匹配算法

A = "ababacbacab"

B = "baca"

假设位置从1开始

这样可以说B是A的一个子串,首先我们想到的办法是枚举A的位置,比如

1.首先枚举位置A[1],即字符'a',然后从A[1]开始比较"abab"是否等于"baca",显然不等

2.接着枚举位置A[2],即字符'b',然后从A[2]开始比较"baba"是否等于"baca",显然,又不匹配

......

经过不断得尝试,我们终于枚举到一个位置A[7]开始的子串"baca"与B相等,my god,真不容易

我们来计算一下时间复杂度,我们枚举A的位置,最多有O(A.length())个位置,并且每个位置最多要匹配O(B.length()),所以,算法复杂度当然是O(A.length()*B.length())的啦


下面我们来见识一下神奇的看毛片算法

看毛片算法的思想是用两个指针i和j来指示A和B中的一个位置

1) 用i来表示当前匹配到A中的哪个位置啦

2) 用j来表示当前匹配到B中的哪个位置啦

3) 并且要满足B[1...j]要和A[i+j-1...i]相等

哦?很难理解啊,下面的图(图1)应该使你能够一拍脑袋,“哦,我太聪明了,这么简单!”


图1

灰色部分相等的啦^ ^


算法开始前,我们先考虑下面一个东东

    指示数组p满足p[j]表示使得B[1...p[j]] = B[j-p[j]+1...j](图3)

    

  图3(图片太大,新窗口打开吧亲)

灰色的两个部分相等

简单推理一下,你猜猜看三个红色的部分是不是也相等(请务必搞懂,非常重要)?答案是肯定的


算法开始咯我们要考虑这个问题,i和j应该怎么增长

①  如果A[i+1] = B[j+1]

     那么,我们只需要将两个灰色框子往后面拖动一格就行了哇(图2),并且,如果j变成B.length(),那么我们的匹配过程就结束咯,聪明的你一定理解吧?


图2

灰色部分相等的啦^ ^


② 如果A[i+1] != B[j+1]呢?

回顾一下i和j的定义先(看图1即可),我们可以将j改为p[j],显然更改后的p[j]任然满足图1哦,改完后,图1接下来扩展成介个样子哩(图4)


图4(对照图1和图3看哦)


调整了一下j的位置,我们又得到了一个新的j啦(j = p[j]),聪明的你如果看不懂图4只能说明。。。傻傻的我讲述得还不够清楚,欢迎留言开骂~

接下来呢,我们得看看B[p[j]+1] == A[i+1]成立否,这里,我们不知不觉又递归到“算法开始咯”,请你去递归着玩玩吧。。。


很开心地告诉你,看到这里,你已经几乎明白了KMP算法的整个流程,可以去上个厕所先,回来之后我们继续剩下的步骤,喝口茶,慢慢欣赏吧








欢迎回来,呃,继续....

罗里吧嗦一大堆,终于可以上代码咯,你会发现这个过程如此简单,亮瞎你的钛合金神眼,这就是编程之美,上!


  for (j = 0, i = 0; i < n; ++i)  {    while (j > 0 && B[j+1] != A[i+1]) j = p[j];//看图4啦    if (B[j+1] == A[i+1]) ++j;    if (j == m)    {      //如果B串都结束了,那么显然成功匹配咯      cout << "匹配成功: " << i - m + 1 << endl;      j = p[j]; //使得可以继续匹配下去,这个你自己画个图就明白为啥要这么做咯    }  }



复杂度分析:我们首先观察i,由于我们是依次扫描A数组中i的位置,所以你有木有发现i一直在增加,并且i只能增加A.length()次?而++i,++j是靠在一起的语句,所以呢j最多也只能增加A.length()次,并且j = p[j]语句说明j一直在减少,由于j只能增加A.length()次,所以j顶多也只能减少A.length()次,否则,j不是成了负数了嘛,而我们观察while(1)里面的内容发现,j每次不是增加就是减少,所以增加和减少(最外层的if elseif)两个语句加起来顶多执行2*A.length()次,所以呢,这个时间按复杂度当然是O(A.length())的啦


聪明的你一定还发现我们还有一个问题木有解决,就是,指示数组P!,我会告诉你P通过O(B.length())就能搞定的么?

假定我们已经知道p[1], ...p[j]是多少了,我们如何来构造p[j+1]呢,诶呀,我都口干舌燥了,喝口水慢慢来,上个图先(图5)


           图5(最好保存下来看,图太大了,坑爹的csdn没有滚动条!!!)

相信你仔细观察上图之后我不用多做解释就能知道p[j+1]的构造过程了吧,你只需要搞清楚p[j]的定义,一切问题都是很轻松的KO掉的

p构造过程源码如下


  p[1] = 0;  for (j = 1; j <= m - 1; ++j)  {    k = j;      while (k > 0 && B[j+1] != B[p[k]+1]) k = p[k];    p[j+1] = (k!=0 ? p[k]+1 : 0);  }



最后,放出完整源码

#include <iostream>#include <string>using namespace std;int main() {  string A = " ababababafcbaababafcc";  //string A = " ababafcc";  string B = " ababafcb";  int p[20];  int n = A.size() - 1;  int m = B.size() - 1;  int i, j, k;  p[1] = 0;  for (j = 1; j <= m - 1; ++j)  {    k = j;    while (k > 0 && B[j+1] != B[p[k]+1]) k = p[k];    p[j+1] = (k!=0 ? p[k]+1 : 0);  }  //进行匹配  for (j = 0, i = 0; i < n; ++i)  {    while (j > 0 && B[j+1] != A[i+1]) j = p[j];    if (B[j+1] == A[i+1]) ++j;    if (j == m)    {      cout << "匹配成功: " << i - m + 1 << endl;      j = p[j];    }  }  return 0;}


感谢matrix67大神的文章http://www.matrix67.com/blog/archives/115