目录
[1. a.对于下列背包问题的实例,应用自底向上动态规划算法求解。](#1. a.对于下列背包问题的实例,应用自底向上动态规划算法求解。)
[b. a中的实例有多少个不同的最优子集?](#b. a中的实例有多少个不同的最优子集?)
c.一般来说,如何从动态规划算法所生成的表中判断出背包问题的实例是不是具有不止一个最优子集?
b.写一段伪代码,使得可以从背包问题的自底向上动态规划算法生成的表中求得最优子集的组成。
c.从一张填好的动态规划表中求得最优子集的组合所用的时间属于O(n)。
a.判断正误:背包问题实例的动态规划表中某一行值的序列总是非递减的。
b.判断正误:背包问题实例的动态规划表中某一列值的序列总是非递减的。
5.假设n种物品中每种物品的数量不限,为该背包问题设计一个动态规划算法并分析该算法的时间效率。
6.对第1题中给出的背包问题的实例应用记忆功能方法。在动态规划表中找出这样的单元格:(1)在这个实例中,从来没有被记忆功能方法计算过的单元格;(2)不需要重新计算就能使用的单元格。
7.证明背包问题的记忆功能算法的时间效率类型和自底向上算法是相同的(参见第3题)。
8.为什么根据公式C(n,k)=C(n-1,k-1)+C(n-1,k)计算二项式系数时,记忆功能法不是一个好方法?
1. a.对于下列背包问题的实例,应用自底向上动态规划算法求解。
|--------|
| 承重量W=6 |
|-----|-----|-------|
| 物 品 | 重 量 | 价值/美元 |
| 1 | 3 | 25 |
| 2 | 2 | 20 |
| 3 | 1 | 15 |
| 4 | 4 | 40 |
| 5 | 5 | 50 |
dp[i][j] = 前 i 个物品,背包容量 j 时的最大价值
递推公式
-
不放第 i 个物品:
dp[i][j] = dp[i-1][j] -
能放第 i 个物品:
dp[i][j] = max( dp[i-1][j], dp[i-1][j-w[i]] + v[i] )dp[0] = [ 0, 0, 0, 0, 0, 0, 0 ]
dp[1] = [ 0, 0, 0,25,25,25,25 ] //物品1 w=3,v=25
dp[2] = [ 0, 0,20,25,25,45,45 ] //物品2 w=2,v=20
dp[3] = [ 0,15,20,35,40,45,60 ] //物品3 w=1,v=15
dp[4] = [ 0,15,20,35,40,45,60 ] //物品4 w=4,v=40
dp[5] = [ 0,15,20,35,40,50,65 ] //物品5 w=5,v=50
b. a中的实例有多少个不同的最优子集?
仅有一个
c.一般来说,如何从动态规划算法所生成的表中判断出背包问题的实例是不是具有不止一个最优子集?
看 DP 表中是否出现 dp[i][j] = dp[i-1][j] = dp[i-1][j-wᵢ]+vᵢ ,若出现则有多个最优子集
2.
a.为背包问题写一段自底向上的动态规划算法的伪代码。
输入:
物品数量 n
背包容量 W
重量数组 w[1..n]
价值数组 v[1..n]
输出:
最大价值 dp[n][W]
// 创建 dp 表:dp[i][j] = 前i个物品,容量j的最大价值
创建二维数组 dp[0..n][0..W]
// 初始化第 0 行、第 0 列为 0
for j = 0 to W:
dp[0][j] = 0
for i = 0 to n:
dp[i][0] = 0
// 自底向上填表
for i = 1 to n:
for j = 1 to W:
if w[i] > j:
// 装不下,不选第i个物品
dp[i][j] = dp[i-1][j]
else:
// 选或不选,取最大
dp[i][j] = max( dp[i-1][j], dp[i-1][ j - w[i] ] + v[i] )
return dp[n][W]
b.写一段伪代码,使得可以从背包问题的自底向上动态规划算法生成的表中求得最优子集的组成。
输入:
dp 表
物品数量 n
背包容量 W
重量数组 w[1..n]
输出:
最优子集(哪些物品被选中)
i = n // 从最后一个物品开始
j = W // 从最大容量开始
创建空集合 result
while i > 0 and j > 0:
if dp[i][j] != dp[i-1][j]:
// 说明选了第 i 个物品
将 i 加入 result
j = j - w[i]
i = i - 1
else:
// 没选第 i 个物品
i = i - 1
输出 result
3.对于背包问题的自底向上动态规划算法,请证明:
a.它的时间效率属于Θ(nW)。
自底向上背包算法使用两层嵌套循环:
- 外层循环遍历 n 个物品 ,执行 n 次
- 内层循环遍历 背包容量 W ,执行 W 次
- 循环体内仅包含常数时间操作(比较、赋值、取最大值)
总基本操作次数为:n×W
b.它的空间效率属于Θ(nW)。
算法使用一个二维 DP 表格存储中间结果:
- 表格行数:n+1(物品数)
- 表格列数:W+1(容量)
- 总存储空间大小:(n+1)(W+1)
忽略常数项,空间规模为:n×W
c.从一张填好的动态规划表中求得最优子集的组合所用的时间属于O(n)。
最优子集通过回溯 DP 表得到:
- 从 i = n 开始,每次 i 减 1
- 最多执行 n 次迭代
- 每次迭代仅包含常数时间操作(比较、减法)
总操作次数与物品数量 n 成正比
4.
a.判断正误:背包问题实例的动态规划表中某一行值的序列总是非递减的。
一行对应固定前 i 个物品 ,列 j 是背包容量越来越大 。容量变大,能装的东西不会变少 ,最大价值不会下降 。所以 dp [i][j] 随 j 增大非递减。
b.判断正误:背包问题实例的动态规划表中某一列值的序列总是非递减的。
一列对应固定背包容量 j ,行 i 是物品越来越多 。可选物品变多,最优价值不会下降 。所以 dp [i][j] 随 i 增大非递减。
5.假设n种物品中每种物品的数量不限,为该背包问题设计一个动态规划算法并分析该算法的时间效率。
设dp[j]是容量为j时的最大价值
dp[j] = max( dp[j], dp[j - w[i]] + v[i] )
输入:
物品数量 n
背包容量 W
重量数组 w[1..n]
价值数组 v[1..n]
输出:最大价值 dp[W]
创建一维数组 dp[0..W],全部初始化为 0
for j = 1 to W: // 遍历所有容量
for i = 1 to n: // 遍历所有物品
if w[i] ≤ j:
dp[j] = max( dp[j], dp[j - w[i]] + v[i] )
return dp[W]
效率为Θ(Wn)
6.对第1题中给出的背包问题的实例应用记忆功能方法。在动态规划表中找出这样的单元格:(1)在这个实例中,从来没有被记忆功能方法计算过的单元格;(2)不需要重新计算就能使用的单元格。
dp(i, j) = 最大价值(前i个物品,容量j)
if i == 0 or j == 0:
dp(i,j) = 0
elif w[i] > j:
dp(i,j) = dp(i-1, j)
else:
dp(i,j) = max( dp(i-1,j), dp(i-1,j-w[i]) + v[i] )
除了递归调用链上的单元格,其余全部未被计算
(2)所有在递归调用中被第一次算出、后续直接复用的单元格:
- dp (0,j) 全部
- dp(1,1), dp(1,2)
- dp(2,1), dp(2,2)
- dp(3,1), dp(3,2)
- dp(4,1)
- dp(5,6)
这些都是不需要重新计算的
7.证明背包问题的记忆功能算法的时间效率类型和自底向上算法是相同的(参见第3题)。
- 记忆化算法最多计算 Θ(nW) 个不同状态,因为计算过的地方不会再算了
- 每个状态花费 Θ(1) 时间
- 总时间:T(n)=状态数×每个状态时间=Θ(nW)×Θ(1)=Θ(nW)
8.为什么根据公式C(n,k)=C(n-1,k-1)+C(n-1,k)计算二项式系数时,记忆功能法不是一个好方法?
计算二项式时会递归求解大量原本不需要的子问题。
- 自底向上动态规划只需要计算与 C (n,k) 直接相关的那些二项式系数,不会计算多余状态。
- 而记忆化递归会沿着递归树,把大量无关的 C (i,j) 再算一遍,造成时间和空间的浪费。
- 此外,递归调用本身也会带来函数调用、栈操作等额外开销。
因此,对于二项式系数问题,自底向上填表比记忆功能法更高效、更直接。