背包问题
一.问题重述
给定n个物品,价值分别是:v1,v2,...,vn,重量分别是:w1,w2,...,wn。现挑选物品放入承重为W的背包,使得背包内物品的价值最大,且背包内物品的总重量小于W.这里假设承重都是正整数。
二.解决方案
首先我们先设一个量:V[i,j],它代表在前i个物品中进行挑选,能放进承重为j的背包中,背包内物品价值最大的物品挑选组合的价值。
那根据这个设定的量,我们可以得出这样的一些结论:
1.若在前i-1个物品中挑选,则最优集合为V[i-1,j],如果在V[i,j]的组合中,背包内是不包括了第i个物品的话,那么V[i,j]=V[i-1,j];
2.如果在V[i,j]的组合中,背包内是包括了第i个物品的话,则该组合等于V[i-1,j]加上第i个物品,也就是说V[i,j]=V[i-1,j-wi](注意:此处的前提条件是V[i,j]包括了第i个物品)
所以,我们可以得出计算V[i,j]的公式:
if j-wi<0
V[i,j]=V[i-1,j]
else
V[i,j]=max{V[i-1,j],vi+V[i-1,j-wi]}
此外,V[0,j]=V[i,0]=0,即背包内无物品或说背包承重为0时,背包内最大价值都是0.
有了解决方案的公式,我们就可以进行问题的解决。
三.示例
下面根据以上所推公式,我们对一个例子进行演示。
例子:
物品 | 重量 | 价值 |
1 | 2 | 12 |
2 | 1 | 10 |
3 | 3 | 20 |
4 | 2 | 15 |
背包承重W=5
那么首先按初始化条件建立表格:
i/j | 0 | 1 | 2 | 3 | 4 | 5 |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | |||||
2 | 0 | |||||
3 | 0 | |||||
4 | 0 |
表格可以按行填写,也可以按列填写,本例按列填写。
首先,V[1,1]表示在前1个物品中挑选,重量不得超过1,我们发现前一个w1=2>1,所以没有解决方案,即V[1,1]=0;接着,V[2,1]表示在前2个物品中挑选,重量不得超过1,我们发现前一个w2=1,所以有解决方案,为{物品二},即V[2,1]=10;然后,V[3,1]表示在前3个物品中挑选,重量不得超过1,我们发现前一个w3=3>1,所以有解决方案,还是{物品二},即V[3,1]=10;最后,V[4,1]表示在前4个物品中挑选,重量不得超过1,我们发现前一个w4=2>1,所以没有解决方案,即V[4,1]=10;得出下表:
i/j | 0 | 1 | 2 | 3 | 4 | 5 |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | ||||
2 | 0 | 10 | ||||
3 | 0 | 10 | ||||
4 | 0 | 10 |
接着,填第3列。
首先,V[1,2]表示在前1个物品中挑选,重量不得超过2,我们发现前一个w1=2,所以有解决方案,为{物品一},即V[1,2]=12;接着,V[2,2]表示在前2个物品中挑选,重量不得超过2,我们发现前一个w2=1<2,且V[1,0]+v2<V[1,2],所以有解决方案,为{物品一},即V[2,2]=12;然后,V[3,2]表示在前3个物品中挑选,重量不得超过2,我们发现前一个w3=3>2,所以有解决方案,还是{物品二},即V[3,2]=12;最后,V[4,2]表示在前4个物品中挑选,重量不得超过2,我们发现前一个w4=2,且V[3,1]+v4>V[3,0],所以有解决方案,为{物品4},即V[4,1]=15;得出下表:
i/j | 0 | 1 | 2 | 3 | 4 | 5 |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 12 | |||
2 | 0 | 10 | 12 | |||
3 | 0 | 10 | 12 | |||
4 | 0 | 10 | 15 |
对以上两个步骤我们可以看出我们可以在表格里得到这样的规律:
i/j | 0 | ... | j-wi | ... | j | ... | W |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
... | 0 | ||||||
i-1 | 0 | V[i-1,j-wi] | V[i-1,j] | ||||
i | 0 | V[i,j] | |||||
... | 0 | ||||||
n | 0 | 目标 |
所以从V[1,1]开始逐列填写,最终可得表如下:
i/j | 0 | 1 | 2 | 3 | 4 | 5 |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 12 | 12 | 12 | 12 |
2 | 0 | 10 | 12 | 22 | 22 | 22 |
3 | 0 | 10 | 12 | 22 | 30 | 32 |
4 | 0 | 10 | 15 | 25 | 30 | 37 |
因此,最大价值为V[4,5]=37。
除了得出最大价值外,我们还想得到组成最大价值的物品,即哪些物品放入了背包,该问题的解决可对已经建好的表格使用回溯法得到。
首先对目标格V[4,5]开始,判断V[4,5]是否等于V[3,5](也就是向上一格比较),如果相等,就是说明在前3个物品挑选与在前4个物品挑选是一样的,也就说第4个物品没有放入背包里;如果不相等,就是背包里放入的第4个物品。根据例子,发现V[4,5]不等于V[3,5],所以第4个物品被放入了背包;接着看[i-1,j-wi],就是看从3个物品里挑选,背包承重为W-w4的格子——[3,3],比较[3,3]与[2,3],发现[3,3]=[2,3],所以第3个物品不包括在背包里,也即是从前3个物品挑选与从前2个物品挑选是一样的,所以看[2,3],比较[2,3]与[2,2],发现[2,3]不等于[1,3],同理,物品2被放入了背包。然后看[1,2](即[2-1,3-1]),[1,2]与[0,2]不等,所以物品1被放入了背包,回溯结束,最后的背包里有物品1,物品2,物品4。
四.具体算法
背包问题算法伪代码:
MFKnapsack(i,j)
//输入:背包可承受最大物品数i与背包可承受最大重量j
//输出:背包内物品的最大值
//V[1,...,n]为物品价值序列,W[1,...,n]为物品重量序列,为全局变量
if(i~=0&&j~=0)
if(F(i,j)<0)
if(j<W(1,i))
value=MFKnapsack(i-1,j);
else
value=max(MFKnapsack(i-1,j),V(1,i)+MFKnapsack(i-1,j-W(1,i)));
else
value=0
return value
FindBag(i,j)
//输入:背包可承受最大物品数i与背包可承受最大重量j
//输出:背包内最优子集包含的物品
if(r(i,j)==r(i-1,j))
i=i-1;
else
s=[s,i];
j=j-W(1,i);
i=i-1;
return s
下面给出具体程序,用matlab写的m文件:
function [value]=MFKnapsack(i,j)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%输入:
% i: --背包承受最大物品数
% j: --背包承受最大重量
%输出:
% value: --最大价值
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
W=[2,1,3,2];%物品重量序列
V=[12,10,20,15];%物品价值序列
k=size(W,2);
F=-ones(k,j);
if(i~=0&&j~=0)
if(F(i,j)<0)
if(j
function [s]=FindBag(i,j)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%输入:
% i: --背包承受最大物品数
% j: --背包承受最大重量
%输出:
% s:
% s(1,1) --最大价值
% s(1,2:end) --背包内包含的物品
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
r=zeros(i,j);
W=[2,1,3,2];%物品重量序列
V=[12,10,20,15];%物品价值序列
for p=1:1:i
for q=1:1:j
r(p,q)=MFKnapsack(p,q);
end
end
k=i;
l=j;
s=[];
sum=0;
while(k>1)
if(r(k,l)==r(k-1,l))
k=k-1;
else
s=[s,k];
sum=sum+V(1,k);
l=l-W(1,k);
k=k-1;
end
end
if(sum~=r(i,j))
s=[s,1];
end
s=[r(i,j),s];
end
参考:(美)Anany Levitin 著 潘彦 译《算法设计与分析基础(第二版)》.清华大学出版社.北京.pag228-231