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

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

使用动态规划进行求解:

子问题定义状态:

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

状态转移方程:

d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − c i ] + w i ) 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)

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

d p [ i − 1 ] [ j − c i ] + w i dp[i-1][j-c_i]+w_i dp[i1][jci]+wi 表示:第 i i i个物品放入背包时的最大价值(即,将前 i − 1 i-1 i1 个物品放入容量为 j − c i j-c_i jci的背包中的最大价值 + w i w_i wi)。

最终需要的答案为 d p [ 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];
}

优化方案:
上述解决方案使用的是一个二维数组 d p [ ] [ ] dp[][] dp[][],每一行的值与其上一行数据相关,(即,前 i i i 个物品产生的最大价值,与前 i − 1 i-1 i1 个物品产生的最大值相关),我们完全可以只用一行数据,来表示每个不同容量带来的最大价值,(即使用 d p [ j ] dp[j] dp[j]来表示背包容量为 j j j 时带来的最大价值。)而省去表示前 i i i 件物品的空间。通过不断更新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) 处,其是一个从 v v v -> c [ i − 1 ] c[i-1] c[i1] 的过程,原因在于:更新值时,依赖的总是 i − 1 i-1 i1 时的数据。例如,如果现在循环中, i = 3 i=3 i=3,此时 第二重循环还未开始,那 d p [ ] dp[] dp[] 数组中的数据保存的是 : i = 2 i=2 i=2 时,不同容量(从 0 0 0 -> v v v)背包的最大价值。而当第二重循环开始后, d p [ ] dp[] dp[] 中的值开始更新,从(2)中的代码可以看出, d p [ j ] dp[j] dp[j] 依赖于 d p [ j ] dp[j] dp[j] d p [ j − c [ i − 1 ] ] dp[j-c[i-1]] dp[jc[i1]] 处的值,而这两个值指的均是 i = 2 i = 2 i=2 时的值,若 j j j 值从 c [ i − 1 ] c[i-1] c[i1] -> v v v 更新, d p [ j − c [ i − 1 ] ] dp[j-c[i-1]] dp[jc[i1]] 必然会在 d p [ j ] dp[j] dp[j] 之前被更新,那么当计算 d p [ j ] dp[j] dp[j] 时, d p [ j − c [ i − 1 ] ] dp[j-c[i-1]] dp[jc[i1]] 表示的是 i = 3 i=3 i=3 时的值,而非 i = 2 i = 2 i=2 ,这样的话,就会出现错误。因此 j j j 不能从 c [ i − 1 ] c[i-1] c[i1] -> v v v 更新,而应该从大到小更新。

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

你可能感兴趣的:(数据结构与算法)