复杂度:

O(len(a)+len(b))

技巧及注意:

在匹配的时候记住先要自身匹配然后再匹配即可,同时边界问题不能忽略,处理好点吧。

#include <cstdio>
#include <cstring>
using namespace std; const int N=10000;
char a[N], b[N];
int p[N]; int main() {
scanf("%s%s", a+1, b+1);
int i, j, siza=strlen(a+1), sizb=strlen(b+1);
j=0;
for(i=2; i<=sizb; ++i) {
while(j && b[j+1]!=b[i]) j=p[j];
if(b[j+1]==b[i]) ++j;
p[i]=j;
}
j=0;
for(i=1; i<=siza; ++i) {
while(j && b[j+1]!=a[i]) j=p[j];
if(b[j+1]==a[i]) ++j;
if(j==sizb) {
printf("%d\n", i-j+1);
return 0;
}
}
puts("-1");
return 0;
}

详解我从matrix67摘下来的。简单易懂,妈妈再也不用担心我的学习了

转自 http://www.matrix67.com/blog/archives/115


如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段。

我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法。KMP算法是拿来处理字符串匹配的。换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串)。比如,字符串A="I'm matrix67",字符串B="matrix",我们就说B是A的子串。你可以委婉地问你的MM:“假如你要向你喜欢的人表白的话,我的名字是你的告白语中的子串吗?”
    解决这类问题,通常我们的方法是枚举从A串的什么位置起开始与B匹配,然后验证是否匹配。假如A串长度为n,B串长度为m,那么这种方法的复杂度是O (mn)的。虽然很多时候复杂度达不到mn(验证时只看头一两个字母就发现不匹配了),但我们有许多“最坏情况”,比如,A= "aaaaaaaaaaaaaaaaaaaaaaaaaab",B="aaaaaaaab"。我们将介绍的是一种最坏情况下O(n)的算法(这里假设 m<=n),即传说中的KMP算法。
    之所以叫做KMP,是因为这个算法是由Knuth、Morris、Pratt三个提出来的,取了这三个人的名字的头一个字母。这时,或许你突然明白了AVL 树为什么叫AVL,或者Bellman-Ford为什么中间是一杠不是一个点。有时一个东西有七八个人研究过,那怎么命名呢?通常这个东西干脆就不用人名字命名了,免得发生争议,比如“3x+1问题”。扯远了。
    个人认为KMP是最没有必要讲的东西,因为这个东西网上能找到很多资料。但网上的讲法基本上都涉及到“移动(shift)”、“Next函数”等概念,这非常容易产生误解(至少一年半前我看这些资料学习KMP时就没搞清楚)。在这里,我换一种方法来解释KMP算法。

假如,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时的情况。

i = 1 2 3 4 6 7 8 9 ……
    A = a b a b a b a a b a b …
    B = a b a b a c b
    j =  1 2 3 4 6 7

此时,A[6]<>B[6]。这表明,此时j不能等于5了,我们要把j改成比它小的值j'。j'可能是多少呢?仔细想一下,我们发现,j'必须要使得B[1..j]中的头j'个字母和末j'个字母完全相等(这样j变成了j'后才能继续保持i和j的性质)。这个j'当然要越大越好。在这里,B [1..5]="ababa",头3个字母和末3个字母都是"aba"。而当新的j为3时,A[6]恰好和B[4]相等。于是,i变成了6,而j则变成了 4:

i = 1 2 3 4 5 7 8 9 ……
    A = a b a b a b a a b a b …
    B = a b a b a c b
    j =  1 2 3 5 6 7

从上面的这个例子,我们可以看到,新的j可以取多少与i无关,只与B串有关。我们完全可以预处理出这样一个数组P[j],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足B[1..P[j]]=B[j-P[j]+1..j]的最大值。
    再后来,A[7]=B[5],i和j又各增加1。这时,又出现了A[i+1]<>B[j+1]的情况:

i = 1 2 3 4 5 6 8 9 ……
    A = a b a b a b a a b a b …
    B = a b a b a c b
    j =  1 2 3 4 6 7

由于P[5]=3,因此新的j=3:

i =  1 2 3 4 5 6 8 9 ……
    A = a b a b a b a a b a b …
    B = a b a b a c b
    j =  1 2 4 5 6 7

这时,新的j=3仍然不能满足A[i+1]=B[j+1],此时我们再次减小j值,将j再次更新为P[3]:

i = 1 2 3 4 5 6 8 9 ……
    A = a b a b a b a a b a b …
    B = a b a b a c b
    j =  2 3 4 5 6 7

现在,i还是7,j已经变成1了。而此时A[8]居然仍然不等于B[j+1]。这样,j必须减小到P[1],即0:

i = 1 2 3 4 5 6 8 9 ……
    A = a b a b a b a a b a b …
    B = a b a b a c b
    j =  1 2 3 4 5 6 7

