动态规划背包问题分析

0/1背包

  问题描述

有N件物品和一个容量为V的背包,第i件物品的体积为c[i],价值为w[i]。求将哪些物品放进背包可以使物品价值总和最大(有两种情况:不要求填满背包和填满背包)。

每件商品只有一件,且只能选择放或者不放入背包。

  解决方案

使用动态规划求解,定义一个递归式opt[i][v]表示前i个物品,在背包容量大小为v的情况下,最大的价值。

opt[i][v] = max(opt[i-1][v], opt[i-1][v-c[i]] + w[i])

其中opt[i-1][v]表示第i件物品不装入背包中的总价值,而opt[i-1][v-c[i]]+w[i]表示第i件物品装入背包中的总价值。

通过初始化不同来区别两种情况,第一种情况,不要求填满背包,则:

for i <-0 to V
    f[i] <- 0

第二种情况,要求填满背包,则

f[0] <- 0
for i <-1 to V
    f[i] <- -65536

    可以理解为,在要求填满背包的情况下,我们只选择上一步的最大价值不小于0的情况,也就是说,在上一步已经满足了填满当时容量的条件,表示从0到V的容量里都填满了物品;而不像不要求填满背包那样,只关心最大价值,而不保证每个容量单位里面都有物品。

    由于每个物品只有1件,所以采用从V向下递减,以此保证每个物品在每次循环过程中只被计算了1遍。

花费的时间复杂度为O(V*T)。

  伪代码

PACKAGE(w, c, V)
#ifdef CANEMPTY
    for i <- 0 to V
        f[i] <- 0
#else
    f[0] <- 0
    for i <- 1 to V
        f[i] <- -65536
#endif
    for i <- 0 to n
        for v <- V downto c[i]
            f[v] = max(f[v-c[i]] + w[i], f[v])
    return f[V]

  代码

#include <iostream.h>

using namespace std;
const int V = 100;
const int T = 8;
int f[V+1];
int w[T] = {3, 4, 6, 2, 3, 5, 1, 4};
int c[T] = {15, 25, 20, 10, 30, 20, 5, 15};

int package()
{
#ifdef EMPTY
    f[0] = 0;
    for(int i = 1; i <= V; i++)
        f[i] = -65536;
#else
    for(int i = 0; i <= V; i++)
        f[i] = 0;
#endif    
    for(int i = 0; i < T; i++)
    {
        for(int v = V; v > c[i]; v--)
        {
            f[v] = f[v - c[i]] + w[i] > f[v] ? f[v - c[i]] + w[i]: f[v];
        }
    }
    return f[V];
}
int main(int argc, char *argv[])
{
    
    cout<<"The Max Value is "<<package()<<endl;
    return 0;
}

完全背包问题

  问题描述

有N种物品(数量不限)和一个容量为V的背包,第i件物品的体积为c[i],价值为w[i]。求将哪些物品放进背包可以使物品价值总和最大(有两种情况:不要求填满背包和填满背包)。

  解决方案

动态规划法,推导出递归公式:

f[j] = max(f[j], f[j – w[i]] + v[i])

其中f[j]表示容量为j时的最大价值,f[j-w[i]]+v[i]表示f[j]中包含了第i个物品。

由于每个物品有n件,所以采用从0向上递加,以此保证每个物品在每次循环过程中只被计算了1遍。

同样,两种情况也只是初始化的值不同,同0-1背包问题。

  伪代码

COMPLETE_PACKAGE(w, c, V, N)
#ifdef CANEMPTY
    for i <- 0 to V
        f[i] <- 0
#else
    f[0] <- 0
    for i <- 1 to V
        f[i] <- -65536
#endif
    for i <- 0 to N
        for j <- c[i] to V        
            f[j] = max(f[j], f[j - c[i]] + w[i])
    return f[V]

  代码

#include <iostream.h>
using namespace std;
const int V = 100;
const int T = 8;
int f[V+1];
int w[T] = { 3,  4,  6,  2,  3,  5, 1,  4};
int c[T] = {15, 25, 20, 10, 30, 20, 5, 15};

