打破思维断层之KMP分析 - wsc - ITeye技术网站

news/2024/5/15 18:35:18/文章来源:https://blog.csdn.net/weixin_33924312/article/details/85550374

打破思维断层之KMP分析 - wsc - ITeye技术网站

打破思维断层之KMP分析

博客分类:算法分析
kmp算法思维断层 

KMP

目的本博客以KMP算法为载体,试图在减少思维断层情况下学习作者算法思想

目录

   1)开脑之字符匹配思路

   2)浅析回溯目的

      3)一定要回溯吗

      4)什么时候回溯?什么时候不回溯?

      5)深入回溯目的

      6)如何更为高效地回溯?

      7)回溯到哪一步?

      8)前缀和后缀应运而生!

      9)你猜我发现什么?一切的一切都是因为这个(关键步骤)

      10)KMP就是这样

第一步:开脑之字符匹配

    字符匹配问题是指:在一个目标字符串(如a b a b a b a a b a b …)中寻找模式字符串(如a b a b a c b)的问题,很多复杂的问题最终都可以抽象成字符串匹配问题


    最简单的办法:像上图一样,当第一次匹配bc匹配失败时,将模式串简单的向后移动一个字符。最终在n=8次的匹配之后,终于来到了目标串中的第5a面前,此次一举成功,整个过程可谓艰辛。

第二步:浅析回溯目的

      上述步骤有什么不对吗?有

问题就出在:不管模式串是什么比如abab或者diekd抑或wwwww,该方法都能够解决问题,这是一个万能方法。

万能方法实际上意味着没有抓住不同事物具有不同的特性,将所有的事情一律平等待之。这个也许有助于社会和谐,但也就意味着会丧失很多不同的人有自己的特殊之处这样的特点。

算法不只是追求解决问题,更主要是追求高效。

该方法为什么如此低效,大家肯定都明白,就是这个万能方法将所有的问题平等待之。

找到平等待之之处,试着改变其策略。

上述问题是如何实现平等待之的呢?回溯

所谓回溯是指:当某一次匹配如第一次,当匹配到了第6个字符的时候,发现bc是不同的,此时我们将指示目标串的指针i(此时指向了6)回溯到i=2,并从模式串的第1个字符开始重新匹配。

这样做有什么作用呢?显然,这样做是因为第3个字符同样是a,和模式串的首字母a相同,为匹配成功提供了可能性。

第三步:一定要回溯吗?

如果已经匹配正确的前5个字符ababa中只有第1个字符是a,而其他字符都不是a,这样就完全没有提供匹配成功的可能性了。

例如匹配目标串abcdef而模式串是abcdeg,同样当比较第6个字fg时出现不同,此时还有必要回溯吗?显然没有,因为第2-5个字符bcde中根本就没有a

第四步:什么时候回溯?什么时候不回溯?

一个显而易见的结论是:当匹配失败的字符前面(除了开始的字符)有和模式串中相同首字母(本例子为a)的时候,就有匹配成功的可能性,就有回溯的必要性;相反,则没有回溯的必要性

第五步:深入回溯目的

回溯的目的其实就是找到所有的匹配成功的可能性例如第一步中第6个字符bc匹配的时候,匹配失败,本例中,前5个字符中(ababa)除了首字母a外还有2a,这两个a都提供了匹配成功的可能性(当然,并不能只是因为有a就回溯,想要更加高效的回溯,需要仔细分析其特性)。

但是,朴素算法的回溯,只是简单的一个一个递增的回溯,并没有利用前5个字符已经成功匹配的事实。你无法忽视这个事实,你也不能忽视这个事实,这就是普通匹配算法低效的原因。

第六步:如何更为高效地回溯?

        再看本案例:


 

   
上述有4步,用数字1234表示出来了。显然,我们已经知道第2步没有必要进行,那第3步有必要进行吗?实际上就是回溯到哪一步,如何高效地回溯的问题,即效率问题。

如何判断回溯到哪一步?

什么时候回溯和什么时候不回溯实际上也是回溯到哪一步的问题,即解决此问题实际上也就解决了第四步的问题。

