kmp算法小结

原创 2018年04月16日 11:52:45

首先来说说kmp算法用在什么问题中:

给你两个字符串a和b,问b在a中是否出现过

在这个问题中,我们称a为文本串,b为模式串(只是个名字,并没有什么特殊意义。。),这种问题第一眼看过去就能想到打暴力嘛,但是,时间复杂度为n*m,未免高了点,那怎么办呢?先看看暴力匹配时的弱点在哪里:

假如给你模式串s=abac,文本串ss=abad……(后面的不重要了),那么暴力打法就会将其一位一位的匹配,前三位还是匹配成功了的,但是,却发现在第四位匹配失败了,那么只好从头开始,从模式串的第一位,文本串的第二位再次尝试,但是,我们之前已经知道了,s[1]=ss[1],s[2]=ss[2],s[3]=ss[3],并且s[1]!=s[2],那么从模式串的第一位,文本串的第二位再次尝试,我们早就知道是不会成功的,但是s[1]=s[3],也就是说可以将模式串的指针跳到s[2]处,文本串的指针依然指向ss[4],这样我们就相当于省掉了匹配s[1]和ss[3]以及匹配s[1]和ss[2]的时间,那么这种算法,就叫做“KMP算法”!从它的匹配过程就可以看出它的匹配时间只有n+m,也就是线性时间,那么kmp算法怎么实现呢,让我来细细讲解一下:

首先,这里给出KMP算法的核心:next数组,这个东西就是告诉模式串的指针,当你在这一位匹配失败时,应该跳到哪里继续匹配,那么next数组怎么算呢,其实,next[i]就等于模式串1~i这一段字符串的长度最大的相同的前缀和后缀的长度,就比如下面这个例子:


这样总能理解next数组的含义了吧,但是仔细想想,在匹配时,假如s[j]这一位匹配失败了,那么其实它应该跳到的地方应该是next[j-1]+1,因为只有前j-1位匹配成功了,所以我们只知道1~j-1的一段后缀与它的一段前缀相同长度,所以只能将模式串右移直到文本串中与它的后缀相同的部分与它的那段前缀重合,然后继续匹配,那么我们不妨将next数组右移一位,然后从0开始编号,设next[0]=-1,用来判断当next[j]=-1时就不往回跳了,因为这时候j=0,没地方跳了,只能将文本串的指针向前一位,改完之后就是这个样子的:

可以发现,改完之后是没有多大改变的,那么next数组怎么求呢?总不能枚举前缀和后缀来求吧,这样的话时间复杂度会很高,求next其实只需要用到递归即可,next[0]初始化为-1,next[1]初始化为0(思考一下为什么),然后对于next[j],其实可以由next[j-1]求得,令k=next[j-1],每一次判断s[j]和s[k+1]是否相等,如果相等,next[j]=next[j-1]+1,否则k=next[k],因为s[j]就是1~s[j-1]的最长相同前缀和后缀中的后缀上再加一个字符,那么我们只需要判断在它的前缀后面也加一个字符(也就是加上s[next[j-1]+1])后他们是不是依然相等,如果相等就让next[j]=next[j-1]+1,否则令k=next[j-1],让k=next[k],继续尝试匹配,直到成功为止,可以知道,这样匹配的话第一次匹配成功时的长度一定是最长的,因为next数组越往前越小,也就是k是会越来越小的,如果不懂为什么的话再仔细回忆一下next数组的定义,然后思考或模拟一下就好了。

那么,上代码吧!(这份代码回答的问题是模式串在文本串中出现了多少次,如果想要上面那个问题的代码只需要在此基础上稍作更改即可)

#include <cstdio>
#include <cstring>

char s[1000010],ss[1000010];
int next[1000010];

int main()
{
    scanf("%s",s);//文本串 
    scanf("%s",ss);//模式串 
    int n=strlen(s);
    int m=strlen(ss);
    next[0]=-1;
    next[1]=0;//初始化 
    for(int i=1;i<m;i++)
    {
        int j=next[i];//由next[j]推出next[j+1],和从next[j-1]推出next[j]是一个道理 
        while(j!=-1&&ss[i]!=ss[j])j=next[j];//如上所述,直到匹配成功为止 
        next[i+1]=j+1;
    }
    int i=0,j=0;//i为文本串的指针,j为模式串的指针 
    int ans=0;//记录出现次数 
    while(i<n)
    {
        if(j==-1||s[i]==ss[j])//假如匹配完全失败(完全失败指一位都没有匹配成功,也就是当j=0时都没有匹配成功,这时候j=next[j]就会使得j=-1)或者是匹配成功 
        {
            i++;//这里可能有人会问,匹配完全失败时为什么跟匹配成功时的操作一样呢?匹配成功时为什么i++j++就不用讲了吧 
            j++;//那么当完全失败时,j是不是要变成0,然后i往后跳一位,从头开始重新匹配,那么完全失败时,j=-1,j++后不就是0了吗,那i++对应的就是i往后跳一位 
            if(j==m)//假如匹配成功 
            {
                ans++;
                j=next[j];//依然跳回去重新匹配 
            }
        }
        else j=next[j];//匹配失败,跳回去 
    }
    printf("%d",ans);
}


KMP算法小结

主要看了这里,感觉讲的十分的不错,总结一下。 首先声明要搜索的串为S,设长度为n,要匹配的串为M,设长度为m. 先考虑暴力的算法,暴力的算法是遍历S的每一个字符,然后从这个字符开始和M串进行匹配。时间...
  • minyuanxiani
  • minyuanxiani
  • 2013-08-28 15:27:54
  • 274

各种排序算法小结

  • 2011年12月23日 16:23
  • 52KB
  • 下载

数据结构排序算法小结

  • 2011年12月24日 11:29
  • 15KB
  • 下载

KMP算法小结 2

如果机房马上要关门了,或者你急着要和MM约会,请直接跳到第六个自然段。     我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法。KMP算法是拿来处理字符串匹配的。换句话说,...
  • minyuanxiani
  • minyuanxiani
  • 2013-08-28 15:30:28
  • 256

C#排序算法小结

原文地址: http://www.cnblogs.com/WangJinYang/p/3553792.html 前言 算法这个东西其实在开发中很少用到,特别是web开...
  • chenqiangdage
  • chenqiangdage
  • 2015-10-14 16:32:46
  • 586

verilog算法小结

  • 2013年05月05日 15:12
  • 142KB
  • 下载

最小二乘法小结

最小二乘法是用来做函数拟合或者求函数极值的方法。在机器学习,尤其是回归模型中,经常可以看到最小二乘法的身影,这里就对我对最小二乘法的认知做一个小结。 1.最小二乘法的原理与要解决的问题    ...
  • f9db33t79p
  • f9db33t79p
  • 2017-05-13 16:07:38
  • 169

数据结构排序算法演示

  • 2008年06月09日 17:45
  • 22KB
  • 下载

数据结构之排序算法总结

  • Evelyn_LiuZhen
  • Evelyn_LiuZhen
  • 2016-08-05 15:50:54
  • 159

\KMP 伪代码\KMP 伪代码\KMP 伪代码

  • 2010年12月14日 13:02
  • 2KB
  • 下载
收藏助手
不良信息举报
您举报文章:kmp算法小结
举报原因:
原因补充:

(最多只允许输入30个字)