【算法精练】背包问题(01背包问题)

目录

1. 背包问题

2. 01背包问题

 3. 优化

总结


在这里插入图片描述

1. 背包问题

经典的背包问题:

        有一个背包,限制背包的体积;有一堆物品,从这堆物品中选择,在不超过背包容量的前提下,选出最大价值的物品;

 从这个问题中可以提取出两个关键的信息:1、物品属性 2、背包属性

比如:有一个背包大小是7,有以下物品:

 从中选出最大价值;背包也有划分:1. 必须装满、2. 不需要装满;

01背包问题:这些物品中,每种物品只有一个(也就是只能选择一次)

完全背包问题:物品有无穷个(可重复选择);

2. 01背包问题

 以这道模板题为例:

 题目链接:

DP41 【模板】01背包

这道题有两问:

  • 求这个背包至多能装多大价值的物品?
  • 若背包恰好装满,求至多能装多大价值的物品?

 先来看第一问:

        以示例一为例:

背包可容纳的体积为5,有以下物品可以选择:分别为1号、2号、3号;

         背包问题属于经典的动态规划,而动态规划有一个经典的特征:递推;直白的说就是:可以通过先前的状态,来得到当前所求的状态;进而一种地推得到最终结果;然而每个地推的过程都是一个相同的子问题,这一点和递归搜索算法也有些类似;对于动态规划问题的解决,核心在于状态表示,即:dp所表示的含义,根据状态表示进而推导出状态转移方程;因此背包问题也是符合这一规律的;

动态规划的问题解决基本包含三大步骤:

  • 状态表示
  • 状态转移方程推导
  • 初始化dp表

状态表示:

        状态的表示往往是根据题目要求而设定的,当然新手很可能无法一次就找准状态表示,但可以通过经验进行总结,一些相似的问题或者一类问题状态表示都具有一定的相似性(规律),掌握这些可以更好的帮助我们解决问题;

较为直白的一些动态规划,一般题目就包含所需的状态表示,比如:

实例中的问题一:求这个背包至多能装多大价值的物品?(不超过背包容量的前提下);

程序不像人一样可以思考,从物品中选择最合适的,只能挨个遍历,然后根据规律判断是否选择;

这里的核心点就如上加粗部分,因此就可以设状态表示:在前 i 个物品中选择,在不超过体积 j 的情况下,所能选出的最大价值;

状态转移方程的推导:

有了状态表示,接下来就是状态转移方程的推导;状态转移方程的推导核心在于状态的分析,比如:在前 i 个物品中选择,在不超过体积 j 的情况下,所能选出的最大价值;

在前i个物品中选,对于一个物品,就有两种选择:1、选;2、不选

 根据这里就可以推出转移方程:

dp[i][j]:在前 i 个物品中选择,在不超过体积 j 的情况下,所能选出的最大价值;

1. 选第i个物品:如果选第 i 个物品,那么 dp[i-1][j - v[i]]就需要存在;

dp[i-1][j - v[i]]:在前i - 1个物品中选,体积不超过 j - v[i],所能选出的最大价值;

dp[i][j] = dp[i-1][j - v[i]] + w[i];

2. 不选第i个物品:如果不选第 i 个物品:dp[i-1][j];dp[i-1][j]:在前i - 1个物品中选,体积不超过 j ,所能选出的最大价值;

dp[i][j] = dp[i-1][j - v[i]] + w[i];

dp[i][j] = dp[i-1][j];

两种情况选择最大值:dp[i][j] = max(dp[i-1][j - v[i]] + w[i] ,   dp[i-1][j]);

初始化dp表:

主要分析以下几:

1、边界:0, 1, n(最大边界),也就是极端情况;

2、下标访问是否越界;

3、初始化数据不能影响后续结果的选择;

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

dp[i-1][j - v[i]] : j - v[i]可能小于0,小于0表示体积为负,这显然是不存在的;

初始化时的数据:求的是最大值(max),dp表初始化的数据要足够小,但也不能为负数;取0最合适;

 代码:

