普通背包问题
不废话,直接上代码。
/**
* 动态规划,背包问题。
* 输入为:int n: 物品的种类数。 int[n] weight: 各件物品的重量。 int[n] value: 各种物品的价值。
* int W: 背包最大的装载重量。
* 输出:V[n][b]的值, 最大的装载价值。 x[n] 各类物品的装载数量。
*
* @author huangyongye
*
*/
public class Bag {
private int[][] V; // 备忘录,V[i][j] 表示只装前面的i种物品时,最大的装载价值
private int[][] I; // 备忘录,I[i][j] 表示最后一个装载的物品的类别。
/**
*
* @param weight 各类物品的重量
* @param value 各类物品的价值
* @param b 最大的载重
* @return
*/
public int maxValue(int[] weight, int[] value, int b) {
// 返回最大的装载价值
int n = weight.length; // 物品的类别数量
V = new int[n+1][b+1]; // 备忘录
I = new int[n+1][b+1];
/* 1.初始化 */
for(int i = 0; i < n+1; i++)
V[i][0] = 0;
for(int j = 0; j < b+1; j++)
V[0][j] = 0;
/* 2.分割子问题,从小到大递推解决 */
for(int i = 1; i < n+1; i++) {
for (int j = 1; j < b+1; j++) {
if(j < weight[i-1]) { // 如果能装的重量小于物品的单个重量
V[i][j] = V[i-1][j];
I[i][j] = I[i-1][j];
}
else {
// 递推方程:V[i][j] = (/*不装i*/V[i-1][j] > /*装i*/V[i][j-weight[i-1]] + value[i-1]) ?
// V[i-1][j] : V[i][j-weight[i-1]] + value[i-1];
if(V[i-1][j] > V[i][j-weight[i-1]] + value[i-1]) {
// 如果不装的话
V[i][j] = V[i-1][j];
I[i][j] = V[i-1][j];
}
else {
// 否则装
V[i][j] = V[i][j-weight[i-1]] + value[i-1];
I[i][j] = i-1;
}
}
}
}
int[] result = labels(I, weight, b);
/* 3.构造解,输出选择结果 */
for(int i: result) {
System.out.print(i + " ");
}
return V[n][b];
}
/**
* 返回装入的物品数量,构造解
* @param I
* @return
*/
public int[] labels(int[][] I, int[] weight, int b) {
int n = weight.length; // 物品的数量
// 初始化
int[] result = new int[n];
for(int i = 0; i < n; i++) {
result[i] = 0;
}
int y = b; // 总重量
while(I[n][y] != 0) {
int j = I[n][y]; // 最后放入的物品
while(I[n][y] == j) {
y = y - weight[j];
result[j]++;
}
}
return result;
}
public static void main(String[] args) {
int[] weight = {2,3,4,7};
int[] value = {1,3,5,9};
int b = 10;
Bag bt = new Bag();
int result = bt.maxValue(weight, value, b);
System.out.println("\nmax value is "+ result);
}
}
0-1背包问题
和普通背包问题相比,0-1背包问题多了一个限制, 就是某类物品只有一个,所以只能放置一遍。所以只需要对递推方程做简单修改就没问题了。具体说明见代码。
/**
* 动态规划:0-1背包问题
* 输入:int[n] weight 物品的重量
* int[n] value 物品的价值
* int b 物品最大的载重量
* 输出:boolean[n] opt 选择或者不选择该物品。
*
* 思路:和普通的背包问题相比,这里的每类物品只有一件。所以递推函数需要做相应的调整。
* V[i][j] = max{装: V[i-1][j], 不装:V[i-1][j-weight[i]] +value[i]}
* 所以背包问题和0-1背包问题只有一点点区别,就是递推方程的第二项的下标是i-1而不是i.
*
* @author huangyongye
*
*/
public class Bag01 {
/**
* @param weight 物品的重量
* @param value 物品的价值
* @param b 背包载重量
* @return maxValue 最大装载价值
*/
public static int maxV(int[] weight, int[] value, int b) {
int maxValue = 0;
int n = weight.length; // 物品类别数
int[][] V = new int[n+1][b+1]; //V[i][j] 表示只装载前i种物品,且重量不超过j时最大装载价值。
int[][] I = new int[n+1][b+1]; //I[i][j] 表示只装载前i种物品,且重量不超过j时达到最大装载价值最后装入的物品。
/* 1.初始化 */
for(int i = 0; i <= n; i++)
V[i][0] = 0;
for(int j = 0; j <= b; j++)
V[0][j] = 0;
/* 2.分割子问题,从小到大,两重循环遍历了所有的可能情况。 */
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= b; j++) {
if(j < weight[i-1]) { // 如果当前载重放不下第i(下标为i-1)个物品,则继续(增加重量)
I[i][j] = I[i-1][j];
continue;
}
else if(V[i-1][j] > V[i-1][j-weight[i-1]] + value[i-1]) { // 不装第i个物品
V[i][j] = V[i-1][j];
I[i][j] = I[i-1][j];
}
else { // 装第i个物品
V[i][j] = V[i-1][j-weight[i-1]] + value[i-1];
I[i][j] = i-1;
}
}
}
maxValue = V[n][b];
// 记录物品是否被选中
boolean[] opt = new boolean[n];
opt = labels(I, weight, b);
/* 输出选择结果 */
for(boolean i: opt) {
System.out.print(i + " ");
}
return maxValue;
}
/**
* 记录物品选择的结果
* @param i
* @param weight
* @param b
* @return
*/
private static boolean[] labels(int[][] I, int[] weight, int b) {
/* 1.初始化*/
int n = weight.length;
boolean[] opt = new boolean[n];
for(int i = 0; i < n; i++) {
opt[i] = false;
}
int y = b;
/* 2.回退找解 */
while(I[n][y] != 0) {
int j = I[n][y];
opt[j] = true;
y = y - weight[j];
}
return opt;
}
public static void main(String[] args) {
int[] weight = {8,6,4,3};
int[] value = {12,11,9,8};
int b = 13;
int maxValue = maxV(weight, value, b);
System.out.println("\nthe maxValue is " + maxValue);
}
}
复杂度分析
上面的两个问题都很容易分析。
第一部分:分割子问题中使用两重循环,内部运算都是常数时间复杂度,所以总的时间复杂度是O(n2)。
第二部分:构造解,每次回退至少一步求解一个装入的物品,所以复杂度为O(b),算上初始化的时间复杂度为O(n)。
所以主要时间复杂度为第一部分,即O(n2)。而空间复杂度,主要是保存了备忘录,二维数组V和I,复杂度为O(nb).