zoukankan      html  css  js  c++  java
  • 01背包问题动态规划详解

    最近开始学习背包问题,在此做些笔记,先学习最简单的0-1背包问题。

    0-1背包问题介绍:

    一,背包问题基本解决

           有一个背包可以存放M斤物品,有N件物品(每件物品只有1件),他们重量分别是w1,w2,w3..........,他们价值分别是p1,p2,p3...............。问怎么装载物品,使背包装的载物品价值最大?

           举例说明:

           背包装10斤物品,有3件物品,重量分别是3斤,4斤,5斤,价值分别是4,5,6;

    可以画一个矩阵,行号0,1,2,3,行号0表示0件物品可放入背包情况下,背包装载最大价值,行号1表示只有第一件物品可以放入背包情况下,背包装载最大价值,行号2表示只有第1件和第2件可以放入背包情况下,背包装载最大价值,......

          列号0,1,2,3.............10表示背包剩余可用容量,列号0表示背包已满时候,剩余容量为0时候,还可以装载的最大价值;列号1表示背包剩余容量为1时候,还可以装载的最大价值,......

          当行号是3,列号是10时候,价值最大,即c[3][10]值最大.

           行号是0,表示有0件物品可放入背包,此时背包装载0件物品,因此价值都为0,即c[0][0]=c[0][1]=.....c[0][10]=0;

           行号是1,表示第一件物品可以放入背包,第一件物品重量是3,当列号为0,1,2时候,一件物品也放不下,背包装载价值是0,当列号3,4,5...........10可以放下,但是只有一件物品,因此最多获取价值是4,即c[1][3]=c[1][4]..........c[1][10] = 4;

           行号是2,表示第一件和第二件物品可以放入背包,第一件物品重3斤,第二件重4斤,当列号0,1,2时候,一件物品也放不下,背包装载价值是0,当列号为3时候,可以放下第一件,但是放不下第二件,价值是4,列号为4时候,可以放下第一件也可以放下第二件,此时分为两种情况,放第二件或不放第二件,取两者最大值即可。放第二件,剩余容量是0,此时价值是p[2]+容量0,行号是1时最大值,即p[2]+c[1][0]=5,不放第二件,则是剩余容量是4,行号是1时,最大值,即c[1][4] = 4,则5>4,我们存放第二件,此时背包装载最大价值是5,当列号为5,6时候,价值5,当列号为7时候,max{不放第二件,放第二件},不放第二件,即列号为7,行号为1最大值,c[1][7]=4;放第二件,为:p[2]+列号为7-w[2]=7-4=3,行号为1时最大值,为:p[2]+c[1][3]=5+4=9,9>4,我们选择存放第二件,此时装载了两件物品,列号8,9,10也是9.

           当行号是2时,我们可以得出规律:最大值=max{存放第二件,不存放第二件}=max{p[2]+c[1][列号-w[2]],c[1][列号]},而列号就是从1到背包容量V之间某个值。

           当行号是1时,我们可以得出规律:最大值=max{存放第一件,不存放第一件}=max{p[1]+c[0][列号-w[1]],c[0][列号]}=max{p[1],0},当列号>=w[1],最大值p[1],否则0。

           我们由此得出一个疑问:为什么列号是0,1,2....10,可以这样(列号=10不变)吗?因为我们只求背包剩余容量为10时,装载价值最大值。

    答案是不可以的,比如求当行号是3,列号是10最大值,即最大值=c[3][10] = max{p[3]+c[2][5],c[2][10]},欲求c[3][10]必须知道c[2][5]和c[2][10],而c[2][5] = max{p[2] + c[1][1],c[1][5]},欲求c[2][5]必须求c[1][1]c[1][5],我们求c[i][10]时候会用到c[i][j],此时0<=j<=10,因此我们必须从行号是0开始,把列号0......10之间,所有c[i][j]求出来,因为后面要用到,这是一个不断递推过程。

           因此,得出结论:

           将前i件物品放入背包中,求背包装载最大价值问题。只考虑第i件物品策略(第i件放还是不放),如果不放第i件物品,求前 i-1 件物品放入容量背包,背包装载最大价值问题,最大价值为c[i-1][v];如果放第i件物品,求 i-1 件物品放入容量 v-w[i] 容量背包,背包装载最大价值问题,最大价值为p[i] +c[i-1][v-w[i-1]];

    因此c[i][j] = max{c[i-1][v],p[i]+c[i-1][v-w[i-1]]};

           因此我们得出核心代码:

    for (int i = 1; i <= N; i++){
        for(int j = 1; j <= M; j++){
            if (c[i][j] >= w[i]){
                if (c[i-1][j] > p[i]+c[i-1][j-w[i]]){
                    c[i][j] = c[i-1][j];
                }else{
                    c[i][j] = p[i] + c[i-1][j-w[i]];
                }
            }else{
                c[i][j] = c[i-1][j];
            }
        }
    }

    下面是用C++简单实现程序(VS2008通过):

     1 #include <cstdio>
     2 const int  K = 100;
     3 
     4 
     5 /*
     6 输入格式:
     7 10 3    //M=10,N=3
     8 3 4     //w[1]=3,p[1]=4
     9 4 5     //w[2]=4,p[2]=5
    10 5 6     //w[3]=5,p[3]=6
    11 */
    12 
    13 int max(int a,int b){
    14     return a>b?a:b;
    15 }
    16 int main(){
    17     int M,N,w[K],p[K],i,j,c[K][K];
    18     scanf("%d%d",&M,&N);
    19     for (i = 1;i <= N;i++)
    20     {
    21         scanf("%d%d",&w[i],&p[i]);
    22     }
    23     for (i = 0;i <= N;i++)
    24     {
    25         for (j = 0;j <= M;j++)
    26         {
    27             c[i][j] = 0;
    28         }
    29     }
    30 
    31     for (i = 1;i <= N;i++)
    32     {
    33         for (j = 1;j <= M;j++)
    34         {
    35             if (w[i] <= j)
    36             {
    37                 c[i][j] = max(c[i-1][j],c[i-1][j-w[i]]+p[i]);   //方式1
    38 /*                if (c[i-1][j] > c[i-1][j-w[i]]+p[i])    //方式2
    39                 {
    40                     c[i][j] = c[i-1][j];
    41                 }else{
    42                     c[i][j] = c[i-1][j-w[i]]+p[i];
    43                 }
    44 */
    45             }else{
    46                 c[i][j] = c[i-1][j];
    47             }
    48         }
    49     }
    50 
    51     printf("%d\n",c[N][M]);
    52     return 0;
    53 }
    View Code

     运行结果:

    二,背包问题优化解决

              在以上解决方案中,所用时间复杂度是O(M*N),空间复杂度是O(M*N),用到了一个M*N的数组。如果仔细观察之后,发现可以用一维数组代替二维数组。

              当行号为0时,数组c[0][0]=c[0][1]=...................=c[0][10] = 0;

              当行号为1时,根据c[i][j] = max{c[i-1][j],p[i]+c[i-1][j-w[i]]},得c[1][j]=max{c[0][j],p[1]+c[0][j- w[1]]}。即c[1][0,1,2.......,9,10]由c[0][0,1,2.......,9,10]得到。

              因此我们可以利用a[j]表示c[0][j],用b[j]表示c[1][j],首先b[0]=a[0] = 0,b[1]=a[1]=0,.........b[10]=a[10]=0。将数组b和数组a值设为一样。然后如果w[i]< j,再根据公式b[j]=max{a[j],p[1]+a[j-w[1]]},重新确定b[j]的值,如果w[i] >= j,则不变。

              但是我们仔细观察一下次公式b[j]=max{a[j],p[1]+a[j-w[1]]},发现b[j]得值由a[k],p[1]和w[1]所得,而k的 取值范围是0<=k<=j,即b[j]由数组a中下标小于j的元素得到。因此我们可以将j从大到小使用来得到b[j],当i = 0时候,a[0] =  a[1] = a[2]=a[3]=..............=a[10]=0,当i=1时,先设定 b[0] = a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],..............=b[10]=a[10],我们先计算 b[10],b[10]=max{a[10],p[1]+a[10-w[1]]}=max{a[10],4+a[7]},此处 p[1]=4,w[1]=3;因为a[10]=b[10],a[7]=b[7],因此b[10]=max{b[10],4+b[7]}=4,至此数组b和 数组a只有第10个元素值不想等,其他元素都相等;b[9]=max{a[9],p[1]+a[9- w[1]]}=max{a[9],4+a[6]},a[9]=b[9],a[6]=b[6],因此b[9]=max{b[9],b[6]+4}=4,至此 数组b和数组a只有第9,10个元素值不想等,其他元素都相等;因此求的 b[8]=max{b[8],4+b[5]},b[7]=max{b[7],4+b[4]}.............b[3]=max{b[3],4+b[0]}。 在求解过程中,我们发现只要按数组b元素下标从大到小求解,就可以用自己求的自己的值。

    至此我们可以利用一个一维数组代替二维数组。

    核心代码:

    for (int i = 1; i <= N; i++){
        for(int j = M; j > 0; j--){
            if (c[i] <= j){
                if (c[j] > p[i]+c[j-w[i]]){
                    c[j] = c[j];
                }else{
                    c[j] = p[i] + c[j-w[i]];
                }
            }
        }
    }

    下面是用C++简单实现程序(VS2008通过):

     1 #include <cstdio>
     2 const int  K = 100;
     3 
     4 
     5 /*
     6 输入格式:
     7 10 3    //M=10,N=3
     8 3 4     //w[1]=3,p[1]=4
     9 4 5     //w[2]=4,p[2]=5
    10 5 6     //w[3]=5,p[3]=6
    11 */
    12 
    13 int max(int a,int b){
    14     return a>b?a:b;
    15 }
    16 
    17 int main(){
    18     int M,N,w[K],p[K],i,j,f[K] = {0};
    19     scanf("%d%d",&M,&N);
    20     for (i = 1;i <= N;i++)
    21     {
    22         scanf("%d%d",&w[i],&p[i]);
    23     }
    24 
    25     for (i = 1;i <= N;i++)
    26     {
    27         for (j = M;j > 0;j--)
    28         {
    29             if (w[i] <= j)
    30             {
    31                 f[j] = max(f[j],p[i]+f[j-w[i]]);
    32             }
    33         }
    34     }
    35 
    36     printf("%d\n",f[M]);
    37     return 0;
    38 }
    View Code

    运行结果:

  • 相关阅读:
    LeetCode-Longest Substring Without Repeating Characters
    LeetCode-Add Two Numbers
    LeetCode-Two Sum
    品格的塑造
    闰年的来历
    float在内存中的存取方法
    矩阵顺时针旋转90度
    研究生毕业论文查重
    PAT1009
    PAT1008
  • 原文地址:https://www.cnblogs.com/usa007lhy/p/3087195.html
Copyright © 2011-2022 走看看