小白也能看懂的背包题解

1|001背包问题以及优化

其实在写题解的时候不是在告诉别人,而是在教会自己!

1|1作者的话

  • 本篇题解面向想学算法的小白,或者对背包问题百思不得其解的小伙伴

  • 作者尽量采用了不同的视角,不同的方式去描述和解决问题

    • 题解模板

    • 图解算法

    • 手工模拟

  • 希望大家喜欢!

  • 由于作者水平有限,如果有路过的小伙伴或者大神看出本篇题解的问题或者不足,还请评论!

  • 本人感激不尽!

1|201背包问题的赘述

用一句话描述背包问题

背包问题就是在有限集里面的最优解问题

  • 有限的物品(每个物品仅有一个)

  • 有限的空间(限制条件)

  • 求最大值

1|301背包问题的代码(未优化)

 #include <bits/stdc++.h>
 using namespace std;
 int n, m; //n代表物品个数,m代表背包空间
 const int N = 1010;
 int f[N][N]; //f[i][j]里面存放的数据为,空间为 j 时,有 i 个物品可选择时的,最大值
 int v[N], w[N];
 
 int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; ++i) {
  cin >> v[i] >> w[i];
  }
  f[0][0] = 0; //状态初始化,但是因为f[][]是全局变量,所有自动初始化,所有本行可加可不加
  //精华内容见文章分析
  //start
  for (int i = 1; i <= n; ++i) {
  for (int j = 0; j <= m; ++j) {
  f[i][j] = f[i - 1][j];
  if (j >= v[i]) {
  f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
  }
  }
  }
  //end
  cout << f[n][m];
  return 0;
 }

1|401背包问题的精华代码解析

抛开代码和动态规划,如果让我们来做选择,我们会怎么做?

  1. 先挑最贵的拿

  2. 然后再去尽可能的去塞满空间

但是,我们说上面的那种拿法很可能不是最优解

贪心并不能保证最优解,只有理智的思考才能到达彼岸

所有我们选择借助表格来帮助判断

首先,规定一下横向和纵向分别代表什么意思

image-20220317162735201

然后,你一步步的填完整张表格

image-20220317163329241

最后,你果断选择了拿音响和吉他,却放弃了笔记本

1|0其实,你可知道,你已经做了一次动态规划

动态规划就是把所有可能的组合不重不漏的列出来,然后循环取其中的最大值。

1|0铺垫了这么多,不知道你是否理解这 短短五行代码的含义了

 for (int i = 1; i <= n; ++i) {
  for (int j = 0; j <= m; ++j) {
  f[i][j] = f[i-1][j ];
  if (j >= v[i]) {
  f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
  }
  }
  }
  1. 为什么是从1开始?

    代码是有实际意义的!

    i 代表物品,如果没有物品,那么价值肯定是0,所有从 0 开始循环没有任何意义。

  2. 内循环,j 表示背包空间。

  3. 如果能保证 j > v[i] 也就是把内循环的判断条件改一下,那么就可以直接写

     f[i][j] = max(f[i][j-1], f[i - 1][j - v[i]] + w[i]);
  4. 第二次判断条件,可以放到 for 循环里面去判断

  5. 状态转移方程

    • 01是什么意思,0 代表不取整个物品, 1 代表取这个物品

    • 动态规划是递推,尾递归或者递归是递归。(递推的下一个值是简历在前面值已知的基础上,而递归则是从顶部开始,一步步去逼近答案)

    • 我们按照最后一个物品取还是不取(这里的最后一个物品并不是全部物品里面的最后一个物品,而是每次外循环所代表的物品总数的最后一个物品)

1|5优化代码

 #include <bits/stdc++.h>
 using namespace std;
 int n, m;
 const int N = 1010;
 int f[N];
 int v[N], w[N];
 
 int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; ++i) {
  cin >> v[i] >> w[i];
  }
     //精华部分
     //start
  for (int i = 1; i <= n; ++i) {
  for (int j = m; j >= v[i]; --j) {
  f[j] = max(f[j], f[j - v[i]] + w[i]);
  }
  }
     //end
  cout << f[m];
  return 0;
 }

1|6优化的代码的精华部分解析

可能你还没看出啦优化了哪里

原本存放结果的二维数组被压缩成一维的了

即,由 f[N][N] ==> f[N]

可以注意到,与未优化的代码相比,背包的空间是由 从小到大循环 变成了 从大到小循环

下面就来解释一下从大到小循环的妙处