#include <iostream>
#include <vector>
using namespace std;
const int N = 1010;
int dp[N][N];int main() {int n, V;cin >> n >> V;vector<int> v(n + 1), w(n + 1);for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];for(int i = 1; i <= n; i++){for(int j = 1; j <= V; j++){dp[i][j] = dp[i-1][j];if(j - v[i] >= 0){dp[i][j] = max(dp[i][j], dp[i-1][j - v[i]] + w[i]);}}}cout << dp[n][V] << endl;
}

 第二问:若背包恰好装满,求至多能装多大价值的物品?

 有了第一问的经验,得出第二问的状态表示就简单许多;

状态表示(dp):在前 i 个物品中选择,体积刚好为 j ,能选出的最大价值;

状态转移方程:

也是分为两种情况:1、选;2、不选

选:dp[i][j] = dp[i-1][j - v[i]] + w[i];

不选:dp[i][j] = dp[i-1][j];

唯一不同的就是需要有一个状态的,去表示某个状态存在;

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

 但需要添加限制条件:第一问中体积不超过 j,不超过j其中也包含刚好为 j;体积要想刚好为j,那么 dp[i-1][j - v[i]] 必须要存在;

dp[i-1][j - v[i]]:  在前 i - 1 个物品中选择,体积刚好为 j - v[i] ,能选出的最大价值;

 如何标识?0肯定不能选,选择 - 1;如果某个状态的值为 -1,表示该状态不存在;

初始化:初始化也并非是将所有的值都初始化为 -1,这样会影响后续数据的选择,只需将初始化(最开始)时的一些不存在的状态初始化为 -1;

也就是 dp[0][j],从前0个物品中选体积为j,怎么选都不可能选出,因此状态不可能存在;如果后续状态依然不存在,dp[i][j] = dp[i-1][j];依然会等于 -1;

第二问也就解决了,代码:

memset(dp, 0, sizeof dp);for(int i = 1; i < V; i++) dp[0][i] = -1;for(int i = 1; i <= n; i++){for(int j = 1; j <= V; j++){dp[i][j] = dp[i-1][j];if(j - v[i] >= 0 && dp[i-1][j - v[i]] != -1){dp[i][j] = max(dp[i][j], dp[i-1][j - v[i]] + w[i]);}}}cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;

总体代码:

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int N = 1010;
int dp[N][N];
int main() {int n, V;cin >> n >> V;vector<int> v(n + 1), w(n + 1);for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];for(int i = 1; i <= n; i++){for(int j = 1; j <= V; j++){dp[i][j] = dp[i-1][j];if(j - v[i] >= 0){dp[i][j] = max(dp[i][j], dp[i-1][j - v[i]] + w[i]);}}}cout << dp[n][V] << endl;memset(dp, 0, sizeof dp);for(int i = 1; i < V; i++) dp[0][i] = -1;for(int i = 1; i <= n; i++){for(int j = 1; j <= V; j++){dp[i][j] = dp[i-1][j];if(j - v[i] >= 0 && dp[i-1][j - v[i]] != -1){dp[i][j] = max(dp[i][j], dp[i-1][j - v[i]] + w[i]);}}}cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;
}

 3. 优化

 看总体代码会发现一个问题:dp在访问时只会访问前一行的dp数据(dp[i-1]),之后就不会访问了,开一个二维数组空间显然是很浪费的;因此可以进行滚动优化;

已经填过的数据不会再使用了,所以这里可以不断的滚动数组,来进行空间的优化;但是使用两个数组进行滚动,还是有些空间浪费,使用一个数组即可;填过一遍数组后,再重新开始填下一轮,当前表中的数据就是下一轮所需的数据;但是需要考虑数据被覆盖的问题;

在访问时,只使用了dp[i-1][j],dp[i-1][j-v[i]];因此填表的顺序需要改变一下;显然 j - v[i] <= j;

