1、01背包问题介绍算法
背包问题是经典的动态规划问题之一;优化
常见的01背包问题就是说有一堆物品,如今要装入一个容器中,这些物品的重量和价值各不一致,而容器的重量又是有限的,没种物品只能装1个或者不装(0个),求当重量限定为w时,这些物品能装进去组合成的最高价值是多少?spa
分析:咱们首先将物品排成一排(随机),依次标记为1号,2号。。。。而后从一号开始依次往里放,放的时候判断当前物品是否是应该放进去:code
若是当前物品放进去以后的最高总价值 比 不放进去的最高总价值 大 ,那么就是要放入,而后总价值取放入以后的blog
反之不放入,总价值依然取以前的值。get
而且,在对同一个物品判断时,从0依次增长容器的容量,直到上限wtable
当前物品放进去以后的最高总价值 = 上一号物品判断时(容量 = 当前容量 - 当前物品重量 时)总价值 + 当前物品的价值class
不放进去的最高总价值 = 上一号物品判断的时候(容量 = 当前容量 时)总价值容器
因此有伪代码以下:遍历
if (当前物品重量 > 当前容量) { 此时最高总价值 = 当前物品不放进去的最高总价值; // 此时物品重量比容量大,放不进去,只能取放不进去的状况 } else { 此时最高总价值 = max(当前物品不放进去的最高总价值, 当前物品放进去的最高总价值); }
所以,咱们能够用一个状态表来对整个过程进行描述。
假设如今是有三个物品 (不是三种)一号、二号、三号,重量分别为3,2,5; 价值分别为7,4,8; 当前容器容量为8,求最大价值。
首先,第一行,为没有任何物品的时候,所有置0;
第二行,判断一号物品能不能放:
当容量扩充为3的时候,一号物品终于能放,价值为7,因为当前也只有一号,因此后面都是7;
第三行,判断二号物品能不能放:
当容量扩充为2的时候,二号物品终于能放,价值为4,与不放进去的最高价值(一号物品在此容量时的值:0)进行比较,因此成功放进去;
当容量扩充为3的时候,二号物品必定放时,价值为4,与不放进去的最高价值(一号物品在此容量时的值:7)进行比较,决定不放进去了;
。。。
当容量扩充为5的时候,二号物品必定放时,最高价值为(一号物品在(当前容量 - 二号物品的重量)时的价值+二号物品的价值)
=(一号物品在容量为(5-2)的时候 + 二号物品的价值)= 7 + 4 =11
。。。【以下表】
容量 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
无 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
一号 | 0 | 0 | 0 | 7 | 7 | 7 | 7 | 7 | 7 |
二号 | 0 | 0 | 4 | 7 | 7 | 11 | 11 | 11 | 11 |
三号 | 0 | 0 | 4 | 7 | 7 | 11 | 11 | 12 | 15 |
因此有代码以下:
public static int getMaxValue(int[] weight, int[] value, int w) { int n = weight.length; int[][] table = new int[n + 1][w + 1]; for (int i = 1; i <= n; i++) { // 物品遍历(第0行确定全是0,因此不用遍历) for (int j = 0; j <= w; j++) { // 背包大小递增 if (weight[i-1] > j) { // 当前物品i的重量比背包容量j大,装不下,只能是不装 table[i][j] = table[i - 1][j]; } else { // 装得下,Max{不装物品i, 装物品i} table[i][j] = Math.max(table[i - 1][j], table[i - 1][j - weight[i-1]] + value[i-1]); } } } return table[n][w]; }
提问:为何要【n+1】行?
——由于以后每个数都须要与上一行作比较,一号物品放入的时候也须要与没放入的时候作比较,因此空出一行做为“没有物品的时候”;
为何要【w+1】列?
——由于在容量递增时,第一个物品能放的时候,须要与容量减去当前物品的重量,也就是容量为0的时候作比较,因此,也是须要一列做为“0容量的时候”;
为何是weight[i - 1] 和 value[i - 1]?
——由于传入的重量和价值必然是从一号物品开始,而咱们的表是从“没有物品”和“0容量”开始,多了一行和一列,因此 i 是当前物品的标号,而当前物品下标为【标号-1】;
2、空间复杂度优化
很显然,上面算法的空间复杂度为矩阵大小【n+1】*【w+1】。
n可能不大,可是实际应用伤的w可能会很大,这样形成了比较大的空间占用。
而仔细观察发现,矩阵中的值只与当前值的左上角的矩阵里的值有关,如图二号物品容量为4时的价值7,只与绿色标注值有关。
容量 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
无 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
一号 | 0 | 0 | 0 | 7 | 7 | 7 | 7 | 7 | 7 |
二号 | 0 | 0 | 4 | 7 | 7 | 11 | 11 | 11 | 11 |
三号 | 0 | 0 | 4 | 7 | 7 | 11 | 11 | 12 | 15 |
而且咱们是按行进行更新的,因此咱们用一个一维数据就能进行状态的更替,不过要注意更新的方向。
因此依赖关系为:下面依赖上面,右边依赖左边;
若是正常地从左到右边,那么右边面等待更新的值须要的依赖(左边)就会被覆盖掉,因此应该每行从右边开始更新。代码以下:
public static int getMaxValueByOne(int[] weight, int[] value, int w) { int n = weight.length; int[] table = new int[w + 1]; for (int i = 1; i <= n; i++) { for (int j = w; j >= 0; j--) { if (weight[i-1] <= j) { table[j] = Math.max(table[j], table[j - weight[i-1]] + value[i-1]); } } } return table[w]; }
如此一来,01背包的空间复杂度就降为了O(w)。