经典动态规划——0/1背包问题(二维数组动规,一维数组动规实现)

Posted 遥远的歌s

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了经典动态规划——0/1背包问题(二维数组动规,一维数组动规实现)相关的知识,希望对你有一定的参考价值。

题目描述

有为N件物品,它们的重量w分别是w1,w2,…,wn,它们的价值v分别是v1,v2,…,vn,每件物品数量有且仅有一个,现在给你个承重为M的背包,求背包里装入的物品具有的价值最大总和?

输入描述:
物品数量N=5件
重量w分别是:2 2 6 5 4
价值v分别是:6 3 5 4 6
背包承重为M=10

输出描述:
背包内物品最大总和为15
示例1
输入
5
10
2 2 6 5 4
6 3 5 4 6
输出
15

分析

对于这类问题,动态规划是最佳的选择,每一次求出局部的最优解,便可以求出整体的最优解,比如上述的题目描述,他给出的背包的大小是10,那么我们只需要依次求出来背包容量从1到10之间(包含1和10),存放这五个物体既可以放下又能够取得最大价值即可,这里优先想到的是用一个二维数组来存储每一次的最优解,dp[i][j],

dp[i][j] 表示当前背包大小容量为j的情况下存放第i个物品的最优解。
用二维数组来解决该问题的话,其状态转移方程如下
前提:j<goods[i]
dp[i][j] = dp[i+1][j]
前提:j>=goods[i]
dp[i][j] = max(dp[i + 1][j - w[i]] + v[i] , dp[i+1][j])

下面进行具体分析:
如果想要用动态规划解决问题,首要的步骤就是初始化,其次是找到状态转移方程,那么这个问题的初始化,我们可以这样想,我想要初始化,那么我先装进去一个物品,只要这个物品比当前的背包容量小于等于,那么我就可以装进去,并且可以将这个物品的价值记录下来,否则当前物品的重量大于背包的重量则装不进去,很显然,这时候的价值为0值得注意的是,我们需要从最后一个物品开始装,倒着向上走。

上述题目示例中,我们先来初始化最后一行,可以得到下图

因为最后一个物品的重量为4,价值为6,那么只要当前背包的容量大于等于4的情况下,就可以放下这个物品,取得当前最优的价值为6。这里如果题目要求只有这一个物品,那么很显然取该行的任何一个值都是当前容量下所能达到的最大价值。可惜往往事与愿违,我们需要放下倒数第二个物品,这里就需要用到了上述的状态转移方程。
我们先看得到的图如下,再具体分析

首先我们发现再j > 5 (这里不包含j=5) 之前,背包的容量都是小于i = 4这个物品的重量的那么就执行:
dp[i][j] = dp[i - 1]
这里可以理解成我当前的容量放不下该物品,那么我只能继承我下面的那已经放好的若干个物品的最优解
当j>=5的时候,我们重点来看:
j=5,意味着背包的容量从现在开始,已经可以放得下这个物品了,那么现在开始就需要考虑是否要取出某个物品后放入该物品能取得更大的价值,我们先假设把w=4,v=6的物品取出来,然后把这个物品放进去,结果发现放入这个物品的后价值变为了4,比原来的6要低,因此不放进去能取得j=5的条件下的最优解,仍然是6。
j=(6,7,8)的时候,同理带入状态转移方程
dp[i][j] = max(dp[i + 1][j - w[i]] + v[i] , dp[i+1][j])
比如j = 7
dp[4][7] = max(dp[4+1][7 - 4] + w[4] , dp[4+1][7])
dp[4+1][7 - 4] + w[4]表示:我现在先取得能放下这个物品的情况下的最优解,再加上这个物品的价值。
很显然,如果dp[5][3]已经取得了最优解的话,那么dp[4][7]就一定能取得最优解,这里也正是为什么要从下向上走的主要原因。
dp[4+1][7]表示:我步放入这个物品的情况下的最优解。