现在所有的问题就转化成了回溯到哪一步的问题。

第七步:回溯到哪一步?

关于回溯到哪一步,直观感受是:匹配失败字符前面,有几个a就进行几次回溯。本例中,前5个字符中(ababa)除了首字母a外还有2a,这两个a都提供了匹配成功的可能性。所以,按道理来讲需要回溯两次。

这样效率确实提高了很多,起码在普通匹配算法中需要匹配4次的情况下,变成了匹配两次,有的时候还不止这样,可能更为高效。

也许你已经看出来了,这种做法有点不太科学或者说还是不够高效?原因在于我们只是根据首字母来判定是不是有回溯的必要。

既然有了好的思路,何不再仔细分析模式串,然后走的更远些。

第八步:前缀和后缀应运而生!

    上面我们已经确定第二步没有必要进行,因为两个首字母(ba)就不同(一票否决)。

        3步有必要吗?


       3步中,我们比较了第1个字符(首字符)a相同之后,再比较第2个字符b发现相同,再比较第三个字符a发现还是相同,于是我们断定此次进行回溯是有可能是有必要的。

插入一个知识点:前后缀

     前缀:

     a是abcdtyd的前缀,ab是abcdtyd的前缀,abc是abcdtyd的前缀

     后缀:

     d是abcdtyd的后缀;yd是abcdtyd的后缀;tyd是abcdtyd的后缀

     再仔细观察:


 

 

发现aba即使已经匹配的字符ababa的前缀,同时又是其后缀。而且还不止这些,aba是字符串ababa中,保证前缀等于后缀的前提下,aba是最大的前缀(也是最大后缀)。

 

解决第七步的实质问题是:前后缀问题,即找到前缀等于后缀,并且是最大的.(如果还有疑惑可以自行举例试一试)

 

第九步:你猜我发现什么?一切的一切都是因为这个(关键步骤)

 

前后缀,提供了成功匹配的可能性,是判断是否有要回溯的必要的依据。但是这个还不够。

        现在我们具体分析一下。

        当第6个字符b和c匹配失败的时候,前5个字符是ababa。其实,并不只是只有最大的前缀=后缀即aba提供匹配成功的可能性。


     
这个图意味着:对于匹配失败的字符(第6个)前面的字符串的所有的前缀同样也是后缀的子字符串例如本例中abaa。提供了匹配成功的可能性。至于是否能够匹配成功,本例中aba匹配的时候,下一个字符是b和目标串中的第6个字符相同,说明匹配成功指示目标串和模式串的指针继续往前走。

 

但是如果aba的后一个字符和目标串的第6个字符匹配失败,例如:

 

 

此次aba的下一个字符be匹配失败,我们就需要检查前缀a的下一个字符是否满足。

如果此次字符很少,也就5个,可能分析起来不具有代表性,我们增加字符的个数。

 

          在匹配第26个字符c和f的时候,匹配失败。

          下面我们分析前25个已经匹配成功的字符。


   
共有4对符合要求的子字符串。

      用图片表示

 


       3次匹配都没有成功,分别向后移动了141822个字符。

       发现第4次匹配时,a之后的c和第26个字符c相同,此时模式串已经向后移动了24个字符,即目标串的第26个字符和模式串的第2个字符串比较。

       如果你已经看懂了上述过程:下面介绍KMP算法所有的一切的根本属性

 

      上述4对符合条件的前缀有如下性质:

        
如果你看过KMP代码,同时发现有两段代码尤为相似的话,其实并不用惊奇。因为不论是KMP算法中调用Next数组,还是计算Next数组,决定这最为关键的特别相似的两步的东西就是这个属性。当然这是后话。

 

第十步:KMP就是这样

        在进行匹配的时候,我们定义两个指针i和j分别指向目标串和模式串,起始值为1。

