动态规划,菜鸟的一点心得

概述

动态规划(dynamic programming),简称DP算法,是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。对于一个问题的某个阶段,在这个阶段以后的发展过程中都不受这阶段以前各段状态的影响,这样的问题通常能够用动态规划的思想去解决。

动态规划的思想

以下举一个简单例子:
  计算sum(n) = 1 + 2 + 3 + 4 + … + n
    假设n = 9,sum(9) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 = 45,
    再假设n = 10,显而易见sum(10) = sum(9) + 10 = 55,
    由此可见,倘若已知sum(n - 1)的值,并记录下来,sum(n)的值就能立刻
    用sum(n) = sum(n - 1) + n计算出来。
  其中,sum(n)为原问题,sum(n - 1)为子问题。若此前不做任何准备,让你计算sum(100),可能需要花费大量时间;而若此前已知sum(99)的值,你总是 能快速知道sum(100)的值。
  这就是动态规划的核心思想,将原问题分解成子问题,并记住已经解决过的子问题的解,从而求解复杂问题。

问题特点

能用动态规划求解问题,常常具备以下特点
1、计数
  有多少种方式走到右下角
  有多少种方法选出k个数使得和是Sum
2、求最大最小值
  从左上角走到右下角路径的最大数字和
  最长上升子序列长度
3、求存在性
  取石子游戏,先手是否必胜
  能不能选出k个数使得和是Sum

求解过程

动态规划求解常常包括以下几个步骤:
1、确定状态
  常常在求解动态规划时需要开一个数组dp[i]或者是dp[i][j],确定状态就是要明确dp[i]或是dp[i][j]的每个元素代表的实际含义是什么。
2、确定状态转移方程
  动态规划中一个阶段的状态总是上一阶段状态和上一阶段决策的结果,使用一个函数来表示前后阶段关系的方程,称为状态转移方程。在动态规划中,确定状态转移方程是最为关键的一步。
  通常确定状态转移方程时,需要明确两个问题:
  1)最后一个决策
    确定什么是最后一个决策,以及最后一个决策如何由上一个决策转移得到,并且确保前面
    的决策总是最优解。
  1)子问题
    子问题和原问题几乎一样,只是子问题的规模小于原问题。
3、确定初始条件和边界情况
  状态转移方程适用于整个求解过程,但常常不满足最开始的情况,求解时总是需要根据初始条件初始化状态转移方程。
  另外,与递归类似,在使用状态转移方程时,需要确定边界情况。

经典背包问题

题目:
  有n个重量和价值分别为wi和vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
限制条件:
  1 <= n <= 100
  1 <= w, i <= 100
  1 <= W <= 10000

求解思路如下:
一开始,用w[i]和v[i]数组顺序存放对应的物品重量和价值
接着,运用动态规划的思想来解决问题

1、确定状态
  我们应该使用dp[i]数组的形式还是使用dp[i][j]数组的形式呢?
  可以以这样的思维来考虑:
  现实生活中在真正挑选时,总是以这样的过程来进行:拿起第n个物品,接着决定是否将该物品放入背包中。
  那么,我们背包中的状态总是由第n-1次挑选物品时的背包状态在决策后转移而来的。而背包状态时背包的重量,即转移方程包括了两个转移的条件,如此,使用dp[i][j]比较合理。
  接着需要确定dp[i][j]的每个元素代表的实际意义是什么。
  显而易见,i是挑选第i个物品,j是当前背包装载的最大重量,而dp[i][j]是挑选第i个物品时,总重量不超过j的最大物品价值。

2、确定状态转移方程
  对于dp[i][j],考虑现实意义,在挑选物品时,需要对挑选与不挑选进行决策,总是有以下情况:
  1)第i个物品的重量大于j,不挑选第i个物品。
  2)第i个物品的重量小于等于j
    a)挑选第i个物品使背包到达j重量后的总价值比不挑选第i个物品使背包到达j重量的总价
    值来得低,不挑选第i个物品。
    b)挑选第i个物品使背包到达j重量后的总价值比不挑选第i个物品使背包到达j重量的总价
    值来得高,挑选第i个物品。
  代码实现如下:

if(j < w[i])
    dp[i][j] = dp[i - 1][j];
else
    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);

3、确定初始条件和边界情况
  初始条件有俩,一个是i = 0的情况,一个是j = 0的情况
  对于i = 0,即挑选第0个物品时,很明显,总价值总是为0
  对于j = 0,即背包重量为0时,也很明显,总价值也总是为0
  为了方便,在初始化时使用memset将dp[i][j]数组归零。

memset(dp, 0, sizeof(dp));

4、完整代码

memset(dp, 0, sizeof(dp));
for(int i = 1; i <= N; i++)
  for(int j = 0; j <= M; j++)
    if(j < w[i])
      dp[i][j] = dp[i - 1][j];
    else
      dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);

5、算法改进
上面的转移方程为dp[i][j]的形式,实际上该问题能够使用dp[j]的形式来进行简化
具体代码如下

memset(dp, 0, sizeof(dp));
for(int i = 1; i <= N; i++)
    for(int j = M; j >= w[i]; j--)
        dp[j] = max(dp[j], dp[j - w[i]] + v[i]);

对于dp[i][j]而言,当在挑选第i个物品时,只有第i-1行的数据需要被使用到,并最终只需保留第i行的决策数据,供挑选第i+1个物品时使用。
而对于dp[j]而言,在挑选第i个物品时,从j=w的情况开始挑选,这样数组中dp[0]到dp[w-1]的数据保存的仍然是挑选完第i-1个物品后的决策数据,同时又能更新挑选第i个物品后的数据。这样,使用dp[j]形式,就能大量减少需要开辟数组的空间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值