算法练习-01背包问题【含递推公式推导】(思路+流程图+代码)

难度参考

        难度:困难

        分类:动态规划

        难度与分类由我所参与的培训课程提供,但需 要注意的是,难度与分类仅供参考。且所在课程未提供测试平台,故实现代码主要为自行测试的那种,以下内容均为个人笔记,旨在督促自己认真学习。

题目

        动态规划经典问题01背包?

        具体内容:

        背包最大重量为4

        物品如下:

重量价值
物品0115
物品1320
物品2430

        问背包能背的最大重量是多少?

思路

        0-1 背包问题的动态规划解法基于以下思路:

  1. 子问题定义

    • 将原问题分解为子问题。在这里,我们考虑"对于给定的背包容量和一组物品,最大价值是多少?"
    • 定义状态 dp[i][w] —— 表示考虑前 i 个物品时,对于不超过 w 重量的背包,所能得到的最大价值。
  2. 初始条件

    • 对于 dp[0][...],即一个物品也不考虑的情况,背包的价值总是 0。
    • 对于 dp[...][0],即背包容量为 0 的情况,背包的价值也总是 0。
  3. 递推关系(状态转移方程):

    • 对于每一个物品 i 和每一个可能的重量 w,我们需要做出一个决策:是否将物品 i 放入背包。
    • 如果不放物品 i,则 dp[i][w] = dp[i-1][w] —— 也就是说,当前的最大价值与前 i-1 个物品时的最大价值相同。
    • 如果放物品 i,我们必须确保当前背包的剩余容量至少是物品 i 的重量 (weights[i-1] <= w),在这种情况下,dp[i][w] = dp[i-1][w-weights[i-1]] + values[i-1] —— 也就是说,当前的最大价值是物品 i 的价值加上剩余重量在前 i-1 个物品中能得到的最大价值。
  4. 填表

    • 我们按照递推关系来填表 —— 从较小的 i 和 w 开始,直到 i 等于物品个数,w 等于背包最大重量。
  5. 解的构造

    • 表格填完后,dp[n][maxWeight] 就是答案 —— 即考虑所有物品和最大背包重量时的最大价值。

        补充-递推公式的推导:

        假设我们有n个物品,每个物品的重量为weights[i],价值为values[i],背包的最大重量限制为maxWeight。我们定义dp[i][w]为考虑前i个物品(物品编号为0i-1),在背包重量限制为w的情况下,能够得到的最大总价值。我们想求的最终答案是dp[n][maxWeight]

        对于每个物品i(从1开始计数,对应数组下标为i-1),以及每个可能的重量限制w,我们面临两种选择:

  1. 不包含当前物品:如果我们决定不包含当前考虑的物品i-1,那么最大价值不会因为当前物品的存在而改变,所以它等于没有考虑当前物品时的最大价值,即dp[i-1][w]

  2. 包含当前物品:如果我们决定包含当前考虑的物品i-1,前提是这个物品的重量不超过当前的重量限制w(即weights[i-1] <= w)。此时,我们需要加上这个物品的价值values[i-1],同时,背包的剩余重量变为w - weights[i-1],因此我们还需要加上在新的重量限制下,考虑前i-1个物品时的最大价值,即dp[i-1][w-weights[i-1]] + values[i-1]

        因此,递推公式如下:

  • 如果weights[i-1] > w(当前物品重量超过背包限制),则dp[i][w] = dp[i-1][w]
  • 否则,dp[i][w] = max(dp[i-1][w], dp[i-1][w-weights[i-1]] + values[i-1])

        这个递推公式基于以下逻辑:对于每个物品,我们都做出是否包含它的决定,以确保在不超过背包重量限制的情况下达到最大价值。这种方式确保了我们能够考虑所有可能的组合,并找到最优解。

示例

        初始状态

        首先,初始化一个表`dp`,其中`dp[i][j]`代表在只考虑前`i`个物品,且背包容量为`j`时的最大价值。初始化时,所有值均为0。

        物品情况:
        - 物品0:价值15,重量1
        - 物品1:价值20,重量3
        - 物品2:价值30,重量4

        动态规划表格是这样的(行表示物品,列表示重量):

重量        0   1   2   3   4
不选物品    0   0   0   0   0

        加入物品0

        加入物品0(价值15,重量1)后,我们更新表格。对于重量1及以上的列,我们可以加入物品0。

重量        0   1   2   3   4
不选物品    0   0   0   0   0
加入物品0   0  15  15  15  15

        加入物品1

        接着,我们考虑加入物品1(价值20,重量3)。我们更新表格,考虑对于每个重量限制,在不超过该重量的情况下,是否加入物品1能获得更高的价值。