因此正着填表必然会导致数据被覆盖;dp[i][j] = max(dp[i - 1][j], dp[i-1][j - v[i]] + w[i]); 把dp[i] 和dp[i-1]看成同一个数组,要更新 j 位置就需要原先的 j 位置 和 j - v[i] 位置的数据;怎么避免覆盖?从右向左进行填表;

 代码:

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int N = 1010;
int dp[N];
int main() {int n, V;cin >> n >> V;vector<int> v(n + 1), w(n + 1);for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];for(int i = 1; i <= n; i++){for(int j = V; j >= 1; j--){if(j - v[i] >= 0){dp[j] = max(dp[j], dp[j - v[i]] + w[i]);}}}cout << dp[V] << endl;memset(dp, 0, sizeof dp);for(int i = 1; i <= V; i++) dp[i] = -1;for(int i = 1; i <= n; i++){for(int j = V; j >= 1; j--){if(j - v[i] >= 0 && dp[j - v[i]] != -1){dp[j] = max(dp[j], dp[j - v[i]] + w[i]);}}}cout << (dp[V] == -1 ? 0 : dp[V]) << endl;
}

 辨别01背包问题:

        在前 i 个中(0~i)选择,刚好为 j (或者不超过j)....;并且数据只能选择一次,不可重复选择;这些都属于01背包问题,有些需要将问题进行转化,转化成01背包问题,有些则较为直白能直接看出;01背包问题的状态表示基本就是以上的结构,至于状态转移方程的推导,需要结合题目进行分析;唯有多加练习;


总结

        好了以上便是本文的全部内容,希望对你有所帮助,感谢阅读!

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

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

相关文章

ubuntu 执行 sudo apt-get update 报错

记录一下&#xff0c;遇到这个问题了&#xff0c;网络上看到的解决办法&#xff0c;亲测有效 执行sudo apt-get update ,却报以下错误&#xff0c;“SECURITY: URL redirect target contains control characters rejecting ” 经检查发现&#xff0c;/etc/apt/source.list 下的…

如何调用 DeepSeek API:详细教程与示例

目录 一、准备工作 二、DeepSeek API 调用步骤 1. 选择 API 端点 2. 构建 API 请求 3. 发送请求并处理响应 三、Python 示例&#xff1a;调用 DeepSeek API 1. 安装依赖 2. 编写代码 3. 运行代码 四、常见问题及解决方法 1. API 调用返回 401 错误 2. API 调用返回…

MySQL初学之旅(5)详解查询

目录 1.前言 2.正文 2.1聚合查询 2.1.1count() 2.1.2sum() 2.1.3avg() 2.1.4max() 2.1.5min() 2.1.6总结 2.2分组查询 2.2.1group by字句 2.2.2having字句 2.2.3group by与having的关系 2.3联合查询 2.3.1笛卡尔积 2.3.2内连接 2.3.3外连接 2.3.4自连接 2.3…

深入解析 vLLM:高性能 LLM 服务框架的架构之美(二)调度管理

深入解析 vLLM&#xff1a;高性能 LLM 服务框架的架构之美&#xff08;一&#xff09;原理与解析 深入解析 vLLM&#xff1a;高性能 LLM 服务框架的架构之美&#xff08;二&#xff09;调度管理 1. vLLM 调度器结构与主要组件 在 vLLM 中&#xff0c;调度器的结构设计围绕任务…

2.20学习

crypto buu-这是什么 下载附件后打开看到是apk文件&#xff0c;试试直接用记事本打开&#xff0c;看到乱码以外&#xff0c;还有一堆有规律的符号&#xff0c;了解后发现是jsfuck编码&#xff0c;搜索在线工具解码就行 misc buu-[BJDCTF2020]藏藏藏 下载附件&#xff0c;得…

【Java八股文】08-计算机网络面试篇

【Java八股文】08-计算机网络面试篇 计算机网络面试篇网络模型网络OSI模型和TCP/IP模型分别介绍一下键入网址到网页显示&#xff0c;期间发生了什么&#xff1f; 应用层- HTTP应用层有哪些协议&#xff1f;HTTP是什么及HTTP报文有哪些部分&#xff1f;HTTP是怎么传输数据的HTTP…

DeepSeek 助力 Vue 开发:打造丝滑的瀑布流布局(Masonry Layout)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

基于知识图谱的问答系统:后端Python+Flask,数据库Neo4j,前端Vue3(提供源码)

基于知识图谱的问答系统&#xff1a;后端PythonFlask&#xff0c;数据库Neo4j&#xff0c;前端Vue3 引言 随着人工智能技术的不断发展&#xff0c;知识图谱作为一种结构化的知识表示方式&#xff0c;逐渐成为问答系统的重要组成部分。本文将介绍如何构建一个基于知识图谱的问答…

