01 背包问题

PS: 我找到的谢的最好的一个 转载如下 

原创文章,转载请注明出处~ http://www.cnblogs.com/justinzhang/archive/2012/04/10/2441199.html

    记得大二参加数学建模的时候,开始接触了动态规划,在听过张老师的课后,当时使用matlab写了背包问题的代码,记得使用递归实现的。总之,对这个问题认识得非常的不清楚。以前强制记了好几次代码,过一段时间又忘了。虽然每次看书的时候还是可以回想起解题思路,但是心里总有种不踏实的感觉,由于上次腾讯笔试的刺激,发现这么多年以来,自己做的事情都有点南辕北辙、舍本逐末。计算机核心的离散数学、编译原理、组合数学、自动机理论,被放在了一边,从不过问。而去追求一些花哨的东西。真的是勿以往之不谏啊~~所以,虽然自己天天都在努力的工作,但是心里始终有不踏实的感觉,总是又被谴责鼻子走的感觉,那是因为自己根本没有抓住本质的东西,所以才会如此被动~~~计算机虽然已经和数学分得很开了,但是计算机毕竟产生于数学,所以逃避数学的做法是万万不可的。如果背离了数学而去搞计算,那么高度肯定是上不去了,以前认为只要把算法学好了就够了,现在才发现,离散数学、组合数学、自动机理论是如此的重要,在以后的学习和工作中,一定要勤思考才行。。

     背包问题有0-1背包问题和fraction背包问题,前者规定每个物品要么选,要么不选,而fraction knanpsack允许选取一个物品的一部分,0-1背包问题是NP难的,而fraction knapsacks的复杂度是O(n*logn), 只需要将单位物品的价值按降序排列,利用贪心策略选取即可得到最优解。

     给定一个背包,容量为C,有n个物品,重量为n维行向量w,价值为n维行向量v. |v|=|w|=n, n维向量y=[0,1,0….yn]代表物品的选法,其中的没有元素yi,要么是0、要么是1,为零代表选取第i个物品,为0表示不选取第i个物品。 目标函数是:Max(Sum(vi*yi)), 约束条件是sum(wi*yi) <=C, 其中 1=< i <=n.

     背包问题具有最优子结构,令f(n,C)代表,有n个待选物品,背包容量为C时的最优解,此时物品选择向量为y=[y1,y2,…yn], 那么当yn=1时,y’=[y1, y2, …yn-1],必然为f(n-1, C-wn)的物品选择向量,当yn=0时,必然为f(n-1,C)的最优物品选择向量。所以背包问题可以由动态规划来求解。

    根据上面的分析,我们可以得到如下的递归式:

    当wn>C时,  f(n,C)=f(n-1,C);

    当wn<=C时,f(n,C) = max(f(n-1,C), vn+f(n-1, C-wn) );

    初始条件为:f(i, 0) = 0; f(0,i) = 0; f(0,0) = 0;

 

    根据上面的分析用递归实现的0-1背包代码如下:

 1:  /*
   2:  *
   3:  *时间:22012年4月10日19:46:27
   4:  *作者:JustinZhang
   5:  *Email:uestczhangchao@gmail.com
   6:  */
   7:   
   8:  #include <iostream>
   9:  #include <iomanip>
  10:  using namespace std;
  11:   
  12:  int w[]={1,3,4,5};//物品重量数组
  13:  int v[]={2,30,44,20};//物品价值数组
  14:  int C=5;//背包容量
  15:  int y[4]={-1,-1,-1,-1};//解向量,y[i]=1表示选取物品,y[i]=0表示不选取物品
  16:   
  17:   
  18:  int f(int n, int C)
  19:  {
  20:      if (n==0 || C==0)//当物品数量为0,或者背包容量为0时,最优解为0
  21:      {
  22:          return 0;
  23:      }
  24:      else
  25:      {
  26:          //从当前所剩物品的最后一个物品开始向前,逐个判断是否要添加到背包中
  27:          for (int i=n-1; i>=0;i++)
  28:          {
  29:              //如果当前要判断的物品重量大于背包当前所剩的容量,那么就不选择这个物品
  30:              //在这种情况的最优解为f(n-1,C)
  31:              
  32:              if (w[i]>C) 
  33:              {
  34:                  y[i]=0;
  35:                  return f(n-1,C);
  36:              }
  37:              else
  38:              {
  39:                  //如果当前待判断的物品重量wi<C,那么就选取f(n-1,C)和vi+f(n-1,C-wi)中的最大值
  40:                  int tmp1 = f(n-1,C);//不选择物品i的情况下的最优解
  41:                  int tmp2 = v[i] + f(n-1,C-w[i]);//选择物品i的情况下的最优解
  42:                  
  43:                  //返回选择物品i和不选择物品i中最优解大的一个
  44:                  if (tmp1 > tmp2)
  45:                  {
  46:                      y[i]=0;//这种情况下表示物品i未被选取
  47:                      return tmp1;
  48:                  }
  49:                  else
  50:                  {
  51:                      y[i]=1;//物品i被选取
  52:                      return tmp2;
  53:                  }
  54:   
  55:              }
  56:          }
  57:   
  58:      }
  59:   
  60:  }
  61:   
  62:   
  63:  int main()
  64:  {
  65:   
  66:      int maxvalue = f(4,5);
  67:      for (int i=0; i<4; i++)
  68:      {
  69:          if (y[i]==1)//为1表示相应的物品被选取
  70:          {
  71:              cout << "Object "<< i+1 << " is selected. " << "It's Vaule is " << setw(2)<< v[i] \
  72:                  << " It's Weight is"<< setw(2)<<w[i] << endl; 
  73:          }
  74:      }
  75:   
  76:      cout << "Maximum Value is: "<< maxvalue << endl;
  77:      return 0;
  78:  }
  79:   
  80:   

  

