动态规划算法的基本思想_算法——动态规划

算法——动态规划

5d61b1de710b49096244f3c33a8ab486.png


 首先学习动态规划,我们先知道什么是动态规划?
  算法导论这本书是这样介绍这个算法的,动态规划与分治方法类似,都是通过组合子问题的解来来求解原问题的。再来了解一下什么是分治方法,以及这两者之间的差别,分治方法将问题划分为互不相交的子问题,递归的求解子问题,再将它们的解组合起来,求出原问题的解。而动态规划与之相反,动态规划应用与子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子子问题)。在这种情况下,分治方法会做许多不必要的工作,他会反复求解那些公共子问题。而动态规划对于每一个子子问题只求解一次,将其解保存在一个表格里面,从而无需每次求解一个子子问题时都重新计算,避免了不必要的计算工作。

定义:
  动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
  动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

动态规划的应用场景:
动态规划方法一般用来求解最优化问题。这类问题可以有很多可行解,每个解都有一个值,我们希望找到具有最优值的解,我们称这样的解为问题的一个最优解,而不是最优解,因为可能有多个解都达到最优值。


  我们解决动态规划问题一般分为四步:
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的背包,如何让背包里装入的物品具有最大的价值总和?

0b24509a8acdf074e9fa8b5484a41494.png

首先要明确这张表是至底向上,从左到右生成的。

  为了叙述方便,用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的背包

b6f5c35dca97de79a572c61241ff6a20.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值