01背包问题及优化(动态规划 java实现)

本文介绍了01背包问题,描述了如何利用动态规划求解最大价值,并提供了Java实现。通过状态转移方程分析,讨论了优化方案,即使用一维数组代替二维数组来减少空间开销。同时,强调了在优化过程中需要注意的更新顺序问题,避免计算错误。

问题描述:
现有一个容量为VVV的背包,以及nnn件物品,其分别占据的容量为cic_ici ,其分别带来的价值为wiw_iwi,(i=1,2,...,ni={1,2,...,n}i=1,2,...,n
要求:这nnn件物品只能放入背包0次或1次(即,不放入或者放入)
:背包的最大价值为多少?

使用动态规划进行求解:

子问题定义状态:

dp[i][j]dp[i][j]dp[i][j]表示 前iii件物品,放入容量为jjj的背包中,可以获得的最大价值。

状态转移方程:

dp[i][j]=max(dp[i−1][j],dp[i−1][j−ci]+wi)dp[i][j] = max(dp[i-1][j],dp[i-1][j-c_i]+w_i)dp[i][j]=max(dp[i1][j],dp[i1][jci]+wi)

其中 dp[i−1][j]dp[i-1][j]dp[i1][j] 表示:第iii个物品不放入背包时的最大价值(即,将前 i−1i-1i1 个物品放入容量为jjj的背包中的最大价值)。

dp[i−1][j−ci]+widp[i-1][j-c_i]+w_idp[i1][jci]+wi 表示:第iii个物品放入背包时的最大价值(即,将前 i−1i-1i1 个物品放入容量为 j−cij-c_ijci的背包中的最大价值 + wiw_iwi)。

最终需要的答案为dp[n][V]dp[n][V]dp[n][V]

代码如下:

public static int knapsackProblem(int v, int[] c, int[] w){
      int n = c.length;
      int[][] dp = new int[n+1][v+1];
      // 背包容量为0时,价值为0
      for(int i = 0; i <= n; i++)
         dp[i][0] = 0;
      // 背包里不放任何物品时,其价值也为0
      for(int j = 0; j <= v; j++)
         dp[0][j] = 0;
      // 计算剩余每个dp[i][j]
      for(int i = 1; i <= n; i++){
         for(int j = c[i-1]; j <= v; j++){ // c[i-1]是因为c是从0开始的,i-1表示的为第i个,w[i-1]同理
            dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-c[i-1]]+w[i-1]); 
         }
      }
      return dp[n][v];
}

优化方案:
上述解决方案使用的是一个二维数组dp[][]dp[][]dp[][],每一行的值与其上一行数据相关,(即,前 iii 个物品产生的最大价值,与前 i−1i-1i1 个物品产生的最大值相关),我们完全可以只用一行数据,来表示每个不同容量带来的最大价值,(即使用dp[j]dp[j]dp[j]来表示背包容量为 jjj 时带来的最大价值。)而省去表示前 iii 件物品的空间。通过不断更新dp[j]来得到最终的结果。

代码如下

public static int knapsackProblemOptimized(int v, int[] c, int[] w){ 
      int n = c.length;
      int[] dp = new int[v+1];
      // 初始化dp,其表示前0个物品放入不同容量的背包,产生的最大价值均为0
      for(int i = 0; i <= v; i++)
         dp[i] = 0;
      // 更新dp值
      for(int i = 1; i <= n; i++)
         for(int j = v; j >= c[i-1]; j--) // (1)
            dp[j] = Math.max(dp[j], dp[j-c[i-1]]+w[i-1]); // (2)
      return dp[v];
} 

注意点
在代码 (1) 处,其是一个从 vvv -> c[i−1]c[i-1]c[i1] 的过程,原因在于:更新值时,依赖的总是 i−1i-1i1 时的数据。例如,如果现在循环中,i=3i=3i=3,此时 第二重循环还未开始,那 dp[]dp[]dp[] 数组中的数据保存的是 :i=2i=2i=2 时,不同容量(从000 -> vvv)背包的最大价值。而当第二重循环开始后,dp[]dp[]dp[] 中的值开始更新,从(2)中的代码可以看出,dp[j]dp[j]dp[j] 依赖于 dp[j]dp[j]dp[j]dp[j−c[i−1]]dp[j-c[i-1]]dp[jc[i1]] 处的值,而这两个值指的均是 i=2i = 2i=2 时的值,若 jjj 值从 c[i−1]c[i-1]c[i1] -> vvv 更新,dp[j−c[i−1]]dp[j-c[i-1]]dp[jc[i1]] 必然会在 dp[j]dp[j]dp[j] 之前被更新,那么当计算 dp[j]dp[j]dp[j] 时, dp[j−c[i−1]]dp[j-c[i-1]]dp[jc[i1]] 表示的是 i=3i=3i=3 时的值,而非 i=2i = 2i=2 ,这样的话,就会出现错误。因此 jjj 不能从 c[i−1]c[i-1]c[i1] -> vvv 更新,而应该从大到小更新。

实质上,使用一维数组来优化的过程,就是将二维数组按行拆分,每次更新保存一行数据,以此来降低存储开销。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值