首先给出01背包问题定义:

设有一背包,容量为V, 有N个物体, 每个物体体积V[i], 价值W[i],求该背包所能带走物体的最大价值。

这是一个动态规划题。解题思路是根据最后一个物体放还是不放,将问题递归分为两类。"一子天下动",这是很常见的思路。

方程:$F[N][V] = max{F[N-1][V], F[N-1][V-V[N]] + W[N]}$。

关键在于F[N][V]的含义,理解透彻后,问题迎刃而解。

下面给出编程实现,复杂度由高到低(为简单起见,设V[i] = W[i])。

方程是递归的,下面给出递归解法:

 1     public static int maxValue(int[] weight, int sum, int i, int limit ,int n){
 2         if (i < 0) {
 3             return sum;
 4         }
 5         int s1 = 0, s2 = 0;
 6         if (sum + weight[i] <= limit) {
 7             s1 = maxValue(weight, sum + weight[i], i - 1, limit, n);
 8         }
 9         s2 = maxValue(weight, sum, i - 1, limit, n);
10         if (s1 >= s2) {
11             return s1;
12         } else {
13             return s2;
14         }
15     }

递归算法复杂度$O(2^N)$, 太高了。怎么降低呢?问题空间大小不过V*N,我们对问题空间进行迭代求解,自然就得出答案。

这时时间复杂度降为O(N*V), 空间复杂度也是为O(N*V)。

 1 public static int maxValueNonRecursion(int[] weight, int limit){
 2         int n = weight.length;
 3         int[][] result = new int[n][limit+1];
 4         for (int i = 0; i < n; i++){
 5             for (int j = 0; j < limit+1; j++) {
 6                 if (i == 0){
 7                     if (j >= weight[i]){
 8                         result[i][j] = weight[i];
 9                     }
10                 }else {
11                     if (j < weight[i]) {
12                         result[i][j] = result[i - 1][j];
13                     } else {
14                         result[i][j] = Math.max(result[i - 1][j], result[i - 1][j - weight[i]] + weight[i]);
15                     }
16                 }
17             }
18         }
19         return result[n-1][limit];
20     }

时间复杂度已降到极限(还可进行细节优化,不过数量级不变),空间复杂度还可降低,因为只保存前后一层的结果,空间复杂度可降至O(V)。为什么不是O(N)? 深入理解迭代过程后,自然就明白了。

注意第二层循环为什么是逆序,这是故意的。因为不逆序,改变新值时,老值也会变化。而逆序后,则完美的解决了这一问题。

 1     public static int maxValueNonRecursionSaveSpace(int[] weight, int limit){
 2         int n = weight.length;
 3         int[] result = new int[limit+1];
 4         for (int i = 0; i < n; i++) {
 5             for (int j = limit; j >= 0; j--) {
 6                 if (j >= weight[i]){
 7                     result[j] = Math.max(result[j], result[j-weight[i]] + weight[i]);
 8                 }
 9             }
10         }
11         return result[limit];
12     }

这时,时间复杂度O(V*N), 空间复杂度O(V)。二者都降无可降,这是可以接受的。

 

01背包应用举例:

将数组元素分成两堆,使它们元素和尽可能相等,可化为01背包。