重量        0   1   2   3   4
不选物品    0   0   0   0   0
加入物品0   0  15  15  15  15
加入物品1   0  15  15  20  20

        在重量为4时,我们比较加入物品1后的价值与之前的状态。但是,我们忽略了一种可能:加入物品1(重量3,价值20)后,还可以再加入物品0(重量1,价值15),因此,对于重量4,最大价值应该是35(物品0和物品1的组合)。

        加入物品2

        最后,我们考虑加入物品2(价值30,重量4)。同样,我们更新表格,考虑对每个重量限制,加入物品2是否能带来更高的价值。

重量        0   1   2   3   4
不选物品    0   0   0   0   0
加入物品0   0  15  15  15  15
加入物品1   0  15  15  20  35
加入物品2   0  15  15  20  35

        在重量为4的情况下,加入物品2的价值(30)并不比已有的组合(物品0和物品1,总价值35)更优,因此我们保持原有的最大价值不变。

        最终结果

        最终,我们得到的动态规划表如下,表明在背包重量限制为4时,我们能获得的最大价值是35,通过组合物品0(价值15,重量1)和物品1(价值20,重量3)。

重量        0   1   2   3   4
不选物品    0   0   0   0   0
加入物品0   0  15  15  15  15
加入物品1   0  15  15  20  35
加入物品2   0  15  15  20  35

        因此,最终答案是,在确保总重量不超过4的条件下,我们最后得到的背包中的最大价值是35。

梳理

        这个方法能够实现的原因在于它利用了动态规划(Dynamic Programming, DP)的两个关键性质:最优子结构和重叠子问题。

        最优子结构意味着问题的最优解包含着其子问题的最优解。对于0-1背包问题来说,每增加一个物品的选择,都是基于之前所有物品选择的最优解上进行的新增决定。换句话说,在考虑是否加入当前物品时,我们可以依赖于之前物品的决策结果,这些决策结果是以最大化价值为目标的。

        重叠子问题指的是在解决问题的过程中,相同的问题会被多次解决。在0-1背包问题中,当我们分别计算每一个重量限制下的最大价值时,会重复计算某些重量限制下的最大价值。通过动态规划,我们可以将这些价值存储在一个表中,避免重复计算,这就是为什么我们使用一个二维数组来存储每个子问题的答案。

        在填充动态规划表时,我们从最小的子问题开始,即背包没有任何物品时的价值是0。然后逐步增加物品和重量,直到考虑了所有物品和所有重量限制。在每一步,针对每一个重量限制,我们都计算了两种情况:

        1. 不加入当前的物品,直接使用之前同样重量限制下的最大价值。
        2. 尝试加入当前的物品,将物品的价值加上剩余重量限制下的最大价值。

        通过比较上述两种情况的价值,我们可以选择较大的一个作为当前重量限制和当前物品下的最大价值。这个过程一直持续到表格被填满,最终我们在表格的右下角得到的值就是我们能够拿到的最大价值。

        简单来说,这方法之所以行之有效,是因为它将一个复杂问题分解为一系列叠加的简单问题并解决它们,同时保留了要求的全局最优解。通过表格的逐步填充,我们保证了在任何给定重量限制下,我们都是在做出最佳决策,以此计算出全局最优解。

代码

#include <iostream> // 引入标准输入输出库
#include <vector> // 引入向量(动态数组)库
using namespace std; // 使用标准命名空间,简化代码// 动态规划解决 0-1 背包问题的函数
int knapsack(const vector<int>& weights, const vector<int>& values, int maxWeight) {int n = weights.size(); // 物品数量// 初始化动态规划表,所有值起始为0vector<vector<int>> dp(n + 1, vector<int>(maxWeight + 1, 0)); // 使用动态规划构建 dp 数组for (int i = 1; i <= n; ++i) { // 遍历每个物品for (int w = 1; w <= maxWeight; ++w) { // 遍历每种重量限制// 如果当前物品可以加入背包if (weights[i - 1] <= w) {// 选择加入当前物品和不加入当前物品中价值更大的一个dp[i][w] = max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]]);} else {// 如果不能加入当前物品,保持之前的最大价值dp[i][w] = dp[i - 1][w];}}}// 返回最大值,即考虑所有物品且不超过最大重量限制时的最大价值return dp[n][maxWeight];
}int main() {int maxWeight = 4; // 背包的最大重量限制vector<int> weights = {1, 3, 4}; // 物品的重量vector<int> values = {15, 20, 30}; // 物品的价值// 输出背包能达到的最大总价值cout << "背包能达到的最大总价值为 "<< knapsack(weights, values, maxWeight) << endl;return 0; // 程序正常结束
}

        时间复杂度:O(n * maxWeight)

        空间复杂度:O(n * maxWeight)

