这个专题我们开始对《algorithm puzzles》一书的学习,这本书是一本谜题集,包括一些数学与计算机起源性的古典命题和一些比较新颖的谜题,序章的几句话非常好,在这里做简单的摘录。
手里拿着一把锤子,看什么都像钉子。我们这个年代最厉害的锤子就是算法。——William Poundstone.
解题是一种实用技能,怎么说呢,有点像游泳吧。我们学习任何使用技能的办法就是模仿和实践。——George Polya.
如果想使得上课不那么无聊,那么没有比加入带有创造力的主题更好的办法了,这些主题的特点就是带有娱乐、幽默、美感和惊喜。——Martin Gardner
给人以最大享受的,不是知识,而是钻研;不是拥有,而是跋涉。——Carl Friedrich Gauss
若是我不小心遗漏了一些多少有用或必要的内容,还请多多包涵,因为人人都会犯错,也不可能预知一切。——Fibonacci
首先我们对“算法谜题”这个概念稍作释义,每个算法问题都应该有所谓的“谜面”,它可以是具体的,也可以是一般的。举个例子来说,给出利用天平检测出含有1枚假币的8枚硬币,这里的“8”就是一个谜面,或者更加抽象的说,在解决算法问题中我们致力于给出一个正确且高效的function(),一个计算的过程、一个有功能的函数,而显然谜题本身应该具备相应的自变量x,而f(x)就是这个问题的解。
在解决算法谜题的时候,我们有一些通用的解题策略,比如穷举、回溯、分治、减治、变治、贪心、动态规划、递归等,这篇文章就是主要从思想层面来介绍这些解决算法问题的工具利器,以达到提升算法思维的目的。
穷举:
幻方:构造一个三阶幻方。(3*3的格子中填充数字1~9,是的每一行、每一列、两条对角线上的数字和都相等)。
很容易想到,我们对于这样一个3*3的方格,有且仅有9!种填充数字的方法,然后我们进行一一验证找到符合的方案即可,然而这个过程貌似太过机械化,计算量也太大。这其实体现了穷举算法的两个特点,其一是解集可知性,我们在这里能够一目了然的知道这个问题中能够出现的所有情况。其二便其庞大的计算量和低效率,这一点使得其在解决谜面范围不确定或者较大的时候,显得捉襟见肘。
然而有没有更好的方法呢?我们易证每行、每列以及对角线的和是15,由对称性的思维,我们能够看出5应该放在正中间。(1-9,2-8,3-7,4-6,可以满足两行两列和两个对角线)剩余的部分我们只需要枚举为数不多的情况,便可得到最终解。
可以看到,这种方法本质上还是一种穷举,但是是一种范围极小的穷举,由于其基于了一定程度的优化处理,这往往是利用穷举策略时的核心所在。
基于递推关系的减治法和增量法:
名流问题:在总人数为n的人群中的名流,就是指不认识任何人,所有其他人都认识的人。问题的任务是:仅仅通过向人们提出形如“你是否认识某某”的问题,来识别出名流。
问题本身很简单,将n个人放入一个名流的预选集合A,我们随机抽取一个人,记为甲,询问他是否认识剩余n-1个人中的一个,记为乙。如果甲认识乙,那么将甲从名流的预选集合中排除,反之,则排除乙。能够看到,通过这一步选择,名流预选集合A的规模缩小了1,那么反复操作,必然可以找到名流。
这个问题非常简单,但是反映出来的思路却很重要。对于一个全局的大问题,我们利用它与一个更小规模的问题的递推关系,来将问题规模缩小,这就是所谓的减治法。相应的,所谓的增量法,就是基于一个小规模问题的解,我们尝试利用它与更大规模问题解的递推关机,来讲问题规模扩大。本质上讲,他们都是基于一种建立递推关系的思想来减小或扩大问题规模的一种方法。
分而治之:
三格骨牌谜题:使用3格骨牌填充一个缺了一块的2^n * 2^棋盘,缺的一格可是放在任意处。
我们将这个3格骨牌放在棋盘的正中间,我们能够看到,填充2^n * 2^n大棋盘的问题,变成了填充4个2^n-1 * 2^n-1小棋盘的问题,对于小棋盘,我们采取相同的策略,不断将某一个小问题分解成若干个小问题。
这里其实可以看到,分治思想和上文中我们介绍的减治好像有相通的地方,都致力将问题子问题话或者说是建立递推关系。可以将减治看作分治的一个特殊情况,他们本质的区别在于,减治将原问题缩小规模后,形成了一个子问题,而分治则会有多个。
变而治之:
数字填充:
有n个不同的数字,还有n个一系列>、<连接的空格。设计以算法将这些数字填到空格之中并满足空格之间的不等式关系。
题目要求仅仅给出一种符合要求的填充方式,因此我们需要考虑所使用的算法一定确定能够生成满足不等关系的方案。那么这里我们将使用变而治之策略中的一种方法——表示变更,即将数据进行预处理。
我们首先将这n个数字从小到大排序,我们用一个a[]来表示,然后开始检索前n-1个空格,如果第i个空格后面是">",那么将a[]中最大的元素放入第i个空格当中,然后删除这个元素。这样做的好处是,通过将n个数字的升序排序,我们能够保证每一次填充都是符合不等关系的,否则,对于一堆散乱的数字,我们的选择项太多而又无法保证不等关系的成立。
最优馅饼切割法:
在一块矩形的馅饼上切n刀,最多能把馅饼切成多少块?规定只能平行于馅饼的边进行横切或者竖切。
我们首先设横切x刀,竖切y刀,首先有x+y=n。其次我们应该能够看到,横切x刀将在原来的基础上得到x+1块小馅饼,纵切y刀将在原来的基础上得到y+1倍块小馅饼。(这个过程很像《度量》一书中给出的关于矩形面积计算公式的由来),设f表示切n刀后得到的小馅饼数,则我们可以得到如下的函数关系。
f = (x+1)(y+1),结合x+y=n我们进行替换,n作为参量,而f我们将其改成与横切数x(因为代换后仅有一个变量x)的函数关系。
即整个问题能够等价转化成这样,f(x) = -x^2 + nx + (n+1),x∈[0,n]且x∈Z,求f(x)的最大值。建立的数学模型后这个问题就显得容易了很多。
这便是变治的另一个层面——数学归约,即将实际问题找到合适而好处理的数学模型,是我们研究的理论知识的一个用武之地,这种在解决实际问题当时是非常重要的一种思路。笔者在《数学建模》的专栏中将会介绍更多的数学建模的实例。