背包问题是很经典的动态规划问题,变种问题也很多,最基本的问题是01背包问题
问题描述:
有n 个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
i:物品 | 1 | 2 | 3 | 4 |
w:体积 | 2 | 3 | 4 | 5 |
v:价值 | 3 | 4 | 5 | 6 |
分析:
01背包问题可以看做是物品的序列,其中x_i=[0,1],表示第i个物品放或者不放。状态转移矩阵是一个二维矩阵,其中行表示物品,列表示背包容量。和之前讲最长公共子序列一样,考虑背包装完后最优的时候,那么拿出最后一件后将背包容量缩小还是最优的。
状态转移公式有如下性质
1. 初始状态i=0 j=0,表示背包容量为0,没有物品,此时价值是0
2. 放入的时候有两种条件:
(1) 当w(i)>j,此时放不进去物品i,价值V(i,j)=V(i-1,j)
(2) 当w(i)<=j,此时能放进物品i,但不一定放进去能达到最优
之所以放进去也不一定最优,因为上面矩阵保存的都是当前状态最优的时候,刚能放进去的时候表示的是只有物品i的时候能放进去j中,但是拿出来了之前放的所有的东西,不一定就是最优了,所以有两个分支。不放进去就是i-1时的状态,放进去了要找i-1的时候背包空间是j-w(i),再放i的时候正好背包满了这时候最优,看两者哪个更大。
实现
w=[2,3,4,5]
v=[3,4,5,6]
bag=8
c=[[0 for i in range(bag+1)] for j in range(len(w)+1)]
for i in range(1,len(w)+1):
for j in range(1,bag+1):
if w[i-1]>j: #放不下i,不放
c[i][j]=c[i-1][j]
else: #放得下i,但放入不一定最优
c[i][j]=max(c[i-1][j],c[i-1][j-w[i-1]]+v[i-1])
print(c[len(w)][bag])
观察之前的状态转移方程和上面的代码,可以发现每一次更新状态V[i][j]的时候只用到了V[I-1]这一层,所以状态转移矩阵可以精简为一个一维数组,在更新V[i][j]的时候此时数组的状态是V[i-1]。但是我们要从后往前更新数组,因为用到了V[i-1]。
b=[0 for i in range(bag+1)]
for i in range(len(w)):
for j in range(bag+1)[::-1]:
if w[i]<=j:
b[j]=max(b[j],b[j-w[i]]+v[i])
print(b)
如果我们想获得装了哪些物品那么只能用二维矩阵回溯,回溯的过程是:
从右下角开始,如果V[i][j]==V[i-1][j],说明物品i没有放,回溯到V[i-1][j]
如果不等于,说明物品i放了,回溯到V[i-1][j-w(i)]
参考
https://www.cnblogs.com/Christal-R/p/Dynamic_programming.html
https://blog.csdn.net/littlethunder/article/details/26575417