算法设计与分析-习题8.2

目录

[1. a.对于下列背包问题的实例,应用自底向上动态规划算法求解。](#1. a.对于下列背包问题的实例,应用自底向上动态规划算法求解。)

[b. a中的实例有多少个不同的最优子集?](#b. a中的实例有多少个不同的最优子集?)

c.一般来说,如何从动态规划算法所生成的表中判断出背包问题的实例是不是具有不止一个最优子集?

2.

a.为背包问题写一段自底向上的动态规划算法的伪代码。

b.写一段伪代码,使得可以从背包问题的自底向上动态规划算法生成的表中求得最优子集的组成。

3.对于背包问题的自底向上动态规划算法,请证明:

a.它的时间效率属于Θ(nW)。

b.它的空间效率属于Θ(nW)。

c.从一张填好的动态规划表中求得最优子集的组合所用的时间属于O(n)。

4.

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 时的最大价值

递推公式

  1. 不放第 i 个物品:dp[i][j] = dp[i-1][j]

  2. 能放第 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) 再算一遍,造成时间和空间的浪费
  • 此外,递归调用本身也会带来函数调用、栈操作等额外开销。

因此,对于二项式系数问题,自底向上填表比记忆功能法更高效、更直接

相关推荐
玛卡巴卡ldf2 小时前
【LeetCode 手撕算法】(子串) 560-和为 K 的子数组
java·数据结构·算法·leetcode
CoovallyAIHub2 小时前
BMW GenAI4Q:每57秒下线一辆车,AI如何为每辆车定制专属质检清单
数据库·算法·架构
不想看见4042 小时前
Rotate Image数组--力扣101算法题解笔记
数据结构·算法
仰泳的熊猫2 小时前
题目 2304: 蓝桥杯2019年第十届省赛真题-特别数的和
数据结构·c++·算法·蓝桥杯
靠沿2 小时前
【优选算法】专题十五——BFS解决FloodFill算法
算法·宽度优先
2401_849644852 小时前
C++代码重构实战
开发语言·c++·算法
fengfuyao9852 小时前
一个改进的MATLAB CVA(Change Vector Analysis)变化检测程序
前端·算法·matlab
2301_815482932 小时前
C++与WebAssembly集成
开发语言·c++·算法
像污秽一样3 小时前
算法设计与分析-习题4.3
数据结构·算法·排序算法