再看j=9的时候,
我需要看dp[4 + 1][9 - 4]的最优解,发现是6,那么我再放入该物品即+v[4],则可以取到价值为10,那如果不放入这个物品则继承之前的最优解dp[4 + 1][9]发现价值只能是6,通过max比较发现,当前最优的解为10,同理j=10也是如此。

因此,只需要通过上述的方法,将剩余物品都遍历一遍,就可以得到如下最优解的图

代码

#include<iostream>
#include<vector>
#include <algorithm>
using namespace std;
int main()

    int goods_num = 0;//货物总数
    int values_num = 0;//背包最大容量
    cin >> goods_num >> values_num;
    vector<int> goods(goods_num + 1, 0);//每个货物重量,下标1开始
    vector<int> values(values_num + 1, 0);//每个货物对应的价值,下标1开始
    for (int i = 1;i <= goods_num;i++)
    
        cin >> goods[i];
    
    for (int i = 1;i <= goods_num;i++)
    
        cin >> values[i];
    
    //dp数组
    vector<vector<int>> dp(goods_num + 1, vector<int>(values_num + 1,0));
    //初始化最后一行
    for (int i = values_num ;i > 0;i--)
    
      //当背包容量大于当前物品重量放入背包种并且记录价值
        if (i  >= goods[goods_num])
            dp[goods_num][i] = values[goods_num];
    
    //开始遍历剩下的goods_num - 1个物品
    for (int i = goods_num - 1;i > 0;i--)
    
      //判断对应容量背包所可以存放的最大价值
        for (int j = 1;j <= values_num;j++)
        
          //若当前背包容量小于该物品的重量,则继承下面已经存访的物品的价值
            if (j < goods[i])
                dp[i][j] = dp[i + 1][j];
          //否则,判断下面位置的背包减去当前物品重量后加上该武平价值和下面价值的最大值
            else
                dp[i][j] = max(dp[i + 1][j - goods[i]] + values[i], dp[i + 1][j]);
        
    
    cout << dp[1][values_num] << endl;

如果已经理解了二维数组的动态规划实现的话,那么也可以通过一维数组来实现,道理一样,只不过每次对背包的容量进行遍历的时候,需要从后向前遍历,因为可能会用到前面的背包容量下的最优解
状态转移方程
j < w[i]
dp[j] = max(dp[j - 1] , dp[j]
j >= w[i]
dp[j] = max(dp[j - w[i]] + v[i] , dp[j]);

这里也不难理解,大家可以自行参考代码

#include<iostream>
#include<vector>
#include <algorithm>
using namespace std;
int main()

    int goods_num = 0;//货物总数
    int values_num = 0;//背包最大容量
    cin >> goods_num >> values_num;
    vector<int> goods(goods_num + 1, 0);//每个货物重量,下标1开始
    vector<int> values(values_num + 1, 0);//每个货物对应的价值,下标1开始
    for (int i = 1;i <= goods_num;i++)
    
        cin >> goods[i];
    
    for (int i = 1;i <= goods_num;i++)
    
        cin >> values[i];
    
    //dp数组
    vector<int> dp(values_num + 1,0);
    //初始化
    for(int i = values_num;i>0;i--)
    
      if(i >= goods[goods_num])
      
        dp[i] = values[goods_num];
      
    
    for(int i = goods_num - 1 ;i>0;i--)
    
      for(int j = values_num ;j>=1;j--)
      
        if(j < goods[i])
        
          dp[j] = max(dp[j - 1],dp[j]);//取前一次和这次的价值比较
        
        else
        
          dp[j] = max(dp[j - goods[i]] + values[i],dp[j]);//当前背包最优解取决于当前能放下这个物品的情况下的最优解加上这个物品的价值和,与当前最优解进行比较
        
      
    
    cout<<dp[values_num]<<endl;

以上是关于经典动态规划——0/1背包问题(二维数组动规,一维数组动规实现)的主要内容,如果未能解决你的问题,请参考以下文章

01背包--动态规划

分享经典的动态规划问题

分享经典的动态规划问题

动态规划第五篇:01背包问题和完全背包问题

动态规划第五篇:01背包问题和完全背包问题

0-1背包