上述例子中,当ij同时指向26的时候,i指向了c,而j指向的是f,出现两者比匹配。此时才有了后来的4次前缀匹配比较。

        第1次第一大前缀s1:acabacabaca 以t和i指向的c不匹配的失败告终。实际上相当于将j = 26改变成j= 12 ,i还是26继续比较。

        第2次第二大前缀s2:acabaca 以b 和i指向的c不匹配的失败告终。实际上相当于将j = 12 改变成 j =8 ,i还是26继续比较。

        直到第4次第四大前缀s4a ci指向的c匹配的成功告终。实际上相当于将j = 4 改变成j =2 i还是26继续比较。

        上述过程的实质是:i=26并不改变,只是不断改变j的值。

        但是当i=26发现匹配失败,然后决定改变j的值,而这个值是根据前面已经匹配的25个字符得出来的,跟目标串没有任何关系。

        如果定义一个数组next,当第j个字符和目标串中第i个字符发生步匹配的时候,只需要查询next[j]就可以得到将j改变成的值。

       Next[j]表示的是:当第j个字符匹配失败时,得到前j-1个字符的最大前缀+1.

      上例中:假设已经得到了next数组


 

      开始I = 26j = 26.发生不匹配。

1)查询next[26]可以得到12,其实12是第一大前缀s1 的大小(11+1得到。比较j = 12时,字符为t ,发现还是与I = 26 字符为c不等。下一步怎么办?

2)当然是进行第2步,将j改为8 ,其实8是第二大前缀s2的大小(7+1得到。比较j = 8时,字符为b ,发现i= 26字符为c不等。

那么到底是如何计算将j 改成了8呢?当然是next[12],因为这一步相当于是i=26j=12时匹配失败,此时调用next[12]得到需要价将j改变成的值。

这实际用到上述的根本属性

2大前缀s2是第1大前缀s1的最大前缀。

 根据next的定义Next[12] = 7+1 = 8 存放的是前11个字符(即s1)的最大前缀(即s2+1

   
上面是在得到next数组的基础上计算的,那么下一步就是如何得到这个next数组呢?

举例:

上例中,第一步计算时用到的next[26] = 12是怎么来的?

假设我们已经知道了next[25] 即知道前24个字符的最大前缀+1的值。

 
从上图中可以看出前24个字符最大前缀是acabacabac 大小为10

下一步:

比较第11个字符a 和第25字符a是否相同

本例中相同,则next[26]=next[25]+1=11+1 = 12 即前25个字符最大前缀+1=12

如果不相同呢?我们将第11个字符改成m,如下图

    
此时前25个字符最大前缀+1 等于???

而已知条件是next[25] =11 即前24个字符最大前缀大小是10.

此时是否会觉得有点熟悉的感觉?问题似乎可以这样表述

 这样和我们本来要解决的问题当第i=26 j=26时,字符不匹配。只是这次i变成了I =25 j=11 。那么前25个字符的最大前缀怎么求呢?当然是将j改成前10个字符的最大前缀+1,实际上用next表示就是next[11]11就是此时的j),然后再判断此时ij对应的字符是否相同。

  j一直再减小,有可能最后都没有办法匹配。如abcdef next[6]但是前几个字符都没有能找到最大前缀=后缀的,此时next[6] =0+1 = 1.

所以已知next[j]的时候,想要求next[j+1],有两种情况(设模式串为P

 1)如果P(next[j]) == P(j+1) next[j+1] = next[j]+1

 2)如果P(next[j]) != P(j+1) 比较P(next[next[j]]) P(j+1) 如果还是不等,继续比较。直到最后没有找到可以成功匹配的,则next[j+1] = 1,abcdefnext[6]=1表示当第6个字符匹配失败的时候,需要和第1个字符匹配。如果仍旧没有匹配成功,相当于刚开始匹配的时候,第一个字符就不匹配如目标串abcde和模式串bctds ,此时查找next[1] ,因为next[1]的含义就是第1个字符就匹配失败,我们定义为next[1]=0.(其实0只是一个标志,你完全可以定义为-1-2),只是当j =-1或者-2 的时候,此时需要模式串的第一个字符和目标串的第二个字符开始比较了,即i++,j +=1或者j+=2.(大部分都是用的0当标志,因为这样代码就可以一起判断)

