❝今天继续昨天的无限背包问题,给出解法二和三。没有看昨天文章的,请先看一下吧。
❞
问题回顾
给定N个物品的重量和价值,要求将这些物品放在承重量为C的背包中。目标是从背包中的物品中获得最大的价值,物品的数量可以假设是无限的。
假设物品是水果,如下所示:
项目:{苹果,橘子,甜瓜}
重量:{1,2,3}
价值:{15,20,50 }
背包承重量:5
让我们尝试将不同种类的水果放入背包中,使它们的总重量不超过5。
5苹果(总重量5)=> 75
1苹果+ 2橙子(总重量5)=> 55
2苹果+ 1瓜(总重量5)=> 80
1 苹果+ 1瓜(总重量5)= > 70
这表明2个苹果+ 1个瓜是最好的组合,因为它给我们带来最大的价值,并且总重量不超过背包承重量。
从上到下加记忆
这个思路相当的直接,就是做个缓存。唯一有点技术含量的就是如何确定缓存的形式。在我们的暴力搜索算法中,我们观察它的递归形式:
profit1 = profits[currentIndex] + solve_knapsack_recursive(dp, profits, weights, capacity - weights[currentIndex], currentIndex)
profit2 = solve_knapsack_recursive(
dp, profits, weights, capacity, currentIndex + 1)
不难发现,在递归调用中,有两个变量,分别是capacitiy和currentIndex,这不是正好可以用二维数组吗。一个维度是capacitiy,一个维度是currentIndex,数组值就代表子问题的解。
代码实现如下:
def solve_knapsack(profits, weights, capacity):
# 初始化为-1,代表无效的值
dp = [[-1 for _ in range(capacity+1)] for _ in range(len(profits))]
return solve_knapsack_recursive(dp, profits, weights, capacity, 0)
def solve_knapsack_recursive(dp, profits, weights, capacity, currentIndex):
n = len(profits)
# 基本问题的解
if capacity <= 0 or n == 0 or len(weights) != n or currentIndex >= n:
return 0
# 缓存未命中,进行计算
if dp[currentIndex][capacity] == -1:
profit1 = 0
if weights[currentIndex] <= capacity:
profit1 = profits[currentIndex] + solve_knapsack_recursive(
dp, profits, weights, capacity - weights[currentIndex], currentIndex)
profit2 = solve_knapsack_recursive(
dp, profits, weights, capacity, currentIndex + 1)
# 缓存子问题的解
dp[currentIndex][capacity] = max(profit1, profit2)
return dp[currentIndex][capacity]
这个问题的时间复杂度是。就是二维数组的元素个数。空间复杂度是,NC就是缓存数组的大小,N是递归调用栈需要的空间。
自底向上的动态规划
所谓自底向上,其实就是换个方式来填充上面解法中的二维数组,没有更多的技术含量。
只是有一点不同,这时,二维数组中(i,j)处的值,代表的是对于capacity=j,可选物品为0-i时,问题的最优解。
但在自上而下的解法中, i的含义是可选物品为i-n时,问题的最优解。
具体怎么填充呢?
当capacity为c时,currentIndex=i时。我们需要从0-i的物品中选择进行填充。
所有的组合无非就是两类:
不包含物品i的组合。对于这类组合,dp[i-1][c]就是它们的最优解。
包含物品i的组合。对这类组合,最优的解就是profit[i] + dp[i][c-weight[i]]。
然后,我们只需要在这两类最优解中选择价值更大的即可。
dp[i][c] = max (dp[i-1][c], profit[i] + dp[i][c-weight[i]])
代码实现如下:
def solve_knapsack(profits, weights, capacity):
n = len(profits)
# 边界检查
if capacity <= 0 or n == 0 or len(weights) != n:
return 0
dp = [[-1 for _ in range(capacity+1)] for _ in range(len(profits))]
# 填充承重量为0的情况
for i in range(n):
dp[i][0] = 0
# 对每一个子数组,对每一个承重量的最优解进行填充
for i in range(n):
for c in range(1, capacity+1):
profit1, profit2 = 0, 0
if weights[i] <= c:
profit1 = profits[i] + dp[i][c - weights[i]]
if i > 0:
profit2 = dp[i - 1][c]
dp[i][c] = profit1 if profit1 > profit2 else profit2
#返回最终结果
return dp[n - 1][capacity]
ok!至此我们就学习完了背包问题。我建议那些对动态规划心有怯怯的同学。先不要着急做很多的动态规划的题。你就下功夫把我现在的几篇文章认真研读几遍,确保你是真正的理解了。
后面的系列教程中,你会发现,动态规划的基本思想是不变的,无非就是加了一些限制条件而已。



本文介绍了无限背包问题的两种动态规划解法:从上到下加记忆和自底向上的动态规划。通过举例说明如何在背包容量限制下,选择物品以获得最大价值,并提供了相应的代码实现。
361

被折叠的 条评论
为什么被折叠?



