diff程序很重要,linux中的源代码补丁都是diff作出来的,diff在比较两个文本文件的不同方面很高效,它是基于行的,diff会将两个文件都按照行分成若干部分,然后计算这些行每一行的校验码,之后的问题就是比较这两个文件的所有行的校验码序列的不同了,这就把问题归结为了序列比对,diff用的是动态规划算法,动态规划和贪心算法相似,但是其思想却是相反的,贪心算法保证每一步都是最小代价的,但是不能保证最终代价最小,而动态规划每一步什么也不知道,它从起点开始,只管局部地按照要求将无所谓的结果铺满全局,然后回溯,在这些繁复的数据之间找到一条从开始到最后的一条路径,为何这条路径就是结果呢?因为每一步都符合要求,所以无论如何最终随便一条按照要求的回溯路线都符合要求,可是可以看出,这条回溯路线的结果是一个正确结果但是却不是唯一的结果,正所谓条条大道通罗马。和贪心算法一样,随便的一条回溯路线不一定是最佳的,寻找最佳结果还要靠别的机制,贪心算法比动态规划好的就是靠近最佳结果的几率更大些,而动态规划只是一个结果,它旨在找到一种方案,然而动态规划有自己的优势,就是如果你的模型建的好,那么它可以在每一步很轻松的情况下达到同样的效果,关键就是建模。
diff的算法就是一个动态规划的例子,比如一个校验码序列P有M个元素,另一个Q有N个元素,它构建一个(M+1)*(N+1)的矩阵,然后引出一条蛇,蛇头勇往直前,呵呵,其实就是往矩阵里面的元素里填数字,从(0,0)一直到(M+1,N+1)的大方向填数字,保证所填的数字单调增长,也就是填入的数字不能比它的参照值小,将要填入的是Vi,j,那么它的参照值就是Vi-1,j-1,Vi-1,j和Vi,j-1,具体怎么做到呢?V值的增加只有一种可能性就是在Pi和Qj相等的情况下,其它情况下都是不增加的,所以Vi,j的值就是三者中最大的,Vi,j的参考值不是有三个吗?那么这种增加的情况和哪个参考值有关联呢?事实上是和对角的那个,也就是和Vi-1,j-1,仔细想一下,只有i和j同步增长才是P和Q的同步推进引起的比较,i和j的单独推进只是P序列或者Q序列的单独向前推进,也就是说在序列比对的时候就是一个gap,这种方式填表很简单,只要有左上,上,左三个值就可以求出当前的这个值,每一步都是那么的局部,最终填完后,找到最大的这个值的位置,很显然是最右下角的这个,然后回溯,怎么回溯呢?一个一个的找到当前值的前驱值所在的位置就可以了,最后将回溯的道路进行标注,得到了P和Q的两条路径,每写一个字符,如果路径和字符序列的方向垂直,那么就写一个空格,最终的结果就是diff的结果,很巧妙吧。不过以上的方式回溯写出来的路径不止一条,每一条路径都是一个结果。
我们可以看出,每一步都是机械的既定规律的模仿,没有什么技巧可言,和贪心算法几乎一样,都是局部的,但是diff算法的每一步并没有和全局的联系起来,知道完成了整个填充之后才在回溯的时候和全局的结果相联系,而贪心算法的每一个都记着自己在干什么,不需要回溯,这个意义上可以看出diff算法的填充过程更像是一个规整过程而不是一个十足的计算过程,贪心算法显得一直很努力,而diff这里所用的算法会耍一些技巧,你不是看不出我在做社么吗,别急,等会我回溯,一条路径被勾勒出来了。