其核心问题就是

代码里面两个 f [ j ] 是否相同

image-20220317214040320

1|0正确答案是:不相同

下面,请听我,细细道来。

  • 先说运算符的优先级问题, "=" 的优先级仅比 "," 高。所有,先算右边的比较,再进行赋值。

  • 那么,第二个 f [ j ] 代表的是什么? 很明显代表上一轮的 j 容量下最大的价值, 也就是 f [ i-1 ][ j ]

  • 所有第一个是更新后的 j 内存下的最大值

1|0为什么要逆序循环

结论 :因为正序循环,第二个f [ i ] 代表这一轮的 也就是 f [ i ] [ j ]

1|0给想真正学懂的小伙伴一个建议,那就是自己去亲自用纸演算一遍

实在不想动手的可以看我下面的演算过程

假设:

物品数量为 3, 背包空间为 4;

物品空间价值
1.吉他 1 1500
2.音响 4 3000
3.笔记本电脑 3 2000

1|0逆序

  f(4) = max(f(4), f(3)+1500)      //此时 f(3) = 0 && f(4) = 0 ==> f(4) = 1500
  f(3) = max(f(3), f(2)+1500)      //下面的原因类似,不再赘述
  f(2) = max(f(2), f(1)+1500)
  f(1) = max(f(1), f(0)+1500)
  ————————————————————————————————————————————————————————————————————————————————————————————————————
  f(4) = max(f(4), f(0)+3000)  //此时, f(0) = 0 && f(4) = 1500 ==> f(4) = 3000
  f(3) = f(3)   //因为 j < v[i], 所有并没有更新下面这些值
  f(2) = f(2)
  f(1) = f(1)
  ---------------------------------------------------------------------------------------------------
 f(4) = max(f(4), f(1)+2000) //此时, f(1) = 1500 && f(4) = 3000 ==> f(4) = 3500
 f(3) = max(f(3), f(0)+2000)      //此时, f(0) = 0 && f(3) = 1500 ==> f(4) = 2000
 f(2) = f(2)    ////因为 j < v[i], 所有并没有更新下面这些值
 f(1) = f(1)

好了,逆序我写完了,正序改你自己尝试一下了吧

想了一下,还是写上吧。

1|0正序

 f(1) = max(f(1), f(0) + 1500) //f(0) = 0 && f(1) = 0 ==> f(1) = 1500
 f(2) = max(f(2), f(1) + 1500) //f(1) = 1500 && f(2) = 0 ==> f(2) = 3000
 f(3) = max(f(3), f(2) + 1500) //f(2) = 3000 && f(3) = 0 ==> f(3) = 4500
 f(4) = max(f(4), f(3) + 1500) //f(3) = 4500 && f(4) = 0 ==> f(4) = 6000
 —————————————————————————————————————————————————————————————————————————————————————————————————————
 f(1) = f(1)   //以下三行, 因为 j < v[i] ,所有数据并没有更新
 f(2) = f(2)
 f(3) = f(3)
 f(4) = max(f(4), f(1) + 3000) //f(1) = 1500 && f(4) = 6000 ==> f(4) = 6000
 -----------------------------------------------------------------------------------------------------
 f(1) = f(1)   //以下两行, 因为 j < v[i] ,所有数据并没有更新
 f(2) = f(2)
 f(3) = max(f(3), f(0)+2000) //f(3) = 4500 && f(0) = 0 ==> f(3) = 4500
 f(4) = max(f(4), f(1)+2000) //f(4) = 6000 && f(1) = 1500 ==> f(4) = 6000

1|7后记

不知道聪明的你发现了没有,其实正序就是完全背包问题的解法

赘述一下完全背包问题是什么问题

  1. 背包空间有限

  2. 每种物品的数量无限

可能你还会发现另一个问题,一个关于完全背包的问题的本质

计算每种物品, 单位空间的价值

在本次假设中:

吉他的单位空间的价值是1500

音响的单位空间的价值是750

笔记本电脑的单位价值是2000/3

1|0好了好了,扯远了!再不结束,可能作者整个人就不好了

预告一下:

接下来还会出 高精 或者 八大排序的算法题解,但由于作者精力有限,不能在短时间内肝出这么多的题解。

如果有哪个算法题解一直困扰着你,还请在评论区打出来。

 

 


__EOF__

本文作者userName
本文链接https://www.cnblogs.com/codezzzsleep/articles/16019835.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   codezzzsleep  阅读(156)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示