zoukankan      html  css  js  c++  java
  • 0 1背包问题

    转载:http://www.360doc.com/content/12/0504/09/9568648_208525818.shtml

    0-1背包问题:

      有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

       这个问题的特点是:每种物品只有一件,可以选择放或者不放。

    算法基本思想:

      利用动态规划思想 ,子问题为:f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。

      其状态转移方程是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}    //这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的

      解释一下上面的方程:“将前i件物品放入容量为v的背包中”这个子问题,如果只考虑第i件物品放或者不放,那么就可以转化为只涉及前i-1件物品的问题,即1、如果不放第i件物品,则问题转化为“前i-1件物品放入容量为v的背包中”;2、如果放第i件物品,则问题转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”(此时能获得的最大价值就是f [i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i])。则f[i][v]的值就是1、2中最大的那个值

    (注意:f[i][v]有意义当且仅当存在一个前i件物品的子集,其费用总和为v。所以按照这个方程递推完毕后,最终的答案并不一定是f[N] [V],而是f[N][0..V]的最大值。)



    优化空间复杂度:

      以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。

      上面f[i][v]使用二维数组存储的,可以优化为一维数组f[v],将主循环改为:

        for i=1..N

          for v=V..0

            f[v]=max{f[v],f[v-c[i]]+w[i]};

      即将第二层循环改为从V..0,逆序。

    解释一下:

      假设最大容量M=10,物品个数N=3,物品大小w{3,4,5},物品价值p{4,5,6}。

      当进行第i次循环时,f[v]中保存的是上次循环产生的结果,即第i-1次循环的结果(i>=1)。所以f[v]=max{f[v],f[v-c[i]]+w[i]}这个式子中,等号右边的f[v]和f[v-c[i]]+w[i]都是前一次循环产生的值。

    当i=1时,f[0..10]初始值都为0。所以

    f[10]=max{f[10],f[10-c[1]]+w[1]}=max{0,f[7]+4}=max{0,0+4}=4;

    f[9]=max{f[9],f[9-c[1]]+w[1]}=max{0,f[6]+4}=max{0,0+4}=4;

    ......

    f[3]=max{f[3],f[3-c[1]]+w[1]}=max{0,f[3]+4}=max{0,0+4}=4;

    f[2]=max{f[2],f[2-c[1]]+w[1]}=max{0,f[2-3]+4}=0;//数组越界?

    f[1]=0;

    f[0]=0;

    当i=2时,此时f[0..10]经过上次循环后,都已经被重新赋值,即f[0..2]=0,f[3..10]=4。利用f[v]=max{f[v],f[v-c[i]]+w[i]}这个公式计算i=2时的f[0..10]的值。

    当i=3时同理。

    具体的值如下表所示:





    因此,利用逆序循环就可以保证在计算f[v]时,公式f[v]=max{f[v],f[v-c[i]]+w[i]}中等号右边的f[v]和f[v-c[i]]+w[i]保存的是f[i-1][v]和f[i -1][v-c[i]]的值。

    当i=N时,得到的f[V]即为要求的最优值。

    初始化的细节问题:

     在求最优解的背包问题中,一般有两种不同的问法:1、要求“恰好装满背包”时的最优解;2、求小于等于背包容量的最优解,即不一定恰好装满背包。

    这两种问法,在初始化的时候是不同的。

    1、要求“恰好装满背包”时的最优解:

    在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。如果不能恰好满足背包容量,即不能得到f[V]的最优值,则此时f[V]=-∞,这样就能表示没有找到恰好满足背包容量的最优值。

    2、求小于等于背包容量的最优解,即不一定恰好装满背包:

    如果并没有要求必须把背包装满,而是只希望价值尽量大,初始化时应该将f[0..V]全部设为0。

    总结

    01背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基本思想,另外,别的类型的背包问题往往也可以转换成01背包问题求解。故一定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及最后怎样优化的空间复杂度。



    0-1背包问题代码:
    代码1

     1 #include <iostream>
     2 #include <vector>
     3 
     4 using namespace std;
     5 
     6 const int MIN=0x80000000;
     7 const int N=3;   //物品数量
     8 const int V=5;  //背包容量
     9 int f[N+1][V+1];
    10 
    11 int Package(int *W, int *C, int N, int V);
    12 
    13 void main(int argc, char *argv[])
    14 {
    15     int W[4] = {0,7,5,8};      //物品权重
    16     int C[4] = {0,2,3,4};      //物品大小
    17     
    18     int result = Package(W, C, N, V);
    19 
    20     if (result > 0)
    21     {
    22         cout << endl;
    23         cout << "the opt value:" << result << endl;
    24         
    25         int i=N,j=V;
    26         
    27           while (i)
    28           {
    29                if (f[i][j] == (f[i-1][j-C[i]] + W[i]))
    30               {
    31                 cout << i << ":" << "w=" << W[i] << ",c=" << C[i] << endl;
    32                 j -= C[i];
    33                }
    34                
    35                i--;
    36           }
    37      }
    38      else
    39      {
    40           cout << "can not find the opt value" << endl;
    41      }
    42 }
    43 
    44 int Package(int *W, int *C, int N, int V)
    45 {
    46     int i,j;
    47     
    48     memset(f, 0, sizeof(f));  //初始化为0
    49 
    50      for (i=0; i <= N; i++)
    51      {
    52          for (j=1; j <= V; j++)  //此步骤是解决是否恰好满足背包容量,
    53           {
    54               f[i][j] = MIN;   //若“恰好”满足背包容量,即正好装满背包,则加上此步骤,若不需要“恰好”,则初始化为0
    55         }
    56     }
    57     
    58      for (i = 1; i <= N; i++)
    59       {
    60           for (j = C[i]; j <= V; j++)
    61           {
    62                f[i][j] = (f[i-1][j] > f[i-1][j-C[i]] + W[i]) ? f[i-1][j] : (f[i-1][j-C[i]] + W[i]);
    63                cout << "f[" << i << "][" << j << "]=" << f[i][j] << endl;
    64           }
    65       }
    66       
    67      return f[N][V];
    68 }

    代码2

     1 #include <iostream>
     2 #include <vector>
     3 
     4 using namespace std;
     5 
     6 const int MIN = 0x80000000;
     7 const int N = 3;   //物品数量
     8 const int V = 5;  //背包容量
     9 int f[V+1];
    10 
    11 int Package(int *W, int *C, int N, int V);
    12 
    13 void main(int argc, char *argv[])
    14 {
    15      int W[4] = {0, 7, 5, 8};      //物品权重
    16     int C[4] = {0, 2, 3, 4};      //物品大小
    17     
    18     int result = Package(W, C, N, V);
    19     
    20      if (result > 0)
    21      {
    22           cout << endl;
    23           cout << "the opt value:" << result << endl;
    24      }
    25      else
    26      {
    27           cout<<"can not find the opt value"<<endl;
    28       }
    29 }
    30 
    31 int Package(int *W, int *C, int N, int V)
    32 {
    33      int i,j;
    34      
    35      memset(f, 0, sizeof(f));  //初始化为0
    36 
    37      for (i = 1; i <= V;i++)   //此步骤是解决是否恰好满足背包容量,
    38       {
    39           f[i] = MIN;    //若“恰好”满足背包容量,即正好装满背包,则加上此步骤,若不需要“恰好”,则初始化为0
    40     }
    41     
    42      for (i = 1; i <= N; i++)
    43       {
    44           for (j = V; j >= C[i]; j--)    //注意此处与解法一是顺序不同的,弄清原因
    45           {
    46                f[j] = (f[j] > f[j-C[i]] + W[i]) ? f[j] : (f[j-C[i]]+W[i]);
    47                cout << "f[" << i << "][" << j << "]=" << f[j] << endl;
    48           }
    49       }    
    50  
    51      return f[V];
    52 }


    参考:

      http://blog.sina.com.cn/s/blog_4cd99cfa0100mer2.html

      http://www.cnblogs.com/tanky_woo/archive/2010/07/31/1789621.html

    逆推装入的物品:

       确定装入背包的具体物品,从value[n][m]向前逆推:
               若value[n][m]>value[n-1][m],则第n个物品被装入背包,且前n-1个物品被装入载重量为m-w[n]的背包中
               否则,第n个物品没有装入背包,且前n-1个物品被装入载重量为m的背包中
     
          以此类推,直到确定第一个物品是否被装入背包为止。逆推代码如下:
                  
        //逆推求装入的物品
        j=m;
        
    for(i=row-1;i>0;i--)
        
    {
            
    if(value[i][j]>value[i-1][j])
            
    {
                c[i]
    =1;
                j
    -=w[i];
            }

        }
  • 相关阅读:
    架构之路(六):把框架拉出来
    读取mdb文件
    基类、子类之间的类型转换
    WPF Trigger
    WPF 打开txt文件
    C# 匿名方法
    自定义显隐式类型转换
    枚举获得Description扩展方法
    IFormattable和IFormatProvider
    WPF DataGrid下滑动态加载数据
  • 原文地址:https://www.cnblogs.com/ldjhust/p/3150604.html
Copyright © 2011-2022 走看看