01背包问题【动态规划】
01背包是典型的动态规划问题。
动态规划的原理
动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。
最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。
01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ), f[i-1,j] }
假设山洞里共有a,b,c,d ,e这5件宝物(不是5种宝物),它们的重量分别是4,5,6,2,2,它们的价值分别是6,4,5,3,6,现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富。
有编号分别为a,b,c,d,e的五件物品,它们的重量分别是4,5,6,2,2,它们的价值分别是6,4,5,3,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
name | weight | value | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |||
a | 4 | 6 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
b | 5 | 4 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
c | 6 | 5 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
d | 2 | 3 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
e | 2 | 6 | 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
e8的值是如何求出来的?
根据01背包状态转换方程,需要考虑2个值:
1)f(i-1, j)
2)max( f(i-1,j), f(i-1, j-w(i)) + v(i) )
其中,
f(i-1,j)表示背包承重为j时,前i-1个能装的最大价值,本例中表示我有一个承重为8的背包,当只有物品a,b,c,d四件可选时,这个背包能装入的最大价值,即d8;
f(i-1, j-w(i)) + v(i)表示背包承重为j-w(i)时,前i-1个能装的最大价值(其中w(i)为第i个物品的重量),再加上i物品的价值v(i),在本例即为d6+6=15。
回溯求最优解组合
通过上面的方法可以求出背包问题的最优解,但还不知道这个最优解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的寻解方式:
V(i,j)=V(i-1,j)时,说明没有选择第i 个商品,则回到V(i-1,j);
V(i,j)=V(i-1,j-w(i))+v(i)时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到V(i-1,j-w(i));
一直遍历到i=0结束为止,所有解的组成都会找到。
压缩优化
1、当前要求状态只与上一个状态有关,因此可以压缩数组维度,例如二维压缩为一维
2、压缩后数组只存储最后一件物品(即最后一行)的结果,只能得到最优解,不能求出最优解的组合
3、压缩后在列上必须倒着更新,否则会覆盖前面的。
参考1
参考2
# 01背包问题
# 输入:第一行为用例个数,第二行包含N和cap,表示物品数和背包容量,之后的N行为物品重量weight和价值value,用空格隔开
# 输出:背包能装的最大价值,若能输出路径,则输出所装的东西
def search_path(dp, arr, i, j): # 递归求装入的物品,最优解回溯,求最优解的组成
if i <= 0:
return
if dp[i][j] == dp[i-1][j]:
search_path(dp, arr, i-1, j)
else: