我们首先来看一下问题:一个旅行者有一个容量为C的背包,现在有n种物品,每件的重量分别是W1、W2、……、Wn,每件物品的价值分别为V1、V2、……、Vn, 需要将物品放入背包中,要怎么样放才能保证背包中物品的总价值最大?具体数据如下表,其中n=4,C=8。
前面已经对动态规划的基本概念做了详细的讲解,动态规划算法的核心是最优子结构、边界、状态转移方程式。现在我们来按这个思路对问题进行分析,分别找出以上三种。
算法分析
这里用F(N,W)表示前N个物品最佳组合对应的价值,W代表当前背包容量。
本题的最优子结构是考虑最后一种物品,即第四个。这里有两种情况,一个是第4种物品装入背包,则此时问题转化为前3种物品放入容量为C-W4的空间内,此时的背包最大值为F(3,8-W4)+V4;另外一种是不选择将第4种物品放入背包,则此时的问题是F(3,8)=F(4,8)。所以这里4种物品和3种物品最优选择之间存在这样的关系,即F(4,8)=Max(F(3,8),F(3,8-W4)+V4)。
现在我们来确定下问题的边界是什么。显然也有两种情况,用公式来表示。
当N=1,C>=W1时,F(N,W)= V1;
当N=1,C<W1时,F(N,W)=0。
整理一下就能得到问题的状态转移方程:
F(N,W)=0(N<=1,C<W1);
F(N,W)=V1(N==1,C>=W1);
F(N,W)=F(N-1,W)(N>1,C<Wn);
F(N,W)=Max(F(N-1,W),F(N-1,C-Wn)+Vn)(N>1,C>=Wn)。
到这里,利用动态规划求解问题的模型分析已完成,现在来看下怎么实现。
同样,我们这里对其进行一步步的填表,这里直接给出最后过程。
1) 如,N=1,W=1,w(1)=2,v(1)=3,有W<w(1),故F(1,1)=F(1-1,1)=0;
2) 又如N=1,W=2,w(1)=2,v(1)=3,有W=w(1),故F(1,2)=max{ F(1-1,2),F(1-1,2-w(1))+v(1) }=max{0,0+3}=3;
3) 如此下去,填到最后一个,N=4,W=8,w(4)=5,v(4)=6,有W>w(4),故F(4,8)=max{ F(4-1,8),F(4-1,8-w(4))+v(4) }=max{9,4+6}=10;所以填完表如上图。
表格填完,最优解即是F(N,C)=F(4,8)=10,但还不知道解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的寻解方式:
1) F(N,W)=V(N-1,W)时,说明没有选择第i 个商品,则回到F(N-1,W);
2) F(N,W)=F(N-1,W-Wn)+Vn时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到F(N-1,W-Wn);
3) 一直遍历到N=0结束为止,所有解的组成都会找到。
如上例子,
1) 最优解为F(4,8)=10,而F(4,8)!=F(3,8)却有F(4,8)=F(3,8-w4)+v4=F(3,3)+6=4+6=10,所以第4件商品被选中,并且回到F(3,8-w4)=F(3,3);
2) 有F(3,3)=F(2,3)=4,所以第3件商品没被选择,回到F(2,3);
3) 而F(2,3)!=F(1,3)却有F(2,3)=F(1,3-w2)+v(2)=F(1,0)+4=0+4=4,所以第2件商品被选中,并且回到F(1,3-w(2))=V(1,0);
4) 有F(1,0)=F(0,0)=0,所以第1件商品没被选择。
至此,此0-1背包问题已经解决。
下面给出代码实现:
void FindMax()//动态规划
{
int i,j;
//填表
for(i=1;i<=n;i++)
{
for(j=1;j<=c;j++)
{
if(j<w[i])//包装不进
{
V[i][j]=V[i-1][j];
}
else//能装
{
if(V[i-1][j]>V[i-1][j-w[i]]+v[i])//不装价值大
{
V[i][j]=V[i-1][j];
}
else//前i-1个物品的最优解与第i个物品的价值之和更大
{
V[i][j]=V[i-1][j-w[i]]+v[i];
}
}
}
}
}
时间效率为O(n*c),由于用到二维数组存储子问题的解,所以动态规划的空间效率为O(n*c)