《编程之美》反思——启发式思考
欣赏完《编程之美》里面的巧妙解法后,我们不妨来思考下作者是怎样想到这些解法的。因为比起理解怎么做,弄清楚为什么那样想更有趣,不是吗?作者提出一个简单的解法后,总能找出一个优化方法。我认为其中的关键是联想。只要能从中联想到正确的知识,优化方法也就得到了,不是吗?
联想到正确的知识需要经验(知识)和方法。经验很重要,但方法也同样重要。以下是一些有助于联想的方法。
抽象
抽象就是从问题中提取有用的,本质的特征,然后将问题用一个简洁但包含同样信息的模型表示出来。复杂的问题经抽象后,可能会变成一个简单的问题,也可能会变成一个曾经遇到的问题,当然也可能仍然是复杂的问题。不管抽象后得到的结果是哪一种,看着抽象后的问题,想出解的可能性必然比直接看原题想的可能性大。例如:2.3寻找发贴的“水王”。
将这题抽象下就是:给你一个数组,里面有超过一半的数字是一样的,你的任务就是找出这个数字。关于数字数组的处理,容易联想到的方法是排序或者哈希。那么将这些方法套到问题上,就可以得到:a. 排序后,直接输出中位数;b. 建立一个大哈希表(数字的值就是它在哈希表中的下标),遍历一次数组,将数都放到哈希表中,在这个过程中,如果发现哪个数的碰撞次数超过一半,就找到了。
这就是将问题抽象后的好处——容易进行知识迁移。
特性利用
特性利用,就是根据问题的特点,定制一个解法。通常来说,这个特制的解法是最优的。使用这种思想需要先找出问题的特点,然后思考是什么导致特点的出现,以及思考特点的使用方法。最后,如果能得到想法,就可以为问题定制一个解法。
在“寻找水王”,即上面提到的问题中,显著的特点是有一个数的出现次数超过一半。从出现次数超过一半的原因的角度思考得不到启发。但是从利用这个特性的角度思考就会得到这样的启发:这个数出现的次数比剩下的数的出现次数总和还要多。将出现次数做减法,剩下的必然是出现次数超过一半的那个数。例如:5,6,5,89,5,56,5这7个数中,5出现的次数比其它数出现次数总和多1。在统计时,如果出现5就将出现次数加1,不出现5就将出现次数减1,会发现出现次数是这样变化的:1,0,1,0,1,0,1。用文字将这个过程表述一下就是:如果下一个数字与5相同,就将出现次数加1,否则就将出现次数减1。更一般的描述是:如果下一个数字与前一个数字相同,就将出现次数加1,不同就将出现次数减1。改成这样时,不管事先是否知道5是出现次数最多的,统计到最后时,都会发现留有次数的是5。调换7个数字的顺序来验证这个一般性的想法,会发现这个想法是对的。将这个想法变成代码就得到了这题的最优解法了。
如果是理解“多重背包”问题的人,读完题目,稍作思考,就会将这题归约到多重背包上,从而得到问题的解法。所以,为了用好归约,平常要注意积累和总结。
逆向思考
逆向思考,就是将未知当作“已知”,然后从“已知”出发到真正的已知。这种方法通常用于如果从最终结果往前推到已知比从已知推到结果容易的问题。然而,实际问题都是不知道从哪边开始会容易点,所以当没其它想法时不妨用一下这种思考方式。
例如1.15,构造数独的初始局面(所谓数独指的是给出一些格子,然后按照一些规则往这些格子填数字,都填满后且处于合法状态则完成)。直观的方法是:随机填一些格子,判断这个局面可完成后,就将其作为初始局面。这个方法有一个缺点:不知道随机填的格子作为初始局面后,用户玩的时候能否达到完成状态。这里,用户能否达到完成状态是未知,而初始局面是已知。应用逆向思考后会变成,先生成一个完成状态的格局,然后去掉一些格子,就得到了必然能达到完成状态的初始局面。这样就得到了一个更好的方法。
结语
砖头能用来做什么?
继续看下去前,不妨花点时间来思考下。如果不能想出超过8种用法,不妨用上面提到的方法来启发下联想。如果还是不能,那看一下提示吧。你可以从硬度,外形,重量,大小,颜色,类似的物体几方面考虑。
你肯定能从提示中想到更多的用法。能想到更多用法的原因有二,一是这些用法你本来就知道,二是给出的提示,指示了思考方向。沿着这个方向下去,就发现了那些被你埋藏的知识。所以,在积累知识的同时,也要积累各种启发思考的方式。