动态规划中的背包问题是一类经典的优化问题,主要涉及到在给定的限制条件下(如背包容量),如何选择物品集合以达到某种最优目标(如价值最大)。这类问题通常可以细分为几种类型,包括0-1背包问题、完全背包问题、多重背包问题以及分数背包问题。下面是对这些类型的一个简要综述:
1. 0-1背包问题
这是最基础的背包问题形式。每种物品只有一件,对于每件物品,只能选择放或不放。状态转移方程可以表示为:(状态只能是二维的);
其中,𝑓[𝑖][v]表示前i件物品中选择一些装入容量为V的背包可以获得的最大价值;
𝑤[𝑖]是第i件物品的重量;c[𝑖]是第i件物品的价值。
说白了,如果装进入第i个物品与不装进物品的区别;这里要注意是从f[i-1][v-c[i]] 开始的;
案例描述:
假设你有一个背包,它的容量是10公斤。现在你面前有4件物品,每件物品有特定的重量和价值:
- 物品1:重量2公斤,价值3元
- 物品2:重量3公斤,价值4元
- 物品3:重量4公斤,价值5元
- 物品4:重量5公斤,价值8元
你的目标是在不超过背包容量的情况下,使背包中物品的总价值最大化。
计算过程:
我们从零开始填充一个动态规划表,其行表示物品数量(从0开始),列表示背包的容量(从0到10)。初始状态为𝑓[0][∗]=0f[0][∗]=0(不考虑任何物品的情况)。
接下来,逐步填充表格:
- 当前背包容量为0时,无论考虑哪件物品,价值都为0。
- 考虑物品1(重量2,价值3):
- 当背包容量小于2时,𝑓[𝑖][𝑗]=𝑓[𝑖−1][𝑗]f[i][j]=f[i−1][j]。
- 当背包容量大于等于2时,𝑓[𝑖][𝑗]=max(𝑓[𝑖−1][𝑗],𝑓[𝑖−1][𝑗−2]+3)f[i][j]=max(f[i−1][j],f[i−1][j−2]+3)。
- 以此类推,直到所有物品都被考虑。
最终,𝑓[4][10]f[4][10]的值就是所求的答案。
def knapsack(capacity, weights, values, n):
# 初始化二维数组
dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)]
# 填充dp数组
for i in range(1, n + 1):
for j in range(1, capacity + 1):
if weights[i-1] <= j:
# 当前重量可以装入背包
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i-1]] + values[i-1])
else:
# 当前重量超出背包容量,继承上一行的值
dp[i][j] = dp[i-1][j]
return dp[n][capacity]
# 物品的重量和价值
weights = [2, 3, 4, 5]
values = [3, 4, 5, 8]
n = len(weights)
capacity = 10
# 调用函数
max_value = knapsack(capacity, weights, values, n)
print("最大价值:", max_value)
该算法时间复杂度:O(VN)
2. 完全背包问题(状态是多维的)
与0-1背包不同,完全背包问题中的每种物品都有无限多件。因此,对于每种物品,可以选择任意数量放入背包。状态转移方程可以表示为:
但实际计算中,可以优化为:
注意这里与01背包问题的区别:
: 是包含之前i个(当前也包括)物品的,然而01背包问题是不包含的;
该算法时间复杂度:O(VN)
完全背包问题与0-1背包问题的主要区别在于,完全背包问题中的每种物品都可以无限次地选择。下面我们来看一个完全背包问题的案例及相应的Python代码实现。
案例描述:
假设你有一个背包,它的容量是10公斤。你有三种类型的物品,每种物品可以无限次地选择,它们的重量和价值如下:
- 物品1:重量2公斤,价值3元
- 物品2:重量3公斤,价值4元
- 物品3:重量5公斤,价值8元
你的目标是在不超过背包容量的情况下,使背包中物品的总价值最大化。
def complete_knapsack_forward(capacity, weights, values, n):
# 初始化二维数组
dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)]
# 遍历物品
for i in range(1, n + 1):
for j in range(1, capacity + 1):
# 从weights[i-1]开始,正向更新
if j >= weights[i-1]:
dp[i][j] = max(dp[i-1][j], dp[i][j - weights[i-1]] + values[i-1])
else:
dp[i][j] = dp[i-1][j]
return dp[n][capacity]
# 物品的重量和价值
weights = [2, 3, 5]
values = [3, 4, 8]
n = len(weights)
capacity = 10
# 调用函数
max_value = complete_knapsack_forward(capacity, weights, values, n)
print("最大价值:", max_value)
3. 多重背包问题
多重背包介于0-1背包和完全背包之间,每种物品有有限个(比如n_i个)。可以采用二进制拆分策略、预处理策略等方法解决。
4. 分数背包问题
分数背包问题中,物品可以分割,即可以选择物品的一部分。这通常可以通过贪心算法解决,根据单位重量价值排序后依次选择。
动态规划解法的核心思想
动态规划解决背包问题的关键在于定义状态和状态转移方程。状态一般定义为𝑓[𝑖][𝑗]f[i][j],表示在前i件物品中选择一些装入容量为j的背包可以获得的最大价值。状态转移方程描述了如何从前一个状态推导出当前状态。
时间复杂度和空间复杂度
- 对于0-1背包和完全背包问题,基本的时间复杂度和空间复杂度均为O(nW),其中n是物品数量,W是背包容量。
- 通过滚动数组等技巧,可以将空间复杂度降低至O(W)。
结论
背包问题是动态规划领域的重要案例,通过对不同类型的背包问题的学习,可以深刻理解动态规划的思想和方法。解决背包问题不仅需要数学抽象能力,还需要对算法效率的考虑,特别是在大数据量和高维度问题中。
ref:
https://www.cnblogs.com/laiyaling/p/14497647.html