运行结果如下:

image

在算法导论上,背包问题的伪代码如下:

image

       我们可以看到复杂度为O(nW),初看之下还以为这个算法的复杂度是多项式时间,但是书中明明说0-1背包问题是NP难问题啊,难道书中说错了吗?其实这里忽略的问题是,在上面的程序中n才是输入规模,而W并不是输入规模,因为它是背包的容量,而背包的数量一直都是为1的,如果在物品数量为n的情况下,背包的容量为2^n,那么这个算法的复杂度就是O(n*2^n), 所以这个问题是NP难的~~

对该算法的实现如下所示:

  1:  /*
   2:  时间:2012年4月10日21:08:25
   3:  作者:JustinZhang
   4:  Email:uestczhangchao@gmail.com
   5:  Desc:算法导论中背包问题的实现,采用非递归的方式;更加深入的理解递归是如何工作的,因为此时没有用递归调用,
   6:  是使用矩阵来记录每一步计算的结果,最后得到最优解;在最优解找到过后,还要给出那些物品被选中,此时是使用c[i][j]
   7:  从c[n][W]往前查找,利用递归公式做出判断(如果当前物品被选中,那么没有该物品的最优解(c[i-1][j-w[i])就等于当前的最优解减去当前物品的价值(c[i][j]-v[i]));
   8:  */
   9:   
  10:  #include <iostream>
  11:  #include <iomanip>
  12:  using namespace std;
  13:   
  14:   
  15:   
  16:  const int n = 4;
  17:  const int W = 5;
  18:   
  19:  int c[n+1][W+1];
  20:   
  21:  /*
  22:  input:
  23:      int v[]:物品的价值数组;
  24:      int w[]:物品重量数组;
  25:      int n:物品个数;
  26:      int W:背包容量;
  27:  
  28:  */
  29:  void knapsack_0_1_problem(int v[],int w[], int n, int W)
  30:  {
  31:      c[0][0] = 0;
  32:      //c[i][j]表示,将i个物品放入容量为j的背包中所具有的价值最大值;
  33:      for (int i=1; i<n; i++)
  34:      {
  35:          c[i][0] = 0;//如果背包容量为0,那么i个物品放入该背包的最优值都是0;
  36:      }
  37:      for (int j=1; j<W; j++)
  38:      {
  39:          c[0][j] = 0;//如果物品的数量为0,那么将0个物品放入大小为j的背包中的最优值就是0;
  40:      }
  41:   
  42:      //下面开始使用递归公式求解最优值
  43:      for(int i=1; i<=n;i++)
  44:      {
  45:          for (int ww=1; ww<=W; ww++)
  46:          {
  47:              //如果当前物品的重量大于背包的体积
  48:              if (w[i]>ww)
  49:              {
  50:                  c[i][ww] = c[i-1][ww];
  51:              }
  52:              else
  53:              {
  54:                  if (c[i-1][ww] > (v[i]+c[i-1][ww-w[i]]))
  55:                  {
  56:                      c[i][ww] = c[i-1][ww];
  57:                  }
  58:                  else
  59:                  {
  60:                      c[i][ww] = v[i]+c[i-1][ww-w[i]];
  61:                  }
  62:              }
  63:          }
  64:   
  65:      }
  66:  }
  67:   
  68:   
  69:   
  70:  int main()
  71:  {
  72:   
  73:      //v[]和w[]分别为价值数组和重量数组,并且下标都是从1开始算的,第0个元素设为-999;
  74:      //这里要下标必须从1开始,因为在迭代的过程当中有c[i][j]=max{c[i-1][j], c[i-1][j-w[i]]},
  75:      //如果元素的下标从0开始取的话,我们在遍历物品价值和重量数组的时候,就会有c[0][j]=max{c[i-1][j], c[-1][j-w[0]]},这里就会出现数组下标为0的情况,
  76:      //所以下标选择从1开始。
  77:      int v[n+1] = {-999, 6, 7, 14, 8};
  78:      int w[n+1] = {-999, 1, 2, 4, 5};
  79:   
  80:      knapsack_0_1_problem(v,w,n,W);
  81:   
  82:      for (int i=0; i<=n; i++)
  83:      {
  84:          for (int j=0; j<=W; j++)
  85:          {
  86:              cout << setw(3) << c[i][j];
  87:          }
  88:          cout << endl;
  89:      }
  90:   
  91:      int remainspace = W;//该变量用来记录背包当前还剩余的容量
  92:   
  93:      //以下过程,从c[n][W]开始向前寻找,那些物品被选择装进背包中;
  94:      //判断条件为:如果第n个物品被选择装进背包中,那么c[n][W]=c[n-1][W-w[n]] + v[n];此时reminspace将变为remainspace-w[n]
  95:      //否则物品n没有被选择,此时背包剩余的容量remainspace不会发生改变;
  96:      //按照上述方法一直往前找,即可以将所有装入背包中物品找到。
  97:   
  98:      for(int i=n; i>=1; i--)
  99:      {
 100:   
 101:          if (remainspace >= w[i])
 102:          {
 103:              
 104:              if ((c[i][remainspace]-c[i-1][remainspace-w[i]]==v[i]))
 105:              {
 106:                  cout << "item " << i << " is selected!" << endl;
107: remainspace = remainspace - w[i];//如果第i个物品被选择,那么背包剩余容量将减去第i个物品的重量 ;

//,2012年5月2日21:45:33 修改为remainspace - w[i];以前为 W-w[i];

 108:              }
 109:          }
 110:                  
 111:      }
 112:   
 113:      cout << "Maximum values is " << c[n][W] << endl;
 114:   
 115:      return 0;
 116:  }

  

运行结果如下:

 

image

posted @ 2012-08-30 11:15  奔跑的兔子  阅读(2547)  评论(1编辑  收藏  举报