首先,允许我标题党了,看毛片算法和毛片没啥关系,如果你不小心进来了,那么我只能说呵呵了,呵呵^ ^
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
#include
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