动态规划 - 完全背包

124 阅读3分钟

4月日新计划更文活动 第14天

前言

动态规划专题,从简到难通关动态规划。

什么是完全背包

完全背包是一种经典的动态规划问题,它是背包问题的一种变形。在完全背包问题中,有一个容量为C的背包和n种物品,第i种物品有一个重量为wi和一个价值为vi。每种物品可以无限次地选择放入背包中,问背包能够容纳的最大价值是多少?

01背包和完全背包问题都是经典的动态规划问题,它们都是背包问题的变体。它们的主要区别在于每种物品选择次数上的限制不同。

假设背包最⼤重量为4。

物品编号重量价值
物品0115
物品1320
物品2430

01背包的核心递推公式为:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

在01背包中,对于一个物品只有选择或者不选择,所以可以根据这两个状态推断出选择方式,

在完全背包中,每种物品可以选择放入背包的次数不受限制,因此将物品1放入背包中的次数增加了,从而得到了不同于01背包问题的解。在此问题中,放入物品0、1、2可以得到背包最大价值为60。

对于完全背包来说,我们就可以去计算每一个物品的价值期望,比如说物品0的价值期望为 15/1 等于15,而物品1的价值期望为 20/3, 物品2为 30/4

那么我们还是按照动态规划五部曲来解决一下这个入门问题

  1. 定义dp数组

dp[i][j]表示当背包容量为j,前i个物品可以装入背包时的最大总价值。

  1. 推导递推公式

在状态转移时,我们需要考虑两种情况:

(1)当前不将第i个物品装入背包中,则dp[i][j] = dp[i-1][j]。 (2)当前将第i个物品装入背包中,则dp[i][j] = dp[i][j-weight[i]] + value[i];

考虑上述两种情况的转移方程,可以列出如下的状态转移方程:

if (j >= weight[i - 1]) { 
	dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i - 1]] + value[i - 1]); 
} else { 
	dp[i][j] = dp[i - 1][j]; 
}
  1. 初始化数组

dp数组中初始值都是0,因为当背包容量为0或者不装入物品时,背包的总价值必须为0。

  1. 确定遍历顺序

在遍历dp数组时,i表示物品可以选择的数量,j表示背包容量,我们需要先枚举物品,再枚举背包容量。这样才能保证在求解物品i时,之前物品的状态已经全部被更新保存在dp数组中。

function testCompletePack(bagWeight, weight, value) {
  let dp = new Array(weight.length + 1).fill().map(() => new Array(bagWeight + 1).fill(0)); 
  for (let i = 1; i <= weight.length; i++) {
    for (let j = 1; j <= bagWeight; j++) { 
      if (j >= weight[i - 1]) {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i - 1]] + value[i - 1]); 
      } else { 
        dp[i][j] = dp[i - 1][j];
      }
    }
  }
  console.log(dp[weight.length][bagWeight]);
}

testCompletePack(4, [1, 3, 4], [15, 20, 30]); // 60