什么是背包问题?
- 问题描述:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
- 如果限定每种物品只能选择0个或1个,则问题称为0-1背包问题。
- 如果限定物品最多只能选择j个(j = 0, 1, 2…),则问题称为有界背包问题。
- 如果不限定每种物品的数量,则问题称为无界背包问题。
使用动态规划解决0-1背包问题:
假如我们有以下三个物品,需要放入容量为4的背包里,使得总价格最大:
我们先使用一个数组items[ ]储存物品名字,数组w[ ]储存物品的重量,数组v[ ]储存物品的价格。然后根据物品个数itemsNum以及背包容量capacity定义一个二维数组:
String[] items = {"Guitar", "Speaker", "Computer"};
int[] w = {1, 4, 3};
int[] v = {1500, 3000, 2000};
int[][] table = new int[itemsNum + 1][capacity + 1];
其中table[ i ][ j ] 表示在前i个物品中能够装入容量为j的背包中的最大价格。
当i = 0时,物品为0,则最大价格也为0,所以将table[ i ][ 0 ]设置为0;
当j = 0时,背包容量为0,则最大价格也为0,所以将table[ 0 ][ j ]设置为0;
从i = 1开始遍历,求出背包容量从1到规定容量的最大价格,步骤如下:
- 当w[ i - 1 ] > j,即当前物品的重量超过了当前背包容量时,就直接使用上一个单元格的装入策略:
table[ i ][ j ] = table[ i - 1 ][ j ]。 - 当w[ i - 1 ] <= j 时, table[ i ][ j ] = max( table[ i - 1 ][ j ],v[ i - 1] + table[ i - 1 ][ j - w[ i - 1 ] ])。
其中table[ i - 1 ][ j ]表示上一个单元格的装入的最大值;
v[ i - 1]表示当前物品的价格;
table[ i - 1 ][ j - w[ i - 1 ] ]表示将i - 1个商品装入到容量为 j - w[ i - 1 ] 的背包中所能达到的最大价格。
遍历结束后,二维数组table的结果应如下图所示:
代码实现:
package DataStructure;
public class DynamicProgramDemo {
public static void main(String[] args) {
String[] items = {"Guitar", "Speaker", "Computer"};
int[] weight = {1, 4, 3};
int[] value = {1500, 3000, 2000};
Knapsack knapsack = new Knapsack(7, items, weight, value);
knapsack.theMaxValue();
/* output:
将Computer放入背包
将Speaker放入背包
*/
}
}
class Knapsack {
public int itemsNum; // 物品数量
public int capacity; // 背包容量
public String[] items; // 物品数组
int[] w; // 物品重量数组
int[] v; // 物品价值数组
public Knapsack() {
}
public Knapsack(int capacity, String[] items, int[] weight, int[] value) {
this.capacity = capacity;
this.items = items;
this.itemsNum = items.length;
this.w = weight;
this.v = value;
}
public void theMaxValue() {
// table二维数组,用于记录不同物品数量不同背包容量时的最大价值
int[][] table = new int[itemsNum + 1][capacity + 1];
// updateItem布尔类型二维数组,用于记录当背包容量不变,而物品数量增多时,最大价值是否更新
boolean[][] updateItem = new boolean[itemsNum + 1][capacity + 1];
for (int i = 1; i <= itemsNum; i++) {
for (int j = 1; j <= capacity; j++) {
// 如果新增物品的重量大于背包容量,使用新增之前的最大价值
if (w[i-1] > j) {
table[i][j] = table[i-1][j];
}else { // 否则,将新增后的最大价值与之前的最大价值进行比较,选其中大的值
// 注意到:新增后的最大值=新增物品的价值+之前的物品能够装入剩下的背包容量的最大价值
if (table[i-1][j] > v[i-1] + table[i-1][j - w[i-1]]) {
table[i][j] = table[i-1][j];
}else {
table[i][j] = v[i-1] + table[i-1][j - w[i-1]];
// 最大价值更新后,需要将updateItem的相应位置设置为true
updateItem[i][j] = true;
}
}
}
}
// 从后往前遍历,先定位到背包容量为capacity时的最大值处:
// 在j=capacity列从下往上(i--)找到updateItem值为true时,说明往背包加入物品items[i-1]后,
// 价值达到了最大,以后无论新增了多少可放入背包的物品,最大值都不再变化,背包里的物品已固定不变。
// 而又因为最大值 = v[i-1] + table[i-1][j - w[i-1]]
// 所以需要在j = j - w[i-1]列,查找达到最大值时所添加的物品
// 如此循环,直到 j <= 0 或 i <= 0
int j = capacity;
int i = itemsNum;
while (j > 0 && i > 0) {
if (updateItem[i][j]) {
System.out.println("将"+items[i-1]+"放入背包");
j -= w[i - 1];
}
i--;
}
}
}