打卡

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/475646.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型LLM训练显存消耗详解

参考论文&#xff1a;ZeRO: Memory Optimizations Toward Training Trillion Parameter Models 大模型的显存消耗一直都是面试常见的问题&#xff0c;这次我就彻彻底底的根据论文ZeRO中的调研和分析做一次分析 显存消耗的两个部分&#xff1a;Model States&#xff08;跟模型的…

Python学习 --- 文件操作

1.文件的基础操作 --- 打开,关闭与读文件 文件的主要操作有:打开,关闭与读写 1. name 是文件的路径,要用字符串的形式来表示 2. mode 模式也要用字符串的形式来表示 3.open函数会返回一个文件对象,该文件对象指向的是被打开的文件 1.read方法在调用完之后会生成一个指…

html表格标签(下):lable标签,select标签和textara标签

html表格标签(下)&#xff1a;lable标签&#xff0c;select标签和textarea标签 lable标签 搭配 input 使用,点击 label 标签就能选中对应的单选/复选框, 能够提升用户体验。 for 属性: 指定当前 label 和哪个相同 id 的 input 标签对应 (此时点击才是有用的) 运行效果&#x…

Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列

写在开头 队列是Java中的一个集合接口&#xff0c;之前的文章已经讲解了List和Set&#xff0c;那么今天就来唠一唠它吧。队列的特点&#xff1a;存储的元素是有序的、可重复的。 队列的两大接口Queue vs Deque Queue 是单端队列&#xff0c;只能从一端插入元素&#xff0c;另…

微信小程序之开发会议OA项目

目录 前言 本篇目标 首页 会议 投票 个人中心 会议OA项目-首页 配置 tabbar mock工具 page swiper 会议信息 会议OA项目-会议 自定义tabs组件 会议管理 会议OA项目-投票 会议OA项目-个人中心 前言 文章含源码资源&#xff0c;投票及个人中心详细自行查看…

【Kuiperinfer】笔记01 项目预览与环境配置

学习目标 实现一个深度学习推理框架设计、编写一个计算图实现常见的算子&#xff0c;例如卷积、池化、全连接学会如何进行算子的优化加速使用自己的推理框架推理常见模型&#xff0c;检查结果是否能够和torch对齐 什么是推理框架&#xff1f; 推理框架用于对已经训练完成的模…

JavaScript中延迟加载的方式有哪些

在web前端开发中&#xff0c;性能优化一直是一个非常重要的话题。当我们开发一个页面时&#xff0c;为了提高用户的体验和页面加载速度&#xff0c;我们往往需要采用一些延迟加载的技术。JavaScript中延迟加载的方式有很多种&#xff0c;下面我将为大家详细介绍几种常用的方式。…

Spring MVC(基于 Spring4.x)基础学习

一、SpringMVC概述 二、SpringMVC的HelloWorld 三、使用RequestMapping映射请求 四、映射请求参数&请求头 五、处理模型数据 六、视图和视图解析器 七、RESTful CRUD 八、SpringMVC表单标签&处理静态资源 九、数据转换&数据格式化&数据校验 十、处理JSON:使用…

IO进程-day1

1、使用fgets统计给定文件的行数。 #include<stdio.h> #include<string.h> #include<stdlib.h>int main(int argc, const char *argv[]) {if(argc ! 2){printf("inout file error\n");printf("usage:./a.out srcfile destfile\n");ret…

ClickHouse从入门到精通(高级)

第1章 Explain查看执行计划 第2章 建表优化 第3章 ClickHouse语法优化规则 第4章 查询优化 第5章 数据一致性(重点) 第6章 物化视图 第7章 MaterializeMySQL引擎 第8章 常见问题排查

PHP支持的伪协议

php.ini参数设置 在php.ini里有两个重要的参数allow_url_fopen、allow_url_include。 allow_url_fopen:默认值是ON。允许url里的封装协议访问文件&#xff1b; allow_url_include:默认值是OFF。不允许包含url里的封装协议包含文件&#xff1b; 各协议的利用条件和方法 php:/…

萝卜大杂烩 | 把微信接入ChatGPT,变成聊天机器人竟然这么简单!(一起来尝试吧~)

本文来源公众号“萝卜大杂烩”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;把微信接入ChatGPT&#xff0c;变成聊天机器人竟然这么简单&#xff01; 最近的 ChatGPT 又再次火热起来了&#xff0c;各种周边工具也是层出不穷&…