【第二十五课】动态规划:01背包问题(acwing-2 / 思路 / 含一维数组优化 / c++代码)

目录

前言

acwing-2 01背包问题

思路 

思路误区(可跳)

代码

一维数组滚动优化

代码 


前言

受到外界干扰够多了,看着"动态规划""背包"这些标题都觉得发怵😂。。。

动态规划(Dynamic Programming, DP)是一种解决优化问题的算法思想,常用于求解最优化问题,我们这里主要学习的就是解决背包问题。(关于为什么叫动态规划,就只是个命名而已不用纠结)

背包问题就是给定一个容量为V,给出N件物品,每件物品有其体积和价值,让我们得出一种解决方案使得在满足背包容量V的前提下,这些选出的物品的总价值能达到最大。

背包问题又按可选择的物品的次数分为01背包(即每件物品最多只能被选择一次)、完全背包(即每件物品可以被选择无限次)、多重背包(即每种物品有限制的放入次数)、分组背包(即物品被分为若干组,每组中的物品最多只能选择一个放入背包)

acwing-2 01背包问题

思路 

(静下心认真看题目) 

我的思考历程:

刚看到这道题的时候,我的第一印象是觉得变量好多,需要在纸上列一下,理清思路。认真读完题之后,我觉得这道题要求的就是我们在满足容量限制的条件下,选择一些(没有限制选多少件物品)物品使得我们所得到的价值最大。

那么顺着思路下去,也就是我们要列举所有的情况,以得到最大价值。

那么如何列举所有情况呢?好像是意识到要利用二维数组,不过行列分别表示什么含义没想出来。(考虑不够深入吧)

看完讲解之后:

我们的思路是没问题的,确实要利用二维数组来列举所有的情况,dp[i][j]数组,下标 :i表示只考虑前i件物品,j表示最大容量不超过j,dp数组的元素的含义是:在 i j 两个因素的限制下,所得到的最大价值。(注意区分数组元素的含义和数组下标含义的不同)

我们运用动态规划的算法思想,将问题分解为更小的子问题,这道题里子问题也就是考虑更少的物品和更小的背包容量。当我们说“从前i个物品中选”,我们实际上是在说:“如果我们只考虑前i个物品,那么我们可以得到什么结果?”

为什么这里子问题是这样的呢?


在背包问题中,我们的目标是在给定背包容量的限制下,选择一些物品以使得总价值最大。这个问题可能看起来很复杂,因为有很多物品可以选择,而且每个物品都有自己的重量和价值。会有非常多情况。但是当我们举一些简单的例子,观察一下。

如果只有一个物品可以选择,那么我们应该如何选择?这个问题的答案很简单:如果这个物品的重量小于或等于背包的容量,那么我们就选择这个物品;否则,我们就不选择任何物品。

然后,我们可以考虑一个稍微复杂一点的问题:如果有两个物品可以选择,那么我们应该如何选择?这个问题的答案也相对简单:我们可以分别考虑选择和不选择每个物品的情况,然后选择总价值最大的那一种。这里的思路是:每件物品我们都可以选择拿或者不拿。在有两个物品可以选择的情况下:

图中已经将我们只有一件物品可选时所得出的结果对于:当有两件物品可选时所发挥的作用用不同颜色标注出来。我们可以清晰的看到子问题对于当前问题的作用

也就是说我们将状态计算(价值计算)分成两种情况:不包含第i个物品 dp[i-1][j] 和 包含第i个物品 dp[ i-1][ j-vi ]+wi

(这两个j表示的都是“只能在前i个物品中选,且容量不大于j” 的情况下的 j )

-

是因为我们把这个大的问题看成了,每次在之前的基础上多加一件可选择的物品,针对这个添加的物品,对原本已经有的结果进行选择添加这件新物品与否的情况判断,最终得到最大值

到这里应该对动态规划解决背包问题的思路可以清楚了。(呼气)

下面是b站的一个带有计算填充二维数组的过程的视频,写的很详细。推荐看一下方便理解。

【动态规划——01背包问题】

下面是y总总结的图,上面思路明白的话,这里的一些名词应该也很容易能理解啦。

由图中状态计算可得:

状态转移方程为:dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]) 

思路误区(可跳)

在学习动态规划的思路之前,我想过这种思路:

