背包问题

-------------------  0-1背包  ------------------- 

问题描述:

N件物品和容量为V的背包;每种物品均只有一件,且第i件物品重量为weight[i],价值为value[i]。求将哪些物品放入背包可使物品重量总和不超过背包容量,且价值总和达到最大?

解题思路:

首先定义问题状态,DP[i][V]表示前i件物品放入容量为V的背包所能获得的最大值(以下分析都如此)。

若只考虑第i件物品的放入策略,即放入或不放入两种情况,问题就可转化为一个只涉及前i-1件物品的问题,得状态转移方程:

DP[i][v] = max{ DP[i-1][v],  DP[i-1][v-weight[i]] + value[i] }

空间复杂度优化:

用一维数组存储DP值,用DP[0,1,...V]表示,DP[v]表示把前i件物品放入容量为v的背包所得到的最大价值,这样可将空间复杂度从O(VN)降低到O(V)。

那么就要解决从二维转成一维过程是否合理的问题:怎样才能在DP[v]表示当前状态(即前i件物品放入容量为v的背包中所得价值)的同时,使得DP[v]和DP[v-weight[i]]分别都能标记前一状态(即前i-1件物品分别放入容量为v和v-weight[i]的背包所得价值)?

解决方法:时间复杂度O(VN)不变,仍为两重for循环,从下图可以看出,当外层为i循环,内层为v循环时,DP[0,1,...V]由于不断覆盖旧值的原因,将始终保存的是蓝色区域下边缘的系列值,对比观察改进前后的两个状态转移方程,可知此处DP[v]的值应采用逆序保存。

伪代码如下:

for i = 1,2...N
    for v = V...1,0
        DP[v] = max{DP[v], DP[v-weight[i]] + value[i]};

 

 

-------------------  完全背包  -------------------

问题描述:

N件物品和容量为V的背包;每件物品无限可用,且第i件物品重量为weight[i],价值为value[i]。求将哪些物品放入背包可使物品重量总和不超过背包容量,且价值总和达到最大?

解题思路:

按照分析01背包思路,同样根据每件物品的放入策略,有很多种情况(不是无限种,有 0 <= k*value[i] <= v 约束条件),可得状态转移方程:

DP[i][v] = max{ DP[i-1][v],  DP[i-1][v-k*weight[i]] + k*value[i] }

空间复杂度优化:

同0-1背包优化方法,DP的值仍采用一维存储,唯一区别在于DP[v]值改为顺序保存。

这就产生了疑问:完全背包中物品的放入策略不再是两种,而是多种情况,当DP用二维存储时,程序为三重for循环,那么优化后采用一维存储为什么没有了关于k的循环?

原因分析:在这里引用背包系列之完全背包中相关解释:

首先想想为什么P01中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。

由此可见,二维存储转成一维存储之前,先要在思维上过渡:

DP[i][v] = max{DP[i-1][v], DP[i-1][v-k*weight[i]] + k*value[i]}

                            ||
                           \  /
                            \/
   DP[i][v] = max{DP[i-1][v], DP[i][v-weight[i]] + value[i]}

                            ||
                           \  /
                            \/
       DP[v] = max{DP[v], DP[v-weight[i]] + value[i]};

 

伪代码如下:

for i = 1,2...N
    for v = 0,1...V
        DP[v] = max{DP[v], DP[v-weight[i]] + value[i]};

 

-------------------  多重背包  -------------------

问题描述:

N件物品和容量为V的背包;第i件最多有num[i]件可用,且第i件物品重量为weight[i],价值为value[i]。求将哪些物品放入背包可使物品重量总和不超过背包容量,且价值总和达到最大?

代码如下:

void ZeroOnePack(int v, int w)
{
    for(int j = V; j >= w; j--)  
        DP[j] = max(DP[j], DP[j-w]+v);
}
void CompletePack(int v, int w)
{
    for(int j = w; j <= V; j++)   
        DP[j] = max(DP[j], DP[j-w]+v);
}
void MultiplePack(int v, int w, int n)
{
    if(w*n >= V)
    {
        CompletePack(v, w);      // 纯粹的完全背包
        return ;    
    } 
    int k = 1;
    while(k < n)
    {
        ZeroOnePack(k*v, k*w);   // 二进制思想分解
        n = n - k;
        k = k * 2;
    }
    ZeroOnePack(n*v, n*w);
}
/************************************************/
// 外层的物品种类循环在main函数中 for(i = 1; i <= N; i++) { // 内层的容量循环在MultiplePack中讨论处理 MultiplePack(v[i], w[i], num[i]); }

posted on 2015-07-06 16:11  huashunli  阅读(154)  评论(0编辑  收藏  举报

导航