01背包问题-动态规划

1,088 阅读3分钟

前言

每次看到 背包问题 就很头疼,干脆把它总结下来。

题目描述

有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和? 比如:number = 4,capacity = 8

image.png 也就是 两个数组
w = [2,3,4,5]
v = [3,4,5,6]
两者之间是一一对应关系。比如第一个物品 体积为2,对应的价值为3.

动态规划总体思路

一个题目是否能够用动态规划来写取决于它能否将大问题拆分为小问题,是否满足最优性原理。如果满足那就 可以寻找大问题和小问题的递推关系。 动态规划找到关系之后在我看来就是一个填表的过程,在填表的过程中每一小格都是当前的最优解,这个最优解是通过前面的小问题推出来的,最后就能够找到大问题的最优解。

思路

为了让逻辑更加清晰,定义一些变量:
v(i): 代表第 i 个物品的价值
w(i): 代表第 i 个物品的体积
dp(i,j): j 代表 当前的 背包容量(我们就是通过不断改变背包容量和物品个数来找到最优解),dp(i,j)就代表前 i 个物品的最大价值
现在我们需要找到递推关系。 可以假设 我们已知 dp(i,j)(前i个物品)的最优解,那么关键就在这里,如何推出小问题的递推公式。可以想到的是当增加一个物品,要么是放进去,要么不放进去。如果不放进去可以得出 dp(i,j) = dp(i - 1,j), 也就是说不放那么当前最优解就是上一个小问题的最优解。
如果放进去 那么 dp(i,j) = dp(i-1,j-w[i]) +v[i], 不放的话,我们可以推理放当前物品之前的价值 肯定是 dp(i-1,j-w[i]), 这里的理解比较关键。 如果放了那就变成了 dp(i-1,j-w[i]) +v[i]. 因此递推关系:
dp(i,j) = max(dp(i-1,j),dp(i-1,j-w[i])+v[i]) 当增加一个物品时,如果放了的话,其实它有可能会拿掉前面的某些物品,可能价值还更低,所以 我们取两者中较大的一个。

代码

function knapsack(W,N,weight,value) {
    let dp = new Array(N+1).fill(0).map(res => new Array(W+1).fill(0))
    for(let i = 1; i <= N; i++) {
    let w = weight[i - 1]
      let v = value[i-1]
      for(let j = 0; j <= W; j++) {
        if(w<=j) {
          dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w]+ v)
        }
        else {
          dp[i][j] = dp[i-1][j]
        }
      }
    }
    return dp[N][W]
  }
  let weight = [2,3,4,5]
  let value  = [3,4,5,6]
  console.log(knapsack(8,4,weight,value))

打印这个数组我们可以看到填进去的表格 image.png

代码优化

从上述代码我们其实可以看到前 i 个物品的最优解 只与 前 i-1 个有关,所以我们其实可以用一维数组来解决问题,每次只要覆盖一维数组的值便可以。但是在这里需要注意的是我们必须从后往前遍历,因为一维数组后面的值是需要上一次前面的值进行推理,如果最开始就是改变前面的了,那么代码就会出现问题。
代码如下:

function k1(W,N,weight,value) {
  let dp = new Array(W + 1).fill(0)
    for (let i = 1; i <=N; i++) {
      let w = weight[i - 1]
      let v = value[i-1]
      for(let j = W; j >=0; j--) {
        if(w<=j)dp[j] = Math.max(dp[j],dp[j-w] + v)
      }
    }
    return dp[W]
 }
  let weight = [2,3,4,5]
  let value  = [3,4,5,6]
  console.log(k1(8,4,weight,value))

同样的,打印数组

image.png

总结

动态规划的关键还是 如何通过 大问题 推出 小问题,而不是通过小问题推出大问题。 本人还是动态规划的小白,如有写得不正确的的地方还请多多谅解🙃!