动态规划:01揹包

问题描述

01揹包是一个可以用动态规划解决的经典问题:给定 n 种物品和一揹包。物品 i 的重量是 wi ,其价值为 vi ,揹包的容量为 c 。问应如何选择装入揹包中的物品,使得装入揹包的物品的总价值最大?
在选择装入揹包的物品时,对每种物品 i 只有两种选择,即装入揹包或不装入揹包。不能将物品 i 装入揹包多次,也不能只装入部分的物品 i 。因此,称为01揹包问题。

形式化描述

给定 c>0,wi>0,vi>0,1in ,要求找出一个 n 元0-1向量 (x1,x2,,xn),xi{0,1},1in , 使得 i=1nwixic , 而且 i=1nvixi 达到最大。因此,0-1揹包是一个特殊的整数规划问题

maxi=1nvixi


i=1nwixicxi{0,1},1in

递归关系

01揹包具有最优子结构性质,证明略。下面来根据其递归关系得到递推公式。
设所给01揹包问题的子问题

maxk=invkxk


k=inwkxkjxk{0,1},ikn

的最优值为

m(i,j) ,即
m(i,j) 是揹包容量为 j ,可选择物品为 i,i+1,,n 时0-1揹包问题的最优值。由0-1揹包问题的最优子结构性质,
分别考虑拿上或者不拿物品 i , 以及揹包剩余容量是否足够装下物品 i ,可以建立计算

m(i,j) 的递归式如下:


m(i,j)={max{m(i+1,j),m(i+1,jwi)+vi}     jwim(i+1,j)     0j<wi


m(n,j)={vn     jwn0     0j<wn

这样编写的程序会计算得到一张

m(i,j) 的二维表,最后的结果是(代码中下标从0开始):


ans=m(0,c)

计算过程

现在有五件物品,重量分别是2,2,6,5,4,价值分别是6,3,5,4,6,揹包最多拿上重量10个单位的物品,如何让揹包里装入的物品具有最大的价值总和?
下表是按照上述算法的计算结果,表格计算顺序为自下向上,从左到右。

iweightvalue012345678910
1260066991212151515
2230033669991011
3650000666661011
4540000666661010
54600006666666

例如,对于 m(2,6) 代表的是揹包容量为6,可选择物品为2,3,4,5时0-1揹包的最优值。那么 m(2,6)=max{m(3,6),m(3,62)+3}=max{6,9}=9

代码

有一点需要注意的是:在上面描述问题的时候始终是从1开始计数的,代码的话是从下标0开始的。可以在网上找一些数据测试一下程序。

#include <iostream>

using namespace std;

//设定一个最大的揹包容量+1的值
#define MAXCP1 10000

void Knapsack(int v[], int w[], int c, int n, int m[][MAXCP1]);

int main()
{
    int c, n;           //揹包容量c 物品个数n
    cin >> c >> n;
    int w[n], v[n], m[n][MAXCP1];   //n个物品的重量w[n],n个物品的价值v[n],以及二维数组m
    for (int i = 0; i < n; i++)
    {
        cin >> w[i] >> v[i];
    }
    Knapsack(v, w, c, n, m);
    cout << m[0][c] << endl;

    return 0;
}

void Knapsack(int v[], int w[], int c, int n, int m[][MAXCP1])
{
    /* *初始化 */
    for (int j = 0; j < w[n-1]; j++)
        m[n-1][j] = 0;
    for (int j = w[n-1]; j <= c; j++)
        m[n-1][j] = v[n-1];

    /* *根据递推关系得到一张二维表 */
    for (int i = n - 2; i > 0; i--)
    {
        for (int j = 0; j < w[i]; j++)
            m[i][j] = m[i+1][j];
        for (int j = w[i]; j <= c; j++)
            m[i][j] = max(m[i+1][j], m[i+1][j-w[i]] + v[i]);
    }
    /* *得到最优值m[0][c] */
    m[0][c] = m[1][c];
    if (c >= w[0])
        m[0][c] = max(m[1][c], m[1][c-w[0]] + v[0]);
}

事实上,代码可以看起来更简洁,如下:

#include <iostream>
#include <vector>

using namespace std;

void Knapsack(int v[], int w[], int c, int n, vector<int> &m);

int main()
{
    int c, n;                   //揹包容量c 物品个数n
    cin >> c >> n;
    int w[n], v[n];             //n个物品的重量w[n],n个物品的价值v[n]
    for (int i = 0; i < n; i++)
    {
        cin >> w[i] >> v[i];
    }
    vector<int> m(c+1, 0);      //m[j]表示揹包容量为j时可以拿到的最大价值
    Knapsack(v, w, c, n, m);
    cout << m[c] << endl;

    return 0;
}

void Knapsack(int v[], int w[], int c, int n, vector<int> &m)
{
    for (int i = 0; i < n; i++)
    {
        for (int j = c; j >= w[i]; j--)
            m[j] = max(m[j], m[j - w[i]] + v[i]);       //表示从拿或者不拿物品i做一个选择
    }
}
点赞