动态规划-01背包

简介: 前言动态规划和递归是一对CP,递归通过将大问题分解成小问题以求得答案,而动态规划则是通过求解小问题来组成大问题的解。虽然其本质都是先求小问题的解,但是问题的推算不同:

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战


前言


动态规划和递归是一对CP,递归通过将大问题分解成小问题以求得答案,而动态规划则是通过求解小问题来组成大问题的解。虽然其本质都是先求小问题的解,但是问题的推算不同:


  • 递归:大问题 -> 小问题

  • 动态规划:小问题 -> 大问题


在平常的代码中,递归是比较符合我们思考的方向的。在编码中,我们经常自然而然的将复杂问题分解成简单问题并分步进行。但是如果要思考如何如何通过小问题来求解大问题,就需要比较强的推理能力了。其中推理的重点在于


  1. 定义dp数组(主要是dp下标及含义)

  2. 递推公式(小问题转化为大问题)

  3. dp初始化(已知小问题的解)

  4. dp遍历生成(逐步推导出答案)


其中的重中之重为递推公式


01背包解题步骤


背包问题是动态规划中比较常见的一种类型,今天我们来学习背包问题中的01背包。

Q:一共有N件物品,第i(i从1开始)件物品的重量为w[i],价值为v[i]。在总重量不超过背包承载上限W的情况下,能够装入背包的最大价值是多少?


如果采用暴力解法的话,我们需要考虑每件物品取/不取的情况,其算法复杂度为O(2^N)。我们可以通过动态规划来使其降低为O(WN)


1.定义dp数组


我们要求的是在总容量为W,物品有N件的情况下,可以取得的最大价值Vmax。有两个变量容量w及物品n,我们设要求的总价值dp[i][j],其中i表示物品可取范围为前i件,即范围[0, i],j表示背包容量。


2.递归公式


分两种情况


  1. 当我们不取第i件物品时
dp[i][j] = dp[i - 1][j]
复制代码

  1. 当我们取第i件物品时
dp[i][j] = dp[i - 1][j - w[i]] + v[i]
复制代码


所以可以得出递推公式

dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
复制代码


3.dp初始化


我们知道,在容量为0的时候时候,dp[i][j]肯定为0

dp[i][0] = 0
复制代码


当所选的物品为0的时候,价值也为0

dp[0][j] = 0
复制代码


4.dp遍历生成

for (let i = 1; i <= N; i++) {
  for (let j = 1; j <= W; j++) {
    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
  }
}
复制代码


01背包实例


Q:一共有N件物品,第i(i从1开始)件物品的重量为w[i],价值为v[i]。在总重量不超过背包承载上限W的情况下,能够装入背包的最大价值是多少?


N = 5;
W = 20;
w = [4, 5, 10, 11, 13];
v = [3, 4, 7, 8, 9];
复制代码
function knapsack() {
  const N = 5;
  const W = 43;
  const w = [4, 5, 10, 11, 13];
  const v = [3, 4, 7, 8, 9];
  // 1.定义dp数组
  // dp[n][w]表示容量为w的情况下可取前n件物品时的最大价值
  const dp = []
  // 2.初始化dp数组
  // 容量为0时价值为0
  for (let i = 0; i <= N; i++) {
    dp[i] = []
    dp[i][0] = 0
  }
  // 可取物品为0时价值为0
  for (let j = 0; j <= W; j++) {
      dp[0][j] = 0
  }
  // 4.dp遍历生成
  // 我们第一层遍历物品
  for (let i = 1; i <= N; i++) {
    // 第二层遍历容量
    for (let j = 1; j <= W; j++) {
      // 3.推导公式
      // 在本例子中要注意dp[i][j]对应的是可取第i个物品的最大价值
      // 但是第i个物品的重量和价值对应的是w[i - 1]和v[i - 1]
      if (j >= w[i - 1]) {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i - 1]] + v[i - 1])
      } else {
        dp[i][j] = dp[i - 1][j]
      }
    }
  }
  return dp[N][W]
}
复制代码


因为我设置的容量刚刚好是所有物品的重量之和,所以得出的结果为所有物品价值之和。

动态规划的解答实际是个填表的过程,如果能做出来就能通过DP查看到不同变量下不同的解,会发现挺有意思的。(PS:推不出递归公式的时候实际想哭!)


更改遍历顺序

刚才我们在遍历生成dp数组的时候,是先遍历物品再变量容量的,那么调换下顺序是否可行呢?


// ...
// 先遍历容量
for (let j = 1; j <= W; j++) {
  // 再遍历物品
  for (let i = 1; i <= N; i++) {
    if (j >= w[i - 1]) {
      dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i - 1]] + v[i - 1])
    } else {
      dp[i][j] = dp[i - 1][j]
    }
  }
}
// ...
复制代码

可以发现,其实答案是一样的,它们的区别只在于填表的顺序不同。方案一是先从左到右,方案二是先从上到下。


最佳方案


我们之前求的是最大价值,但是如果要输出最大价值的方案呢


function knapsack() {
  const N = 5;
  const W = 29;
  const w = [4, 5, 10, 11, 13];
  const v = [3, 4, 7, 8, 9];
  const dp = []
  // 使用trace变量来记录选择
  const trace = []
  for (let i = 0; i <= N; i++) {
    // 初始化二维trace
    trace[i] = []
    dp[i] = []
    dp[i][0] = 0
  }
  for (let j = 0; j <= W; j++) {
    dp[0][j] = 0
  }
  for (let i = 1; i <= N; i++) {
    for (let j = 1; j <= W; j++) {
      if (j >= w[i - 1]) {
        // 不同之处主要是在这里要记录trace
        // trace[i][j] === 1 表示当前最大价值方案选取了第i件物品
        if (dp[i - 1][j] >= dp[i - 1][j - w[i - 1]] + v[i - 1]) {
          trace[i][j] = 0
        } else {
          trace[i][j] = 1
        }
        dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i - 1]] + v[i - 1])
      } else {
        dp[i][j] = dp[i - 1][j]
        trace[i][j] = 0
      }
    }
  }
  // 通过trace推出选取的物品
  const ans = []
  let rest = W
  let i = N
  while(i > 0) {
    if (trace[i][rest] === 1) {
      rest -= w[i - 1]
      ans.push(i)
    }
    i--
  }
  return ans
}
复制代码


结语


动态规划中的01背包是非常经典的题型,大家好好理解学习一定可以掌握的。


参考



相关文章
动态规划之01背包问题和完全背包问题
动态规划之01背包问题和完全背包问题
|
算法 C语言 C++
【动态规划】背包问题(01背包,完全背包)
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
124 0
动态规划——01背包问题、完全背包问题
动态规划——01背包问题、完全背包问题
91 0
动态规划:01背包问题
动态规划:01背包问题
91 0
背包问题——01背包|完全背包 2
背包问题——01背包|完全背包
185 0
|
算法 决策智能
背包问题——01背包|完全背包 1
背包问题——01背包|完全背包
312 0
|
测试技术 容器
动态规划之背包问题——背包三讲(01背包,完全背包,多重背包)(三)
动态规划之背包问题——背包三讲(01背包,完全背包,多重背包)(三)
211 0
动态规划之背包问题——背包三讲(01背包,完全背包,多重背包)(三)