背包问题详解

news2024/11/23 18:35:03

前言

本文主要讲解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.coloradmin.cn/o/1591937.html

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

相关文章

电源监视继电器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跳位监视、合位监视、电源监控继电器…

Django之rest_framework(一)

一、请求和响应对象介绍 REST framework引入了2个新的对象:Request和Response 1.1、Request rest_framework.request.Request 该对象扩展了常规的HttpRequest ,增加了对REST框架灵活的请求解析和请求认证的支持 官网:Requests - Django REST framework 主要属性: data 这…

react使用npm i @reduxjs/toolkit react-redux

npm i reduxjs/toolkit react-redux 创建一个 store文件夹&#xff0c;里面创建index.js文件和子模块文件夹 index,js文件写入以下代码 import {configureStore} from reduxjs/toolkit // 导入子模块 import counterReducer from ./modules/one import two from ./modules/tw…

计算机网络——CSMA/CD协议以及相关习题

目录 前言 引言 CSMA/CD协议 CSMA与CSMA/CD的区别 CSMA/CD流程 前言 本博客是博主用于复习计算机网络的博客&#xff0c;如果疏忽出现错误&#xff0c;还望各位指正。 引言 最早的以太网&#xff0c;许多计算机都连接在一根总线上工作——广播通信方式。 总线的特点想…

APP开发教学:开发同城O2O外卖跑腿系统源码详解

同城O2O外卖跑腿系统&#xff0c;满足了人们对于外卖送餐和生活服务的需求。今天&#xff0c;小编将为您讲解如何开发同城O2O外卖跑腿系统源码。 1.前期准备 首先&#xff0c;我们需要明确系统的功能需求和用户需求&#xff0c;包括外卖订购、配送员接单、支付功能等。其次&am…

排序1——C语言

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

vue2响应式原理----发布订阅模式

很多人感觉vue2的响应式其实用到了观察者发布订阅。我们先来看一下简单的发布订阅的代码&#xff1a; // 调度中心 class Dep {static subscribes {}// 订阅所有需求static subscribe (key, demand) {// 对需求分类收集if (!Dep.subscribes[key]) Dep.subscribes[key] []Dep…

使用腾讯云服务器如何搭建网站?新手建站教程

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网txyfwq.com整理使用腾讯云服务器建站教程&#xff0c;…

前端下载url文件(解决PDF, 图片自动在浏览器打开)

常规下载方法&#xff1a; /* 方法1 */ window.open(下载url地址, _blank)/* 方法2 */ const link document.createElement("a"); link.download true; link.href 下载url地址; link.click(); document.body.removeChild(link);pdf文件默认在浏览器中展示解决方案…

Linux:Zabbix + Grafana10.4.2(3)

1.部署zabbix 下面这篇文章写了详细的部署zabbix过程 &#xff0c;使用的centos9系统 Linux&#xff1a;部署搭建zabbix6&#xff08;1&#xff09;-CSDN博客https://blog.csdn.net/w14768855/article/details/137426966?spm1001.2014.3001.5501下面这篇文章使用的是centos7…

RTSP/Onvif安防视频EasyNVR平台 vs.多协议接入视频汇聚EasyCVR平台:设备分组的区别

EasyNVR安防视频云平台是旭帆科技TSINGSEE青犀旗下支持RTSP/Onvif协议接入的安防监控流媒体视频云平台。平台具备视频实时监控直播、云端录像、云存储、录像检索与回看、告警等视频能力&#xff0c;能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、W…

二叉树练习day.8

235.二叉搜索树的最近公共祖先 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共…

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

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

ubuntu22下使用vscode调试redis7源码环境搭建

ubuntu22下使用vscode调试redis7源码环境搭建 ##vscode launch.json配置文件 {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387"version": "0.2.0&…

【神经网络与深度学习】循环神经网络基础

tokenization tokenization&#xff1a;分词 每一个词语都是token 分词方法&#xff1a;转为单个词、转为多个词语 N-gram表示法 准备词语特征的方法 &#xff08;把连续的N个词作为特征&#xff09; 如 ”我爱你“——>[我&#xff0c;爱&#xff0c;你] 2-gram——[[我…

游戏服务器DDOS克星-抗D盾(游戏盾)

随着网络游戏市场的不断扩大和发展&#xff0c;游戏服务器遭受DDOS攻击的频率也在逐年增加。DDOS攻击的主要目的是使游戏服务器瘫痪&#xff0c;使得游戏无法正常进行&#xff0c;导致游戏运营商巨额损失。鉴于此&#xff0c;针对游戏服务器的防DDOS攻击技术德迅云安全自主研发…

Ubuntu 22上安装Anaconda3。下载、安装、验证详细教程

在Ubuntu 22上安装Anaconda3&#xff0c;你可以遵循以下步骤&#xff1a; 更新系统存储库&#xff1a; 打开终端并运行以下命令来更新系统存储库&#xff1a; sudo apt update安装curl包&#xff1a; 下载Anaconda安装脚本通常需要使用curl工具。如果系统中没有安装curl&#x…

【Python函数和类4/6】递归与匿名函数

目录 目标 匿名函数 多个形参 匿名函数的局限性 递归 语言例子 数学例子 递归的实现 递归代码 练习 总结 目标 在之前的博客中&#xff0c;我们学习了定义函数、调用函数以及设置函数的参数。在今天&#xff0c;我们会补充函数的两个常见的知识点&#xff0c;一个是匿…

前端css笔记(pink老师)

css css书写顺序 自适应屏幕 html { width: 100%; height: 100%; display: table; } body { display: table-cell; } 用了这个方法以后&#xff0c;如果希望页面内的盒子也适应屏幕大小&#xff0c;则使用以下方法&#xff0c;会根据父亲的宽高计算出该盒子的宽高 width:xx%; …

策略模式(知识点)——设计模式学习笔记

文章目录 0 概念1 使用场景2 优缺点2.1 优点2.2 缺点 3 实现方式4 和其他模式的区别5 具体例子实现5.1 实现代码 0 概念 定义&#xff1a;定义一个算法族&#xff0c;并分别封装起来。策略让算法的变化独立于它的客户&#xff08;这样就可在不修改上下文代码或其他策略的情况下…