本文大量参考了博客算法-动态规划 Dynamic Programming--从菜鸟到老鸟里的内容,加上一点自己的理解。
递归
递归和动态规划很是相近,但又有所区别。从定义上看,递归在数学上可以表示为“数学归纳法”;由于我个人对数学归纳法的解法实在是印象深刻,但凡遇到之类的问题,都会用数学老师教的解法来做:1.找到初始状态(a_0);2.找到递归定义式。实际上,递归程序也是这么写的。
需要注意的是,递归是非常消耗内存的,递归层次非常深的程序通常有很长的调用链,计算机为了保证状态的返回又不得不将这些调用链一一保存。但是,递归的一大好处是,它形式上非常好理解。由于我们只要搞懂初始状态和递归形式,剩下的只要有足够大的空间和足够长的时间,就可以解出来。
由于递归的这些特性,通常我们遇到递归类问题时,可以先用递归思路做出来(往往是更简单的,但也有例外),再看是否有必要,将递归算法改进为有状态保存的备忘录算法。试图直接找出备忘录算法往往是很困难的,关于“树”的很多问题,包括遍历、查找等,往往是递归不到十行,循环要二三十行。
动态规划
这里建议看上面的博客,总结得非常好。这部分我也是刚学,就只提炼自己的理解。动态规划的问题往往可以用递归来做,倒过来也是一样的。那么它们具体的区别是什么呢?这就涉及到动态规划关注的两个问题了:1.最优子结构;2.重叠子问题。
最优子结构的意思是,一个问题的最优解是从多个子问题最优解中找出来的。举个例子,当一家公司要考虑开一个新项目时,往往需要综合多个领域,包括财务、技术、人员等等,老总自然不可能一个个自己琢磨;他会把这个调研任务分为不同领域交给各个专业的手下,从手下得到每个领域的最优方案,再从这些方案中提炼出一个最佳方案。需要注意的是,每个子问题的最优解可以从它的所有最优子孙问题解中找到,有形式上的递归。
重叠子问题就很好理解了,就是针对递归问题中不保存状态的问题。递归解法是通过计算机的调用链来返回状态的,所以每次遇到同样问题都要再算一遍。针对这种情况,动态规划要将每一个问题的解都保存下来,在自底向上的过程中,会很方便进行查找。
我个人对动态规划还有一个理解。虽然无论是递归还是动态规划,我们都需要找到初始状态和递归定义式,但是程序或者算法的进行过程是完全不同的。递归的算法是从一棵树的顶点一个个向下查找,直到初始状态,然后再一层层返回;而动态规划是从初始状态逐次递推,并且保存递推过程中的每一个状态量。简而言之,一个自顶向下,一个自底向上。
当然这里只是很干的理论,更深层次的理解必须要经过实战才能获得。具体的例题可以看上文博客的那几个。这里还有一个补充博客,提到了动态规划的另一种思路——自顶向下。其实就是把返回值保存在数组中(但是文章利用全局数组的方式不推荐,要么外面套一层函数,或者用闭包)。这种方法最大的问题是,没有解决调用链很长占用内存的问题,所以只是一个补充了解但是不推荐。
补充博客链接:递归和动态规划