01背包问题详解

01背包问题详解


1. 01背包

1.1 题目

有N件物品和一个容量为V的背包。放入第i件物品占用空间大小Ci,产生的价值是Wi。求解将哪些物品装入背包可使价值总和最大。


1.2 基本思路

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或者不放。
用子问题来定义状态:即F[i,j]表示前 i 件物品放入一个容量为 j 的背包可以获得的最大价值。那么其状态转移方程是:
F[i,j]=max{ F[i-1,j], F[i-1,j-Ci]+Wi}
这个方程的意思是,当选择前 i 件物品,放入容量为 j 的背包中的最大价值,只有 2 种情况,对应着第 i 件商品放或者不放入背包。
如果不放第 i 件商品,则等同于问题第 i-1 件商品放入背包为 j 的价值
如果放入第 i 件商品,则等同于问题第 i-1 件商品放入背包容量为 j-ci 的价值加上其本身的价值

所以基于这个状态方程,可以写成如下的代码:

//注:c[i]表示第i件物品占用的空间大小,w[i]表示第i件物品的价值

//首先定义二维价值数组F[i,j],对应着问题第i件物品放入容量为j的背包的价值
var F = [];
for (var i = 0; i < =N; i++) {
  F[i] = [];
}

//第0件物品,没有价值,将其赋值为0
for (var j = 0; j <= V; j++) {
  F[0][j] = 0;
}

//外循环i表示选择第i件物品
for (var i = 1; i <= N; i++) {
  //内循环,表示空间容量大小为j的包
  for (var j = c[i]; j <= V; j++) {
    F[i][j] = Math.max(F[i - 1][j], F[i - 1][j - c[i] + w[i]]); //状态方程
  }
}

1.3 优化空间复杂度

上面的方法时间和空间复杂度都为 O(VN)。时间复杂度不能在优化了。空间复杂度可以优化到 O(V)。
即用一个 V 大小的 F 数组存储,背包容量为 j 时,对应的最大价值。

现在的问题时如何消除 i 的空间,即当选择第 i 件商品时,内循环结束,能够确定F[j]就是原来的F[i][j]
即:

//将
F[i][j] = Math.max(F[i - 1][j], F[i - 1][j - c[i] + w[i]]); //状态方程(1)
//转为
F[j] = Math.max(F[j], F[j - c[i] + w[i]]); //转换方程(2)

内循环的意义是,对于取 i 个物品,背包容量从 0 到 V 能获得的最大价值。
由于状态方程(1)中,第 i 次循环,只用到 i-1 时的 F 结果,那么,去掉 i 是合理的,因为在第 i 次循环时,F 数组已经是 i-1 的时候的结果。
现在需要关注的是:F[j] = Math.max(F[j], F[j - c[i] + w[i]]) 这个数组,在 j 从 c[i]到 V 的过程中,我们要确保等式右边 F 数组,一定得是上一轮计算的值。如果循环变量 j 从c[i]到 V 的顺序递增,那么F[j-c[i]]用到的肯定是这轮计算后的 F 的值,这样就不对。所以需要将循环顺序从 V 到 c[i]的顺序递减,此时就能确保每次使用的都是上一轮的 F 数组。
即:

//外循环i表示选择第i件物品
for (var i = 1; i < =N; i++) {
  //内循环,表示空间容量大小为j的包
  for (var j = V; j >= c[i]; j--) {
    F[j] = Math.max(F[j], F[j - c[i] + w[i]]); //状态方程
  }
}

1.4 初始化细节问题

在求解背包问题时,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。
第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为 0,其他f[1...V]均为-Number.NEGATIVE_INFINITY
第二种问法,没有要求必须把背包装满,只是希望价格尽量大,初始化时应该将 F[0...V]全部设为 0.

原因是,恰好装满背包,只有背包容量为 0 的时候,才能确定是为 0,其他大小都不能确定价值。不要求装满背包,只是希望价格尽量大,那么初始值都可以是 0,表示不装,也是一种解。


1.5 一个常数优化

对于内层循环,可以将循环下限进行改进。从意义上理解,内循环 j 表示空间从c[i]到容量 V 的的 j 容量大小,即最少是背包大于 c[i]大小的物品才有意义,但是联系外循环放入第 i 个物品,即,i~N 之间的物品肯定不会放入。那么将这些物品的空间进行求和,然后用背包容量减去不放入的,则能得到一个界限 bound=V-sum{w[i]...w[n]},此时只要比较 bound 和 c[i]谁大,谁就是循环的下限。

//外循环i表示选择第i件物品
for (var i = 1; i <= N; i++) {
  //内循环,表示空间容量大小为j的包
    var bound=Math.max(V-sum(c[i]...c[n]),c[i]);
  for (var j = V; j >= bound; j--) {
    F[j] = Math.max(F[j], F[j - c[i] + w[i]]); //状态方程
  }
}

1.6 参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值