动态规划之01背包问题

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 
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值