最近两天整背包算法整的懵逼了,为什么懵逼?首先对着这个公式看了好久,如下:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
或
f[v]=max{f[v],f[v-c[i]]+w[i]}
是不是觉得很懵逼?没错,看起来好像理解了,但是又不是很明白,直到我看到了如下的东西
体积 | 1 | 2 | 3 | 4 | 5 | 6 |
物品1 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 |
物品2 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 |
物品3 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 |
物品4 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 | 当前重量和物品下的最大价值 |
然后问题就转化为了在矩阵里面填入满足条件的价值;即当前物品种类和当前重量下的最大价值;比如我们来看坐标(物品1,3)就代表,当存在物品1时,背包容量为3的情况下,价值最大是多少?
(物品2,3)就代表,当存在物品1,物品2是,背包容量为3的情况下,价值最大是多少?以此类推:
(物品4,6)代表存在物品1,2,3,4,背包容量为6的情况下,价值最大是多少;
理解了矩阵的含义,我们再来看公式,说白了就是根据矩阵前面的情况来决定矩阵当前格子内填的数目:
比如F [i][v],举个栗子,i=物品2,v=5时就是F[物品2][5],我们就在表格上找到这一格,当到了处理这一格时,就已经说明物品1那一行和物品2那一行的前4个格子都已经填上了数字,代表了当前物品种类下和背包容量下的最大价值;
接下来就是要决定F[物品2][5]这一格里面填什么了;
F[i-1][V]:我们填入实例就是F[物品1][5]的价值:背包为5的情况下,还没有放入物品2的最大价值是多少?就是F[物品1][5];
F[i-1][v-c[i]]+w[i]:首先这个位置有一个值得注意的点,变为F[i][V-c[i]]+w[i];那么则由一个物品变为了一类物品,C[i],我们这里理解为物品2的重量,W[i]我们理解为物品2的价值;因为这个时候我们要的应该是同时存在物品1情况下容量为(5-物品2体积)的价值,即F[物品1][5-物品2体积]这一格空格的值;
F[i][v-c[i]]+w[i]:即F[物品2][5-物品2体积]+物品2价值;F[物品1][5-物品2体积]代表了存在物品1和物品2情况下,体积为5-物品2体积情况下的最大价值;
那么MAX{
f[i-1][v],f[i][v-c[i]]+w[i]}就是比较{F[物品1][5],F[i-1][v-c[i]]+w[i]:即F[物品2][5-物品2体积]+物品2价值}谁的价值更大,那么 F[物品1][5]就填入哪个值代表重量为5的情况下,存在物品1和物品2情况下的价值最大值;
直到一直这样把这个矩阵填写完,那么矩阵的最后一行最后一列就是 有最多种类的物品和最大的背包容量下的最大价值是多少;附上算法源码:
Pakage
**p:代表背包的二维数组,即矩阵本体;
ItemPakage* ItemEnum 代表物品种类的矩阵,即纵坐标
int volt:代表背包的体积容量,即横坐标的最大值
int MaxValue(Pakage **p,ItemPakage* ItemEnum,int nTypeNum,int nvolt)
{
for (int i=0; i<=nTypeNum; i++)
{
p[i][0].nItem=0;
p[i][0].sumvolt=0;
}
for (int j=0; j<=nvolt; j++)
{
p[0][j].nItem=0;
p[0][j].sumvalue=0;
}
for (int i=1; i<=nTypeNum; i++)
{
for (int j=1;j<=nvolt;j++)
{
if (j>=ItemEnum[i-1].Volt)
{
if (p[i-1][j-ItemEnum[i-1].Volt].sumvalue+ItemEnum[i-1].Value>p[i-1][j].sumvalue)
{
p[i][j]=p[i-1][j-ItemEnum[i-1].Volt];
p[i][j].sumvalue+=ItemEnum[i-1].Value;
p[i][j].nItem=p[i][j].nItem+1;
p[i][j].ArrItem[p[i][j].nItem-1]=ItemEnum[i-1];
}
else
{
p[i][j]=p[i-1][j];
}
}
else
p[i][j]=p[i][j-1];
}
}
return p[nTypeNum][nvolt].sumvalue ;
}
{
for (int i=0; i<=nTypeNum; i++)
{
p[i][0].nItem=0;
p[i][0].sumvolt=0;
}
for (int j=0; j<=nvolt; j++)
{
p[0][j].nItem=0;
p[0][j].sumvalue=0;
}
for (int i=1; i<=nTypeNum; i++)
{
for (int j=1;j<=nvolt;j++)
{
if (j>=ItemEnum[i-1].Volt)
{
if (p[i-1][j-ItemEnum[i-1].Volt].sumvalue+ItemEnum[i-1].Value>p[i-1][j].sumvalue)
{
p[i][j]=p[i-1][j-ItemEnum[i-1].Volt];
p[i][j].sumvalue+=ItemEnum[i-1].Value;
p[i][j].nItem=p[i][j].nItem+1;
p[i][j].ArrItem[p[i][j].nItem-1]=ItemEnum[i-1];
}
else
{
p[i][j]=p[i-1][j];
}
}
else
p[i][j]=p[i][j-1];
}
}
return p[nTypeNum][nvolt].sumvalue ;
}
最后就是需要注意边界条件了;因为二维矩阵的纵坐标是物品个数,横坐标为物品体积,都是从1开始的,但是矩阵本身初始记录位置是从0开始的,为了避免编程过程中思维的多次转换以及单独的边界条件处理(0的情况)建议简单化;4个物品体积为8的问题,不是开(4x8)32个格子的矩阵而是要开(5X9)45个格子的矩阵,涉及到0的位置全部填0;
接下来就是愉快的编写算法了!