背包问题
概念
背包问题是一个经典的组合优化问题,其目标是在给定的一组物品中选择一些物品放入背包中,使得物品的总价值最大化,同时要求背包的总重量不超过背包的容量限制。
背包问题有两种常见的变体:完全背包和0-1背包。
鉴于完全背包计算过程相对0-1背包简单,这里先讲完全背包。
完全背包(无限背包)
在完全背包问题中,每个物品可以选择放入背包中的次数是无限的,即可以重复选择。每个物品有一个固定的重量和价值,背包有一个固定的容量限制。目标是选择一些物品放入背包中,使得物品的总价值最大化,同时背包的总重量不能超过容量限制。
完全背包问题可以使用贪心算法来解决。具体的算法思路是按照物品的单位重量价值从大到小进行排序,然后依次选择单位重量价值最大的物品放入背包中,直到背包的总重量达到容量限制或者所有物品都被选择完。
假设有一个背包的容量限制为10,现在有以下物品可供选择:
物品1:重量3,价值8
物品2:重量4,价值10
物品3:重量5,价值15
接下来我们用使用贪心算法解决这个问题。
1、定义背包容量capacity、物品的重量数组weights和物品的价值数组values。
2、创建一个一维数组dp,长度为capacity + 1,用于保存背包容量从0到capacity时的最大价值。
3、初始化dp[0]为0,表示背包容量为0时的最大价值为0。
4、对于每个背包容量i,从1到capacity,进行循环计算最大价值:
- 对于每个物品j,从0到n-1,进行循环:
-
- 如果物品的重量weights[j]小于等于当前背包容量i,则可以考虑放入该物品:
-
-
- 更新dp[i]为dp[i - weights[j]] + values[j]和dp[i]的较大值,表示选择放入物品j后的最大价值。
-
5、循环结束后,dp[capacity]即为背包容量为capacity时的最大价值。
6、返回dp[capacity]作为结果。
代码示例:
public class KnapsackProblem {
public static int knapsack(int capacity, int[] weights, int[] values) {
int n = weights.length;
int[] dp = new int[capacity + 1];
for (int i = 0; i <= capacity; i++) { // 遍历背包容量
for (int j = 0; j < n; j++) { // 遍历物品数组
if (weights[j] <= i) { // 如果物品的重量小于等于当前背包容量
dp[i] = Math.max(dp[i], dp[i - weights[j]] + values[j]); // 更新背包容量为i时的最大价值
}
}
}
return dp[capacity]; // 返回背包容量为capacity时的最大价值
}
public static void main(String[] args) {
int capacity = 10;
int[] weights = {3, 4, 5};
int[] values = {8, 10, 15};
int maxValue = knapsack(capacity, weights, values);
System.out.println("Maximum value: " + maxValue);
}
}
输出结果:
Maximum value: 30
0-1背包
在0-1背包问题中,每个物品要么完整地放入背包中,要么不放入,不能进行切割(即每个背包最多只能被选一次)。每个物品有一个固定的重量和价值,背包有一个固定的容量限制。目标是选择一些物品放入背包中,使得物品的总价值最大化,同时背包的总重量不能超过容量限制。
可以使用动态规划来解决0-1背包问题。具体的算法思路是创建一个二维数组dp,其中dp[i][j]表示在前i个物品中,背包容量为j时的最大价值。然后使用递推关系式计算dp数组的值,最终dp[n][W]就是问题的解,其中n为物品的数量,W为背包的容量。
同样假设有一个背包的容量限制为10,现在有以下物品可供选择:
物品1:重量3,价值8
物品2:重量4,价值10
物品3:重量5,价值15
我们的目标是选择一些物品放入背包中,使得物品的总价值最大化,同时背包的总重量不能超过10。
对于这个问题,我们可以使用动态规划来解决。
1、创建一个二维数组dp,大小为(物品数量+1)×(背包容量+1)。dp[i][j]表示在前i个物品中,背包容量为j时的最大价值。
2、初始化第一行和第一列为0,表示没有物品或者背包容量为0时,最大价值都为0。
3、使用递推关系式计算dp数组的值。对于每个物品i,考虑两种情况:
-
如果物品i的重量大于当前背包容量j,则无法放入背包中,此时dp[i][j]等于dp[i-1][j],即前i-1个物品在背包容量为j时的最大价值。
-
如果物品i的重量小于等于当前背包容量j,则可以选择放入背包中或者不放入。我们选择其中的最大值作为dp[i][j]的值:
-
- 如果选择放入物品i,则总价值为dp[i][j-w[i]] + v[i],其中w[i]为物品i的重量,v[i]为物品i的价值。(这里理解起来有点难)
-
- 如果选择不放入物品i,则总价值为dp[i-1][j],即前i-1个物品在背包容量为j时的最大价值。
4、最终,dp[n][W]就是问题的解,其中n为物品的数量,W为背包的容量。
代码示例:
public class KnapsackProblem {
public static int knapsack(int capacity, int[] weights, int[] values) {
int n = weights.length;
int[][] dp = new int[n + 1][capacity + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= capacity; j++) {
if (weights[i - 1] > j) {
// 当前物品的重量大于背包容量,无法放入背包
dp[i][j] = dp[i - 1][j];
} else {
// 可以选择放入或不放入当前物品,取最大值
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
}
}
}
return dp[n][capacity];
}
public static void main(String[] args) {
int capacity = 10;
int[] weights = {3, 4, 5};
int[] values = {8, 10, 15};
int maxValue = knapsack(capacity, weights, values);
System.out.println("Maximum value: " + maxValue);
}
}
输出结果:
Maximum value: 25