递归?这次真不行 —— “动态规划” 入门理解(下)
书接前文:昨天我们发布了《“动态规划” 入门理解(上)》,说到用Excel 的公式就可以把“台阶走法问题”的解决过程清晰表达出来。
说起来这个规律也确实很简单:如果想知道“5级台阶”有多少种走法,只要把“4级台阶”和“3级台阶”的走法加起来就可以。显然,把这个规律写成程序应该也十分简单。比如下面就是用Python和VBA两种代码计算 “踏上第10级台阶” 的示例。为了方便大家理解,我们用中文给变量命名。
VBA解决方案:
Python解决方案:
上面代码中有一个地方可能会让同学感到一点困惑,就是在每次循环中计算完第 i 阶走法后,要对 “前一阶总数” 和 “前两阶总数” 两个变量重新赋值。
这是因为在计算完第i阶后,我们接下来就要计算第 i+1 阶;而对于第 i+1 阶来说,它的前一阶就是第 i 阶,而其前两阶则是指第 i 阶的前一阶。因此为了准备下一次运算,每次都要重新定义“前X阶”。
具体来讲:第一次循环时i=3,也就是说我们要计算踏上第3阶共有几种走法。而此时它的“前两阶”是第1阶,“前一阶”则是第2阶。所以在循环中将这两个走法总数加在一起,就是第3阶的走法总数 3 。
接下来,在下一次循环时 i=4,也就是要计算第4阶有多少种走法,而第4阶的走法是它的前一阶和前两阶之和。那么第4阶的前一阶和前两阶是谁呢?是第3阶和第2阶!
换句话说,本来在i=3时,第2阶是“前一阶”;然而到了i=4时,第2阶就只能退位变成了“前两阶”,而第3阶则新人上位变成了新的“前一阶”。
因此在计算第4阶之前,我们必须先调整“前两阶走法总数”和“前一阶走法总数”两个变量,使他们代表第2阶和第3阶的数值。这就是每次循环中执行变量替换的原因。
而至于第1阶的走法嘛,在i=4时已经没有用处,所以早已完成历史使命,直接被拍在沙滩上、抛弃掉了。
程序编好了。但是这与动态规划有什么关系呢?
别着急,我们再回来看看这个解法的规律。如果用 “走法数量(n)” 代表走到第 n 个台阶的走法总数,那么写成公式后看起来是不是觉得有些眼熟呢?
走法数量(n)=走法数量(n-1)+走法数量(n-2)
我想凡是学完《全民一起玩Python 提高篇》第46回“递归算法”的同学,应该都会有对此有印象:这不就是本节作业第4题“斐波那契数列”嘛?
既然如此,当然本题也可以使用递归来解决,比如下面的程序一样能够算出走到10阶的走法总数:
不过把这种递归方法跟我们之前的代码放在一起比较,我们就会发现二者的区别:
虽然这两者都是把“求第N个台阶”的问题,化解为“求第N-1台阶和第N-2台阶”两个子问题,但是仔细思考会发现:在递归算法下,很多子问题都会被重复计算很多次!
比如当执行到 “steps(5)”,也就是求解走到第5级有多少种走法时:
1. 递归程序要求计算 steps(3)和 steps(4),也就是第3级和第4级的走法;
2. 于是程序进入递归,去计算steps(3) ;
3. 等计算完steps(3) 之后,回来计算steps(4)
4. 而在计算steps(4) 的过程中,递归函数又要求计算steps(3)和 steps(2) 。
于是我们看到:仅在这个过程中,steps(3) 就被重复计算了两次!再想一想递归恐怖的层层树形展开,如果计算steps(10),不知有多少子问题被反复计算。
所以对于这个问题,使用递归虽然代码简单,但运算效率却会极其低下。低到什么程度呢?杨老师刚刚在自己电脑上做了一个试验,求解第40级台阶花去了整整20秒的时间。
然而使用前面迭代式的写法呢?看一眼代码就会发现,第3阶走法、第4阶走法、第5阶走法 …… 所有这些子问题都只计算过一遍!显然,这会比递归算法中的大量重复运算快很多。
事实也是如此。同样在杨老师的电脑上做实验,使用这种迭代方法,即使计算第 400 级台阶也只是一眨眼的事情,完全感觉不到运行时间:
所以这一次,递归真不行,真正实用的方案还是前面这种从第3台阶开始逐层迭代的方法。而总结一下,这种迭代方法的特点是:
(1) 把求取第 N 项数值的问题,变换为“求取前面若干项数值”这种“子问题”;
(2) 逐次计算子问题,从而推导出第 N 项;
(3) 在推导过程中,每个子问题只计算一遍。
那么现在就可以揭开谜底了:符合上面这几个特点的算法,就是基本的“动态规划”。
觉得好看就点个在看吧
杨老师课程全集
全民一起玩Python |全民一起VBA
均在网易云课堂发布,欢迎加入、一起进步。
二维码: