【背包问题】背包问题之0-1背包、完全背包、多重背包

时间:2021-05-07 18:41:48

最近要解决一个背包问题,查阅网上的文章后,摘取自己需要的记录下来,不定期补充理解内容。

注:参考文献中内容多来自《背包问题九讲》


1. 0-1背包(ZeroOnePack)

问题描述:有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件不放进去,这时所得价值为:f[i-1][v]

第二种是第i件放进去,这时所得价值为:f[i-1][v-c[i]]+w[i]

(第二种是什么意思?就是如果第i件放进去,那么在容量v-c[i]里就要放进前i-1件物品)

最后比较第一种与第二种所得价值的大小,哪种相对大,f[i][v]的值就是哪种。

1for i=1..N2   for v=V..03        f[v]=max{f[v],f[v-c[i]]+w[i]};

2. 完全背包(CompletePack)

问题描述:有N种物品和一个容量为V的背包,(每种物品都有无限件可用)第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

完全背包按其思路仍然可以用一个二维数组来写出:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

注意:0-1背包代码中内循环逆序,完全背包中代码内循环顺序,具体见参考文件1;

for i=1..N    for v=0..V        f[v]=max{f[v],f[v-c[i]]+w[i]}


3. 多重背包(MultiplePack)

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

基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}

这里同样转换为01背包:

普通的转换对于数量较多时,则可能会超时,可以转换成二进制。

【参考文献1】http://www.cppblog.com/tanky-woo/archive/2010/07/31/121803.html


4. 多重背包问题求解

说明:以下内容和代码来自于参考文献二;

多重背包问题类似于完全背包问题,可以像完全背包一样作为0-1背包问题的扩展问题直接求解,也可以采用二进制拆分策略拆分为0-1背包问题求解;

方法一:直接求解,类似于完全背包问题。

#include <iostream>  using namespace std;  const int N = 3;//物品个数  const int V = 8;//背包容量  int Weight[N + 1] = {0,1,2,2};  int Value[N + 1] = {0,6,10,20};  int Num[N + 1] = {0,10,5,2};  int f[N + 1][V + 1] = {0};  /* f[i][v]:表示把前i件物品放入容量为v的背包中获得的最大收益。 f[i][v] = max(f[i - 1][v],f[i - 1][v - k * Weight[i]] + K * Value[i]);其中1 <= k <= min(Num[i],V/Weight[i]) //初始化 f[i][0] = 0; f[0][v] = 0; */  int MultiKnapsack()  {      int nCount = 0;      //初始化      for (int i = 0;i <= N;i++)      {          f[i][0] = 0;      }      for (int v = 0;v <= V;v++)      {          f[0][v] = 0;      }      //递推      for (int i = 1;i <= N;i++)      {          for (int v = Weight[i];v <= V;v++)          {              f[i][v] = 0;              nCount = min(Num[i],v/Weight[i]);//是当前背包容量v,而不是背包的总容量              for (int k = 0;k <= nCount;k++)              {                  f[i][v] = max(f[i][v],f[i - 1][v - k * Weight[i]] + k * Value[i]);              }          }      }      return f[N][V];  }  int main()  {      cout<<MultiKnapsack()<<endl;      system("pause");      return 1;  }  

方法二:拆分为0-1背包

一种是直接对每一件物品进行拆分成min(Num[i],V/Weight[i])件,之后在拆分后的集合上进行0-1背包的求解;

另一种是二进制拆分,二进制拆分能保证对于0,,,Num[i]间的每一个整数,均可以用若干个系数的和表示。

对每i件物品,拆分的策略为:新拆分的物品的重量等于1件,2件,4件,..,(2^(k - 1)),Num[i] - (2^(k - 1))件,其中k 是满足Num[i] - 2^k + 1 > 0 的最大整数。

举例:某物品为13件,则其可以分成四件物品,其系数为1,2,4,6.这里k = 3。注意,

(1)最后一个物品的件数的求法和前面不同,其直接等于 该物品的最大件数 - 前面已经分配之和。

(2)分成的这几件物品的系数和为Num[i],表明第i种物品取的件数不能多于Num[i]。

(3)如果第i个物品的重量Weight[i] * 物品的个数Num[i] >= 背包总重量V,可以不用拆分。

这里(3)的原因是:物品拆分后,其个数肯定增加,那么复杂度肯定上去。因此,我们可以选择性地对物品进行拆分,不用拆分的背包类似完全背包问题,可以调用完全背包函数求解;否则调用0-1背包函数求解;

#include <iostream>  using namespace std;    const int N = 3;//物品个数  const int V = 8;//背包容量  int Weight[N + 1] = {0,1,2,2};  int Value[N + 1] = {0,6,10,20};  int Num[N + 1] = {0,10,5,2};    int f[V + 1] = {0};  /* f[v]:表示把前i件物品放入容量为v的背包中获得的最大收益。 f[v] = max(f[v],f[v - Weight[i]] + Value[i]); v的为逆序 */  void ZeroOnePack(int nWeight,int nValue)  {      for (int v = V;v >= nWeight;v--)      {          f[v] = max(f[v],f[v - nWeight] + nValue);      }  }    /* f[v]:表示把前i件物品放入容量为v的背包中获得的最大收益。 f[v] = max(f[v],f[v - Weight[i]] + Value[i]); v的为增序 */  void CompletePack(int nWeight,int nValue)  {      for (int v = nWeight;v <= V;v++)      {          f[v] = max(f[v],f[v - nWeight] + nValue);      }  }    int MultiKnapsack()  {      int k = 1;      int nCount = 0;      for (int i = 1;i <= N;i++)      {          if (Weight[i] * Num[i] >= V)          {              //完全背包:该类物品原则上是无限供应,              //此时满足条件Weight[i] * Num[i] >= V时,              //表示无限量供应,直到背包放不下为止.              CompletePack(Weight[i],Value[i]);          }          else          {              k = 1;              nCount = Num[i];              while(k <= nCount)              {                  ZeroOnePack(k * Weight[i],k * Value[i]);                  nCount -= k;                  k *= 2;              }              ZeroOnePack(nCount * Weight[i],nCount * Value[i]);          }      }      return f[V];  }    int main()  {      cout<<MultiKnapsack()<<endl;      system("pause");      return 1;  }  


【参考文献2】http://blog.csdn.net/insistgogo/article/details/11176693