lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

1.  问题

分组的背包问题是一种扩展的背包问题,它的特点是将物品分为若干组,每组中的物品互相冲突,最多只能选择一件。给定一个背包的容量和每组物品的重量和价值,求如何选择物品使得背包内的总价值最大

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

2.  算法

这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有:

f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于组k}

使用一维数组的伪代码如下:

for 所有的组k
    for v=V..0
        for 所有的i属于组k
            f[v]=max{f[v],f[v-c[i]]+w[i]}

注意这里的三层循环的顺序,甚至在本文的第一个beta版中我自己都写错了。“for v=V..0”这一层循环必须在“for 所有的i属于组k”之外。这样才能保证每一组内的物品最多只有一个会被添加到背包中。

另外,显然可以对每组内的物品应用P02中“一个简单有效的优化”。

 

分组的背包问题可以用动态规划来解决。我们可以定义一个二维数组dp [i] [j],表示前i组物品在容量为j的背包下能获得的最大价值。那么对于第i组物品,我们有两种选择:

  • 不选这一组物品,那么dp [i] [j] = dp [i-1] [j];
  • 选这一组物品中的某一件,假设是第k件,那么dp [i] [j] = dp [i-1] [j-w [k]] + v [k],其中w [k]和v [k]分别表示第k件物品的重量和价值。

因此,我们可以得到状态转移方程:

dp[i][j]=max{dp[i1][j],kgroup[i]max(dp[i1][jwk]+vk)}

其中group [i]表示第i组物品中所有物品的编号集合。

 

3. 具体实现

3.1  二维实现

public class GroupKnapsack {
    public static int groupKnapsack(int capacity, int[][] items) {
        // items[i][0]表示第i个物品的重量
        // items[i][1]表示第i个物品的价值
        // items[i][2]表示第i个物品所属的组号
        int n = items.length; // 物品总数
        int m = 0; // 组数
        for (int i = 0; i < n; i++) {
            m = Math.max(m, items[i][2]); // 找出最大的组号
        }
        int[][] dp = new int[m + 1][capacity + 1]; // 创建动态规划数组
        for (int i = 1; i <= m; i++) { // 遍历每一组
            for (int j = 0; j <= capacity; j++) { // 遍历每一种容量
                dp[i][j] = dp[i - 1][j]; // 先假设不选这一组物品
                for (int k = 0; k < n; k++) { // 遍历每一个物品
                    if (items[k][2] == i && j >= items[k][0]) { // 如果物品属于这一组且容量足够
                        dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - items[k][0]] + items[k][1]); // 更新最大价值
                    }
                }
            }
        }
        return dp[m][capacity]; // 返回最终结果
    }

    public static void main(String[] args) {
        int capacity = 10; // 背包容量
        int[][] items = {{2, 1, 1}, {3, 3, 1}, {4, 8, 2}, {6, 9, 2}, {2, 8, 3}, {3, 9, 3}}; // 物品信息
        System.out.println(groupKnapsack(capacity, items)); // 输出最大价值
    }
}

 

3.2  一维实现

public class GroupKnapsack {
    public static int groupKnapsack(int capacity, int[][] items) {
        // items[i][0]表示第i个物品的重量
        // items[i][1]表示第i个物品的价值
        // items[i][2]表示第i个物品所属的组号
        int n = items.length; // 物品总数
        int m = 0; // 组数
        for (int i = 0; i < n; i++) {
            m = Math.max(m, items[i][2]); // 找出最大的组号
        }
        int[] dp = new int[capacity + 1]; // 创建动态规划数组
        for (int i = 1; i <= m; i++) { // 遍历每一组
            for (int j = capacity; j >= 0; j--) { // 逆向遍历每一种容量
                for (int k = 0; k < n; k++) { // 遍历每一个物品
                    if (items[k][2] == i && j >= items[k][0]) { // 如果物品属于这一组且容量足够
                        dp[j] = Math.max(dp[j], dp[j - items[k][0]] + items[k][1]); // 更新最大价值
                    }
                }
            }
        }
        return dp[capacity]; // 返回最终结果
    }

    public static void main(String[] args) {
        int capacity = 10; // 背包容量
        int[][] items = {{2, 1, 1}, {3, 3, 1}, {4, 8, 2}, {6, 9, 2}, {2, 8, 3}, {3, 9, 3}}; // 物品信息
        System.out.println(groupKnapsack(capacity, items)); // 输出最大价值
    }
}

自有实现及带测试案例:

public class GroupKnapsack {

public static void main(String[] args) {
int capacity = 10; // 背包容量
// items[i][0]表示第i个物品的重量
// items[i][1]表示第i个物品的价值
// items[i][2]表示第i个物品所属的组号
int[][] items = {{2, 1, 1}, {3, 3, 1}, {4, 8, 2}, {6, 9, 2}, {2, 8, 3}, {3, 9, 3}}; // 物品信息
System.out.println(dp(capacity, items));
System.out.println(dp2(capacity, items));
}

public static int dp(int capacity, int[][] items) {
int n = items.length;
int m = 0;
for (int i = 0; i < n; i++) {
m = Math.max(m, items[i][2]);
}

int[][] dp = new int[m + 1][capacity + 1];

for (int i = 1; i <= m; i++) {
for (int j = 0; j <= capacity; j++) {
dp[i][j] = dp[i - 1][j];
for (int k = 0; k < n; k++) {
if (items[k][2] == i && j >= items[k][0]) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - items[k][0]] + items[k][1]);
}
}
}
}


return dp[m][capacity];

}

public static int dp2(int capacity, int[][] items) {

int n = items.length;
int m = 0;
for (int i = 0; i < items.length; i++) {
m = Math.max(m, items[i][2]);
}

int[] dp = new int[capacity + 1];
for (int i = 1; i <= m; i++) {
for (int j = capacity; j >= 0; j--) {
for (int k = 0; k < n; k++) {
if (items[k][2] == i && j >=items[k][0]) {
dp[j] = Math.max(dp[j], dp[j - items[k][0]] + items[k][1]);
}
}
}
}

return dp[capacity];

}
}

运行:

20
20

4. 小结

分组的背包问题将彼此互斥的若干物品称为一个组,这建立了一个很好的模型。不少背包问题的变形都可以转化为分组的背包问题(例如P07),由分组的背包问题进一步可定义“泛化物品”的概念,十分有利于解题。

 

5. 参考资料

  • C++ 算法篇 动态规划----背包之六 分组背包 - CSDN博客 https://blog.csdn.net/weixin_43736974/article/details/108606665
  • 动态规划之背包问题系列 - 知乎 https://zhuanlan.zhihu.com/p/93857890
  • 背包问题算法全解析:动态规划和贪心算法详解 - 知乎 https://zhuanlan.zhihu.com/p/625429755
posted on 2023-07-01 21:18  白露~  阅读(87)  评论(0编辑  收藏  举报