总结KMP算法确实巧妙,但是不论算法如何巧妙,最终都是在问题性质之上去解决问题。就像贪婪选择的贪婪选择属性和最优子结构和动态规划的重叠子问题和最优子结构一样,一旦你能够找到这些性质,就抓住了其核心、其本质解决起来得心应手。

       

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_778713.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新功能!从 Dropbox 部署到 Windows Azure 网站

目前,有许多选项可用于将源代码发布到 Windows Azure 网站。例如,您可以从 Visual Studio 或 Web Matrix 等开发工具进行发布,从计算机上的本地 Git 存储库发布代码,甚至可以从 TeamFoundation Service、GitHub、CodePlex 或 Bitb…

hexo+NexT(v7.72+)超全二十一大主题美化,打造“超炫”网站(一)

前言: 安装好一个基本的next主题框架显然是有点简陋,许多普通网站的功能基本没有,需要我们自己添加 且NexT(v7.72)最新版本的主题和之前在结构上有一些不同,但是网上大部分教程还是停留在v6.0版本中,导致我在配置博客功…

hexo+NexT(v7.72+)超全二十一大主题美化,打造“超炫”网站(二)

先分享一点小经验 修改了模板,但是没有生效? 修改了模板以后不生效,建议先hexo clean,然后再hexo generate。只执行hexo generate,可能模板后者静态文件不会被替换。 知道晚了,以为设置错误,两…

hexo+NexT(v7.72+)超全二十一大主题美化,打造“超炫”网站(四)

(14)Hexo博客NexT主题美化之自定义文章底部版权声明 参考文章 效果如下 1.在目录themes/next/layout/_macro/下添加my-copyright.swig &#xff0c;内容如下&#xff1a; {% if page.copyright %} <div class"my_post_copyright"> <script src"//c…

ICP和公安网备案以及网站底部添加相应备案号

网站备案 网站建立成功后要分别进行公信网备案和公安备案然后部署在网站底部 公信网备案 用阿里云&#xff0c;买一个服务器进行个人信息填写 等待一段时间&#xff08;一周到三周不等&#xff09;的审核后通过 公安网备案 比较公信网&#xff0c;这个就要快很多 1.打开全…

提高网站打开速度的7大秘籍

2019独角兽企业重金招聘Python工程师标准>>> 很多站长使用虚拟主机来做网站&#xff0c;网页内容一旦很多&#xff0c;网站打开速度就会特别慢&#xff0c;如果说服务器、带宽、CDN这类硬指标我们没有经济实力去做&#xff0c;不妨通过网页代码优化的方式来提高速度…

优化杭州某著名电子商务网站高并发千万级大型数据库经验之- 读写分离

为什么80%的码农都做不了架构师&#xff1f;>>> 好久没写博客了&#xff0c;一方面是日常工作繁忙&#xff0c;另外一方面是想更多的时间陪陪家里人&#xff0c;享受春天的美好时光&#xff0c;还在写一本《程序员&#xff0c;你伤不起》的一本书要由人民邮电出版社…

大型网站技术架构(八)网站的安全架构

2019独角兽企业重金招聘Python工程师标准>>> 从互联网诞生起&#xff0c;安全威胁就一直伴随着网站的发展&#xff0c;各种Web攻击和信息泄露也从未停止。常见的攻击手段有XSS攻击、SQL注入、CSRF、Session劫持等。 1、XSS攻击 XSS攻击即跨站点脚本攻击&#xff08;…

微软发布IIS漏洞补丁,影响我国五分之一网站

2015年4月14日&#xff0c;微软发布月度例行安全公告&#xff0c;共释放出11项更新&#xff0c;一举修复包括Windows操作系统、IE浏览器、Office办公软件、.NET Framework、Server软件、Office Services和Web Apps在内的26个安全漏洞。在这11项更新中&#xff0c;有4项更新综合…

Scrapy框架学习笔记(2)——小说网站简单信息的爬取

本项目参考书上的例子自己找了一个小说网站&#xff08;以笔趣阁为例&#xff09;对各种热门书目信息爬取&#xff0c;用于练手&#xff0c;希望能够对和我一样的初学者有帮助。 友情链接 本文参考书目作者博客链接&#xff1a;https://cloud.tencent.com/developer/article/…

使用 varnish + nginx + lua 搭建网站的降级系统

