背包问题可能解的数量_【crash DP】无限背包问题的三种解法之二和三

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

今天继续昨天的无限背包问题,给出解法二和三。没有看昨天文章的,请先看一下吧。

问题回顾

给定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!至此我们就学习完了背包问题。我建议那些对动态规划心有怯怯的同学。先不要着急做很多的动态规划的题。你就下功夫把我现在的几篇文章认真研读几遍,确保你是真正的理解了。

后面的系列教程中,你会发现,动态规划的基本思想是不变的,无非就是加了一些限制条件而已。

3729f1528454f51db9fb5de07b0b4e06.gif

2fbd0d598533e4c164ccdb6162f1da53.png

fcd8845305f4746fdf3e1df99ff79578.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>