用动态规划求解 0-1背包问题

今天下午又把 0-1 背包问题看了下,发现之前的写法虽然答案正确,但是和动态规划的思想相关度不大。不是想当然地从一个二维数组的 [0][0] 元素开始求解。

直接放上代码吧,因为已经写得很详细了。
其中 weight 是存储了每件物品重量的 vector,value 是存储了每件物品价值的 vector,c 表示背包容量。

/**
最优子结构:问题的最优解包含了其子问题的最优解
重叠子问题:用来解原问题的递归算法可以反复地解同样的子问题,而不是总在产生新的子问题。
             当一个递归算法不断地调用同一问题时,我们说该最优问题包含重叠子问题
自底向上求解问题:利用最优子结构性质,递归地从子问题的最优解逐步构造出整个问题的最优解
*/

#include 
#include 

using namespace std;

class Knapsack
{
public:
    int knapsack(vector<int>& weight, vector<int>& value, const int c)  //c表示背包容量
    {
        int n = weight.size();    //有n件物品
        //vector> maxValue(n+1, vector(c+1));
        vector<vector<int>> maxValue;
        maxValue.resize(n+1);
        for(unsigned i=0; i<maxValue.size(); ++i)
        {
            maxValue[i].resize(c+1);
        }
        /*背包容量为0时,价值为0*/
        for(int i=0; i<=n; ++i)
            maxValue[i][0] = 0;
        /*没有物品的时候,价值也为0*/
        for(int i=0; i<=c; ++i)
            maxValue[0][i] = 0;
        //求出第一列的值,即背包容量为1
        for(int i=n; i>0; --i)
        {
            if(weight[i-1] >= 1)
                maxValue[i][1] = 0;
            else
                maxValue[i][1] = value[i-1];
        }
        //求出最后一行的值,即只有一件物品 n
        for(int i=c; i>0; --i)
        {
            if(weight[n-1] >= i)
                maxValue[n][i] = 0;
            else
                maxValue[n][i] = value[n-1];
        }
        /*maxValue[i][j] 是背包容量为j,可选物品为 i, i+1, ..., n时0-1背包问题的最优值*/
        /*自底向上求解问题*/
        for(int j=1; j<=c; ++j)
        {
            for(int i=n; i>=1; --i)
            {
                /*因为i是从0开始的,所以weight和value的 i-1 对应的物品其实就是 maxValue 中下标为i的物品*/
                /*先让可选物品为 i-1, i, i+1, ..., n 时的最优值等于可选物品为 i, i+1, ..., n 时的最优值*/
                maxValue[i-1][j] = maxValue[i][j];
                /*如果当前物品的重量小于总重量,那么下一个子问题的最优值要么包含当前物品,要么不包含当前物品*/
                if(weight[i-1] <= j)
                {
                    maxValue[i-1][j] = max(maxValue[i][j], maxValue[i][j-weight[i-1]]+value[i-1]);
                }
            }
        }
        return maxValue[1][c];
    }
};

int main()
{
    int w[4] = {2,3,4,5};
    int v[4] = {3,4,5,7};
    vector<int> weight(w, w+4);
    vector<int> value(v, v+4);
    int c = 9;
    Knapsack kp;
    int maxWeight = kp.knapsack(weight, value, c);
    cout << maxWeight << endl;

    return 0;
}

晚上上完算法课,看到有另一种写法,好像和我之前在别人的博客上说的思想相似。回来又看了下,发现原来是对 maxValue[i][j] 的理解不同:在上面的写法中,maxValue[i][j] 被解释为 “背包容量为j,可选物品为 i, i+1, …, n时0-1背包问题的最优值”,这通常来说是一种反向思维。
而我也可以这样理解:maxValue[i][j] 是 “背包容量为j,可选物品为 1, 2, 3, …, i 时的最优值”,这样代码写起来反而更易理解。
两种方法的递归式如图:用动态规划求解 0-1背包问题_第1张图片

按第二种理解时的代码如下:

#include 
#include 

using namespace std;

class Knapsack
{
public:
    int knapsack(vector<int>& weight, vector<int>& value, const int c)
    {
        int n = weight.size();
        vector<vector<int>> maxValue(n+1, vector<int>(c+1));
        /*背包容量为0时,价值为0*/
        for(int i=0; i<=n; ++i)
            maxValue[i][0] = 0;
        /*没有物品的时候,价值也为0*/
        for(int i=0; i<=c; ++i)
            maxValue[0][i] = 0;
        /*与之前不同的是,这里的 maxValue[i][j] 表示候选物品为 1, 2, ..., i, 背包容量为 j 时问题的最优值*/
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=c; ++j)
            {
                maxValue[i][j] = maxValue[i-1][j];  //对于当前物品,可能放入也可能不放入
                if(weight[i-1] <= j)
                {
                    maxValue[i][j] = max(maxValue[i-1][j], maxValue[i-1][j-weight[i-1]] + value[i-1]);
                }
            }
        }
        return maxValue[n][c];
    }
};

int main()
{
    int w[4] = {2,3,4,5};
    int v[4] = {3,4,5,7};
    vector<int> weight(w, w+4);
    vector<int> value(v, v+4);
    int c = 9;
    Knapsack kp;
    int maxWeight = kp.knapsack(weight, value, c);
    cout << maxWeight << endl;

    return 0;
}

你可能感兴趣的:(算法)