01背包问题详解 具体样例模拟版
01背包
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 v i v_i vi, w i w_i wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0< v i v_i vi, w i w_i wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
01背包问题,最基础的背包问题,每件物品只有一件,可以选择放或不放
状态转移方程为 f[i][j] = max(f[i - 1][j] , f[i - 1][j - v[i]] + w[i])
其中**f[i][j]**是前i件物品,背包容量是j的最大价值
当选择不放第i件物品时,那么问题就转化为 前i-1件物品放入容量为v的背包中 ,即f[i][j] = f[i - 1][j]
当选择放第i件物品时,那么问题就变成了 前i-1件物品放入剩下的容量为v-c[i]的背包中 ,即f[i][j] = f[i - 1][j - v[i]] + w[i]
代码如下
import java.io.*;
import java.util.*;
public class Main {static final int N = 1010;static int[] volume = new int[N];static int[] value = new int[N];static int[][] maxValue = new int[N][N]; public static void main(String[] args) throws Exception {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int v = sc.nextInt();for(int i = 1; i <= n; i++) {volume[i] = sc.nextInt();value[i] = sc.nextInt();}//计算最大价值for(int i = 1; i <= n; i++) {for(int j = 0; j <= v; j++) {maxValue[i][j] = maxValue[i - 1][j];if(j >= volume[i]) {maxValue[i][j] = Math.max(maxValue[i - 1][j], maxValue[i - 1][j - volume[i]] + value[i]);}}}System.out.println(maxValue[n][v]);sc.close();}
}
下面通过一个具体的例子模拟一下整个过程
有三个物品,编号为1、2、3,其体积和价值如下表,背包的容积是10
| 编号 | 体积 | 价值 |
|---|---|---|
| 1 | 4 | 5 |
| 2 | 7 | 9 |
| 3 | 5 | 6 |
以下过程十分详细,但看起来也很恶心,如果已经理解了整个过程,可以直接看后面的一维优化
当 i = 1 (考虑物品 1)
外层循环 i 代表当前考虑的物品编号,内层循环 j 代表当前背包容量。
当 j = 0 :
maxValue[1][0] = maxValue[0][0] = 0 ,因为 j < volume[1] ,不放入物品 1。
当 j = 1 :
maxValue[1][1] = maxValue[0][1] = 0 ,因为 j < volume[1] ,不放入物品 1。
当 j = 2 :
maxValue[1][2] = maxValue[0][2] = 0 ,因为 j < volume[1] ,不放入物品 1。
当 j = 3 :
maxValue[1][3] = maxValue[0][3] = 0 ,因为 j < volume[1] ,不放入物品 1。
当 j = 4 :
maxValue[1][4] = Math.max(maxValue[0][4], maxValue[0][4 - 4] + value[1]) = Math.max(0, 0 + 5) = 5 ,可以放入物品 1。
当 j = 5 :
maxValue[1][5] = Math.max(maxValue[0][5], maxValue[0][5 - 4] + value[1]) = Math.max(0, 0 + 5) = 5 ,可以放入物品 1。
当 j = 6 :
maxValue[1][6] = Math.max(maxValue[0][6], maxValue[0][6 - 4] + value[1]) = Math.max(0, 0 + 5) = 5 ,可以放入物品 1。
当 j = 7 :
maxValue[1][7] = Math.max(maxValue[0][7], maxValue[0][7 - 4] + value[1]) = Math.max(0, 0 + 5) = 5 ,可以放入物品 1。
当 j = 8 :
maxValue[1][8] = Math.max(maxValue[0][8], maxValue[0][8 - 4] + value[1]) = Math.max(0, 0 + 5) = 5 ,可以放入物品 1。
当 j = 9 :
maxValue[1][9] = Math.max(maxValue[0][9], maxValue[0][9 - 4] + value[1]) = Math.max(0, 0 + 5) = 5 ,可以放入物品 1。
当 j = 10 :
maxValue[1][10] = Math.max(maxValue[0][10], maxValue[0][10 - 4] + value[1]) = Math.max(0, 0 + 5) = 5 ,可以放入物品 1。
此时 maxValue[1] 数组的值为 [0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5] 。
当 i = 2 (考虑物品 2)
当 j = 0 :
maxValue[2][0] = maxValue[1][0] = 0 ,因为 j < volume[2] ,不放入物品 2。
当 j = 1 :
maxValue[2][1] = maxValue[1][1] = 0 ,因为 j < volume[2] ,不放入物品 2。
当 j = 2 :
maxValue[2][2] = maxValue[1][2] = 0 ,因为 j < volume[2] ,不放入物品 2。
当 j = 3 :
maxValue[2][3] = maxValue[1][3] = 0 ,因为 j < volume[2] ,不放入物品 2。
当 j = 4 :
maxValue[2][4] = maxValue[1][4] = 5 ,因为 j < volume[2] ,不放入物品 2。
当 j = 5 :
maxValue[2][5] = maxValue[1][5] = 5 ,因为 j < volume[2] ,不放入物品 2。
当 j = 6 :
maxValue[2][6] = maxValue[1][6] = 5 ,因为 j < volume[2] ,不放入物品 2。
当 j = 7 :
maxValue[2][7] = Math.max(maxValue[1][7], maxValue[1][7 - 7] + value[2]) = Math.max(5, 0 + 9) = 9 ,可以放入物品 2。
当 j = 8 :
maxValue[2][8] = Math.max(maxValue[1][8], maxValue[1][8 - 7] + value[2]) = Math.max(5, 0 + 9) = 9 ,可以放入物品 2。
当 j = 9 :
maxValue[2][9] = Math.max(maxValue[1][9], maxValue[1][9 - 7] + value[2]) = Math.max(5, 0 + 9) = 9 ,可以放入物品 2。
当 j = 10 :
maxValue[2][10] = Math.max(maxValue[1][10], maxValue[1][10 - 7] + value[2]) = Math.max(5, 0 + 9) = 9 ,可以放入物品 2。
此时 maxValue[2] 数组的值为 [0, 0, 0, 0, 5, 5, 5, 9, 9, 9, 9] 。
当 i = 3 (考虑物品 3)
当 j = 0 :
maxValue[3][0] = maxValue[2][0] = 0 ,因为 j < volume[3] ,不放入物品 3。
当 j = 1 :
maxValue[3][1] = maxValue[2][1] = 0 ,因为 j < volume[3] ,不放入物品 3。
当 j = 2 :
maxValue[3][2] = maxValue[2][2] = 0 ,因为 j < volume[3] ,不放入物品 3。
当 j = 3 :
maxValue[3][3] = maxValue[2][3] = 0 ,因为 j < volume[3] ,不放入物品 3。
当 j = 4 :
maxValue[3][4] = maxValue[2][4] = 5 ,因为 j < volume[3] ,不放入物品 3。
当 j = 5 :
maxValue[3][5] = Math.max(maxValue[2][5], maxValue[2][5 - 5] + value[3]) = Math.max(5, 0 + 6) = 6 ,可以放入物品 3。
当 j = 6 :
maxValue[3][6] = Math.max(maxValue[2][6], maxValue[2][6 - 5] + value[3]) = Math.max(5, 0 + 6) = 6 ,可以放入物品 3。
当 j = 7 :
maxValue[3][7] = Math.max(maxValue[2][7], maxValue[2][7 - 5] + value[3]) = Math.max(9, 5 + 6) = 11 ,可以放入物品 3。
当 j = 8 :
maxValue[3][8] = Math.max(maxValue[2][8], maxValue[2][8 - 5] + value[3]) = Math.max(9, 5 + 6) = 11 ,可以放入物品 3。
当 j = 9 :
maxValue[3][9] = Math.max(maxValue[2][9], maxValue[2][9 - 5] + value[3]) = Math.max(9, 5 + 6) = 11 ,可以放入物品 3。
当 j = 10 :
maxValue[3][10] = Math.max(maxValue[2][10], maxValue[2][10 - 5] + value[3]) = Math.max(9, 5 + 6) = 11 ,可以放入物品 3。
此时 maxValue[3] 数组的值为 [0, 0, 0, 0, 5, 6, 6, 11, 11, 11, 11] 。

观察这个二维状态转移方程可以发现,maxValue[i][j] 的计算只依赖于 maxValue[i - 1][j] 和 maxValue[i - 1][j - volume[i]],也就是说,当前状态 i 只和上一个状态 i - 1 有关。因此,我们可以只使用一个一维数组 maxValue[j] 来保存状态,在每一轮更新时,直接覆盖上一轮的状态,从而将空间复杂度从 (O(nv)) 优化到 (O(v))。
优化后版本
import java.io.*;
import java.util.*;
public class Main {static final int N = 1010;static int[] volume = new int[N];static int[] value = new int[N];static int[] maxValue = new int[N]; public static void main(String[] args) throws Exception {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int v = sc.nextInt();for(int i = 1; i <= n; i++) {volume[i] = sc.nextInt();value[i] = sc.nextInt();}//计算最大价值for(int i = 1; i <= n; i++) {for(int j = v; j >= volume[i]; j--) {maxValue[j] = Math.max(maxValue[j], maxValue[j - volume[i]] + value[i]);}}System.out.println(maxValue[v]);}
}
到这肯定会有这样的疑问:为什么内层循环要从大到小枚举
因为如果从小到大枚举的话,j - volume[i] 严格小于 j ,也就是maxValue[j - volume[i]] 可能已经在这一次中被更新过了
将其复原的话会变成 maxValue[i][j] = Math.max(maxValue[i][j], maxValue[i][j - volume[i]] + value[i]);
而不是i - 1
还是按照上面的例子详细说明:
当 i = 1 时,物品 1 的重量 volume[1] = 4,价值 value[1] = 5。
如果内层循环从小到大枚举:
当 j = 4 时:
maxValue[4] = Math.max(maxValue[4], maxValue[4 - 4] + value[1]) = Math.max(0, 0 + 5) = 5
当 j = 5 时:
maxValue[5] = Math.max(maxValue[5], maxValue[5 - 4] + value[1]) = Math.max(0, 0 + 5) = 5
当 j = 8 时:
maxValue[8] = Math.max(maxValue[8], maxValue[8 - 4] + value[1]),此时 maxValue[8 - 4] 已经在 j = 4 时被更新为放入物品 1 后的状态,即 maxValue[4] = 5,所以 maxValue[8] = Math.max(0, 5 + 5) = 10。这就意味着我们在计算 maxValue[8] 时,又一次使用了物品 1,相当于物品 1 被重复放入了背包,违背了 0 - 1 背包问题每个物品只能使用一次的规则。
当 i = 1 时,物品 1 的重量 volume[1] = 4,价值 value[1] = 5。
如果内层循环从大到小枚举:
当 j = 10 时:
maxValue[10] = Math.max(maxValue[10], maxValue[10 - 4] + value[1]) = Math.max(0, 0 + 5) = 5
当 j = 9 时:
maxValue[9] = Math.max(maxValue[9], maxValue[9 - 4] + value[1]) = Math.max(0, 0 + 5) = 5
当 j = 8 时:
maxValue[8] = Math.max(maxValue[8], maxValue[8 - 4] + value[1]),此时 maxValue[8 - 4] 还没有被更新,仍然是上一轮(即没有考虑物品 1)的状态,所以 maxValue[8] = Math.max(0, 0 + 5) = 5。
通过从大到小枚举,我们保证了在计算 maxValue[j] 时,maxValue[j - volume[i]] 是上一轮(即 i - 1 )的状态,避免了同一个物品被重复使用的问题,从而正确地实现了 0 - 1 背包问题的优化。
综上所述,内层循环从大到小枚举可以避免同一个物品被重复使用的问题,保证了状态转移的正确性。
相关文章:
01背包问题详解 具体样例模拟版
01背包 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。 输入格式 …...
网络初识 - Java
网络发展史: 单机时代(独立模式) -> 局域网时代 -> 广域网时代 -> 移动互联网时代 网络互联:将多台计算机链接再一起,完成数据共享。 数据共享的本质是网络数据传输,即计算机之间通过网络来传输数…...
zk基础—5.Curator的使用与剖析一
大纲 1.基于Curator进行基本的zk数据操作 2.基于Curator实现集群元数据管理 3.基于Curator实现HA主备自动切换 4.基于Curator实现Leader选举 5.基于Curator实现分布式Barrier 6.基于Curator实现分布式计数器 7.基于Curator实现zk的节点和子节点监听机制 8.基于Curator创…...
大模型快速 ASGI 服务器uvicorn
基础概念类 1. 什么是 Uvicorn,它的作用是什么? 答案:Uvicorn 是一个基于 Python 的快速 ASGI(异步服务器网关接口)服务器。它的主要作用是作为 Web 应用程序的服务器,负责接收客户端的请求,并…...
每日一题(小白)回溯篇4
深度优先搜索题:找到最长的路径,计算这样的路径有多少条(使用回溯) 分析题意可以得知,每次向前后左右走一步,直至走完16步就算一条走通路径。要求条件是不能超出4*4的范围,不能重复之前的路径。…...
消息队列基础概念及选型,常见解决方案包括消息可靠性、消息有序、消息堆积、重复消费、事务消息
前言 是时候总结下消息队列相关知识点啦!我搓搓搓搓 本文包括消息队列基础概念介绍,常见解决方案包括消息可靠性、消息有序、消息堆积、重复消费、事务消息 参考资料: Kafka常见问题总结 | JavaGuide RocketMQ常见问题总结 | JavaGuide …...
基于STM32与应变片的协作机械臂力反馈控制系统设计与实现---3.3 机械结构改装
3.3 机械臂结构改装设计与实施 一、改装需求分析 1.1 改装类型分级 改装级别涉及范围典型改动周期成本I级(小型)末端执行器工具快换装置1-3天$500-2000II级(中型)关节模块电机/减速器升级1-2周$2000-8000III级(大型)本体结构材质/拓扑优化1-3月$8000+1.2 关键参数变更评…...
k8s进阶之路:本地集群环境搭建
概述 文章将带领大家搭建一个 master 节点,两个 node 节点的 k8s 集群,容器基于 docker,k8s 版本 v1.32。 一、系统安装 安装之前请大家使用虚拟机将 ubuntu24.04 系统安装完毕,我是基于 mac m1 的系统进行安装的,所…...
云服务器实战:用 Nginx 搭建高性能 API 网关与反向代理服务(附完整配置流程)
在如今的 Web 系统架构中,“接口统一出口”已成为必备设计模式——无论是前后端分离、微服务架构,还是多端接入(Web、小程序、App),一个稳定、高性能、可扩展的 API 网关至关重要。 而 Nginx,作为轻量级高…...
C++ STL 详解 ——list 的深度解析与实践指南
在 C 的标准模板库(STL)中,list作为一种重要的序列式容器,以其独特的双向链表结构和丰富的操作功能,在许多编程场景下发挥着关键作用。深入理解list的特性与使用方法,能帮助开发者编写出更高效、灵活的代码…...
按键切换LCD显示后,显示总在第二阶段,而不在第一阶段的问题
这是一个密码锁的程序,当在输入密码后,原本是要重置密码,但是程序总是在输入密码正确后总是跳转置设置第二个密码,而第一个密码总是跳过。 不断修改后, 解决方法 将if语句换成switch语句,这样就可以分离程序…...
护网蓝初面试题
《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...
C++11: 智能指针
C11: 智能指针 (一)智能指针原理1.RAll2.智能指针 (二)C11 智能指针1. auto_ptr2. unique_ptr3. shared_ptr4. weak_ptr (三)shared_ptr中存在的问题std::shared_ptr的循环引用 (四)删除器(五&a…...
去中心化预测市场
去中心化预测市场 核心概念 预测市场类型: 类别型市场:二元结果(YES/NO),例如“BTC在2024年突破10万美元?” 多选型市场:多个选项(如总统候选人),赔付基于…...
从零实现本地大模型RAG部署
1. RAG概念 RAG(Retrieval-Augmented Generation)即检索增强生成,是一种结合信息检索与大型语言模型(大模型)的技术。从外部知识库(如文档、数据库或网页)中实时检索相关信息,并将其…...
使用 Python 连接 PostgreSQL 数据库,从 `mimic - III` 数据库中筛选数据并导出特定的数据图表
要使用 Python 连接 PostgreSQL 数据库,从 mimic - III 数据库中筛选数据并导出特定的数据图表,你可以按照以下步骤操作: 安装所需的库:psycopg2 用于连接 PostgreSQL 数据库,pandas 用于数据处理,matplot…...
【Linux系统篇】:探索文件系统原理--硬件磁盘、文件系统与链接的“三体宇宙”
✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:Linux篇–CSDN博客 文章目录 一.认识硬件--磁盘物理存储结构1.存储介质类型2.物理存储单元3…...
Tracing the thoughts of a large language model 简单理解
Tracing the thoughts of a large language model 这篇论文通过电路追踪方法(Circuit Tracing)揭示了大型语言模型Claude 3.5 Haiku的内部机制,其核心原理可归纳为以下几个方面: 1. 方法论核心:归因图与替换模型 替换模型(Replacement Model) 使用跨层转码器(CLT)将原…...
OpenCV边缘检测技术详解:原理、实现与应用
概述 边缘检测是计算机视觉和图像处理中最基本也是最重要的技术之一,它通过检测图像中亮度或颜色急剧变化的区域来识别物体的边界。边缘通常对应着场景中物体的物理边界、表面方向的变化或深度不连续处。 分类 OpenCV提供了多种边缘检测算法,下面我们介…...
Java学习——day22(Java反射基础入门)
文章目录 1.反射的定义2. 认识反射的关键API2.1 Class2.2 Field2.3 Method2.4 Constructor 3. 示例代码讲解与分析4. 编写反射示例代码的步骤4.1 定义测试类4.2 编写主程序,使用反射获取信息4.3 通过反射创建对象并调用方法 5. 总结6.今日生词 Java反射笔记 1.反射的…...
BN 层做预测的时候, 方差均值怎么算
✅ 一、Batch Normalization(BN)回顾 BN 层在训练和推理阶段的行为是不一样的,核心区别就在于: 训练时用 mini-batch 里的均值方差,预测时用全局的“滑动平均”均值方差。 🧪 二、训练阶段(Trai…...
JS 其他事件类型
页面加载 事件 window.addEvent() window.addEventListener(load,function(){const btn document.querySelector(button)btn.addEventListener(click,function(){alert(按钮)})})也可以给其他标签加该事件 HTML加载事件 找html标签 也可以给页面直接赋值...
AI Agent设计模式五:Orchestrator
概念 :中央任务调度中枢 ✅ 优点:全局资源协调,确保任务执行顺序❌ 缺点:单点故障风险,可能成为性能瓶颈 import operator import osfrom langchain.schema import SystemMessage, HumanMessage from langchain_opena…...
在Hive中,将数据从一个表查询并插入到另一个表
1. 确认目标表结构 确保目标表已存在且结构与查询结果匹配。若不存在,需先创建: CREATE TABLE target_table ( id INT, name STRING ) PARTITIONED BY (dt STRING) STORED AS ORC; 2. 选择插入方式 覆盖插入(替换现有数据࿰…...
Android Fresco 框架缓存模块源码深度剖析(二)
Android Fresco 框架缓存模块源码深度剖析 一、引言 本人掘金号,欢迎点击关注:https://juejin.cn/user/4406498335701950 在 Android 应用开发中,图片加载和处理是常见且重要的功能。频繁的图片加载不仅会消耗大量的网络流量,还…...
MySQL基础 [三] - 数据类型
目录 数据类型分类 编辑 数值类型 tinyint bit 浮点类型 float decimal 字符串类型 char varchar varchar和char的比较和选择 日期和时间类型 enum和set enum类型 set类型 enum和set的类型查找 数据类型分类 数值类型 tinyint TINYINT[(M)] [UNSIGNED]是 …...
不用训练,集成多个大模型产生更优秀的输出
论文标题 Collab: Controlled Decoding using Mixture of Agents for LLM Alignment 论文地址 https://arxiv.org/pdf/2503.21720 作者背景 JP摩根,马里兰大学帕克分校,普林斯顿大学 动机 大模型对齐(alignment)的主要目的…...
随笔1 认识编译命令
1.认识编译命令 1.1 解释gcc编译命令: gcc test1.cpp -o test1 pkg-config --cflags --libs opencv 命令解析: gcc:GNU C/C 编译器,用于编译C/C代码。 test1.cpp:源代码文件。 -o test1:指定输出的可执行文件名为t…...
【数据结构】并查集应用
修改数组 题目:检查ai是否出现过,出现过就不断1,使成为第一个没有出现过的。这样得到一个不重复数组。 祖先是该数字能使用的最小数字,当使用完后,祖先把这个格子占了,下次再来,就得使用后一个…...
python基础-16-处理csv文件和json数据
文章目录 【README】【16】处理csv文件和json数据【16.1】csv模块【16.1.1】reader对象【16.1.2】在for循环中, 从reader对象读取数据【16.1.3】writer对象【16.1.5】DictReader与DictWriter对象 【16.4】json模块【16.4.1】使用loads()函数读取json字符串并转为jso…...
