背包问题详解(01背包,完全背包,多重背包,分组背包)

背包问题是组合优化问题中最经典的一类问题。它有多个变体,包括 0/1 背包、完全背包、多重背包和分组背包。下面详细讲解这几种背包问题及其解决方案。

1. 0/1 背包问题

问题描述

给定 n 个物品,每个物品有一个重量和一个价值,背包的最大容量为 W。每个物品只能选择放入背包或不放入背包,如何选择物品使得背包中的总价值最大化,并且不超过背包容量 W

动态规划解决方案
  • 定义状态

    • dp[i][w] 表示前 i 个物品中,总重量不超过 w 时的最大价值。
  • 状态转移方程

    • 如果第 i 个物品不放入背包:dp[i][w] = dp[i-1][w]
    • 如果第 i 个物品放入背包:dp[i][w] = dp[i-1][w - weight[i-1]] + value[i-1]
    • 综合状态转移方程:dp[i][w] = max(dp[i-1][w], dp[i-1][w - weight[i-1]] + value[i-1])
  • 初始化

    • dp[0][w] = 0,表示没有物品时,背包的最大价值为 0。
  • 计算

    • 使用嵌套循环填充 dp 数组,从 i=1n,从 w=0W
示例代码
function knapsack($weights, $values, $capacity) {
    $n = count($weights);
    $dp = array_fill(0, $n + 1, array_fill(0, $capacity + 1, 0));

    for ($i = 1; $i <= $n; $i++) {
        for ($w = 0; $w <= $capacity; $w++) {
            if ($weights[$i - 1] <= $w) {
                $dp[$i][$w] = max($dp[$i - 1][$w], $dp[$i - 1][$w - $weights[$i - 1]] + $values[$i - 1]);
            } else {
                $dp[$i][$w] = $dp[$i - 1][$w];
            }
        }
    }
    return $dp[$n][$capacity];
}

2. 完全背包问题

问题描述

在完全背包问题中,每个物品可以放入背包多次(即无限次)。如何选择物品使得背包中的总价值最大化,并且不超过背包容量 W

动态规划解决方案
  • 定义状态

    • dp[w] 表示背包容量为 w 时的最大价值。
  • 状态转移方程

    • 如果第 i 个物品可以放入背包:dp[w] = max(dp[w], dp[w - weight[i-1]] + value[i-1])
  • 初始化

    • dp[0] = 0,表示背包容量为 0 时的最大价值为 0。
  • 计算

    • 使用一层循环填充 dp 数组,从 w=0W
示例代码
function completeKnapsack($weights, $values, $capacity) {
    $n = count($weights);
    $dp = array_fill(0, $capacity + 1, 0);

    for ($w = 0; $w <= $capacity; $w++) {
        foreach ($weights as $i => $weight) {
            if ($weight <= $w) {
                $dp[$w] = max($dp[$w], $dp[$w - $weight] + $values[$i]);
            }
        }
    }
    return $dp[$capacity];
}

3. 多重背包问题

问题描述

每个物品有一个数量限制,即每个物品最多可以放入背包指定次数。如何选择物品使得背包中的总价值最大化,并且不超过背包容量 W

动态规划解决方案
  • 定义状态

    • dp[w] 表示背包容量为 w 时的最大价值。
  • 状态转移方程

    • 如果第 i 个物品可以放入背包:dp[w] = max(dp[w], dp[w - k * weight[i-1]] + k * value[i-1]),其中 k 为物品的数量限制。
  • 初始化

    • dp[0] = 0,表示背包容量为 0 时的最大价值为 0。
  • 计算

    • 使用一层循环填充 dp 数组,从 w=0W
示例代码
function multipleKnapsack($weights, $values, $counts, $capacity) {
    $n = count($weights);
    $dp = array_fill(0, $capacity + 1, 0);

    for ($i = 0; $i < $n; $i++) {
        $weight = $weights[$i];
        $value = $values[$i];
        $count = $counts[$i];
        
        for ($w = $capacity; $w >= $weight; $w--) {
            for ($k = 1; $k <= $count; $k++) {
                if ($w >= $k * $weight) {
                    $dp[$w] = max($dp[$w], $dp[$w - $k * $weight] + $k * $value);
                } else {
                    break;
                }
            }
        }
    }
    return $dp[$capacity];
}

4. 分组背包问题

问题描述

物品被分为若干组,每组的物品可以选择放入背包,但每组只能选择一个物品。如何选择物品使得背包中的总价值最大化,并且不超过背包容量 W

动态规划解决方案
  • 定义状态

    • dp[w] 表示背包容量为 w 时的最大价值。
  • 状态转移方程

    • 对于每一组物品,遍历选择其中一个物品,更新 dp 数组。
  • 初始化

    • dp[0] = 0,表示背包容量为 0 时的最大价值为 0。
  • 计算

    • 使用一层循环填充 dp 数组,从 w=0W
示例代码
function groupKnapsack($groups, $capacity) {
    $dp = array_fill(0, $capacity + 1, 0);

    foreach ($groups as $group) {
        $temp = $dp;
        foreach ($group as $item) {
            $weight = $item['weight'];
            $value = $item['value'];
            for ($w = $capacity; $w >= $weight; $w--) {
                $temp[$w] = max($temp[$w], $dp[$w - $weight] + $value);
            }
        }
        $dp = $temp;
    }
    return $dp[$capacity];
}

// Example usage:
$groups = [
    [['weight' => 2, 'value' => 3], ['weight' => 3, 'value' => 4]],
    [['weight' => 4, 'value' => 5], ['weight' => 5, 'value' => 6]],
    [['weight' => 1, 'value' => 2], ['weight' => 2, 'value' => 3]]
];
$capacity = 8;
echo groupKnapsack($groups, $capacity); // Output: 12

总结

背包问题有多种变体,每种变体有其独特的挑战和解决方案。通过理解这些变体的定义、状态转移方程和计算方法,你可以根据具体的需求选择合适的算法来优化解决方案。动态规划是解决这些问题的常用技术,通过分解问题和缓存中间结果来提高效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值