﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-puppy居-随笔分类-数据结构&amp;算法</title><link>http://www.cnitblog.com/puppypyb/category/8858.html</link><description>puppy居士</description><language>zh-cn</language><lastBuildDate>Sun, 25 Aug 2013 12:33:57 GMT</lastBuildDate><pubDate>Sun, 25 Aug 2013 12:33:57 GMT</pubDate><ttl>60</ttl><item><title>[ZT]编译原理学习导论 </title><link>http://www.cnitblog.com/puppypyb/archive/2013/08/24/88453.html</link><dc:creator>puppy</dc:creator><author>puppy</author><pubDate>Sat, 24 Aug 2013 15:50:00 GMT</pubDate><guid>http://www.cnitblog.com/puppypyb/archive/2013/08/24/88453.html</guid><wfw:comment>http://www.cnitblog.com/puppypyb/comments/88453.html</wfw:comment><comments>http://www.cnitblog.com/puppypyb/archive/2013/08/24/88453.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/puppypyb/comments/commentRss/88453.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/puppypyb/services/trackbacks/88453.html</trackback:ping><description><![CDATA[<p><span style="font-family: 宋体">大学课程为什么要开设编译原理呢？这门课程关注的是编译器方面的产生原理和技术问题，似乎和计算机的基础领域不沾边，可是编译原理却一直作为大学本科的必修课程，同时也成为了研究生入学考试的必考内容。编译原理及技术从本质上来讲就是一个算法问题而已，当然由于这个问题十分复杂，其解决算法也相对复杂。我们学的数据结构与算法分析也是讲算法的，不过讲的基础算法，换句话说讲的是算法导论，而编译原理这门课程讲的就是比较专注解决一种的算法了。在</span>20<span style="font-family: 宋体">世纪</span>50<span style="font-family: 宋体">年代，编译器的编写一直被认为是十分困难的事情，第一</span>Fortran<span style="font-family: 宋体">的编译器据说花了</span>18<span style="font-family: 宋体">年的时间才完成。在人们尝试编写编译器的同时，诞生了许多跟编译相关的理论和技术，而这些理论和技术比一个实际的编译器本身价值更大。就犹如数学家们在解决著名的哥德巴赫猜想一样，虽然没有最终解决问题，但是其间诞生不少名著的相关数论。</span></p>
<p>&nbsp;</p>
<p><strong><span style="font-family: 宋体; font-size: 15pt">推荐参考书</span></strong><strong></strong></p>
<p><span style="font-family: 宋体">虽然编译理论发展到今天，已经有了比较成熟的部分，但是作为一个大学生来说，要自己写出一个像</span>Turboc C,Java<span style="font-family: 宋体">那样的编译器来说还是太难了。不仅写编译器困难，学习编译原理这门课程也比较困难。</span></p>
<p><span style="font-family: 宋体">正是因为编译原理学习相对困难，那么就要求有好的教师和好的教材。教师方面不是我们能自己更改的，而在教材方面我们却可以按自己的意愿来阅读。我下面推荐几本好的编译原理的教材。我推荐的书籍都是国外的经典教材，因为在国内的教材中，确实还没发现什么让人满意的。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">第一本书的原名叫《</span>Compilers Principles,Techniques,and Tools<span style="font-family: 宋体">》</span>,<span style="font-family: 宋体">另外一个响亮的名字就是龙书。原因是这本书的封面上有条红色的龙，也因为这本书在编译原理基础领域确实太有名气了</span>,<span style="font-family: 宋体">所以很多国外的学者都直接取名为龙书。最近机械工业出版社已经出版了此书的中文版，名字就叫《编译原理》。该书出的比较早，大概是在</span>85<span style="font-family: 宋体">或</span>86<span style="font-family: 宋体">年编写完成的，作者之一还是著名的贝尔实验室的科学家。里面讲解的核心编译原理至今都没有变过，所以一直到今天，它的价值都非凡。这本书最大的特点就是一开始就通过一个实际的小例子，把编译原理的大致内容罗列出来，让很多编译原理的初学者很快心里有了个底</span>,<span style="font-family: 宋体">也知道为什么会有这些理论，怎么运用这些理论。而这一点是我感觉国内的教材缺乏的东西，所以国内的教材都不是写给愿意自学的读者，总之让人看了半天，却不知道里面的东西有什么用。</span></p>
<p><span style="font-family: 宋体">第二本书的原名叫《</span>Modern Compiler Design<span style="font-family: 宋体">》</span>,<span style="font-family: 宋体">中文名字叫做《现代编译程序设计》。该书由人民邮电出版社所出。此书比较关注的是编译原理的实践，书中给出了不少的实际程序代码，还有很多实际的编译技术问题等等。此书另外一个特点就是其&#8220;现代&#8221;而字。在传统的编译原理教材中，你是不可能看到如同</span>Java<span style="font-family: 宋体">中的&#8220;垃圾回收&#8221;等算法的。因为</span>Java<span style="font-family: 宋体">这样的解释执行语言是在近几年才流行起来的东西。如果你想深入学习编译原理的理论知识，那么你肯定得看前面那本龙书，如果你想自己动手做一个先进的编译器，那么你得看这本《现代编译程序设计》。</span></p>
<p><span style="font-family: 宋体">第三本书就是很多国内的编译原理学者都推荐的那本《编译原理及实践》。或许是这本书引入国内比较早吧，我记得我是在高中就买了这本书，不过也是在前段时间才把整本书看完。此书作为入门教程也的确是个不错的选择。书中给出的编译原理讲解也相当细致，虽然不如前面的龙书那么深入，但是很多地方都是点到为止，作为大学本科教学已经是十分深入了。该书的特点就是注重实践，不过感觉还不如前面那本《现代编译程序设计》的实践味道更重。此书的重点还是在原理上的实践，而非前面那本那样的技术实践。《编译原理及实践》在讲解编译原理的各个部分的同时，也在逐步实践一个现代的编译器</span>Tiny C.<span style="font-family: 宋体">等你把整本书看完，差不多自己也可以写一个</span>Tiny C<span style="font-family: 宋体">了。作者还对</span>Lex<span style="font-family: 宋体">和</span>Yacc<span style="font-family: 宋体">这两个常用的编译相关的工具进行了很详细的说明，这一点也是很难在国内的教材中看到的。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">推荐了这三本教材，都有英文版和中文版的。很多英文好的同学只喜欢看原版的书，不我的感觉是这三本书的翻译都很不错，没有必要特别去买英文版的。理解理论的实质比理解表面的文字更为重要。</span></p>
<p>&nbsp;</p>
<p><strong><span style="font-family: 宋体; font-size: 15pt">编译原理的实质</span></strong><strong></strong></p>
<p><span style="font-family: 宋体">前面已经说过，学习编译原理其实也就是学习算法而已，没什么特别的。只不过这些算法的产生已经形成了一套理论。下面我来看看编译原理里面到底有什么高深的理论吧。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">几乎每本编译原理的教材都是分成词法分析，语法分析（</span>LL<span style="font-family: 宋体">算法，递归下降算法，</span>LR<span style="font-family: 宋体">算法），语义分析，运行时环境，中间代码，代码生成，代码优化这些部分。其实现在很多编译原理的教材都是按照</span>85,86<span style="font-family: 宋体">出版的那本龙书来安排教学内容的，所以那本龙书的内容格式几乎成了现在编译原理教材的定式，包括国内的教材也是如此。一般来说，大学里面的本科教学是不可能把上面的所有部分都认真讲完的，而是比较偏重于前面几个部分。像代码优化那部分东西，就像个无底洞一样，如果要认真讲，就是单独开一个学期的课也不可能讲得清楚。所以，一般对于本科生，对词法分析和语法分析掌握要求就相对要高一点了。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">词法分析相对来说比较简单。可能是词法分析程序本身实现起来很简单吧，很多没有学过编译原理的人也同样可以写出各种各样的词法分析程序。不过编译原理在讲解词法分析的时候，重点把正则表达式和自动机原理加了进来，然后以一种十分标准的方式来讲解词法分析程序的产生。这样的做法道理很明显，就是要让词法分析从程序上升到理论的地步。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">语法分析部分就比较麻烦一点了。现在一般有两种语法分析算法，</span>LL<span style="font-family: 宋体">自顶向下算法和</span>LR<span style="font-family: 宋体">自底向上算法。</span>LL<span style="font-family: 宋体">算法还好说，到了</span>LR<span style="font-family: 宋体">算法的时候，困难就来了。很多自学编译原理的都是遇到</span>LR<span style="font-family: 宋体">算法的理解成问题后就放弃了自学。其实这些东西都是只要大家理解就可以了，又不是像词法分析那样非得自己写出来才算真正的会。像</span>LR<span style="font-family: 宋体">算法的语法分析器，一般都是用工具</span>Yacc<span style="font-family: 宋体">来生成，实践中完全没有比较自己来实现。对于</span>LL<span style="font-family: 宋体">算法中特殊的递归下降算法，因为其实践十分简单，那么就应该要求每个学生都能自己写。当然，现在也有不少好的</span>LL<span style="font-family: 宋体">算法的语法分析器，不过要是换在非</span>C<span style="font-family: 宋体">平台，比如</span>Java,Delphi,<span style="font-family: 宋体">你不能运用</span>YACC<span style="font-family: 宋体">工具了，那么你就只有自己来写语法分析器。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">等学到词法分析和语法分析时候，你可能会出现这样的疑问：&#8220;词法分析和语法分析到底有什么？&#8221;就从编译器的角度来讲，编译器需要把程序员写的源程序转换成一种方便处理的数据结构（抽象语法树或语法树）</span>,<span style="font-family: 宋体">那么这个转换的过程就是通过词法分析和语法分析的。其实词法分析并非一开始就被列入编译器的必备部分，只是我们为了简化语法分析的过程，就把词法分析这种繁琐的工作单独提取出来，就成了现在的词法分析部分。除了编译器部分，在其它地方，词法分析和语法分析也是有用的。比如我们在</span>DOS,Unix,Linux<span style="font-family: 宋体">下输入命令的时候，程序如何分析你输入的命令形式，这也是简单的应用。总之，这两部分的工作就是把不&#8220;规则&#8221;的文本信息转换成一种比较好分析好处理的数据结构。那么为什么编译原理的教程都最终把要分析的源分析转换成&#8220;树&#8221;这种数据结构呢？数据结构中有</span>Stack, Line,List&#8230;<span style="font-family: 宋体">这么多数据结构，各自都有各自的特点。但是</span>Tree<span style="font-family: 宋体">这种结构有很强的递归性，也就是说我们可以把</span>Tree<span style="font-family: 宋体">的任何结点</span>Node<span style="font-family: 宋体">提取出来后，它依旧是一颗完整的</span>Tree<span style="font-family: 宋体">。这一点符合我们现在编译原理分析的形式语言，比如我们在函数里面使用函树，循环中使用循环，条件中使用条件等等，那么就可以很直观地表示在</span>Tree<span style="font-family: 宋体">这种数据结构上。同样，我们在执行形式语言的程序的时候也是如此的递归性。在编译原理后面的代码生成的部分，就会介绍一种堆栈式的中间代码，我们可以根据分析出来的抽象语法树，很容易，很机械地运用递归遍历抽象语法树就可以生成这种指令代码。而这种代码其实也被广泛运用在其它的解释型语言中。像现在流行的</span>Java,.NET<span style="font-family: 宋体">，其底层的字节码</span>bytecode,<span style="font-family: 宋体">可以说就是这中基于堆栈的指令代码的。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">关于语义分析，语法制导翻译，类型检查等等部分，其实都是一种完善前面得到的抽象语法树的过程。比如说，我们写</span>C<span style="font-family: 宋体">语言程序的时候，都知道，如果把一个浮点数直接赋值给一个整数，就会出现类型不匹配，那么</span>C<span style="font-family: 宋体">语言的编译器是怎么知道的呢？就是通过这一步的类型检查。像</span>C++<span style="font-family: 宋体">语言这中支持多态函数的语言，这部分要处理的问题就更多更复杂了。大部编译原理的教材在这部分都是讲解一些比较好的处理策略而已。因为新的问题总是在发生，旧的办法不见得足够解决。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">本来说，作为一个编译器，起作用的部分就是用户输入的源程序到最终的代码生成。但是在讲解最终代码生成的时候，又不得不讲解机器运行环境等内容。因为如果你不知道机器是怎么执行最终代码的，那么你当然无法知道如何生成合适的最终代码。这部分内容我自我感觉其意义甚至超过了编译原理本身。因为它会把一个计算机的程序的运行过程都通通排在你面前，你将来可能不会从事编译器的开发工作，但是只要是和计算机软件开发相关的领域</span>,<span style="font-family: 宋体">都会涉及到程序的执行过程。运行时环境的讲解会让你更清楚一个计算机程序是怎么存储，怎么装载，怎么执行的。关于部分的内容，我强烈建议大家看看龙书上的讲解，作者从最基本的存储组织，存储分配策略，非局部名字的访问，参数传递，符号表到动态存储分配</span>(malloc,new)<span style="font-family: 宋体">都作了十分详细的说明。这些东西都是我们编写平常程序的时候经常要做的事情，但是我们却少去探求其内部是如何完成。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">关于中间代码生成，代码生成</span>,<span style="font-family: 宋体">代码优化部分的内容就实在不好说了。国内很多教材到了这部分都会很简单地走马观花讲过去，学生听了也只是作为了解，不知道如何运用。不过这部分内容的东西如果要认真讲，单独开一学期的课程都讲不完。在《编译原理及实践》的书上，对于这部分的讲解就恰到好处。作者主要讲解的还是一种以堆栈为基础的指令代码，十分通俗易懂，让人看了后，很容易模仿，自己下来后就可以写自己的代码生成。当然，对于其它代码生成技术，代码优化技术的讲解就十分简单了。如果要仔细研究代码生成技术，其实另外还有本叫做《</span>Advance Compiler Desgin and Implement<span style="font-family: 宋体">》</span>,<span style="font-family: 宋体">那本书现在由机械工业出版社引进的，十分厚重，而且是英文原版。不过这本书我没有把它列为推荐书给大家，毕竟能把龙书的内容搞清楚，在中国已经就算很不错的高手了，到那个时候再看这本《</span>Advance Compiler Desgin and Implement<span style="font-family: 宋体">》也不迟。代码优化部分在大学本科教学中还是一个不太重要的部分，就是算是实践过程中，相信大家也不太运用得到。毕竟，自己做的编译器能正确生成执行代码已经很不错了，还谈什么优化呢？</span></p>
<p>&nbsp;</p>
<p><strong><span style="font-family: 宋体; font-size: 15pt">关于实践</span></strong><strong></strong></p>
<p><span style="font-family: 宋体">编译原理的课程毕竟还只是讲解原理的课程，不是专门的编译技术课程。这两门课程是有很大的区别的。编译技术更关注实际的编写编译器过程中运用到的技术，而原理的课关注讲解其基本理论。但是计算机科学本身就是一门实践性很强的课程，如果能够学以致用，那才叫真正的学会。李阳在讲解疯狂英语的时候就说到，只要当你会实际中运用一个单词一个词组的时候你才能叫学会了这个单词或者词组，而不是只是知道了它的拼写和意思。其实任何学习都是一样的，如果缺少了实践的结合，你不能算学会。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">编译原理的课程主要就是讲解编译器产生的理论和原理，那么很简单，自己写个编译器就是最好的实践过程了。不过你得小心，编译系统可能是所有软件系统中最复杂的系统之一</span>,<span style="font-family: 宋体">不然为什么大学里面还会把编译器的编写开成一门叫做编译原理的课程来讲？我很佩服那些学了操作系统原理就开始自己写操作系统，学了编译原理就开始自己写编译器的人们，确实，在中国，敢这么做的学生太少了。且不管你这样做能不能做成功，至少有了这个尝试，会让你的程序设计，系统规划安排的功底增进不少。我下面给出一些关于实践过程中可能会遇到的困难，希望能够在你陷入困境的前帮你一把。</span></p>
<p>&nbsp;</p>
<p style="text-indent: -18pt; margin: 0cm 0cm 0pt 18pt">1. Lex<span style="font-family: 宋体">和</span>Yacc. <span style="font-family: 宋体">这两工具是作为词法分析很语法分析的工具。如果你自己写一个编译器，我十分不建议你连词法分析这种事情都亲手来写。</span>Lex<span style="font-family: 宋体">和</span>Yacc<span style="font-family: 宋体">应该是作为每本编译原理的教材的必备内容，可是在国内的教材中缺很少看到。这两个工具是</span>Unix<span style="font-family: 宋体">系统下的小东西，如果你要在</span>Windows<span style="font-family: 宋体">中运用，那么你最好去下在</span>cygwin<span style="font-family: 宋体">这个软件。它是个在</span>Windows<span style="font-family: 宋体">下模拟</span>Unix<span style="font-family: 宋体">的东东，里面就包含了</span>flex.exe<span style="font-family: 宋体">和</span>bison.exe(yacc)<span style="font-family: 宋体">这两个工具</span>.<span style="font-family: 宋体">这两个工具使用起来还挺麻烦的</span>(<span style="font-family: 宋体">其实</span>unix <span style="font-family: 宋体">下的很多十分有用的工具都是这样</span>), <span style="font-family: 宋体">不过在《编译原理与实践》这本书上对于这两个工具的讲解十分详细</span>,<span style="font-family: 宋体">还列举了不少实际的例子。</span><br /><br /></p>
<p style="text-indent: -18pt; margin: 0cm 0cm 0pt 18pt">2. <span style="font-family: 宋体">做解释型语言比做生成机器代码的编译器简单。虽然说，做解释型的编译器，像</span>Java<span style="font-family: 宋体">那样的，你还得自己去写解释器，不过这样你就不必去查找机器代码的资料了。如果你做生成的最终机器代码编译器可能会遇到问题还有就是寄存器为基础的代码生成方法。前面说过，如果你生成的是以堆栈为基础的代码，那么其代码生成过程十分简单，需要考虑的东西也不多，如果你考虑最终的机器代码生成的话，你必须考虑机器的寄存器如何分配等麻烦的问题。</span><br /><br /></p>
<p style="text-indent: -18pt; margin: 0cm 0cm 0pt 18pt">3. <span style="font-family: 宋体">考虑用别人已经生成的语法文件，尽量不要自己动手写词法文件和语法文件</span>.<span style="font-family: 宋体">以前一个朋友曾经说过</span>,<span style="font-family: 宋体">写出一个好的程序语言的语法定义</span>,<span style="font-family: 宋体">就几乎完成了一个编译器的一半</span>.<span style="font-family: 宋体">确实是这样</span>,<span style="font-family: 宋体">语法文件的编写是个很难的事情</span>.<span style="font-family: 宋体">现在网上到处都可以找到比如</span>C<span style="font-family: 宋体">语言</span>,C++,Java, Tiny C,Minus C<span style="font-family: 宋体">等语言的词法文件和语法文件</span>,<span style="font-family: 宋体">你完全可以自己下下来来用</span>.</p>
<p>&nbsp;</p>
<p><span style="font-family: 宋体">在《编译原理及实践》的书中，作者给出了一个</span>Tiny C<span style="font-family: 宋体">的全部代码</span>.<span style="font-family: 宋体">我自我感觉作者的这个编译器做得很不错</span>,<span style="font-family: 宋体">相对于其它</span>php,perl<span style="font-family: 宋体">等语言的源代码来说</span>,<span style="font-family: 宋体">简单得多</span>,<span style="font-family: 宋体">容易看懂</span>,<span style="font-family: 宋体">而且很清晰地展现了一个完成的编译系统的实现过程</span>.<span style="font-family: 宋体">其源代码可以在作者的网站上下载</span>.</p>
<p>&nbsp;</p>
<p><strong><span style="font-family: 宋体; font-size: 15pt">后话</span></strong><strong></strong></p>
<p><span style="font-family: 宋体">编译原理的学习可能算是一个困难的历程</span>,<span style="font-family: 宋体">特别是对于那些不对编译系统感兴趣的同学来说</span>.<span style="font-family: 宋体">既然它已经作为了大学本科的必修课程</span>,<span style="font-family: 宋体">那么就说明的它引申出来的一套理论在整个计算机科学领域还是占有相对重要的地位</span>.</p>
<p><span style="font-family: 宋体">如果我们考究一下历史</span>,<span style="font-family: 宋体">就会发现很多被称为程序设计大师的人都是编译领域的高手</span>.<span style="font-family: 宋体">写出第一个微型机上运行的</span>Basic<span style="font-family: 宋体">语言的比尔盖茨</span>,<span style="font-family: 宋体">设计出</span>Delphi<span style="font-family: 宋体">的</span>Borland<span style="font-family: 宋体">的</span>&#8221;<span style="font-family: 宋体">世界上最厉害的程序员</span>&#8221;, Sun<span style="font-family: 宋体">的</span>JAVA<span style="font-family: 宋体">之父</span>, <span style="font-family: 宋体">贝尔实验室的</span>C++<span style="font-family: 宋体">之父</span><strong>&#8230;</strong></p><img src ="http://www.cnitblog.com/puppypyb/aggbug/88453.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/puppypyb/" target="_blank">puppy</a> 2013-08-24 23:50 <a href="http://www.cnitblog.com/puppypyb/archive/2013/08/24/88453.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转】直面经典：重温KMP（不着一图，尽得精髓）</title><link>http://www.cnitblog.com/puppypyb/archive/2011/04/20/73379.html</link><dc:creator>puppy</dc:creator><author>puppy</author><pubDate>Wed, 20 Apr 2011 03:39:00 GMT</pubDate><guid>http://www.cnitblog.com/puppypyb/archive/2011/04/20/73379.html</guid><wfw:comment>http://www.cnitblog.com/puppypyb/comments/73379.html</wfw:comment><comments>http://www.cnitblog.com/puppypyb/archive/2011/04/20/73379.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/puppypyb/comments/commentRss/73379.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/puppypyb/services/trackbacks/73379.html</trackback:ping><description><![CDATA[<p>KMP算法，每一个初学者都曾被它搞迷糊，在数据结构教材上，这个算法出现的如此之早，你怎能指望一个还没搞懂二叉树遍历的人来理解KMP呢，记得越快，忘得越快。直到多年以后回过头来看看，这才发现KMP算法如神谕般震撼了我。实在无法想象当初Knuth、Pratt、Morris三人竟然同时发现了它。 </p>
<p><br>我们假设一个场景，你手上拿着一串红蓝两种颜色的珠子，墙上挂着一串更长的珠子，同样是红蓝两色的,你的任务就是找出和你手中珠子排列顺序相同的一段。 </p>
<p><br>最简单也最容易想到的方法，就是从墙上第一颗开始，拿手中的珠子挨个去和墙上珠子去比，都相同那就OK了，不相同再从墙上第二颗开始，以此类推。（文章中我就不给代码了，需要代码的直接跳到正文最后去复制）</p>
<p><br>有人会想：如果我手里的珠子都是红色的，我还用这种方法，我傻呀？对，Knuth当年也是这样想的。<br>你看，假设你手里是连续的100颗红色珠子，墙上从第一颗开始是99颗红色珠子，那么当你比到第100颗你才发现少了一颗，你要从第二颗开始再去数一遍吗？没人会这么做，除了程序员。</p>
<p><br>当然，正常人都要从第100颗之后再去找连续的红色珠子。有了这样的觉悟，我们可以开始进入KMP算法了。</p>
<p><br>现在抛开珠子的比喻，我们开始用术语："串"，现在手上的珠子称为模式串，墙上的珠子称为主串。任务就是从主串当中寻找模式串。为了突出KMP算法的优势，我们的串都是由0和1组成的。</p>
<p><br>原始方法的最大问题就是，每当不匹配，就要回头再做比较，这个术语叫回溯。KMP算法用一种巧妙的方法避免了主串的回溯。也就是说，主串从头到尾只需要扫描一遍。（现在还没有办法连模式串都不回溯，所以下文说到回溯均指主串的回溯）</p>
<p><br>到这里，产生了两个疑问：不回溯是可能的吗？任何情况下都可以不回溯吗？<br>第一个问题，我们前面已经肯定了，就是某些情况下，例如连续100个1时，我们不需要回溯。<br>第二个问题，我们的担心是，例如前5个相同，第6个不同，难道你能直接从主串第7个开始和模式串比较？万一从第2个开始恰好和模式串匹配，你就漏掉了。</p>
<p><br>而KMP的神奇就在于，如果第6个不同，那么接下来我会拿模式串的第1个至第5个之间的一个来和主串第6个比较，至于具体是哪个，由next值决定，这个后面再说。<br>这个方法保险吗？何止保险，万无一失！这就证明给你看：<br>当第6个失配时，有五种情况：<br>模式串前4个与主串第6个之前的相同<br>模式串前3个与主串第6个之前的相同<br>模式串前2个与主串第6个之前的相同</p>
<p>模式串前1个与主串第6个之前的相同</p>
<p>模式串前0个与主串第6个之前的相同</p>
<p>无论主串与模式串如何变化，总也逃不出这五种情况，而这五种情况的后续比较方法正好就是拿模式串1至5中间的的一个来继续比较。例如前4个相同，那当然从第5个开始比较；而前面没有相同的，那自然从模式串第1个开始。</p>
<p><br>这就说明了，任何情况下，主串都不需要回溯，前提是我们拥有next值。</p>
<p><br>（等等，你忽略了一个问题，如果next值是跟主串有关系怎么办？）<br>（Re：之前的比较已经说明了一个问题，模式串前5个与主串是相同的，主串这部分已经没有利用价值了，就算要回溯我在模式串上进行就可以了，这个解释总该满意了吧？）</p>
<p><br>好了，KMP算法可以开始工作了。<br>方法如下：<br>1 一开始还像原始方法那样，挨个比较。<br>2 等失配时，假设这时是主串的第i个，模式串的第j个，拿模式串的next(j)个继续和主串第i个进行比较<br>2.1 如果相同，那么再挨个比下去<br>2.1 如果不同，那么重复步骤2<br>3 比到模式串的最后一位仍然相同，则完成任务<br>4 比到主串的最后一位仍然未完成任务，则放弃</p>
<p><br>第2步的next(j)我们看做是一个函数，你不要管它为什么这样神奇，总之如果它能告诉你究竟是哪一个，你就只管用就没错了。</p>
<p><br>呵呵，看起来仿佛是神的指引呀：你只管比，出错了让神来告诉你由哪一个接着比。<br>这就是KMP算法了。打完收功。</p>
<p>&nbsp;</p>
<p><br>-----------------------------------------------<br>（但是，还没有说next(j)怎么出来的呀，这样也行啊？）<br>其实next(j)的求法才是KMP算法最关键的地方，要理解了它，才算是理解了KMP呀！<br>我们来探索一下next(j)内部的原理。<br>前面已经提到，其实next(j)与主串一点关系也没有，这告诉我们，只需要模式串就能生成next(j)。<br>这么说，next(j)的值完全可以看成一个函数，它的自变量是模式串和失配位置j。<br>假设某一个模式串里，next(6) = 3的话，这意味着什么呢？<br>就是说如果第6个位置失配了，那么我直接拿模式串第3个来和主串第6个比较，之所以能这么比，只有可能是模式串的第1、2个和主串的第4、5个是匹配的，但是主串的4、5个和模式串的4、5个也是匹配的，由相等关系的传递性，我们得知模式串的1、2个和4、5个是匹配的。</p>
<p><br>这给了我们提示，如果模式串内部存在相同的片段，例如123和345相同，或者12和56相同等等这样的情况，那么我们就可以在后一个相同片段的结束出失配时，从前一个相同片段的结束处继续比较。<br>而如果不存在相同的片段，那么就说明主串失配位置之前的部分不会再有匹配了（否则矛盾了），我们可以从主串失配位置的后一个位置继续比较了。</p>
<p><br>这样的一对相同片段恰好要从模式串的第一个开始，这样给了我们便利，只需要：<br>模式串第1个和第2个开始依次比较，</p>
<p>然后第1个和第3个开始依次比较，然后是第1个和第4个，依次类推，就能找到全部可能的相同片段了。<br>凑巧，这也恰好是一个找寻模式串的任务，自己既是主串又是模式串。<br>上述的自我比较过程中，在每一次比较相同时，我们都可以记下一个next值，<br>而出现失配时，我们从主串的下个位置重新开始比，等等，想起什么来了，对，主串不需要回溯。我们不是记下了前一个next值吗？<br>而如果第一次就不同呢，所以我们进行一个规定，next(1)=0，next(2)=1，这样在第1个和第2个比较时，next(2)的值已经有了，随后每一个比较都已经有了当前位置的next值了。这里next值为0是表示失配位置前不可能有匹配了，这时主串从失配位置的后一个继续比，而这个位置的next值我们同样记为0。</p>
<p><br>这样看起来，寻找next值的过程和KMP本身比较的过程何其相似呀！<br>不过我们注意到一点，它们并不需要相似，实际上求next值的过程不用KMP方法，用原始方法一样可以求出来，但是要麻烦一些，需要后一趟比较不要把前一趟记下的next值覆盖了。</p>
<p><br>至此KMP算法的精髓应该介绍完了吧，还剩下最后一点，就是所谓的next修正值。<br>这个是在求next值的过程中修正的，不修正也不影响匹配结果，修正算是一种优化吧！<br>具体说来，修正是这么回事：</p>
<p><br>如果在j出失配时，不是要跳到next(j)吗？而如果又失配了，不是要跳到next(next(j))吗，如果又失配，就跳到next(next(next(j)))&#8230;&#8230;<br>为了省略不停的跳的过程，我们注意到，如果第j个和第next(j)个是相同的，那么j失配了，next(j)一定失配。既然这样，那又何必再比呢，所以计录next值的时候，就判断一下是否相同，如果相同，就直接使用上一个next值。</p>
<p><br>至此，KMP算是全部结束完毕了。代码网上满天飞，我这就不给了。</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/oyd/archive/2008/10/20/3110435.aspx">http://blog.csdn.net/oyd/archive/2008/10/20/3110435.aspx</a></p>
<img src ="http://www.cnitblog.com/puppypyb/aggbug/73379.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/puppypyb/" target="_blank">puppy</a> 2011-04-20 11:39 <a href="http://www.cnitblog.com/puppypyb/archive/2011/04/20/73379.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>