动态规划详解

来源:互联网 发布:淘宝详情制作软件 编辑:程序博客网 时间:2024/05/02 01:04


说在讲解之前的话:

通过这一章的学习,才真正体会到解决问题是有流程可走的,光懂很多算法也不行,还要有思路去解决,哪怕用最复杂的算法实现,起码是一种解决办法。

当看到一道编程题,首先做的是分析如何求解这个题目,如果能一下想到最快捷的算法更好;不能的话,就先用笨方法去解决,最后看能否对方法进行优化,删去一些重复的地方,使用学过的数据结构和算法去重新解题等等。

最好的情况下,是能写出表达式,通过表达式不仅可以很方便的写出代码,而且还能通过表达式来获得优化的途径。

下面讲解动态规划时,会结合这个观点来描述。


一:理论思想(这部分很重要,指导怎样去构建算法)

1:动态规划浅析

动态规划(Dynamic Programming,简称DP)是通过组合子问题的解来解决问题的。

比较分治法于动态规划的区别:
分治法:将问题划分为一些独立的子问题,递归的求解各子问题,然后合并子问题的解而得到原问题的解。

动态规划DP适用于子问题不是独立的情况,这样如果用分治法,也会作许多重复的工作,而DP只对子问题求解一次,将其结果保存在一张表中,从而避免了每次遇到各个子问题时重新计算的情况。

DP算法的设计可以分为四个步骤:
①.描述最优解的结构。
②.递归定义最优解的值。
③.按自底而上的方式计算最优解的值。
④.由计算出的结果创造一个最优解。


2:利用四个步骤去解题

通过研究一两个问题,发现解决动态规划问题还是有迹可循的,关键是解题的正确思路和解题步骤,这四步是很重要的。下面通过几个例子的概述详细的说一下这四步的用法,具体的例子解答见后面的例题:

①.描述最优解的结构。

这一步是很关键的,其他几步都是在此基础上进行下去的。

当我们解决一个问题时,先考虑我要求解最终结果,应该从哪些值的结果而来。这就引申出一个问题:解的结果从何而来?

装配线问题

最后的出口(1,n)从第(1,n-1)位置或(2,n-1)来。(具体细节,就不考虑了,毕竟还要分(1,n)(2,n),然后决定最后出口的最小值)。那好,是否具有最优子结构问题:从出发点到第i个位置的最优解组成了解决该问题的最优解。

钢条切割问题

长度为n,可以从第i个位置来。那么将i+1->n看成一个整体,切割1->i。是否具有最优子结构问题?有,当前i段的最优解,组成了解决该问题的最优解。(当然求解该问题时,要对i从1遍历到n,一般动态规划问题都有这样的遍历。)

矩阵链相乘

求n个矩阵的乘积,可以从第i个位置来划分,划分成小问题,递归求解左边最优解,右边最优解,左右最优解之和组成了问题在该划分下的一个最优解。将i从1到n-1遍历,将各个划分下的最优解之和比较得出最优解。

 

总结:

i)为了得到最终解,逆推出可能的不同划分,然后对每个划分求解最优值!一般得出的都是递归的算法。

通常都遵循如下模式:

a)做出一个选择,并假定此选择就是最优解(一般都要通过遍历选出最优值)

b)确定产生哪些子问题,以及如何最好的刻画子问题空间(下面有讲)

c)利用“剪切粘贴”方式证明:每个子问题的最优解,构成原问题的最优解。(一般不进行验证,但严格来说这一步很重要)

ii)刻画子问题空间

原则是:保存子问题空间尽可能简单,只在必要时才扩展它。

也就是说,尽量在递归时只涉及一个变量,例如“钢条切割”的长度为变量。但是在“矩阵链乘法问题”中,一次选择会产生两个子问题,并且两个子问题不能归并为一个递归形式,且两个递归之间涉及的数据不同,不能替换,此时应该扩展,用两个变量来表示。


②.递归定义最优解的值。

说白了,就是写出递归表达式,此时应该注意边界条件,例如装配线问题中的出口时间消耗和入口时间消耗。

③.找出最快的时间方式(通常采用自底向上的求解)

如果单纯根据表达式求解的话,需要用到递归,问题是解决了,但时间复杂度可能达到是O(2^n)。这明显是不理想的。但对于动态规划问题,通常采用自底向上的方式,需要建立数组来保持,以减少递归的消耗。那么这里就有个问题,数组保存哪些值呢?利用数组就是为了防止递归带来的时间消耗,因此数组保存的是要递归时的返回值。比如装配线问题递归表达式中的f1[n-1]以及f2[n-2]。有了这样的数组,将i从1->n增长,一步一步求解。

④.由计算出的结果创造一个最优解。

通常只用求解最优解的话,是不需要这一步的。但要给出方案,就要保存路径信息。只需要额外设置数组,保持求解第i个值时的选择。


3:动态规划原理

应用动态规划方法求解最优化问题,要具备两个要素:最优子结构和子问题重叠。

①最优子结构

严格来说,具有最优子结构包括两层含义:

a)问题的最优解由相关子问题的最优解组合而成。

b)这些子问题之间相互独立,互不干扰,可以独立进行求解。(例如无权最长路径就不符合这个条件)

当求解一个子问题时用到了某些资源,导致这些资源在求解其他子问题时不可用。就不具备最优子结构。


当递归式中只有一个子问题时,显然具备无关性;当具有两个时,就要考察一下是否子问题相互独立。

②子问题重叠

如果递归算法反复求解相同的子问题,就具备子问题重叠。


4:分析动态规划算法的运行时间

方法1:用子问题的总数和每个子问题需要考察多少种选择这两个因素的乘积来粗略分析。

方法2:利用子问题图来分析:子问题有多少个顶点,每个顶点有多少条边。二者的乘积也能用来描述。


5:自底向上和带备忘的自顶向下

自底向上:先通过分析最优解的递归表达式,看看数组和哪些低层次的数组有关系,然后再决定先求哪些数组再求高层次数组,最后实现算法!注意:算法的实现与数组的关系结构有很紧密的联系。

带备忘的自顶向下:一般的自顶向下只是通过递归表达式写出递归函数即可。带备忘的自顶向下,添加了三个方面的代码:(1)在调用函数前,先分配备忘数组并初始化,递归函数中传递此数组。(2)刚进入函数时,先判断要求的数组值是否已经更新为有效值,是的话,直接退出;否则,正常运行一般的递归函数。(3)在函数出口的地方,也就是return之前,要更新数组值。

相比之下:虽然二者时间复杂度形式一样,自底向上的方法具有更小的系数,因为其没有递归调用的开销,表的维护开销也更小。但是带备忘的自顶向下更容易编写代码。自底向上的方法必须研究递归表达式中数组和低层次数组之间的关系(求该数组,需要提前知道哪些数组)。


6:备份数组的选取以及重构最优解数组

备份数组:保存的是递归程序的返回值。一般是一维数组或二维数组,分析递归表达式等号右边用到的数组,通过其形式决定备份数组的维数。

重构最优解数组:一般其维数与备份数组维数相同。保存的是分割信息,需要通过分析得到其保存的值。比如最长公共子序列(b[i,j]='箭头');钢条切割(切割点i位置);矩阵链乘法(分割点)等。



二:动态规划例题

下面是例题,带有详细的分析。

1:三角形求值问题

2:动态规划之装配线调度

3:动态规划之钢条切割问题

4:动态规划之矩阵链相乘

5:动态规划之最长公共子序列

6:动态规划之最优二叉搜索树



0 0
原创粉丝点击