算法——动态规划
首先学习动态规划,我们要先知道什么是动态规划?
算法导论这本书是这样介绍这个算法的,动态规划与分治方法类似,都是通过组合子问题的解来来求解原问题的。再来了解一下什么是分治方法,以及这两者之间的差别,分治方法将问题划分为互不相交的子问题,递归的求解子问题,再将它们的解组合起来,求出原问题的解。而动态规划与之相反,动态规划应用与子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子子问题)。在这种情况下,分治方法会做许多不必要的工作,他会反复求解那些公共子问题。而动态规划对于每一个子子问题只求解一次,将其解保存在一个表格里面,从而无需每次求解一个子子问题时都重新计算,避免了不必要的计算工作。
定义:
动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
动态规划的应用场景:
动态规划方法一般用来求解最优化问题。这类问题可以有很多可行解,每个解都有一个值,我们希望找到具有最优值的解,我们称这样的解为问题的一个最优解,而不是最优解,因为可能有多个解都达到最优值。
我们解决动态规划问题一般分为四步:
1、定义一个状态,这是一个最优解的结构特征
2、进行状态递推,得到递推公式
3、进行初始化
4、返回结果
一般可以使用递归或者递推的写法来实现动态规划,这里以经典的求解斐波拉契数列为例子。如果没有学过动态规划,可能只会用递归的方式求解斐波拉契数列。代码如下:
long long fab(int n) { //简单递归法if (n < 2) return 1;else return fab(n - 1) + fab(n - 2);}
事实上,这个递归会有很多重复的计算,以下递归树可以直观地表示。实际其时间复杂度为O(2n),指数级的算法在数字稍大的时候基本不能用,更何况当问题规模较大时递归调用系统栈也是非常耗时的。
这里将动态规划的递归写法用简单递归的方法对比,比较当n=50时,两者所消耗的时间。
#include#include#includeusing namespace std;vector<long long> a; //存储计算结果 long long fab1(int n) { //动态规划递归写法 if (n < 2) return 1; //递归边界 //以-1表示没有计算过 else if (a[n] == -1) a[n] = fab1(n - 1) + fab1(n - 2); return a[n];}long long fab2(int n) { //简单递归调用法 if (n < 2) return 1; else return fab2(n - 1) + fab2(n - 2);}int main() { clock_t start, end; int n; cin >> n; a.resize(n+1,-1); long long result; start = clock(); result= fab1(n); end = clock(); cout << "动态规划递归算法运行时间:" << (end - start)*1.0 / CLOCKS_PER_SEC << "s" << endl; cout << "运行结果:" << result << endl; start = clock(); result = fab2(n); end = clock(); cout << "直接递归算法运行时间:" << (double)(end - start) / CLOCKS_PER_SEC << "s" << endl; cout << "运行结果:" << result; return 0;}
程序运行结果表示,动态规划递归算法的时间消耗几乎为0,而简单递归却用了一分多钟。这是因为把已经计算的结果记录下来,每次遇到需要计算已经计算的子问题时,直接使用上次计算过的结果,这样就把时间复杂度从O(2n)降到了O(n)。
经典例题——背包问题
题目描述:
假设山洞里共有a,b,c,d ,e这5件宝物(不是5种宝物),它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富。
有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
首先要明确这张表是至底向上,从左到右生成的。
为了叙述方便,用e2单元格表示e行2列的单元格,这个单元格的意义是用来表示只有物品e时,有个承重为2的背包,那么这个背包的最大价值是0,因为e物品的重量是4,背包装不了。
对于d2单元格,表示只有物品e,d时,承重为2的背包,所能装入的最大价值,仍然是0,因为物品e,d都不是这个背包能装的。
同理,c2=0,b2=3,a2=6。
对于承重为8的背包,a8=15,是怎么得出的呢?
根据01背包的状态转换方程,需要考察两个值,
一个是f[i-1,j],对于这个例子来说就是b8的值9,另一个是f[i-1,j-Wi]+Pi;
在这里,
f[i-1,j]表示我有一个承重为8的背包,当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值
f[i-1,j-Wi]表示我有一个承重为6的背包(等于当前背包承重减去物品a的重量),当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值
f[i-1,j-Wi]就是指单元格b6,值为9,Pi指的是a物品的价值,即6
由于f[i-1,j-Wi]+Pi = 9 + 6 = 15 大于f[i-1,j] = 9,所以物品a应该放入承重为8的背包