背包问题详解

前言

本文主要讲解01背包问题,读者如果能完全搞懂01背包,那么稍作思考也能解决完全背包、多重背包问题。至于分组背包、有依赖的背包等问题博主也没有继续深入,但是应该都是在01背包的基础上拓展,读者若有兴趣可查阅其他文章。

01背包问题

有n个物品,每个物品有且仅有1件,每个物品有重量和价值两个属性,现在有一个固定容量的背包,要将这些物品放入背包内,使得背包内物品总价值最大,问要如何放?
举个具体的例子,有5件物品,物品的重量和价值如下所示:

物品编号重量价值
124
233
358
445
522

背包容量为5时应该怎么放?

解决思路

对于n个物品w容量的背包或许毫无头绪。可以从简单的开始思考,如果是只有1个物品w容量呢?那就很简单了。容量大于等于物品重量时放入,小于时无法放入,这很好理解吧。列出容量与价值的表格如下:
该表格的含义是当有1件物品、背包容量为w时,背包能装的最大价值
在这里插入图片描述
可以看到,当背包容量大于2时,能装的最大价值只能是4,因为只有一件物品,背包再大也没东西可以装。

在此问题的基础上拓展一下,当存在编号1、2两件物品时,还是按照之前的思路,只考虑物品2能不能放入,先列出一个临时表格如下:
在这里插入图片描述
注意表格中三个标红的地方。

  1. 当背包容量为2时,虽然不能放入2号物品,但是可以放入1号物品,所以第2行第2列应该填4。含义是当同时存在物品1、2,背包容量为2时,最大价值为4。
  2. 当背包容量为3时,虽然可以放入物品2,但是放入物品2后背包容量剩余0,无法再放入其他物品,此时背包的价值是3;如果不放入,背包容量为3,只存在物品1时,最大价值为4;4>3,所以选择不放入,第2行第3列填4。**含义是当同时存在物品1、2,背包容量为3时,最大价值为4。**容量为4时同理。
  3. 当背包容量为5时,选择放入物品2,此时背包剩余容量为2,然后发现只存在物品1、容量为2时最大价值为4,此时背包总价值为3+4=7;如果不放入,背包容量为5,只存在物品1时,最大价值为4;7>4,所以选择放入物品2,第五列填7。
    更正后的表格为:
    该表格的含义是当有1、2两件物品、背包容量为w时,背包能装的最大价值
    在这里插入图片描述
    多件物品与两件物品思路一样,往下继续计算便能得到最终的表格
    当有1、2、3、4、5五件物品、背包容量为w时,背包能装的最大价值
    在这里插入图片描述

上面说的一堆东西可以凝练成一个东西,那就是状态转移方程
01背包的状态转移方程为 v [ i ] [ j ] : { v [ i − 1 ] [ j ] j < i t e m [ i ] . w e i g h t M a x ( v [ i − 1 ] [ j ] , i t e m [ i ] . v a l u e + v [ i − 1 ] [ j − i t e m [ i ] . w e i g h t ] ) j > = i t e m [ i ] . w e i g h t v[i][j] :\begin{cases} v[i-1][j] \quad j < item[i].weight\\ Max(v[i-1][j],item[i].value + v[i-1][j-item[i].weight]) \quad j >= item[i].weight\end{cases} v[i][j]:{v[i1][j]j<item[i].weightMax(v[i1][j],item[i].value+v[i1][jitem[i].weight])j>=item[i].weight

代码实现

需要注意的是当遍历第一件物品并且 j < item[i].weight时取v[i-1]可能会出现数组越界或者空指针异常等问题;而且计算机是从0开始计数,人类的习惯是从1开始计数;所以为了防止程序异常和便于理解,v[0][j]表示没有物品时背包的价值。
网上大多数代码都是只输出最大价值,博主代码除了输出最大价值外,还输出了具体是拿哪几件物品(多个方案时只输出一个)。前面主要是测试用例和实体类定义,觉得啰嗦可以直接跳到88行看核心方法。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class Main {public static void main(String[] args) {List<Item> items = new ArrayList<>();items.add(new Item(2,4));items.add(new Item(3,3));items.add(new Item(5,8));items.add(new Item(4,5));items.add(new Item(2,2));
//        items.add(new Item(7,21));
//        items.add(new Item(2,18));
//        items.add(new Item(6,9));
//        items.add(new Item(3,15));
//        items.add(new Item(5,6));Backpack res1 = backpack_0_1(items, 7);String itemString = Arrays.toString(res1.getItems().toArray());System.out.println(String.format("取物品%s,能得到最大价值%s",itemString, res1.getValue()));}public static class Backpack {// 背包中存放的物品编号private List<Integer> items = new ArrayList<>();// 物品总价值private int value = 0;public List<Integer> getItems() {return items;}public void setItems(List<Integer> items) {this.items = items;}public int getValue() {return value;}public void setValue(int value) {if (value < 0) {throw new RuntimeException("物品价值不能小于0");}this.value = value;}}public static class Item {private int volume;private int value;public Item() {}public Item(int volume, int value) {if (volume <= 0) {throw new RuntimeException("物品体积必须大于0");}if (value <= 0) {throw new RuntimeException("物品价值必须大于0");}this.volume = volume;this.value = value;}public int getVolume() {return volume;}public void setVolume(int volume) {if (volume <= 0) {throw new RuntimeException("物品体积必须大于0");}this.volume = volume;}public int getValue() {return value;}public void setValue(int value) {if (value <= 0) {throw new RuntimeException("物品价值必须大于0");}this.value = value;}}public static Backpack backpack_0_1(List<Item> items,Integer backpackVolume){if (backpackVolume < 0) {throw new RuntimeException("背包体积不能小于0");}if (backpackVolume == 0 || items.size() == 0) {return new Backpack();}// 为了便于理解,在第0个位置填充null,保证第n个物品在items[n]的位置items.add(0,null);Backpack[][] table = new Backpack[items.size()][backpackVolume + 1];// 从体积为1开始遍历每一件物品for (int i = 1;i <= backpackVolume;i++) {for (int j = 1;j < items.size();j++) {Item item = items.get(j);// 不存在该物品时,容量为i时背包的最优解Backpack bestBackpack = table[j-1][i] == null ? new Backpack() : table[j-1][i];// 物品体积大于当前背包体积,不放入物品if (item.getVolume() > i) {table[j][i] = bestBackpack;} else {// 剩余体积int residueVolume = i - item.getVolume();// 剩余体积能装的最大价值Backpack residueBestBackpack = table[j - 1][residueVolume] == null ? new Backpack() : table[j - 1][residueVolume];int value =  residueBestBackpack.getValue();// 如果放入后的价值大于放入前的价值,则放入该物品。否则不放入if (item.getValue() + value > bestBackpack.getValue()) {Backpack backpack = new Backpack();backpack.setValue(item.getValue() + value);backpack.getItems().addAll(residueBestBackpack.getItems());backpack.getItems().add(j);table[j][i] = backpack;} else {table[j][i] = bestBackpack;}}}}return table[items.size()-1][backpackVolume];}
}

完全背包问题与多重背包问题

如果能彻底搞懂01背包问题那么这两种背包问题相信大部分人都能想得出来。如果暂时没想到或许对比一下01背包问题和完全背包问题的状态转移方程就能明白了。
01背包 v [ i ] [ j ] : { v [ i − 1 ] [ j ] j < i t e m [ i ] . w e i g h t M a x ( v [ i − 1 ] [ j ] , i t e m [ i ] . v a l u e + v [ i − 1 ] [ j − i t e m [ i ] . w e i g h t ] ) j > = i t e m [ i ] . w e i g h t v[i][j] :\begin{cases} v[i-1][j] \quad\quad j < item[i].weight\\ Max(v[i-1][j],item[i].value + v[i-1][j-item[i].weight]) \quad j >= item[i].weight\end{cases} v[i][j]:{v[i1][j]j<item[i].weightMax(v[i1][j],item[i].value+v[i1][jitem[i].weight])j>=item[i].weight

完全背包 v [ i ] [ j ] : { v [ i − 1 ] [ j ] j < i t e m [ i ] . w e i g h t M a x ( v [ i − 1 ] [ j ] , i t e m [ i ] . v a l u e + v [ i ] [ j − i t e m [ i ] . w e i g h t ] ) j > = i t e m [ i ] . w e i g h t v[i][j] :\begin{cases} v[i-1][j] \quad\quad j < item[i].weight\\ Max(v[i-1][j],item[i].value + v[i][j-item[i].weight]) \quad j >= item[i].weight\end{cases} v[i][j]:{v[i1][j]j<item[i].weightMax(v[i1][j],item[i].value+v[i][jitem[i].weight])j>=item[i].weight

没错,就只是v[i]和v[i-1]的不同。
01背包问题是每类物品有且仅有一件,如果选择放入该物品,那么该物品就没有了,所以就只能是 item[i].value + v[i-1][j-item[i].weight];完全背包问题是每类物品有无限件,哪怕我选择放入该物品之后还可以继续放入物品,所以就是 item[i].value + v[i][j-item[i].weight]。
如果还有点懵,不如回想一下本文的第一个例子,只有一件物品时,01背包容量大于物品重量后价值不会发生变化。但是完全背包的价值会随着容量的扩大从而阶梯性的增加,因为当剩余又能够放入一件物品时价值就会增加。

至于多重背包问题也是在此基础上拓展,无非是多了一个什么时候取v[i]什么时候取v[i-1]的问题罢了,就留给读者自行思考了。

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

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

相关文章

排序1——C语言

排序 1. 复杂度2. 插入排序2.1 直接插入排序2.2 希尔排序 3. 选择排序3.1 直接选择排序3.2 堆排序 排序在生活中很常见&#xff0c;比如在网购时&#xff0c;按价格排序&#xff0c;按好评数排序&#xff0c;点餐时&#xff0c;按评分排序等等。而排序有快和慢&#xff0c;快的…

「JS 基础」迭代器和生成器 Iterator Generator 入门

前言 JavaScript的生成器(Generators)和迭代器(Iterators)是ES6引入的功能,使得开发者可以更方便地实现自定义的迭代逻辑。 迭代器 迭代器是一种接口,它为各种不同的数据结构(如数组或者映射)定义了一个标准的遍历方法。具体来说,一个迭代器对象必须实现一个 next…

电源监视继电器HRTH-J-2H2D AC220V 导轨安装 JOSEF约瑟

系列型号&#xff1a; HRTH-Y-2H2D-X-T跳位监视、合位监视、电源监控继电器&#xff1b; HRTH-Y-2Z-X-T跳位监视、合位监视、电源监控继电器&#xff1b; HRTH-Y-2H-X-T跳位监视、合位监视、电源监控继电器&#xff1b; HRTH-J-2H2D-X-T跳位监视、合位监视、电源监控继电器…

分布式事务方案与Seata详解

分布式事务与Seata详解 一、分布式事务1.什么是分布式事务2.分布式事务解决方案-2PC3.分布式事务解决方案-3PC4.分布式事务解决方案-TCC5.分布式事务解决方案-XA6.可靠消息最终一致性6.1 本地消息表6.2 事务消息 7.最大努力通知8.SAGA9.分布式事务解决方案思考 二、Seata 简介与…

[leetcode] 427. 建立四叉树

给你一个 n * n 矩阵 grid &#xff0c;矩阵由若干 0 和 1 组成。请你用四叉树表示该矩阵 grid 。 你需要返回能表示矩阵 grid 的 四叉树 的根结点。 四叉树数据结构中&#xff0c;每个内部节点只有四个子节点。此外&#xff0c;每个节点都有两个属性&#xff1a; val&#x…

30万的源码和300的源码有什么区别?

源码的质量很大程度上取决于其来源、开发者的技术水平和项目的具体需求。有些源码可能确实存在一些问题&#xff0c;比如代码结构混乱、注释不清晰、性能不佳等。 而价高优秀的源码都采用了高效的数据结构和算法&#xff0c;代码结构清晰&#xff0c;逻辑严谨&#xff0c;具有良…

2024最方便申请SSL证书方法介绍

申请SSL证书其实就像你去官方机构办个身份证&#xff0c;证明你的网站是合法且安全的。这里给你白话一点的简单步骤&#xff1a; 步骤一&#xff1a;确定需求 1. 域名&#xff1a;确保你有一个要申请证书的域名&#xff0c;就是你的网站地址&#xff0c;比如 www.example.com。…

本地知识库搭建教程来了,跟着做就行

你是不是常常因为找不到几个月前那个重要的工作笔记而头疼&#xff1f;或者是厌倦了反复在互联网的海洋中搜寻相同的信息&#xff1f;一个本地的知识库可能是你需要的解决方案。今天&#xff0c;我就要为你分享如何简单地搭建起自己的知识库&#xff0c;让你的信息更有组织、更…

.NET8 上的 Bing :动态PGO的影响

自从我上次更新大家有关.NET在Bing技术栈中的状态以来已经过去了一年多&#xff0c;尤其是位于核心位置的高性能工作流执行引擎。在这段时间里&#xff0c;这个引擎的应用范围只增不减&#xff0c;特别是随着Microsoft Copilot的发布。虽然我们的工作流引擎起源于Bing&#xff…

Harmony鸿蒙南向外设驱动开发-Pin_auth

功能简介 口令认证是端侧设备不可或缺的一部分&#xff0c;为设备提供一种用户认证能力&#xff0c;可应用于设备解锁、支付、应用登录等身份认证场景。用户注册口令后&#xff0c;口令认证模块就可为设备提供密码解锁的功能&#xff0c;保证设备的安全使用。口令识别的整体架…

【Python使用】python高级进阶知识md总结第8篇:TCP 网络应用程序开发流程,1. TCP 网络应用程序开发流程的介绍【附代码文档】

python高级进阶全知识知识笔记总结完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;操作系统&#xff0c;虚拟机软件。ls命令选项&#xff0c;mkdir和rm命令选项。压缩和解压缩命令&#xff0c;文件权限命令。编辑器 vim&#xff0c;软件安装。获取进程编号…

Solana 上创建自己的 SLPToken:简明指南

Solana 定义 Solana 是由 Solana Labs 创建的区块链平台&#xff0c;旨在提供高吞吐量和低延迟的去中心化应用&#xff08;DApps&#xff09;开发环境。它采用一系列创新技术&#xff0c;如 PoH&#xff08;Proof of History&#xff09;共识机制和 Tower BFT&#xff08;BFT …