通常一个网站数据库挂掉后&#xff0c;后果将是非常严重的。基本上整个网站基本不可用了。对于一些网站来说&#xff0c;当数据库挂掉后&#xff0c;如果能提供基本的浏览服务&#xff0c;也是不错的。本文将尝试使用 varnish nginx lua 搭建网站降级系统来实现整个目标。 降…

大型网站的核心构架要素

当今的互联网时代&#xff0c;技术日新月异。如何打造一个高可用、高性能、易扩展、可伸缩且安全的网站?如何让网站随应用所需灵活变动? 相较于传统企业应用系统&#xff0c;大型互联网网站应用系统的部署架构至少需要具备五大核心要素&#xff1a;高性能、高可用、伸缩性、扩…

购书网站前端实现(HTML+CSS+JavaScript)

目录 购书阅读静态网页设计与实现 一、主页设计HTML 1、效果展示及实现 2、完整代码 二、主页样式布局CSS 三、空间功能实现Javascript 主要功能 Javascript完整代码&#xff1a; 总结 购书阅读静态网页设计与实现 ① 网盘下载&#xff1a;关注我的博客最后的微信公众…

口罩预约管理系统——系统网站实现(前端+PHP+MySQL)

口罩预约管理系统网站实现 一、前言 二、系统登陆逻辑及界面实现 三、用户模块 1、用户预约系统界面 2、用户查看我的订单界面 3、用户修改预约信息 四、管理员模块 1、管理员登陆界面 2、查看用户预约订单 3、修改/删除用户信息 五、快递员模块 1、快递员登录配送系…

云服务器 ECS 建站教程:GitLab的安装及使用

GitLab的安装及使用前言 GitLab是利用 Ruby on Rails 一个开源的版本管理系统&#xff0c;实现一个自托管的Git项目仓库&#xff0c;可通过Web界面进行访问公开的或者私人项目。 它拥有与Github类似的功能&#xff0c;能够浏览源代码&#xff0c;管理缺陷和注释。可以管理团队对…

使用python requests库,使用bs4解析网页内容提取url,使用广度优先算法,爬取一个网站的所有网页

实现一个类&#xff0c;抓取一个网站所有页面 实现思路&#xff1a;一边添加url&#xff0c;一边抓取&#xff0c;一直进行下去就可以了&#xff0c;直到列表遍历完成&#xff0c;说明没有新的url可供抓取&#xff0c;即抓取完成。 实际上是图的广度优先遍历。 import urllib.…

大型网站架构演变和知识体系

之前也有一些介绍大型网站架构演变的文章&#xff0c;例如LiveJournal的、ebay的&#xff0c;都是非常值得参考的&#xff0c;不过感觉他们讲的更多的是每次演变的结果&#xff0c;而没有很详细的讲为什么需要做这样的演变&#xff0c;再加上近来感觉有不少同学都很难明白为什么…

网站api访问不到服务器,实现一个 RESTful API 服务器

RESTful 是目前最为流行的一种互联网软件结构。因为它结构清晰、符合标准、易于理解、扩展方便&#xff0c;所以正得到越来越多网站的采用。什么是 RESTREST(REpresentational State Transfer)&#xff0c;首次出现在 2000 年 Roy Thomas Fielding 的博士论文中&#xff0c;它指…

记一次真实的网站被黑经历

前言 距离上次被DDOS攻 击已经有10天左右的时间&#xff0c;距离上上次已经记不起具体那一天了&#xff0c;每一次都这么不了了只。然而近期一次相对持久的攻 击&#xff0c;我觉得有必要静下心来&#xff0c;分享一下被黑的那段经历。 在叙述经历之前&#xff0c;先简单的介绍…

android 弹出菜单 toast,Android学习第二天:Toast(提醒)、Menu(菜单)、Intent的显式和隐式(包括打开、适配网站,调用拨号界面等)...

1.Toast提醒为昨天写的按钮程序添加一个提醒&#xff0c;在MainActivity中添加如下代码&#xff1a;Button bt1 (Button) findViewById(R.id.button_1);bt1.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View view) {Toast.makeText(MainActi…