i 的含义是从这些可选择的物品中选 i 件物品,每一个i都计算出符合条件的最大价值,这样在下一次i的数量每次加一的时候,我们只需要在没有选择过的物品中进行挑选符合容量限制且使得价值最大的物品,以此来求得最终问题的答案。

好吧,我继续问:如果仅仅是看这种思路,而不考虑已有的动态规划思想的知识储备的情况下,这种思路是否可行呢? 

取自bing:

这种思路在某些特定情况下可能是可行的,但并不适用于所有情况

这种思路的一个主要问题是,它假设每次选择物品都是独立的,即选择某个物品不会影响到其他物品的选择。然而,在许多实际问题中,物品之间往往是有关联的。例如,选择了某个物品可能会限制我们选择其他物品的能力。在这种情况下,我们不能简单地从剩余的物品中挑选一个符合容量限制且使得价值最大的物品。(关于这种情况我又继续问了举出一个例子:如下图)

这种方法可能并不总是能得到最优解,原因在于它没有充分利用动态规划的优势——记忆化搜索和避免重复计算。在动态规划中,我们会记录下在每个状态(即考虑了前i个物品,背包容量为j时的最大价值)下的最优解,然后在这个基础上考虑下一个物品。这样可以避免在考虑每个物品时都要从头开始计算,大大提高了效率。

好啦,思路就说到这里啦。

代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;//n表示物品数量 m表示背包容量
int v[N],w[N];//v数组存储每件物品的体积,w数组存储每件物品的权值
int dp[N][N];//dp[i][j]二维数组的每个元素表示的是:只从前i个物品中选择,且容量要小于j的情况下的最大价值
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i];//读取每件物品
    //当i为0,也就是有0个可选物品,那么其对应的所有j容量值的情况下价值都为0,刚好初始化的时候就是全部初始化为0,因此我们i从1开始
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            //我们双层循环就是为了进行状态计算,给dp数组赋值
            //不包含第i个物品
            dp[i][j]=dp[i-1][j];
            //包含第i个物品 只有当第i个物品的体积小于当前最大容量的时候我们才考虑这种情况,否则没有意义
            if(j>=v[i])dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<dp[n][m];
    return 0;
}

一些必要的解释就放在注释里啦。 

只要我们清楚dp数组及其下标的含义,对应到代码中每一步的作用也就很清晰了。

一维数组滚动优化

为什么需要优化?因为我们发现使用二维数组记录价值的时候,首先这个二维数组在面临较大的数据量的时候,本身要开辟很大空间。 其次上面我们推出,

状态转移方程为:dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]) 

我们发现当前dp[i][j]的计算实际上是根据dp[i-1][]这一行推出来的

这说明我们每次计算当前行的时候,只需要参考它紧挨着的上一行就可以了,在这个思考下,我们发现既然只看上一行,那除了上一行的前面所有的行其实都没有对吧?那这样就造成了空间的浪费。因此我们想着能不能优化成一维数组。

我们发现是可以的。我们仅保留背包容量,这样dp[j]每次的初始值表示的就是我们想要的每次的"前一行"的数据,要注意的是,我们要进行倒序遍历,因为正序遍历的话,我们会更新每一位值,那么在后面计算过程中,需要用到前面数据的时候,就会拿到错误的、被更新过的数据,而不是我们所说的上一行的数据,因此,需要倒序遍历。

思路就是这样啦,下面有两个视频推荐看看,会更清楚明白一些。

先看这个视频大概知道优化是个什么东西,(当然能理解更好)

【带你学透01背包问题(滚动数组篇) | 从此对背包问题不再迷茫!】

看完上面那个视频,在此基础上再看一下下面这个,思路很清晰,个人感觉讲的非常好。看完应该可以完全理解了。

【01背包一维数组优化-NOIP/CSP/蓝桥杯/青少年C++编程】 

代码 

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i];

    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=v[i];j--)
        {
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[m];
    return 0;
}

关于我们内层循环条件是 当 j>=v[i] 的情况下,因为如果这个物品的重量都超出了此时正在考虑的背包容量,那就不需要放这个物品的,也就不需要考虑这个物品的价值对上一次价值的更改或者说影响了。


先写到这里啦。

动态规划的学习开始的很艰难,不过相信会越来越清晰的!

如果有问题欢迎指出,非常感谢!!一起加油!!!

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值