• 背包问题-01背包


    背包问题-01背包

    背包问题-01背包

    1 问题描述

    背包问题主要分为三种:01背包 完全背包 多重背包.

    01背包就是本文要讨论的问题.

    有N件物品和一个容量为W的背包,每种物品 均只有一件.第 I 件物品的容量是W[I],价值是V[I].求解将哪些物品装入背包可使价值总和最大.

    完全背包

    有N种物品和一个容量为W的背包,每种物品都有 都有无数件.第 I 件物品的容量是W[I],价值是V[I].求解将哪些物品装入背包可使价值总和最大.

    多种背包

    有N中物品和一个容量为W的背包,每种物品最多有 N[I]件.第 I 件物品的容量是 W[I],价值是V[I].求解将哪些物品装入背包可使价值总和最大.

    三种不同的问题,最要的差别是背包的数量:01背包是每种都只有一件,完全背包都有无数件,多种背包都有有限件.

    2 问题思路

    2.1 问题定义

    下面分析01背包问题.这是所有背包问题的基础

    有N件物品和一个容量为W的背包,每种物品 均只有一件.第 i 件物品的容量是w[i],价值是v[i].求解将哪些物品装入背包可使价值总和最大.

    定义dp[i][j] := 从前i个物品中选出总重量不超出j的物品是总价是的最大值.每个背包标号为1,2,3…n.是从1开始的.

    选择每个背包的时候都有两种选择:选择或这不选.所有第i个物品的价值应该是:max(dp[i-1][j],dp[i-1][j-w[i]]+v[j]).

    初始值: i = 0时,dp[i][j] = 0;

    dp[i-1][j]表示不选择第i个背包,就跟前一个背包的价值是一样的.

    dp[i-1][j-w[i]]+v[i]表示选择第i个背包,那么选择第 i-1 个背包时候容量的限制值就是j-w[i]而不是j,dp[i-1][j-w[i]]就是第i-1个背包的价值.

    所以该问题的状态方程如下:

    dp[0][j] = 0;
    if(j < w[i]) // 此时装不下第i个背包
      dp[i][j] = dp[i-1][j];
    else
      dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
    

    2.2 实例演讲

    下面以一个具体的实例来分析:

    n = 4,w = 5;

    (w,v) = ((2,3),(1,2),(3,4),(2,2))

    根据状态方程画出表格结果如下:

    最大容量W物品个数ijjjjjj
    54012345
    物品重量物品价值0000000
    231003333
    122023555
    343023567
    224023567

    最后是这个采用动态规划解决此问题的代码

     1:  #include <stdio.h>
     2:  #include <stdlib.h>
     3:  
     4:  /** author: cjr
     5:    * date: 2014-08-11
     6:    * 01背包问题-linux下测试通过
     7:    */
     8:  
     9:  // 这么简单的函数就不解释了吧!
    10:  int max(int a,int b)
    11:  {
    12:    if(a > b)
    13:        return a;
    14:    else
    15:        return b; 
    16:  }
    17:  
    18:  int main()
    19:  {
    20:    int i,j,n,max_weight;
    21:    int **dp = NULL,*weight=NULL,*value=NULL;
    22:    scanf("%d %d",&n,&max_weight);
    23:  
    24:    weight = malloc(sizeof(int)*n);
    25:    value = malloc(sizeof(int)*n);
    26:  
    27:    // 动态二维数组并赋初值为0
    28:    dp = (int**)malloc(sizeof(int*)*(n+1));
    29:    for(i = 0; i <= n; i++)
    30:    {
    31:      dp[i] = (int*)malloc(sizeof(int)*(max_weight+1));
    32:      memset(dp[i],0,(max_weight+1)*sizeof(int));
    33:    }
    34:    //注意这里是从1开始的不是0,要根据问题定义相一致
    35:    for(i = 1; i <= n; i++)
    36:        scanf("%d %d",&weight[i],&value[i]);
    37:  
    38:    for(i = 1; i <= n; i++)
    39:    {
    40:      for(j = 1; j <= max_weight; j++)
    41:        {
    42:            if(j < weight[i])
    43:                dp[i][j] = dp[i-1][j];
    44:            else
    45:                dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
    46:            printf("%d",dp[i][j]);
    47:        }
    48:        printf("
    ");
    49:    }
    50:     printf("%d
    ",dp[n][max_weight]);
    51:  
    52:    return 0;
    53:  }
    54:  
    

    3 问题思考

    3.1 优化-定义问题

    3.1.1 索引的改变

    dp[i+1][j]:= 从前 i 个背包中选出总重量不超过 j 的物品时总价值的最大值

    dp[0][j] = 0;
    if(j < w[i])
      dp[i+1][j] = dp[i][j];
    else
      dp[i+1][j] = max(dp[i][j],dp[i][j-w[i]]+v[i]);
    

    这样i的循环就可以从0开始了,那么背包和价值都是从0开始索引了,即第一个背包的索引是0,而不是上面的1.

    3.1.2 顺序的改变

    dp[i][j]:= 从第 i 个物品开始挑选总重量不超过j时总价值的最大值.

     1:  dp[n][j] = 0;
     2:  for(i = n-1; i >= 0; i--)
     3:  { 
     4:     for(j = 0; j <= W; i++)
     5:        if(j < w[i])
     6:           dp[i][j] = dp[i+1][j];
     7:        else
     8:           dp[i][j] = max(dp[i+1][j],dp[i+1][j-w[i]]+v[i]);
     9:     }
    10:  }
    

    具体的推算结果表格如下表,读者可以自己尝试一下.这个我是看的参考书,自己也推出来了.

    ij012345
    0023567
    1022466
    2002446
    3002222
    4000000

    3.2 优化-复杂度

    该问题的时间复杂度是o(N*W),空间复杂度是o(N*W);时间复杂度已经无法再优化,但是空间复杂度还是可以优化的,用一维数组dp[v]来存放结果,但是内层的重量循环必须 逆序 !!!

    首先要知道for(i~n)表示前 i 件物品存入的状态.当用一维数组dp[w]时,dp[w]表示把前i件物品放入容量为w的背包里得到的价值.把i从1~n循环后,最后的dp[w]就是所求的最大值.

    现在的状态方程是 dp[w] = max(dp[w],f[w-w[i]]+v[i])

    1:  w = 0; dp[w]=0;
    2:  for(i = 1; i <= n; i++)
    3:      for(j = 1; j <= W; j++)
    4:        dp[j] = max(dp[j],dp[w-w[i]]+v[i]);   
    

    思考为什么要逆序?

    还是用上面的例子画表格[正序]

    最大重量物品个数ijjjjjjj
    54
    物品重量物品价值012345
    正向顺序->->->->->->
    初始值0000000
    231003366
    122023579
    343
    224

    分析:

    当装第一个物品时:

    当j < 2,f[j] = 0; 
    当j = 3,f[j] = 3; f[3] = max(f[3]=0; f[3-w[1]]+v[1]=3)
    当j = 4,f[j] = 6? f[4] = max(f[4]=0(这是上一行的结果);f[4-w[1]]+v[1]=6);
    同理 j=5;f[j]=6;
    

    j = 4/5时,把该背包装了两次,明显不合题意! 所以正向顺序的错误就是出在此处!.

    还是用上面的例子画逆序的表格

    最大重量物品个数ijjjjjjj
    54
    物品重量物品价值012345
    正向顺序<-<-<-<-<-<-
    初始值0000000
    231003333
    122023555
    343
    224

    此时的结果就是正确的了.

    上面是用实际的例子来说明这个原因的.实际上也可以从理论上解释.

    正序的时候,dp[i][j]变成dp[i][j-w[i]]+v[i]了而不是dp[i-1][j-w[i]+v[i];这样状态方程就改变了.

    所以正序情况下dp[v]已经更新了,随着j的增大,dp[j-w[i]]的值不再是上一个状态的值了,相反逆序的情况下就没有改变.

    所以逆序能够保证dp[j]和dp[j-w[i]]是上一个状态的,但是正序确实完全背包问题的重要解决方案!

    3.3 初始值的思考

    部分的blog中都谈论到这个问题,本人思考的不是很明白;

    • 要求背包必须装满
      dp[0]=0; dp[1~W]=-INF;表示当重量容纳值为0时,只接受一个重量为0的物品.
      
    • 要求背包可以空下
      dp[0~W]=0; 表示任意容积的背包都有一个有效的解为0;
      

    具体解释如下: 初始化的dp数组事实上就是在没有任何物品可以放入背包时的合法状态.

    如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing恰好装满.

    其他容量的背包均没有合法的解.属于未定义的状态,他们的值就都应该是-INF了.

    如果背包并非必须被装满,那么任何容量的背包都有一个合法解"什么都不装",

    这个解的价值就是0,所以初始值状态的值也就全部为0了.

    4 问题延伸

    4.1 01背包问题的其他解法

    待完善…

    4.2 01背包问题的实际引用

    待完善…

    Date: 2014-08-14 Thu

    Author: Chen Jingran

    Org version 7.8.11 with Emacs version 24

    Validate XHTML 1.0
  • 相关阅读:
    gvim编辑器 命令列表
    svn 使用
    php 实现 从一个目录中 把指定类型文件 重新组装到新的目录
    gvim 编辑器代码折叠
    简单的 文件 管理操作
    javascript DOM object 操作
    gvim代码补全
    gvim 编辑器配置
    javascript DOM model操作
    SQL分页语句 (转载)
  • 原文地址:https://www.cnblogs.com/programmer-cjr/p/3913743.html
Copyright © 2020-2023  润新知