01背包问题
你有一个背包,最多能容纳的体积是V。
现在有n个物品,第i个物品的体积为vi ,价值为wi。
求背包最大可以容纳的价值。
思路:首先我们设置一个数组m[i][j],数组横向i为第0-i个物品,纵向j表示背包所容纳重量。则我们可得到i<=n,j<=v;数组m的值则表示当背包中可以存在第0-i个物品时的价值。
列表可知,每当可以放入一个新物品时且此时背包可以容纳该物品(j>v[i])我们有两个选择:放或不放;
若不放则m[i][j]=m[i-1][j];若放则要考虑是否放入(使得价值最大)则m[i][j]=max(m[i-1][j],m[i-1][j-v[i]]+w[i]);这里表示取放入该物品或不放入该物品的最大价值;
m[i-1][j-v[i]]:首先j-v[i]表示先拿背包总体积减去当前物品的体积,所剩下可以容纳前i-1个物品的体积,那么m[i-1][j-v[i]]就表示当前物品加上前i-1个物品中的某个物品的最大价值。
注意:
初始化:分i=0,j=0两种情况
要注意背包的价值是否大于零(与初始化相关)
两个for循环可以交换位置
例题:
代码实现:
第一问:
二维数组
注意这里背包的序号是从0开始的
核心代码
最好画图分析两个初始化的情况,注意m数组的i,j与背包序号的数组,价值的数组要对应
一维数组的实现:
由递推公式m[i][j]=max(m[i-1][j],m[i-1][j-v[i]]+w[i])我们可以看出,m[i]只与m[i-1]有关,意思就是,如果可以把i-1那一层拷贝到第i层,我们就可以不用再考虑二维数组。而一维数组就可以实现,我们可以用一个一维数组,也就是说每次遍历i时都在i-1这一层的基础上操作所以递推公式可以化为
m[j]=max(m[j],m[j-v[i]]+w[i])。
那么如何初始化呢,如果背包的价值均大于零,那么我们可以把它们全初始化为0即可;
若物品:
序号 | 重量 | 价值 |
0 | 1 | 15 |
1 | 3 | 20 |
2 | 4 | 30 |
看到这个代码有3个疑问:
为什么j要逆序循环呢?我们来看m 的数组
0 | 1 | 2 | 3 | 4 |
0 | 0 | 0 | 0 | 0 |
初始化后全为0;
m[j]的含义是,容量为j的书包价值最大为m[j]
i=0时(只装物品一时
j=0时很显然m[j]=0;
若正向循环m[j]=max(m[j],m[j-w[i]]+val[i])
m[1]=max(m[1],m[1-w[0]]+val[0])=m[0]+val[0]=15;
m[2]=max(m[2],m[2-w[0]]+val[0])=m[1]+val[0]=30;
这时我们就可以看到val[0]被重复加了,因为一个物品只有一件,所以该代码违背了这个原则
我们来尝试一下逆向循环:
很显然J不可能有等于零的情况
m[4]=max(m[4],m[4-w[0]]+val[0])=m[3]+val[0]=15;
m[3]=max(m[3],m[3-w[0]]+val[0])=m[2]+val[0]=15;
m[2]=max(m[2],m[2-w[0]]+val[0])=m[1]+val[0]=15;
m[1]=max(m[1],m[1-w[0]]+val[0])=m[0]+val[0]=15;
i=1时,
重申m[j-w[i]]的意思是,当前背包的容积减去当前物品的体积还剩余的体积内所装的价值最大
m[4]=max(m[4],m[4-w[1]]+val[0])=m[1]+val[1]=35;
m[3]=max(m[3],m[3-w[1]]+val[0])=m[0]+val[1]=20;
可以根据下面的列表来检验对错;
可以看出,此时的val[0]并没有重复加,并且符合一个物品只有一个的题意;
为什么j>=w[i]呢?
j=5;
i=0时,j>=1;
i=1时,j>=3;
i=2时,j>=4;
由此可见, j 遍历的次数越来越少。
i=0时
0 | 1 | 2 | 3 | 4 |
0 | 15 | 15 | 15 | 15 |
i=1时
0 | 1 | 2 | 3 | 4 |
0 | 15 | 15 | 20 | 35 |
i=2
0 | 1 | 2 | 3 | 4 |
0 | 15 | 15 | 20 | 35 |
j>=w[i]意思就是书包的容积要大于当前物品的体积,因为如果小于的话,我们也不会考虑是否要放这个物品。
完全背包问题:
一维数组的实现:
完全背包的一维比较好理解;基于01的一维,我们只需要将第二个循环逆序即可:01的 j 是从v减到w[i],而完全背包是从w[i] 增到 v ;
我们分析过
若正向循环m[j]=max(m[j],m[j-w[i]]+val[i])
m[1]=max(m[1],m[1-w[0]]+val[0])=m[0]+val[0]=15;
m[2]=max(m[2],m[2-w[0]]+val[0])=m[1]+val[0]=30;
也就是说val[i] 会被重复加,再仔细剖析一下:当i=0时,即此时只有序号为0的物品时,我们可以选择加上当前物品也就是m[2-w[0]]+val[0],这里m[2-w[0]]意思是我们选择加当前物品,背包的空间为2时,减去当前物品的体积后,剩下的容积中所容纳的最大价值 也就是m[1] 可以容纳的价值15。
而 j 从w[ i ]到 v 的意思是,从当前物品的体积到背包的最大体积,所以 j 只可能大于等于当前物品的体积,若 j 小于当前物品的体积,也就是装不下,我们也就不考虑装不装的问题了。
二维数组的实现:
与01有两个不同之处:
第一个就是初始化的问题:因为物品可以无限放,所以当i=0即背包中只能放第0件物品时,我们要着重分析;
第二个就是嵌套循环中的第二个循环:
当我们选择放入当前物品时,价值为m[ i ][ j-w[i-1]]+val[i-1];意思就是,当前可选择的物品是从0-i,
j-w[i-1]是当前背包的容积减去上个物品的体积
还要注意一点:因为不同于一维数组,是直接在同一层上不断覆盖值,二维数组每一层初始都是0,所以嵌套循环的第二层的 j 的范围不能像一维数组那样,而是要讨论if(j-w[i-1]<0)的情况。