SUMTEC -- There's a thing in my bloglet.

But it's not only one. It's many. It's the same as other things but it exactly likes nothing else...

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

本来我打算等我得出一个明确的、更好的结果之后,再来发表的。但是因为看到一篇错误的结论,实在忍不住了,只好提前写出来。我希望这样能够让更多的人有所进步。

 

昨天在公司随手抄起来一本书,讲的是如何教育小孩的,作者叫郑委(好名字,有官途)。书名倒是不记得了,好像是什么“为了孩子,请家长每天改进自己1%”。书名不是重要的,重要的是,这书里面提到一个什么叫做“智慧”的问题。智慧按照郑委通知的解释(或者他引用的别人的,忘了,这也不重要),应该是“多动脑,少说话”。我这里引申一下:除了管住你的嘴之外,还要管住你的手。

 

为什么我写这个呢,是因为我看到最近一篇博客:

这篇博客中提到的Nakatsu算法,该同志前不久也写过一篇:

 

 

我在当时就提出这篇文章里面的结论是有问题的,而现在的新结论仍然是错误的。更可怕的是,我为了研究这个问题,还搜索过国内的一些文献,发现有人就是以IV这篇的“第一次成功匹配的对角线”作为最长公共字符串作为理论依据的。可惜我现在一下子找不到了,有空我回头补一下。

 

这说明一个我觉得很严重的问题,那就是有不少人在做事情的时候,不经过哪怕稍微严格一点的检验,就得出一个结论。最近我还遇到另外一个类似的案例:有一天我看一位同事的代码,发现该同事不知其所以然,在胡乱的应用。于是我就问他:“你知道这个方法返回的是一个什么吗?”该同事很迅速的回答:“一个表达式。”这个函数的返回类型确实是表达式(Expression),可这么回答能得出什么有效的信息呢?于是我又引导:“我的意思是,这个方法返回的这个东西,他有什么含义。” 该同事更加迅速的回答:“他是一个对象。”

 

当然,这两个案例的成因可能各不相同,前者可能是一种“找到答案的冲动和成就感”导致没有经过细心检验就发布结果,而后者回答一个无比正确但是没有任何意义的行为,则是“通过在已知名词字典中进行相似度检索,找出第一个相似度达到x%的条目”这种不经过思考的方式导致的。然而,这两者都有一个共通的地方,那就是:

1、大脑没管住其它器官,导致其它器官没有经过大脑就行事了;

2、结论通常不正确、精确,或者没有意义。

 

其实这种问题很自然,因为没有经过长时间的训练,一个普通人总是会按照“直觉”来行事。虽然经过长时间训练的人也会通过直觉行事,但这种直觉的正确与否,还是要看是否经过训练的。比如就Nakatsu算法而言,只要你不是长期的研究、使用,甚至尝试优化该算法,那么你根本就不可能存在一个对该算法的正确“直觉”。所以,当我们对一个不了解的算法进行研究时,一定要怀抱一颗畏惧的心,小心谨慎的使用你的脑子进行逻辑上的推断。引申到开发当中,也是一样的。比如说,下面这些问题,就需要你非常小心谨慎的进行思考(通常都需要逻辑推断思考):

1、为什么这里需要一个类?(或者可以转换成:需不需要一个类。延伸出去,可以是接口、方法等等。)

2、为什么这个类需要继承另一个类?(延伸:实现一个接口,两者之间的耦合关系等。)

3、为什么这里需要一个XX模式?(延伸:使用XX框架。)

4、为什么需要采取这种算法而不是哪一种?(延伸:……自己动脑子吧。)

 

真正能做到的人,在我看来是少数,极少数。以我目前的同事来说,能做到的,可能不到10%。你要想成为优秀的人,要做的是思考,而不是加班。加再多的班,方法错了,只会把你训练成更加容易犯错误的人。 

 

大道理不多讲了,我讲一下我对Nakatsu算法的认识吧。首先,没有看过的该算法的同志,请先看前面提到的两篇文章,很感谢万仓一黍同志对该算法描述方面的贡献。当然,如果你对该算法一点都不感兴趣,那后面可以不用看了。又或者你对于算法的掌控能力不够,那么你需要特别注意了,后面的内容可能会有点难。

 

为了便于解释,我这里给出一个比较长的对比串:

A串:5617823948123478102374891023748120374819023748901723849072134
B串:5617823944113274305364891223746120374819023743434123849072134

 

