kmp算法详解 by huangchao

http://chaoswork.com/blog/2011/06/14/kmp%E7%AE%97%E6%B3%95%E5%B0%8F%E7%BB%93/

写得非常详细,和matrix67的一起看效果非常好

 

主要看了这里,感觉讲的十分的不错,总结一下。

首先声明要搜索的串为S,设长度为n,要匹配的串为M,设长度为m.

先考虑暴力的算法,暴力的算法是遍历S的每一个字符,然后从这个字符开始和M串进行匹配。时间复杂度为O(nm).

怎么在此基础上进行优化?假设现在从某个位置(设为s)开始和M串进行匹配,如果匹配不成功,暴力算法是从这个位置的下一个位置(s+1)进行匹配,直观上来说就是匹配的字符串向后“滑动”了一位。

image

图1

能不能想办法让M向后移动的距离最大化?考虑最好的情况,如果和M匹配的S中的m个字符和M中的字符没有一个相等,那么能向右移动m位;考虑最坏的情况,比如上图,只能移动一位。

而KMP就是在这里做文章,让M串向后“滑动”的距离最大化。

image

图2

考虑上面的图,M中灰色部分已经和S的灰色部分匹配上了,而灰色部分后一个字符不匹配,则现在M要向后滑动,假设一直向后滑动,直到如图位置又和S再一次匹配上了,那么从这里我们可以得到如下的结论:

  • A段字符串是M的一个前缀。
  • B段字符串是M的一个后缀。
  • A段字符串和B段字符串相等。

这样,如果暂时不考虑S,只看M的话,假设已经匹配的M的字串(即图中M中灰色部分)为subM,则subM有个【相等】的【前缀】和【后缀】。而且M在遇到不匹配的时候可以直接滑动到使subM的前缀和subM的后缀重合的地方。而M向后滑动的时候,第一次subM的前缀和后缀重合意味着此时这个相等的subM的前缀和后缀的长度是最大的。

我们的任务就是要寻找subM的最长的前缀和后缀相等的串。

知道了这一点,离KMP的真谛也就不远了。现在结合这上面的图模拟一下KMP算法的整个流程:

  • 将S串和M串从第一个字符开始匹配;
  • 如果匹配成功,则subM即灰色部分增加;
  • 如果不成功,则M向后滑动使滑动后的subM的前缀和滑动前的subM的后缀重合,再进行匹配,如果还不成功,则再次滑动M,直到匹配成功或者M滑动到X处。如果到了X处,则从M串的起始位置进行匹配。

从上面的步骤可以知道,KMP的关键就是要知道当S串中的字符和M串中的字符不匹配时,S串要和M串中的哪个字符继续进行匹配。这个就是在利用状态机模型来解释KMP算法时的状态转移.

KMP是通过一个定义了一个next数组,这个next数组保存了如果S中的字符和M中的字符不匹配时S要和M中的哪个字符重新进行匹配的坐标值。如图2中所示是例子,S中的X位置和M不匹配了,那么S要和M中A段后面的字符进行比较,从图中来看是M向后滑动了。

换句话说,next[i]总是保存了当M[i]不匹配时要从M[next[i]]处进行匹配,这个M[next[i]]可能会匹配,如果还不匹配?那么可能会在M[next[next[i]]]处匹配了。这里同时隐含着一个信息,就是i之前的一段字符和next[i]之前的一段字符是相同的,也就是M[0…i-1]相等的前缀和后缀。

现在考虑next[0],next[1]…next[i]都已经知道了,那么图示如下:

image

设j=next[i],灰色部分表明这两段字符是相等的,如果i位置的字符和j位置的字符相等,那么next[i+1]=j+1;因为前一段灰色部分和j位置的字符组成的字符串和后一段灰色的与i连接所形成的字符串是相等的。这正是前面对next数组的定义。如果不相等,则要找到从i开始包括i往前的一段字符串与从0开始的一段字符串相等,这样形成相等的前缀和后缀。所幸我们知道next[next[i]]的值,因为next[i]前面的字串也有最长的公共前缀和后缀,而这个公共的前缀与现在i以及往前形成的字串可能相等,这样一直向前找,如果找不到,则说明i位置的字符从来没有在之前出现过。

这样求出来的next数组其实是从下标1开始的,因为下标0之前是个空串,下标1则对应着M串的第0个字符。我们设next[0]=-1,仅仅是个标志而已,没有什么特殊的含义。

