动态规划 01背包问题

472 阅读5分钟
题目来源:牛客网 01背包


现有一个容量大小为V的背包和N件物品,每件物品有两个属性,体积和价值,请问这个背包最多能装价值为多少的物品

输入描述:

第一行两个整数V和n 接下来n行,每行两个整数体积和价值,1≤N≤1000,1≤V≤20000 每件物品的体积和价值范围在[1,500]

输出描述:

输出背包最多能装的物品价值

Example:

Input:  6 3
        3 5
        2 4
        4 2
Output: 9

原理

根据动态规划解题步骤(问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成)找出01背包问题的最优解以及解组成,然后编写代码实现。

动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。

最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。

实现细节(重要)

核心数据结构是dp[i][j],指在背包当前容量为j的情况下,前i个物品最佳组合的价值。

  • 01背包的数据从dp[0][0]开始增长,显然dp[0][0]是值为0的情况。
  • 此后以先讨论前i个物品在不同当前剩余背包容量即j的情况下的最大可容纳值。
  • w[i]表示当前第i件物品的体积:
  1. 如果dp[i][j]中的j小于w[i],说明在当前要装的第j个物品不能被放入dp[i-1][j]表示的情况,即j<w[i] dp[i][j] = dp[i-1][j]。在这种情况下没得选,只能将第i件物品放弃,即保持dp[i-1][j]表示的情况。
  2. 如果dp[i][j]中的j大于w[i],说明在当前要装的第j个物品可以被放入dp[i-1][j]表示的情况,这个时候有两种选择:放、不放。那选择标准肯定是最大化的价值,即j>=w[i] dp[i,j] = max {dp[i-1,j],dp[i-1,j-w[i]]+v[i]},其比较过程在max函数中完成。关键是理解不放的时候比放的时候价值还要大的情况,即同样在前i-1的种类中,可能dp[i-1,j-w[i]]会比dp[i-1,j]小很多的情况(当前可用背包容量j-w[i]太小,即使加上了v[i]也不足以超过同样种类容量为j时的最佳方案)。

表格填完最优解即是右下角的值。

代码

#include<iostream>
#include<algorithm>
using namespace std;

int main(){
    int V = 0, n = 0;
    //读入背包容量和物品数量
    cin>>V>>n;
    int w[n+1],v[n+1],dp[n+1][V+1];

    //初始化矩阵值
    for(int i = 0;i<n+1;i++){
        for(int j = 0;j<V+1;j++){
            dp[i][j] = 0;
        }
    }

    //依次读入物品体积及价值
    for(int i = 1; i<=n; i++){
        cin>>w[i]>>v[i];
    }

    //按前i件物品的顺序填表
    for(int i = 1;i<=n;i++){
        //当当前剩余容量j小于背包的最大值时
        for(int j = 1;j<=V;j++){
            //代码实现两种情况
            if(j<w[i]){
                dp[i][j] = dp[i-1][j];
            }else{
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]);
            }
        }
    }

    //输出右下角的值
    cout<<dp[n][V]<<endl;
    return 0;
}

背包问题最优解回溯

通过上面的方法可以求出背包问题的最优解,但还不知道这个最优解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的两种寻解方式

  1. dp[i][j] = dp[i-1][j]时,即在i-1种物品的最优情况下,不加入(可能是由于空间不够或者是实现细节所述的非最优解情况)第i种物品是前i种物品的最优情况,即回到dp[i-1,j]
  2. dp[i,j] = dp[i-1,j-w[i]]+v[i]时,即在i-1种物品的最优情况下,加入了第i种物品,该物品是最优解组成的一部分,随后我们得回到装该物品之前,即回到dp[i-1,j-w[i]]

根据上述方法遍历表格即可获得最优解的记录。