【动态规划】01背包问题

01背包问题


问题描述

给定 N 种物品和一个最大载重量为 C 的背包,物品 i 的重量是 wi,其价值为 vi 。
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

问题分析

对于每个物品,只能选择装或者不装,不能选择只装物体的一部分,因此不能使用单位重量的价值进行排序的方法(贪心)来解决,需要用到动态规划来解决。


动态规划的三个核心

  1. 最优子结构
  2. 边界
  3. 状态转移方程

对于该问题而言,由于第i+1件物品只有两种选择(选与不选),因此前i+1件产品的最优解就是,子结构就是前i件物品装在承重为j的容器中,可以用result[i][j]来表示。

边界就是在只装第一件物品时的情况。

通过上面的对子结构的分析,可以得到状态转移方程:
1. j < w[i]时,即剩余载重量不足以装下当前物品,应有最优解即是前i-1件时的解,result[i][j] = result[i-1][j]
2. j >= w[i]时,即还可以装下当前物品,因此解应为 装与不装 当前物品两种情况中的最大解。如果不装,即result[i-1][j];如果装,,即result[i-1][j-w[i]] + v[i]。因此取最大值结果为result[i][j] = max( result[i-1][j], result[i-1][j-w[i]] + v[i] )


代码

1.二维数组

int getLargestValue_1( vector<int> & v, vector<int> & w, int c ) {
    vector< vector<int> > res( N+1, vector<int>( c+1, 0 ) );
    /*  该处初始化边界可以省略
        由于开始的一列为0,不影响结果  */
    /*for( int i = 1; i <= c; i++ ) {
        if( i < w[i] ) {
            res[1][i] = 0;
        } else {
            res[1][i] = v[1];
        }
    }*/

    for( int i = 1; i <= N; i++ ) {  //遍历所有物品
        for( int j = 1; j <= c; j++ ) {  //遍历容器容量
            if( j < w[i] ) {  //放不下
                res[i][j] = res[i-1][j];
            } else {  //放的下
                res[i][j] = max( res[i-1][j], res[i-1][j-w[i]] + v[i] );
            }
        }

        /* 输出遍历每件物品的结果 */
        /* for( int j = 1; j <= c; j++ ) {
            cout << res[i][j] << ' ';
        }
        cout << endl; */
    }

    return res[N][c];
}

2.两个一维数组

由于每次遍历之用到了当前行与上一行,因此可优化空间到两个一维数组。

int getLargestValue_2( vector<int> & v, vector<int> & w, int c ) {
    vector<int> preResult( c+1, 0 );  //存储上次得到的结果,对应上一行
    vector<int> result( c+1, 0 );  //存储当前行结果

    for( int i = 1; i <= N; i++ ) {
        for( int j = 1; j <= c; j++ ) {
            if( j < w[i] ) {
                result[j] = preResult[j];
            } else {
                result[j] = max( preResult[j], preResult[j-w[i]] + v[i] );
            }
        }
        for( int j = 1; j <= c; j++ ) {
            cout << preResult[j] << ' ';
        }
        cout << endl;
        preResult = result;
    }

    return result[c];
}

3. 一维数组

我们还可以发现每次使用的都是上一行当前列元素上面与左面的元素(preResult[j], preResult[j-w[i]] ),因此我们可以将每行的遍历顺序改变,即从右向左计算,即可将每次的计算结果直接覆盖到当前位置。

int getLargestValue_3( vector<int> & v, vector<int> & w, int c ) {
    vector<int> result( c+1, 0 );

    for( int i = 1; i <= N; i++ ) {
        for( int j = c; j > 0; j-- ) {  //从右向左计算
            if( j >= w[i] ) {
                result[j] = max( result[j], result[j-w[i]] + v[i] );
            }
        }
    }
    return result[c];
}

可以使用以下代码测试:

int main() {
    vector<int> v{0, 8, 10, 6, 3, 7, 2};  //价值
    vector<int> w{0, 4, 6, 2, 2, 5, 1};  //重量
    int c = 12;  //最大承载重量

    cout << getLargestValue_3( v, w, c ) << endl;

    return 0;
}

最终答案为24。

猜你喜欢

转载自blog.csdn.net/liushall/article/details/80558650