终于,A[8]=B[1],i变为8,j为1。事实上,有可能j到了0仍然不能满足A[i+1]=B[j+1](比如A[8]="d"时)。因此,准确的说法是,当j=0了时,我们增加i值但忽略j直到出现A[i]=B[1]为止。
    这个过程的代码很短(真的很短),我们在这里给出:

j:=0;
for i:=1 to n do
begin
   while (j>0) and (B[j+1]<>A[i]) do j:=P[j];
   if B[j+1]=A[i] then j:=j+1;
   if j=m then
   begin
      writeln('Pattern occurs with shift ',i-m);
      j:=P[j];
   end;
end;

最后的j:=P[j]是为了让程序继续做下去,因为我们有可能找到多处匹配。
    这个程序或许比想像中的要简单,因为对于i值的不断增加,代码用的是for循环
。因此,这个代码可以这样形象地理解:扫描字符串A,并更新可以匹配到B的什么位置。

现在,我们还遗留了两个重要的问题:一,为什么这个程序是线性的;二,如何快速预处理P数组。
    为什么这个程序是O(n)的?其实,主要的争议在于,while循环使得执行次数出现了不确定因素。我们将用到时间复杂度的摊还分析中的主要策略,简单地说就是通过观察某一个变量或函数值的变化来对零散的、杂乱的、不规则的执行次数进行累计。KMP的时间复杂度分析可谓摊还分析的典型。我们从上述程序的j 值入手。每一次执行while循环都会使j减小(但不能减成负的),而另外的改变j值的地方只有第五行。每次执行了这一行,j都只能加1;因此,整个过程中j最多加了n个1。于是,j最多只有n次减小的机会(j值减小的次数当然不能超过n,因为j永远是非负整数)。这告诉我们,while循环总共最多执行了n次。按照摊还分析的说法,平摊到每次for循环中后,一次for循环的复杂度为O(1)。整个过程显然是O(n)的。这样的分析对于后面P数组预处理的过程同样有效,同样可以得到预处理过程的复杂度为O(m)。
    预处理不需要按照P的定义写成O(m^2)甚至O(m^3)的。我们可以通过P[1],P[2],…,P[j-1]的值来获得P[j]的值。对于刚才的B="ababacb",假如我们已经求出了P[1],P[2],P[3]和P[4],看看我们应该怎么求出P[5]和P[6]。P[4]=2,那么P [5]显然等于P[4]+1,因为由P[4]可以知道,B[1,2]已经和B[3,4]相等了,现在又有B[3]=B[5],所以P[5]可以由P[4] 后面加一个字符得到。P[6]也等于P[5]+1吗?显然不是,因为B[ P[5]+1 ]<>B[6]。那么,我们要考虑“退一步”了。我们考虑P[6]是否有可能由P[5]的情况所包含的子串得到,即是否P[6]=P[ P[5] ]+1。这里想不通的话可以仔细看一下:

1 2 3 4 5 6 7
    B = a b a b a c b
    P = 0 0 1 2 3 ?

P[5]=3是因为B[1..3]和B[3..5]都是"aba";而P[3]=1则告诉我们,B[1]、B[3]和B[5]都是"a"。既然P[6]不能由P[5]得到,或许可以由P[3]得到(如果B[2]恰好和B[6]相等的话,P[6]就等于P[3]+1了)。显然,P[6]也不能通过P[3]得到,因为B[2]<>B[6]。事实上,这样一直推到P[1]也不行,最后,我们得到,P[6]=0。
    怎么这个预处理过程跟前面的KMP主程序这么像呢?其实,KMP的预处理本身就是一个B串“自我匹配”的过程。它的代码和上面的代码神似:

P[1]:=0;
j:=0;
for i:=2 to m do
begin
   while (j>0) and (B[j+1]<>B[i]) do j:=P[j];
   if B[j+1]=B[i] then j:=j+1;
   P[i]:=j;
end;

最后补充一点:由于KMP算法只预处理B串,因此这种算法很适合这样的问题:给定一个B串和一群不同的A串,问B是哪些A串的子串。

串匹配是一个很有研究价值的问题。事实上,我们还有后缀树,自动机等很多方法,这些算法都巧妙地运用了预处理,从而可以在线性的时间里解决字符串的匹配。我们以后来说。

昨天发现一个特别晕的事,知道怎么去掉BitComet的广告吗?把界面语言设成英文就行了。
    还有,金山词霸和Dr.eye都可以去自杀了,Babylon素王道。

Matrix67原创
转贴请注明出处


我就放上我写的模板:

#include <cstdio>
#include <cstring> using namespace std; const int N=100000000;
char a[N], b[N];
int p[N]; int main() {
scanf("%s%s", a, b);
int i, j, siza=strlen(a), sizb=strlen(b);
j=p[0]=-1;
for(i=1; i<sizb; ++i) {
while(j>=0 && b[j+1]!=b[i]) j=p[j];
if(b[j+1]==b[i]) ++j;
p[i]=j;
} j=-1;
int ok=0;
for(i=0; i<siza; ++i) {
while(j>=0 && b[j+1]!=a[i]) j=p[j];
if(b[j+1]==a[i]) ++j;
if(j==sizb-1) {
printf("%d\n", i-j);
//j=p[j]; //默认找第一个串,如果想找多个匹配串,就取消注释并且注释后面2行
ok=1;
break;
}
}
if(!ok) printf("-1\n"); return 0;
}