int complete_package()
{
#ifdef EMPTY
    for(int i = 0; i <= V; i++)
        f[i] = 0;
#else
    f[0] = 0;
     for(int i = 1; i <= V; i++)
         f[i] = -65536;
#endif
    
    for(int i = 0; i < T; i++)
        for(int v = c[i]; v <= V; v++)
            f[v] = (f[v - c[i]] + w[i]) > f[v] ? (f[v - c[i]] + w[i]):f[v];
    return f[V];
}
int main(int argc, char *argv[])
{
    cout<<"The Max Value is "<<complete_package()<<endl;
    return 0;
}

多重背包问题

  问题描述

有N种物品(不定个数)和一个容量为V的背包,第i件物品的体积为c[i],价值为w[i],数量为n[i]。求将哪些物品放进背包可以使物品价值总和最大(有两种情况:不要求填满背包和填满背包)。

  解决方案

可以转换为0-1背包问题。转换方式为,将第i件物品合并成若干种物品,n[i] – 2^k + 1 > 0即k < log(n[i] – 1),取k的最大值,以2^0,2^1…2^(k-1),n[i]-2^k+1为系数,每件合并后的物品的体积和价值都乘以相应的系数组成不同的物品。如:

第i件物品有13件,那么k的最大值为3,系数为1,2,4,6,组成的新物品的体积分别为c[i], 2*c[i],4*c[i]和6*c[i],价值为w[i],2*w[i],4*w[i]和6*w[i]。这样,对所有的物品都进行类似的合并,组成一个新的物品集合,然后利用0-1背包来解决剩下的问题。

    组成新物品系数选定的理由:可以看出,新的系数可以保证在任意组合后,都能获取到0~n[i]个物品的数值,如:n[i]=13,那么我想用其中的10个这样的物品,可以由合并后的系数4和6组成。

  伪代码

Merge_Goods(w, c, n, N)
    j <- 0
    for i <- 0 to N
        p <- 1
        while p < n[i] + 1
            nw[j] <- p * w[i]
            nc[j] <- p * c[i]
            p <- p * 2
            j <- j + 1
        if p / 2 < n[i]
            nw[j] <- (n[i] - p / 2) * w[i]
            nc[j] <- (n[i] - p / 2) * c[i]
    nN = j
    return nN and nw and nc

PACKAGE(nw, nc, V, nN)
#ifdef CANEMPTY
    for i <- 0 to V
        f[i] <- 0
#else    
    f[0] <- 0
    for i <- 1 to V
        f[i] <- -65536
#endif
    for i <- 0 to nN
        for v <- V downto nc[i]
            f[v] <- max(f[v-nc[i]] + nw[i], f[v])
    return f[V]

  代码

#include <iostream.h>

#define MAXNUM 1000
#define EMPTY
int nc[MAXNUM];
int nw[MAXNUM];
int merge_goods(int c[], int w[], int n[], int N)
{
    int j = 0;
    for(int i = 0 ; i < N; i++)
    {
        int p = 1;
        while(p < n[i] + 1)
        {
            nc[j] = p * c[i];
            nw[j] = p * w[i];
            p = p * 2;
            j++;
        }
        if(p/2 < n[i])
        {
            nc[j] = (n[i] - p / 2) * c[i];
            nw[j] = (n[i] - p / 2) * w[i];
            j++;
        }
    }
    return j;
}

int multi_package(int c[], int w[], int n[], int V, int N)
{
    int nN = merge_goods(c, w, n, N);
    int f[V+1];
#ifdef EMPTY
    for(int i = 0 ; i <= V; i++)
        f[i] = 0;
#else
    f[0] = 0;
    for(int i = 1; i <= V; i++)
        f[i] = -65536;
#endif
    for(int i = 0; i < nN; i++)
    {
        for(int j = V; j >= nc[i]; j--)
        {
            f[j] = (f[j - nc[i]] + nw[i] > f[j]) ? (f[j - nc[i]] + nw[i]) : f[j];
        }
    }
    return f[V];
}

int main(int argc, char *argv[])
{
    int T = 8;
    int V = 300;
    int w[] = { 3,  4,  6,  2,  3,  5, 1,  4};
    int c[] = {15, 25, 20, 10, 30, 20, 5, 15};    
    int n[] = { 7, 13,  8,  4, 15, 12, 9, 11};
    cout<<"Max Value is "<<multi_package(c, w, n, V, T)<<endl;
    return 0;
}

二维背包问题

    问题描述

