0-1背包问题
一、问题描述:
给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为j。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?(对于一种物品,要么装入背包,要么不装。所以对于一种物品的装入状态可以取0和1,此问题称为0-1背包问题。)有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
二、问题分析:
要求将i个物品装进容量为j的背包的最大价值:即取以下俩个中的较大的一个
一个是:i-1个物品装进容量为(j)的背包的最大价值;
一个是:i-1个物品装进容量为(j减去第i个物品的重量)的背包的价值,再加上第i个物品的价值。
01背包的状态转换方程 F[i,j] = Max{ F[i-1,j-Wi]+Vi( j >= Wi ), F[i-1,j] } 【主要就是状态转换方程】
F[i,j]表示在前i件物品中选择若干件放在承重为 j 的背包中,可以取得的最大价值。Vi表示第i件物品的价值。
三、程序实现代码:
#include<iostream>
using namespace std;
const int MIN=0;
int F[200][200];//前i个物品装入容量为j的背包中获得的最大价值
int max(int a,int b) //一个大小比较函数,用于当总重大于第I行时
{
if(a>=b)
return a;
else return b;
}
int Knapsack(int n,int w[],int v[],int x[],int C)//数组是当做指针传递的
{
int i,j;//F[i][j] 前i个物品装入容量为j的背包中获得的最大价值
for(i=0;i<=n;i++)
for(j=0;j<=C;j++)
F[i][j]=MIN; //初始化为0
关键代码//
for(i=1;i<=n;i++)
for(j=w[i];j<=C;j++)//这里j是从w[i]开始,背包容量小于第i件物品重量,则状态转换方程中下标出现负值,无意义
{
F[i][j]=max(F[i-1][j],F[i-1][j-w[i]]+v[i]);//建立背包表
cout<<"F["<<i<<"]["<<j<<"]="<<F[i][j]<<endl;
}
j=C;//确定背包容量
for(i=n;i>0;i--)
{//已知背包容量,不断减去最大的物品重量来确定背包中装的物品
if(F[i][j]>F[i-1][j])//如果将前i个物品装进去比将前i-1个物品装进去总价值大,说明总价值最大的背包中有第i个物品。
{
x[i]=1;
j=j-w[i];//不用担心结果小于零,因为在上面的二重循环中已经处理过了
}
else
x[i]=0;
}
cout<<"选中的物品是:"<<endl;
for(i=1;i<=n;i++)
cout<<x[i]<<endl;
/*也可以这样输出背包中的物品
while(i)
{
if(F[i][j]==(F[i-1][j-w[i]]+v[i]))//逐个输出每个物品
{
cout<<i<<":"<<"v="<<v[i]<<",w="<<w[i]<<endl;
j-=w[i];
}
i--;
}
*/
return F[n][C];
}
int main()
{
int s;//获得的最大价值
int n,i;
int C;//背包最大容量
int *w,*v,*x;
cout<<"请输入背包的最大容量:"<<endl;
cin>>C;
cout<<"物品数:"<<endl;
cin>>n;
//n+1是因为数组从1开始方便理解
//重量 价值 和物品的状态 均对应着存到数组中,物品从1开始。
w = new int[n+1]; //物品的重量
v = new int[n+1]; //物品的价值
x = new int[n+1]; //物品的选取状态 选中则是1 没选中为0
cout<<"请分别输入物品的重量:"<<endl;
for(i=1;i<=n;i++)
cin>>w[i];//w[0]空出
cout<<"请分别输入物品的价值:"<<endl;
for(i=1;i<=n;i++)
cin>>v[i];//v[0]空出
s=Knapsack(n,w,v,x,C); //调用核心函数
cout<<"最大物品价值为:"<<endl;
cout<<s<<endl;
delete []w;
delete []v;
delete []x;
system("pause");
return 0;
}
四、过程分析:
我们对程序关键代码执行过程进行分析:
i=1,w[1]=2,所以j从2开始
j=2: F[i-1][j]=F[1-1][2];F[i-1][j-w[i]]=F[1-1][2-2];v[i]=v[1]
F[0][2]=0, F[0][0]+v[1]=0+6=6
F[1][2]=6 将第一个物品装进去
j=3: F[i-1][j]=F[1-1][3];F[i-1][j-w[i]]=F[1-1][3-2];v[i]=v[1]
F[0][3]=0, F[0][1]+v[1]=0+6=6
F[1][3]=6 将第一个物品装进去
j=4: F[i-1][j]=F[1-1][4];F[i-1][j-w[i]]=F[1-1][4-2];v[i]=v[1]
F[0][4]=0, F[0][2]+v[1]=0+6=6
F[1][4]=6 将第一个物品装进去
j=5: F[i-1][j]=F[1-1][5];F[i-1][j-w[i]]=F[1-1][5-2];v[i]=v[1]
F[0][5]=0, F[0][3]+v[1]=0+6=6
F[1][5]=6 将第一个物品装进去
j=6: F[i-1][j]=F[1-1][6];F[i-1][j-w[i]]=F[1-1][6-2];v[i]=v[1]
F[0][6]=0, F[0][4]+v[1]=0+6=6
F[1][6]=6 将第一个物品装进去
j=7: F[i-1][j]=F[1-1][5];F[i-1][j-w[i]]=F[1-1][5-2];v[i]=v[1]
F[0][7]=0, F[0][5]+v[1]=0+6=6
F[1][7]=6 将第一个物品装进去
j=8: F[i-1][j]=F[1-1][5];F[i-1][j-w[i]]=F[1-1][5-2];v[i]=v[1]
F[0][8]=0, F[0][6]+v[1]=0+6=6
F[1][8]=6 将第一个物品装进去
j=9: F[i-1][j]=F[1-1][5];F[i-1][j-w[i]]=F[1-1][5-2];v[i]=v[1]
F[0][9]=0, F[0][7]+v[1]=0+6=6
F[1][9]=6 将第一个物品装进去
j=10: F[i-1][j]=F[1-1][5];F[i-1][j-w[i]]=F[1-1][5-2];v[i]=v[1]
F[0][10]=0, F[0][8]+v[1]=0+6=6
F[1][10]=6 将第一个物品装进去
i=3,w[3]=6,j从6开始
j=6: F[i-1][j]=F[3-1][6];F[i-1][j-w[i]]=F[3-1][6-6];v[i]=v[3]
F[2][6]=9, F[2][0]+v[3]=0+5=5
F[3][6]=9 将第一个和第二个物品装进去
j=7: F[i-1][j]=F[3-1][7];F[i-1][j-w[i]]=F[3-1][7-6];v[i]=v[3]
F[2][7]=9, F[2][1]+v[3]=0+5=5
F[3][7]=9 将第一个和第二个物品装进去
…………………
其它的以此类推,最终我们可以建立以下最大价值表格:
五、问题变形
1、变形:
在求最优解的背包问题中,一般有两种不同的问法:1、要求“恰好装满背包”时的最优解;2、求小于等于背包容量的最优解,即不一定恰好装满背包。
这两种问法,主要区别是在初始化的时候。
a: 要求“恰好装满背包”时的最优解:
在初始化时除了F[i][0]为0其它F[i][j]均设为-∞,这样就可以保证最终得到的s是一种恰好装满背包的最优解。如果不能恰好满足背包容量,即不能得到s的最优值,则此时s=-∞,这样就能表示没有找到恰好满足背包容量的最优值。
b: 求小于等于背包容量的最优解,即不一定恰好装满背包:
如果并没有要求必须把背包装满,而是只希望价值尽量大,初始化时应该将F[i][j]全部初始化为0。
2、解析:
因为只有F[i][0]=0,若在每一步恰好装满,例:
求F[1][2]要求不一定恰好装满背包,求F[1][3]要求恰好装满背包
F[1][2]=max(F[0][2],F[0][2-2]+v[1]) 可以得到一个正数
=max(F[0][2],F[0][0]+v[1])=f[0][0]+v[1] 【F[0][j]=-oo,F[0][0]=0】
F[1][3]=max(F[0][3],F[0][3-2]+v[1]) 结果是负数
=max(F[0][3],F[0][1]+v[1])=F[0][1]+v[1]=-oo 【F[0][j]=-oo,F[0][1]=-oo】