小结:kmp的更多相关文章

  1. [数据结构]KMP小结

    KMP小结   By Wine93 2013.9 1.学习链接: http://www.matrix67.com/blog/archives/115 2.个人小结 1.KMP在字符串中匹配中起着巨大作 ...

  2. KMP Algorithm 字符串匹配算法KMP小结

    这篇小结主要是参考这篇帖子从头到尾彻底理解KMP,不得不佩服原作者,写的真是太详尽了,让博主产生了一种读学术论文的错觉.后来发现原作者是写书的,不由得更加敬佩了.博主不才,尝试着简化一些原帖子的内容, ...

  3. ACM - KMP题目小结 (更新中)

    KMP算法题型大致有两类,一类是next数组的应用,一类是匹配问题. next数组大多数是求字符串周期,或者是与前缀后缀有关,也可以应用在DP中.需要对next数组有一定理解才能做得出. next数组 ...

  4. KMP算法小结

    最近看了一些关于KMP算法的资料,在此写一篇博客总计一下. 1.KMP算法介绍 KMP算法是一种字符串搜索的改进算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称 ...

  5. 第4章学习小结_串(BF&KMP算法)、数组(三元组)

    这一章学习之后,我想对串这个部分写一下我的总结体会. 串也有顺序和链式两种存储结构,但大多采用顺序存储结构比较方便.字符串定义可以用字符数组比如:char c[10];也可以用C++中定义一个字符串s ...

  6. ACM -- 算法小结(四)KMP(POJ3461)

        KMP -- POJ3461解题报告 问题描述:给出字符串P和字符串T,问字符串P在字符串T中出现的次数 Sample Input 3 BAPC BAPC AZA AZAZAZA VERDI ...

  7. kmp学习小结

    KMP 简要说明 \(kmp\)是一个非常神奇的东西.它的\(fail(next)\)数组\(f[i]\)就表示\(1\)~\(i\)这个串的最长公共前缀后缀长度.根据这个\(fail\)数组,在匹配 ...

  8. KMP小结

    1. KMP模版: 代表题目:POJ 3641 Oulipo KMP http://blog.csdn.net/murmured/article/details/12871891 char P[MAX ...

  9. KMP概念上小结

    kmp算法的时间复杂度是O(m+n) 主要作用: 1.最长公共前后缀问题 2.原串中含有几个模式串问题 3.循环节问题

随机推荐

  1. hdu1151 二分图(无回路有向图)的最小路径覆盖 Air Raid

    欢迎参加——BestCoder周年纪念赛(高质量题目+多重奖励) Air Raid Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65 ...

  2. Linux tar命令exclude选项排除指定文件或目录

    在linux中可以用tar打包目录以方便传输or备份,我们先来看一个例子 test 文件夹有如下文件 [root@lee ~]# ll test 总用量 -rw-r--r--. root root 4 ...

  3. 【持续集成】使用Jenkins实现多平台并行集成

    使用Jenkins实现多平台并行集成 二月 15, 2012 暂无评论 我们的后端C应用都是支持跨平台的,至少目前在Linux和Solaris上运行是没有问题的,这样一来我们在配置持续集成环境时就要考 ...

  4. 【转】SQL删除重复记录,只保留其中一条

    SQL:删除重复数据,只保留一条用SQL语句,删除掉重复项只保留一条在几千条记录里,存在着些相同的记录,如何能用SQL语句,删除掉重复的呢 1.查找表中多余的重复记录,重复记录是根据单个字段(peop ...

  5. Java for LeetCode 060 Permutation Sequence

    The set [1,2,3,…,n] contains a total of n! unique permutations. By listing and labeling all of the p ...

  6. Java for LeetCode 029 Divide Two Integers

    Divide two integers without using multiplication, division and mod operator. If it is overflow, retu ...

  7. MFC 密码框

    使用Edit Control 在属性面板中,设置“行为”为password

  8. 集群管理 secondaryNameNode和NameNode(转)

    为了达到以下负责均衡,需要调整以下 改变负载 三台机器,改变负载 host2(NameNode.DataNode.TaskTracker) host6(SecondaryNameNode.DataNo ...

  9. 一、HTML和CSS基础--网页布局--网页简单布局之结构与表现原则

    结构.表现和行为分离,不仅是一项技术,更主要的是一种思想,当我们拿到一个网页时,先考虑设计图中的文字内容和内容模块之间的关系,重点放在编写html结构和语义化,然后考虑布局和表现形式.,减少HTML与 ...

  10. Webloigic监控

    http://www.huilog.com/?p=688 http://www.beansoft.biz/weblogic/docs92/jmx/accessWLS.html http://docs. ...