那么根据前面所述,可以很容易的写出初始化next数组的代码

   1: void kmpGetNext()
   2: {
   3:     int i=0, j=-1;
   4:     b[i]=j;
   5:     while (i<m)
   6:     {
   7:         while (j>=0 && p[i]!=p[j]) j=b[j];
   8:         i++; j++;
   9:         b[i]=j;
  10:     }
  11: }

知道了next数组的值,则和S串进行匹配则相对简单了,因为如果碰到不匹配的时候去查找next数组即可,直到找出和当前字符匹配的那个字符。如果找不到怎么办?找不到则会得到-1,也就是没有字符和他进行匹配,那么跳过这个字符,直接从下一个字符进行匹配即可。

代码如下:

   1: void kmpSearch()
   2: {
   3:     int i=0, j=0;
   4:     while (i<n)
   5:     {
   6:         while (j>=0 && t[i]!=p[j]) j=b[j];
   7:         i++; j++;
   8:         if (j==m)
   9:         {
  10:             report(i-j);
  11:             j=b[j];
  12:         }
  13:     }
  14: }

看到上面的代码,两层循环,貌似这个代码并不是线性的,其实不然。外层循环了n次这个没有问题,关键是里面的while循环,这个循环的次数是多少并不好确定,然而考虑单单考虑j的值的变化,会发现第七行j增加1,而第6行j则减少,可能减少1,可能减少2,可能少的更多,但是j<0时循环就终止了,也就是说j有n次增加的机会,会有多少次减少的机会?或者问j最多减少多少次?j减少的次数最多的时候,就是每次减少1,这样最多的会减少n次,也就是说第六行的循环最多会执行n次。平摊到每个循环,则执行次数为O(1),所以kmpSearch的时间复杂度仍然是线性的O(n),同理,kmpGetNext的时间复杂度为O(m).详情请参考matrix67大牛的文章,下面有犀利的评论:

倒数第七段

“…每一次执行while循环都会使j减小(但不能减成负的),而另外的改变j值的地方只有第五行。每次执行了这一行,j都只能加1;因此,整个过程中j最多加了n个1。于是,j最多只有n次减小的机会(j值减小的次数当然不能超过n,因为j永远是非负整数)。这告诉我们,while循环总共最多执行了n次。… ”

这里不大明白,整个过程中j是在回退然后前进的,假设第一遍比较回退一次,第二遍比较回退两次,于是总共加起来j减小和变大的次数都要大于n,不是吗?

             回复:我每年新交1个MM,我100年内会失恋200次吗?

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
KMP算法(Knuth-Morris-Pratt算法)是一种用于解决字符串匹配问题的高效算法。它的主要思想是利用匹配失败时的信息,尽量减少比较次数,提高匹配效率。 KMP算法的核心是构建一个部分匹配表(Partial Match Table),也称为Next数组。这个表记录了在匹配失败时应该将模式串向右移动的位置。 构建部分匹配表的过程如下: 1. 首先,将模式串中的第一个字符的Next值设为0,表示当匹配失败时,模式串不需要移动; 2. 然后,从模式串的第二个字符开始,依次计算Next值; 3. 当第i个字符与前面某个字符相同的时候,Next[i]的值为该字符之前(不包括该字符)的相同前缀和后缀的最大长度; 4. 如果不存在相同的前缀和后缀,则Next[i]的值为0。 有了部分匹配表之后,KMP算法的匹配过程如下: 1. 用i和j来分别表示模式串和主串的当前位置; 2. 如果模式串中的字符和主串中的字符相同,那么i和j都向右移动一位; 3. 如果模式串中的字符和主串中的字符不同,那么根据部分匹配表来确定模式串的下一个位置; 4. 假设当前模式串的位置为i,根据部分匹配表中的值Next[i],将模式串向右移动Next[i]个位置; 5. 重复上述步骤,直到找到匹配或者主串遍历完毕。 KMP算法的时间复杂度为O(m + n),其中m和n分别是模式串和主串的长度。相比于暴力匹配算法的时间复杂度为O(m * n),KMP算法能够大幅减少比较次数,提高匹配效率。 综上所述,KMP模式匹配算法通过构建部分匹配表并利用匹配失败时的信息,实现了高效的字符串匹配。在实际应用中,KMP算法被广泛地应用于文本编辑、数据搜索和字符串处理等领域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值