AI助力下的PPT革命:DeepSeek 与Kimi的高效创作实践

清华大学出品《DeepSeek&#xff1a;从入门到精通》分享 在忙碌的职场中&#xff0c;制作一份高质量的PPT往往需要投入大量时间和精力&#xff0c;尤其是在临近截止日期时。今天&#xff0c;我们将探索如何借助 AI 工具 —— DeepSeek 和 Kimi —— 让 PPT 制作变得既快捷又高…

基于Flask的京东商品信息可视化分析系统的设计与实现

【Flask】基于Flask的京东商品信息可视化分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 系统能够灵活地执行SQL查询&#xff0c;提取出用于分析的关键数据指标。为了将这…

Spring Cloud — 深入了解Eureka、Ribbon及Feign

Eureka 负责服务注册与发现&#xff1b;Ribbon负责负载均衡&#xff1b;Feign简化了Web服务客户端调用方式。这三个组件可以协同工作&#xff0c;共同构建稳定、高效的微服务架构。 1 Eureka 分布式系统的CAP定理&#xff1a; 一致性&#xff08;Consistency&#xff09;&am…

Ubuntu 22.04 一键部署MinerU1.1.0

MinerU MinerU是一款将PDF转化为机器可读格式的工具&#xff08;如markdown、json&#xff09;&#xff0c;可以很方便地抽取为任意格式。 MinerU诞生于书生-浦语的预训练过程中&#xff0c;我们将会集中精力解决科技文献中的符号转化问题&#xff0c;希望在大模型时代为科技发…

如何才能写出好的prompt?

好的prompt设计需要遵循"明确具体、提供上下文、设定输出格式"三大原则。以下通过"解释量子计算"的案例展示优化过程: 优化前: Prompt:解释量子计算 输出结果: “量子计算是一种基于量子力学原理的新型计算模式,利用量子比特的叠加和纠缠特性实现并行…

计算机毕业设计Python农产品推荐系统 农产品爬虫 农产品可视化 农产品大数据(源码+LW文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

c++:模板进阶

1.非类型模板参数 我们之前的模板参数是类型模板参数&#xff0c;而非类型模板参数是常量&#xff0c;和宏功能类似 但是宏有个缺点&#xff0c;因为同一个宏的常量在一个项目中只有一个值&#xff0c;所以不能满足更加灵活多变的项目需求&#xff0c;但是非类型模板参数就可以…

Java 集合数据处理技巧:使用 Stream API 实现多种操作

​ 在 Java 开发中&#xff0c;对集合数据进行处理是非常常见的需求&#xff0c;例如去重、排序、分组、求和等。Java 8 引入的 Stream API 为我们提供了一种简洁、高效的方式来处理集合数据。本文将详细介绍如何使用 Stream API 实现多种集合数据处理操作&#xff0c;并给出相…

计算机网络基础杂谈(局域网、ip、子网掩码、网关、DNS)

目录 1. 简单局域网的构成 2. IP 地址 3. 子网掩码 4. IP地址详解自定义IP 5. IP 地址详解 6. 网关 7. DNS 域名解析 8. ping 1. 简单局域网的构成 交换机是组建局域网最重要的设备&#xff0c;换句话说&#xff0c;没有交换机就没法搭建局域网 交换机不能让局域网连…

基于SpringBoot的高校教学资料管理系统的设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

Rust 未来会成为主流的编程语言吗?

Rust是由Mozilla团队主导开发的编程语言&#xff0c;首次亮相是在2010年。自发布以来&#xff0c;Rust凭借其内存安全性、出色的性能和对并发操作的支持&#xff0c;逐渐吸引了众多开发者的关注。据Stack Overflow的2021年调查数据显示&#xff0c;Rust连续多年被开发者评为最喜…

【Java】代理模式

代理模式 代理模式是指给某一个对象提供一个代理&#xff0c;并由代理对象来控制对真实对象的访问 代理模式是一种结构型设计模式 背景 如果不采用代理&#xff0c;对一个类的多个方法进行监控时&#xff0c;重复的代码总是重复出现&#xff0c;不但破坏了原方法&#xff0c;…