一维、二维或者多维是针对于代价来说的,以前讨论的三种背包问题都是在代价为一维的条件下,获取最大价值的,该问题的代价是二维的,即可以理解为背包有体积和承重两种代价限制,分别为VU,获取在这两种代价的上限内所能得到的最大价值。

    解决方案

如果理解了之前的背包问题,该问题的解决办法也就一目了然了,简单的说,就是将之前的f[v]变为f[v][u],将以前的一次循环,变为了两次循环,其它没有理论上的不同了。

如果是0-1背包问题,uv倒序循环;如果是完全背包问题,则uv正序进行循环。

f[i][u][v] = max(f[i-1][u][v] , w[i] + f[i-1][u-a[i]][v-b[i]])

    伪代码(只写出0-1背包,且不要求某个代价为上限值的情况)

 

Two_Package(w, a, b, U, V, N)
    for i <- 0 to U
        for j <- 0 to V
            f[i][j] <- 0
    for i <- 0 to N
        for u <- U downto a[i]
            for v <- V downto b[i]
                f[v][u] = max(f[u-a[i]][v-b[i]]+w[i], f[u][v])
    return f[u][v]

 

  代码

#include <iostream.h>

int two_package(int w[], int a[], int b[], int U, int V, int N)
{
    int f[U+1][V+1];
    for(int i = 0; i <= U; i++)
        for(int j = 0; j <= V; j++)
            f[i][j] = 0;
    
    for(int i = 0; i < N; i++)
    {
        for(int u = U; u >= a[i]; u--)
        {
            for(int v = V; v >= b[i]; v--)
                f[u][v] = (f[u - a[i]][v - b[i]] + w[i] > f[u][v]) ? (f[u - a[i]][v - b[i]] + w[i]):f[u][v];
        }
    }
    return f[U][V];
}

int main(int argc, char *argv[])
{
    const int V = 120;
    const int U = 140;
    const int T = 8;
    
    int w[T] = { 3,  4,  6,  2,  3,  5,  1,  4};
    int a[T] = {15, 25, 20, 10, 30, 20,  5, 15};
    int b[T] = {10, 30, 15, 10, 20, 20, 10, 20};
    cout<<"Max Value is "<<two_package(w, a, b, U, V, T)<<endl;
    return 0;
}

分组背包问题

  问题描述

有N种物品和一个容量为V的背包,第i件物品的体积为c[i],价值为w[i]。现将所有物品分成若干组,每组中的物品相互冲突,即在选择时,每组中只能最多选择一件物品,求将哪些物品放进背包可以使物品价值总和最大(有两种情况:不要求填满背包和填满背包)。

  解决方案

可以将k个分组看成k个物品,当做0-1背包问题处理,然后再对每个分组中的单个物品进行选择。

f[k][v] = max(f[k][v], f[k][v-c[k][i]]+w[i])

  伪代码

Group_Package(w, c, K, V, N)
    for i <- 0 to V
        f[i] <- 0
    
    for k <- 0 to K
        for v <- V downto 0
            for i <- 0 to N
                if v > c[k][i]
                    f[v] = max(f[v], f[v - c[k][i]] + w[k][i])

    return f[V]

  代码

#include <iostream.h>

const int K = 3;
const int N = 4;

int w[K][N] = {
    {3, 4, 6, 2},
    {3, 5, 1, 4},
    {6, 2, 3, 5}
};

int c[K][N] = {
    {15, 25, 20, 10},
    {20, 15, 30, 20},
    {30, 30, 20, 5}
};
int group_package(int V)
{
    int f[V + 1];
    for(int i = 0; i <= V; i++)
        f[i] = 0;
    
    for(int k = 0; k < K; k++)
    {
        for(int v = V; v >= 0; v--)
        {
            for(int i = 0; i < N; i++)
            {
                if(c[k][i] <= v)
                    f[v] = (f[v - c[k][i]] + w[k][i] > f[v]) ? (f[v - c[k][i]] + w[k][i]) : f[v];
            }
        }
    }
    return f[V];
}

int main(int argc, char *argv[])
{
    int V = 60;
    cout<<"Max Value is "<<group_package(V)<<endl;
    return 0;
}

 

 

 

原文地址:https://www.cnblogs.com/geekma/p/2793355.html