之所以需要使用这么长的对比串,是因为较短的串很难展现出问题所在。在我们开始分析之前,需要先手动分析一下整个计算过程中的数据是长什么样的。当然,你可以写程序,但请不要进行任何的优化。因为在没有搞清楚原理之前,优化的方式很可能是错的。这两个串我们可以得出如下的结果:(请忽略黑块,那是双屏大小不一致导致的)


点击看大图

 

这里面请看较靠左边的那一条细蓝色折线路径,靠右边的是我在开发过程中产生的错误路径结果,请忽略不计。在我们正式分析之前,我们还需要手动的去分析一下最长公共字符串到底是什么样的:

如果你希望动脑筋的话,先不要点开
561782394812347810237489102 374812037481902374890 1723849072134
=========X=X=X=XX=X=X====_=_===X==============XXX_=_===========
5617823944113274305364891 2237461203748190237434341 23849072134


我们现在可以分析,到底为什么选择的是这样的一条路径了。其实,只要稍微仔细看图,有的地方同一行会连续出现相同的数字。出现相同数字的原因是,某一格N(i,j)在该对角线的前一个元素N(i-1,j-1)开始,到该行的前一列N(i-1,j)处为止,没有找到相同的自符。这一含义如果我们做进一步的延伸,我们可以明确得知,如果出现这样的情况,那么表示此时采取了和该行前一列N(i-1,j)完全相同的最长公共字符串匹配方式,而与对角线前一个元素N(i-1,j-1)没有必然的关联。

 

进一步,我们可以得出这么一个结论:这是一棵多叉树。这个多叉树的形状如下所示:

 

(因制作原因,右上角那堆文字里面少了一个“解”字,抱歉。)

上图中,红色线段所指示的,就是最终最优解的路径。计算过程中会产生很多明确知道不是最优解的路径,计算结束的那一瞬间也会存有为计算完毕的次优解路径。前面这一句话你需要仔细琢磨,因为里面藏着一些比较难理解的部分。我这里给解开一下:

1、在计算结束之前,我们不知道这个到底哪一条路径会是最优解路径;

2、在计算某一对角线之后,如果不是找到最优解,则所有已经遇到MaxValue的点向上一直找到第一个分支点,都是可以确定肯定不是最优解路径的一部分;

3、在计算出最优解之前,本次计算对角线中所产生的任何分支,都有可能(虽然未必)是最优解路径的一部分。

 

正因为这个特性,导致了以下两个优化的约束:

1、不能提前丢弃任何新的分支;

2、部分历史分支可以剪除,但是如果该分支下面的任意一个字分支能够到达最近的对角线,则不能够剪除。(尽管有一些子分支是可能可以剪除的)

 

而实际上我们会发现,真是的树形结构比上面的糟糕很多,因为有很多分支是在末端产生的,这意味着能够剪除的分支非常有限。虽然最优情况空间复杂度可以达到O(M),但是最糟糕的情况下是O(M*P),一般情况下我估计可能是O(M*M)。其实该算法的空间复杂度是有可以优化的余地的,但不是通过用数组存放目前遇到的最X的一列,这在前面的描述中已经证明是不可行的。(好吧,我没有严格的证明,如果你觉得需要,我回头再补。)至于如何优化我这里先不写,因为整体的优化仍没有结束,现在的优化集中在时间复杂度上面。很可能在考虑到时间复杂度的优化之后,现在空间复杂度的优化就不能用了。

 

 

为了避免阅读此文的人再次受到其中一些不正确回复的影响,产生错误的印象,我这里再次把我在VII文中的回复复制一遍:
为了方便对比,我把我计算出来的结果和你的结果放一块做比较:
1561782394812347810237489102 374812037481902374890 1723849072134
2=========X=X=X=XX=X=X====_=_===X==============XXX_=_===========
35617823944113274305364891 2237461203748190237434341 23849072134
4=========_____________=_= =_=_=_____=_==_=====_____ __=_=___=__   ???????????????????????
5561782394             8 1 2 3 4     7 81 02374        8 9   1     23403410234890123472134

 

 

上面的第1、3行是A、B串,第2行是我计算出来的文本差异结果。第5行是VII文中提到的程序所计算出来的“最长公共子串”的结果。是公共串,那就必然只存在A、B串都存在的串,并且是按顺序匹配的。而第4行则是试图匹配B串的结果,从这里可以看到,后面有一大截是无法匹配的。由此可见,该算法的优化过程是错误的。


posted on 2011-03-11 14:15  Sumtec  阅读(3091)  评论(6编辑  收藏  举报