举个例子
要确定哪些物品被放入背包以达到最大价值,可以在计算 dp 数组的同时记录选择的物品。具体来说,可以使用一个额外的数组来记录每个状态的选择情况。以下是一个详细的步骤和代码实现:
n = 3
W = 5
weights = [2, 1, 3]
values = [6, 3, 5]
初始化 dp 和 choice 数组
dp = [[0] * (W + 1) for _ in range(n + 1)]
choice = [[0] * (W + 1) for _ in range(n + 1)]
print("初始状态:")
print("dp:")
for row in dp:
print(row)
print("choice:")
for row in choice:
print(row)
初始状态:
第1个物品(重量2,价值6)
# 第1个物品
for j in range(1, W + 1):
if j >= weights[0]:
if dp[0][j] < dp[0][j - weights[0]] + values[0]:
dp[1][j] = dp[0][j - weights[0]] + values[0]
choice[1][j] = 1
else:
dp[1][j] = dp[0][j]
else:
dp[1][j] = dp[0][j]
print("处理第1个物品后:")
print("dp:")
for row in dp:
print(row)
print("choice:")
for row in choice:
print(row)
第2个物品(重量1,价值3)
# 第2个物品
for j in range(1, W + 1):
if j >= weights[1]:
if dp[1][j] < dp[1][j - weights[1]] + values[1]:
dp[2][j] = dp[1][j - weights[1]] + values[1]
choice[2][j] = 1
else:
dp[2][j] = dp[1][j]
else:
dp[2][j] = dp[1][j]
print("处理第2个物品后:")
print("dp:")
for row in dp:
print(row)
print("choice:")
for row in choice:
print(row)
第3个物品(重量3,价值5)
for j in range(1, W + 1):
if j >= weights[2]:
if dp[2][j] < dp[2][j - weights[2]] + values[2]:
dp[3][j] = dp[2][j - weights[2]] + values[2]
choice[3][j] = 1
else:
dp[3][j] = dp[2][j]
else:
dp[3][j] = dp[2][j]
print("处理第3个物品后:")
print("dp:")
for row in dp:
print(row)
print("choice:")
for row in choice:
print(row)
回溯路径
# 回溯路径,确定选择的物品
selected_items = []
j = W
for i in range(n, 0, -1):
if choice[i][j] == 1:
selected_items.append(i - 1) # 物品编号从0开始
j -= weights[i-1]
# 返回最大价值和选择的物品
max_value = dp[n][W]
selected_items = selected_items[::-1]
print(f"最大价值: {max_value}")
print(f"选择的物品: {selected_items}")
最终,选择的物品是第2个(编号1)和第3个(编号2)物品,最大价值为11。
完整代码
def knapsack_with_items(n, W, weights, values):
# 初始化 dp 和 choice 数组
dp = [[0] * (W + 1) for _ in range(n + 1)]
choice = [[0] * (W + 1) for _ in range(n + 1)]
# 填充 dp 和 choice 数组
for i in range(1, n + 1):
for j in range(1, W + 1):
if j >= weights[i-1]:
if dp[i-1][j] < dp[i-1][j-weights[i-1]] + values[i-1]:
dp[i][j] = dp[i-1][j-weights[i-1]] + values[i-1]
choice[i][j] = 1
else:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = dp[i-1][j]
# 回溯路径,确定选择的物品
selected_items = []
j = W
for i in range(n, 0, -1):
if choice[i][j] == 1:
selected_items.append(i - 1) # 物品编号从0开始
j -= weights[i-1]
# 返回最大价值和选择的物品
return dp[n][W], selected_items[::-1]
# 示例
n = 3
W = 5
weights = [2, 1, 3]
values = [6, 3, 5]
max_value, selected_items = knapsack_with_items(n, W, weights, values)
print(f"最大价值: {max_value}")
print(f"选择的物品: {selected_items}")
一维数组的栗子
使用一维数组可以优化空间复杂度,将空间复杂度从O(nW) 降低到 O(W)。我们可以通过滚动数组的方式来实现这一点。
注意:从后往前遍历背包容量
如果背包容量j从前往后会出现什么问题呢?
n = 3
W = 5
weights = [2, 1, 3]
values = [6, 3, 5]
# 初始化 dp 数组
dp = [0] * (W + 1)
print("初始状态:")
print("dp:", dp)
# 第1个物品
for j in range(weights[0], W + 1):
if dp[j] < dp[j - weights[0]] + values[0]:
dp[j] = dp[j - weights[0]] + values[0]
print("处理第1个物品后:")
print("dp:", dp)
从前往后遍历背包容量 j 会导致同一个物品被多次选择,从而违反了01背包问题的约束。例如,在处理第1个物品时,当 j=4 时,dp[4] 被更新为 dp[2] + 6,而 dp[2] 已经被更新过,导致物品1被重复使用。
初始化
第1个物品(重量2,价值6)
for j in range(W, weights[0] - 1, -1):
if dp[j] < dp[j - weights[0]] + values[0]:
dp[j] = dp[j - weights[0]] + values[0]
choice[1][j] = 1
print("处理第1个物品后:")
print("dp:", dp)
print("choice:")
for row in choice:
print(row)
第2个物品(重量1,价值3)
第3个物品(重量3,价值5)
回溯路径
完整代码
def knapsack_with_items_one_dim(n, W, weights, values):
# 初始化 dp 数组
dp = [0] * (W + 1)
# 初始化 choice 数组,用于记录选择的物品
choice = [[0] * (W + 1) for _ in range(n + 1)]
# 填充 dp 和 choice 数组
for i in range(1, n + 1):
for j in range(W, weights[i-1] - 1, -1):
if dp[j] < dp[j - weights[i-1]] + values[i-1]:
dp[j] = dp[j - weights[i-1]] + values[i-1]
choice[i][j] = 1
# 回溯路径,确定选择的物品
selected_items = []
j = W
for i in range(n, 0, -1):
if choice[i][j] == 1:
selected_items.append(i - 1) # 物品编号从0开始
j -= weights[i-1]
# 返回最大价值和选择的物品
return dp[W], selected_items[::-1]
# 示例
n = 3
W = 5
weights = [2, 1, 3]
values = [6, 3, 5]
max_value, selected_items = knapsack_with_items_one_dim(n, W, weights, values)
print(f"最大价值: {max_value}")
print(f"选择的物品: {selected_items}")