动态规划——背包问题【01背包】

问题描述:

有N种物品和一个容量为V的背包,每种物品仅用一次。第i件物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。


例如:

N=5,V=10
         重量   价值
第一个物品: 10     5
第二个物品: 1     4
第三个物品: 2     3
第四个物品: 3     2
第五个物品: 4     1


首先我们考虑贪心策略,选取最大价值物品装入背包,得到的最大价值为:5。
但是正解为10(后四个物品)。所以我们直接来看背包算法。
定义dp[i][j]:选取i个物品,背包容量为j时,所能获得的最大价值。
由于一个物品只能拿一次或者不拿,那么我们分别分析拿还是不拿。
(1)j < w[i],这时背包容量不足以放下第i件物品,只能不拿dp[i][j] = dp[i-1][j]
(2)j > w[i],这时背包可以放下第i件物品,但是这时我们需要考虑拿这件物品是否会获得更大价值。

  • 如果拿,dp[ i ][ j ] = dp[ i - 1 ][ j - w[ i ] ] + v[ i ]。这里dp[i-1][j-w[i]]指的是因为我们现在要选取第i个物品,那么之前就肯定选取了i-1个物品;既然想让背包容纳这个物品,那么选取之前背包需要留有w[i]的位置。
  • 如果不拿,dp[ i ][ j ] = dp[ i - 1 ][ j ]

那么到底拿还是不拿,就需要比较哪个结果更大

这就是动态规划的思想:在求值的时候考虑之前的状态。综上所述,我们得到了
状态转移方程:
  if(j >= w[i])
    dp[ i ][ j ] = max( dp[ i - 1 ][ j ], dp[ i - 1][ j - w[ i ] ] + v[ i ] );
  else
    dp[ i ][ j ] = dp[ i - 1 ][ j ];


空间压缩

注意到每次求解dp[i][j]值用到了上一行(i-1)的值:dp[i-1][j] 和 dp[i-1][j-w[i]],考虑用dp[j]来表示之前dp[i][j]的状态。

  • 在第 i 次循环开始之前,dp[ j ] = dp[ i -1 ][ j ];(0 <= j <= V)
  • 在第 i 次循环结束之后,dp[ j ] = dp[ i ][ j ];(0 <= j <= V)

那么在计算第 i 次循环的dp值时,拿或者不拿的选择变为下列情况:

  • 如果放弃物品 i ,即dp[ i ][ j ] = dp[ i - 1][ j ],dp[ j ]保持不变即可
  • 如果选取物品 i ,即dp[ i ][ j ] = dp[ i - 1][ j - w[ i ] ],很幸运,当前的dp[ j - w[ i ] ]就是dp[ i - 1][ j - w[ i ] ]

至此,我们得到了空间压缩后的状态转移方程:
 for(int i = 0; i < N; i++){
  for(int j = V; j >= w[ i ]; j—){
   dp[ j ] = max(dp[ j ], dp[ j - w[ i ] ] + v[ i ]);
  }
 }

注意:外层循环为第 i 件物品,内层循环为背包价值,在压缩空间之后,内层循环的顺序不可更改,必须为降序,这是因为,由于将空间压缩成一维数组,所以只能保存某一种时刻的状态,但是我们的dp方程,需要利用上一状态的值,也就是,dp[ j - w[ i ] ] 这句,并且,很显然,用到位置是容量 < 当前容量( j )的值,也就是说,我们更改dp[ j ]的值时候,必须保证,j 之前的数值为上个状态的值,不能优先于 j 更新。所以必须按照从大到小的次序更新。

发布了104 篇原创文章 · 获赞 60 · 访问量 5871

猜你喜欢

转载自blog.csdn.net/Q_1849805767/article/details/103230949