01背包问题详解 具体样例模拟版

news2025/4/9 16:28:18

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

编号体积价值
145
279
356

以下过程十分详细,但看起来也很恶心,如果已经理解了整个过程,可以直接看后面的一维优化


当 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 背包问题的优化。
综上所述,内层循环从大到小枚举可以避免同一个物品被重复使用的问题,保证了状态转移的正确性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络初识 - Java

网络发展史&#xff1a; 单机时代&#xff08;独立模式&#xff09; -> 局域网时代 -> 广域网时代 -> 移动互联网时代 网络互联&#xff1a;将多台计算机链接再一起&#xff0c;完成数据共享。 数据共享的本质是网络数据传输&#xff0c;即计算机之间通过网络来传输数…

每日一题(小白)回溯篇4

深度优先搜索题&#xff1a;找到最长的路径&#xff0c;计算这样的路径有多少条&#xff08;使用回溯&#xff09; 分析题意可以得知&#xff0c;每次向前后左右走一步&#xff0c;直至走完16步就算一条走通路径。要求条件是不能超出4*4的范围&#xff0c;不能重复之前的路径。…

k8s进阶之路:本地集群环境搭建

概述 文章将带领大家搭建一个 master 节点&#xff0c;两个 node 节点的 k8s 集群&#xff0c;容器基于 docker&#xff0c;k8s 版本 v1.32。 一、系统安装 安装之前请大家使用虚拟机将 ubuntu24.04 系统安装完毕&#xff0c;我是基于 mac m1 的系统进行安装的&#xff0c;所…

C++ STL 详解 ——list 的深度解析与实践指南

在 C 的标准模板库&#xff08;STL&#xff09;中&#xff0c;list作为一种重要的序列式容器&#xff0c;以其独特的双向链表结构和丰富的操作功能&#xff0c;在许多编程场景下发挥着关键作用。深入理解list的特性与使用方法&#xff0c;能帮助开发者编写出更高效、灵活的代码…

按键切换LCD显示后,显示总在第二阶段,而不在第一阶段的问题

这是一个密码锁的程序&#xff0c;当在输入密码后&#xff0c;原本是要重置密码&#xff0c;但是程序总是在输入密码正确后总是跳转置设置第二个密码&#xff0c;而第一个密码总是跳过。 不断修改后&#xff0c; 解决方法 将if语句换成switch语句&#xff0c;这样就可以分离程序…

护网蓝初面试题

《网安面试指南》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: 智能指针 &#xff08;一&#xff09;智能指针原理1.RAll2.智能指针 (二)C11 智能指针1. auto_ptr2. unique_ptr3. shared_ptr4. weak_ptr &#xff08;三&#xff09;shared_ptr中存在的问题std::shared_ptr的循环引用 &#xff08;四&#xff09;删除器&#xff08;五&a…

从零实现本地大模型RAG部署

1. RAG概念 RAG&#xff08;Retrieval-Augmented Generation&#xff09;即检索增强生成&#xff0c;是一种结合信息检索与大型语言模型&#xff08;大模型&#xff09;的技术。从外部知识库&#xff08;如文档、数据库或网页&#xff09;中实时检索相关信息&#xff0c;并将其…

【Linux系统篇】:探索文件系统原理--硬件磁盘、文件系统与链接的“三体宇宙”

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;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边缘检测技术详解:原理、实现与应用

概述 边缘检测是计算机视觉和图像处理中最基本也是最重要的技术之一&#xff0c;它通过检测图像中亮度或颜色急剧变化的区域来识别物体的边界。边缘通常对应着场景中物体的物理边界、表面方向的变化或深度不连续处。 分类 OpenCV提供了多种边缘检测算法&#xff0c;下面我们介…

BN 层做预测的时候, 方差均值怎么算

✅ 一、Batch Normalization&#xff08;BN&#xff09;回顾 BN 层在训练和推理阶段的行为是不一样的&#xff0c;核心区别就在于&#xff1a; 训练时用 mini-batch 里的均值方差&#xff0c;预测时用全局的“滑动平均”均值方差。 &#x1f9ea; 二、训练阶段&#xff08;Trai…

JS 其他事件类型

页面加载 事件 window.addEvent() window.addEventListener(load,function(){const btn document.querySelector(button)btn.addEventListener(click,function(){alert(按钮)})})也可以给其他标签加该事件 HTML加载事件 找html标签 也可以给页面直接赋值

AI Agent设计模式五:Orchestrator

概念 &#xff1a;中央任务调度中枢 ✅ 优点&#xff1a;全局资源协调&#xff0c;确保任务执行顺序❌ 缺点&#xff1a;单点故障风险&#xff0c;可能成为性能瓶颈 import operator import osfrom langchain.schema import SystemMessage, HumanMessage from langchain_opena…

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摩根&#xff0c;马里兰大学帕克分校&#xff0c;普林斯顿大学 动机 大模型对齐&#xff08;alignment&#xff09;的主要目的…

随笔1 认识编译命令

1.认识编译命令 1.1 解释gcc编译命令: gcc test1.cpp -o test1 pkg-config --cflags --libs opencv 命令解析&#xff1a; gcc&#xff1a;GNU C/C 编译器&#xff0c;用于编译C/C代码。 test1.cpp&#xff1a;源代码文件。 -o test1&#xff1a;指定输出的可执行文件名为t…

Hyperlane 框架路由功能详解:静态与动态路由全掌握

Hyperlane 框架路由功能详解&#xff1a;静态与动态路由全掌握 Hyperlane 框架提供了强大而灵活的路由功能&#xff0c;支持静态路由和动态路由两种模式&#xff0c;让开发者能够轻松构建各种复杂的 Web 应用。本文将详细介绍这两种路由的使用方法。 静态路由&#xff1a;简单…

铰链损失函数 Hinge Loss和Keras 实现

一、说明 在为了了解 Keras 深度学习框架的来龙去脉&#xff0c;本文介绍铰链损失函数&#xff0c;然后使用 Keras 实现它们以进行练习并了解它们的行为方式。在这篇博客中&#xff0c;您将首先找到两个损失函数的简要介绍&#xff0c;以确保您在我们继续实现它们之前直观地理解…

瑞数信息发布《BOTS自动化威胁报告》,揭示AI时代网络安全新挑战

近日&#xff0c;瑞数信息正式发布《BOTS自动化威胁报告》&#xff0c;力求通过全景式观察和安全威胁的深度分析&#xff0c;为企业在AI时代下抵御自动化攻击提供安全防护策略&#xff0c;从而降低网络安全事件带来的影响&#xff0c;进一步增强业务韧性和可持续性。 威胁一&am…