再论KMP算法

面对一个新问题,先尝试用自己全部可以想到的方法去解决,从特殊逐渐地推向普遍,不要管什么效率、性能、准确、可靠等问题,更不要想着在一开始就试图设计一种最优最美的解决方案。我觉得这不仅是解决一般问题的基本思路,也是任何事物发展变化的一般规律。我在面对一个新问题时,不敢贸然地去下结论,去衡量它的复杂度。在我以往解决的一些问题中,往往是表面上看起来简单的小问题,在不断深入解决的过程中,一步一步变得更加复杂,不是把问题故意给想复杂了,而是因为自己在逐渐求解的过程中对问题的理解更加深入了,此外很多问题中往往嵌套着另一个问题甚至更多的问题,不是简单地解决眼前这个问题那么容易。

对于一个系统来说,表面上看,我们每解决系统的一个子问题可以让系统变得更加强壮和确定,但实际上,随着我们解决问题数量的增多,我们的内心会越来越不安,因为这意味着系统会变得越来越复杂,越来越特殊,越来越不可靠,直到最后人们实在忍受不了,在对前一个系统总结反思的基础之上,又重新构建一个新的系统。有时候我问自己:问题到底是它自己产生的,还是我们无意创造出来的?我在很早的时候,就开始对此产生疑问,因为在我看来,我们解决问题的本身是在创造新的问题。可能有人会批判说我这是瞎说,我们怎么可能故意去给自己制造麻烦呢!是的,我们不会故意给自己制造麻烦,但我们确实是在无意中给自己制造麻烦。当然,可能生活中的问题不全是这样,但我认为制造麻烦这种情况是现实存在的。

举个例子:你的手里捏着一个充满气的气球,当你按下某个位置时候,其他的地方一定会因此而受到影响,我们解决了一个系统的某个小角落的问题,但正是因为这个问题的解决,会导致系统的其它部位因此而出现新的故障。第一,系统开始出现故障一定不是某个点位的小问题,你能发现的问题仅仅只是很多问题显化出来的问题。第二,解决问题的本身其实就是让系统继续沉淀历史,直到某一天又出现问题,如果你连之前的问题都没有解决,那系统也不会走到今天。第三,故障暴露出来的是很多因素的一种综合,解决问题就是对认识到的这些因素进行综合分析和解决,对于眼下这个问题来说,这些是问题产生的因素,而对系统其它的某个部位来说,这些是确保它们不出故障的正面作用因素,消除某个因素也就意味着让它们出现故障。这三种情况,是目前我的逻辑里面可以认识到的三种情况,我相信实际的情况远比这三种要多得多,我只能解决我认识到的问题,对于那些认识不到的,我根本无法言论。

我是一个追求完美主义的人,但我也深知完美永远只能是活在理性思维中的一种理想,在现实的世界根本无法达到,除非你让时间停止流动,你让事物停止运动。当然,正是人们对完美主义的不懈追求和努力,我们才能进入到当前这个文明秩序的社会,也有人对文明社会持消极态度,这也不是没有道理的,没到世界的边缘,没到世界的时间的终点,一切的论断都不是绝对的,从历史中我们可以看到很多因为时间尺度的变化而颠覆人们之前认知的例子。如果说,一切都不那么可靠,那我们努力追求完美的意义又何在呢?我们经常会说,过程比结果重要,但是没有结果的过程似乎难以让人接受。是的,我们难以接受的事情远比我们能够接受的事情要多得多,这也是我们比其它动物看起来更加智慧的代价。

以前经常用库中提供的字符串查找函数,也没有深入的思考过底层的实现原理,第一次遇到KMP算法,大体上明白了算法设计的出发点是为了减少不必要的重复操作,但是在具体的代码实现上,我还是遇到了不少牵绊,可以说这就是我的逻辑漏洞,我只认识到了我能够想到的一些特殊情况,对于之外的其它情况我也没法认识到,我的思维没有覆盖到全部的逻辑分支,因此我卡在了NEXT数组的计算上。等我认识到了之前我漏掉的那种情况之后,我才明白了它为什么要这样去实现。在以后的复盘反思中,我认为自己做的工作就是列举所有我能想到的情况,将解决方案从特殊逐渐推向普遍的一个过程。

关于KMP算法的解释和代码请参见我的另一篇文章《javascript实现KMP算法

KMP算法的重点也在NEXT数组的计算上,因为解决重复查找的核心就在要匹配的字符串上下功夫,比如字符串“abcdabceabcdabcd”,我们会发现它自己就存在着很多重复,我们可以很好的利用这些特性,去减少重复性的匹配。如果要查找的字符串不存在任何重复的地方,那么KMP算法也就退化为一般的查找方法,即一个一个去查找。

NEXT数组中记录的是以当前字符为结束的字符串可以与从头开始的字符串的最大匹配长度,它的求解过程使用到了回溯,即当前不匹配之后回到上一个匹配状态,如果上一个匹配状态下还是无法继续匹配,那么再往前回溯一步,直到回到最开始的状态,也就是第一个字符的位置,从头开始匹配。这实质上是一种状态机算法,但我们没有构造专门的状态机,因为NEXT数组本身就记录了状态序列,也就相当于合二为一。

对于KMP的理解,让我认识到了我无法想通我无法认识到的情况分支,只有我认识到并列举出了这种情况,我才能够真正理解它为什么这样,而不是那样。这让我想起自己在以前学校学习的时候,经常会被一大堆的数学、物理、化学公式所困扰,我不会用一个我没有全面认识到的公式,在现在的我看来,任何一个公式其背后都蕴含着大量的历史积淀,它是对大量情况处理的简练凝结,我们用公式要用的踏实,最核心一点就是要理解它背后的历史,这也是我在后来的学习中,每遇到一个新知识点,都要先了解它出现的历史背景的原因。可能每个人的思维习惯不一样,而我比较偏好这种,这也是为什么遇到一个新的知识,我总是比别人慢一拍的原因,但我觉得经过自己一番折腾之后,我对问题的认识更加深刻。我觉得,表面花样很多,背后道理一条。有时候我们觉得孩子反应慢,不是因为他不够聪明,而是因为他想的更加深刻,在他还没有全部弄清之前,他不敢轻易论断。

从回溯机制中我们也可以看出,唯有记住我们曾经走过的路,才不至于在一个地方再次跌倒。我们要善于在记忆中开辟一个地方,记录总结自己过往的人生,每当遇到难坎时不必马上从头再来,可以先从最近一次类似情况出发,这样效率也会提高不少。但是,如果我们不善于记录总结,那我们的人生就没有任何可参考借鉴地方,每次我们都似乎是新的开始,每次都是从头再来。

所以,要想提高人生效率,芝麻开花节节高,就请从总结记录自己的人生开始吧!

    原文作者:李伯特
    原文地址: https://www.jianshu.com/p/5282cfd8b68f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