动态规划系列 —— 背包问题

news2024/12/24 8:56:54

什么是背包问题

背包问题是有N件物品,容量为V的背包

每个物品有两个属性:体积,价值,分别用数组vw表示

第i件物品的体积为v[i],价值为w[i]

计算在背包装得下的情况下,能装的最大价值是多少?

根据不同的限制条件,主要有以下三种背包问题:

  • 01背包
  • 完全背包
  • 多重背包

01背包

每件物品最多用一次

朴素做法

定义二维数组dp,dp[i][j]表示

  • 哪些集合:从前i个物品中选,装不超过j容量的所有选法
  • 什么属性:这些选法中,最大的价值

根据dp数组的定义,dp[N][V]就是最终要求解的答案

接下来看一个普遍的位置dp[i][j],其表示的集合可以划分为哪些集合?

所有从前i个物品中选,装不超过j容量的所有选法中,可以被不重不漏地划分为:

  1. 不包含第i件物品
  2. 包含第i件物品

在这里插入图片描述

dp[i][j] = max(前i件物品中选且不包含第i件物品的最大价值,前i件物品中选且包含第i件物品的最大价值)

前i件物品中选且不包含第i件物品的最大价值,和dp[i - 1][j]的定义是一样的

前i件物品中选且包含第i件物品的最大价值,这种情况可以将所有的选法中第i件物品的价值提出来,除了第i件物品的价值外,还要加上在前i-1件物品中选,容量不超过j - v[i]的最大价值,这和dp[i - 1][j - v[i]]的定义一样

因此dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i])

我们只要在计算dp[i][j]时,保证其依赖的项已经计算过,计算过程就是正确的,

怎么保证之前的已经计算过呢?按照如下的循环方式计算:

public  static  int  bag01 ( int N, int V, int [] w, int []v) {  
    int [][] dp = new  int [N+ 1 ][V+ 1 ];  
    for ( int  i  =  1 ;i <= N;i++) {  
        for ( int  j  =  0 ;j <= V;j++) { 
            dp[i][j] = dp[i- 1 ][j];  
            if (j >= v[i]) { 
                dp[i][j] = Math. max (dp[i][j], dp[i- 1 ][j-v[i]] + w[i]); 
            } 
        } 
    }  
    return dp[N][V]; 
}

空间优化

可以发现在计算第i层时,只用到了第i-1层,这样可以用一个只有两层的数组滚动计算

进一步,可以发现使用到的j,要么是第i-1层的j,要么是第i-1层j的左侧,不是j左右两侧都依赖,因此可以优化为只使用一层的数组

需要注意在计算每一层的数据时,不能从左往右计算,因为会将本层的数据覆盖掉本该是上一层的数据

需要从右往左计算:

public  static  int bag01(int N,int V,int[] w,int []v) {
    int[] dp = new  int[V+1];
    for (int i = 1;i<=N;i++) {
        for (int j = V;j >= v[i];j--) {
            dp[j] = Math.max(dp[j], dp[j-v[i]] + w[i]);
        }
    }
    return dp[V];
}

完全背包

每件物品可以用无数多次

朴素做法

定义二维数组dp,dp[i][j]表示

  • 哪些集合:从前i个物品中选,装不超过j容量的所有选法
  • 什么属性:这些选法中,价值的最大的价值

所有从前i个物品中选,装不超过j容量的所有选法中,可以被不重不漏地划分为:

  1. 第i个物品不选
  2. 第i个物品选1个
  3. 第i个物品选2个
  4. 第i个物品选k个(k * v[i] 需要小于等于j)

即:

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i], dp[i - 1][j - 2*v[i]] + 2*w[i] ..... dp[i-1][j - k[i]] + k*w[i])

代码实现为:

public  static  int bagComplete(int N,int V,int[] w,int []v) {
    int[][] dp = new  int[N+1][V+1];
    for (int i = 1;i<=N;i++) {
        for (int j = 0;j <= V;j++) {
            for (int k = 0;k * v[i] <= j;k++) {
                dp[i][j] = Math.max(dp[i][j], dp[i][j-k * v[i]] + k * w[i]);
            }
        }
    }
    return dp[N][V];
}

时间优化

上面的朴素做法在时间复杂度上是一个三重循环,为O(N ^ 3),可以优化为O(N ^ 2)

我们列出dp[i][j]dp[i][j - v[i]]的公式对比:

可以发现dp[i][j]从第二项开始直到结束,和dp[i][j - v[i]]的第一项直到结束,十分相似

区别在于dp[i][j]的每一项比dp[i][j - v[i]]的每一项多了w[i]

那么dp[i][j]从第二项开始往后的最大值,等于dp[i][j - v[i]]的值加上 w[i],再结合dp[i][j]的第一项得到:

dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i])

在这里插入图片描述

代码可以优化为:

public  static  int bagComplete(int N,int V,int[] w,int []v) {
    int[][] dp = new  int[N+1][V+1];
    for (int i = 1;i<=N;i++) {
        for (int j = 0;j <= V;j++) {
            dp[i][j] = dp[i-1][j];
            if (j >= v[i]) {
                dp[i][j] = Math.max(dp[i][j], dp[i][j - v[i]] + w[i]);
            }
        }
    }
    return dp[N][V];
}

空间优化

同样,完全背包也可以优化为只用一层数组:

由于使用的上一层j位置的值,和当前层j左边位置的值,因此需要在每一层从左往右遍历,这点和01背包不一样:

public  static  int bagComplete02(int N,int V,int[] w,int []v) {
    int[] dp = new  int[V+1];
    for (int i = 1;i<=N;i++) {
        for (int j = v[i];j <= V;j++) {
            dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    return dp[V];
}

01背包和多重背包的区别

这两个问题的最终版代码十分相似,唯一的区别就是:

  • 01背包:每一层从右往左
  • 完全背包:每一层从左往右

多重背包

每件物品能用的次数由一个数组c表示,第i件物品能用c[i]次

朴素做法

定义二维数组dp,dp[i][j]表示

  • 哪些集合:从前i个物品中选,装不超过j容量的所有选法
  • 什么属性:这些选法中,价值的最大的价值

所有从前i个物品中选,装不超过j容量的所有选法中,可以被不重不漏地划分为:

  1. 第i个物品不选
  2. 第i个物品选1个
  3. 第i个物品选2个
  4. 第i个物品选k个(k * v[i] 需要小于等于j,且k小于等于c[i]

代码如下:

public  static  int bagMulti(int N,int V,int[] w,int []v, int[] c) {
    int[][] dp = new  int[N+1][V+1];
    for (int i = 1;i<=N;i++) {
        for (int j = 0;j <= V;j++) {
            for (int k = 0;k * v[i] <= j && k <= c[i]; k++) {
                dp[i][j] = Math.max(dp[i][j], dp[i][j - k * v[i]] + k * w[i]);
            }
        }
    }
    return dp[N][V];
}

时间优化

朴素做法的时间复杂度为O(N^3),可否像完全背包那样优化呢?

我们列举dp[i][j]和 dp[i][j - v[i]]的每一项:

在这里插入图片描述

我们需要dp[i][j - v[i]]中,除了最后一项以外其他项的最大值,但无法根据整体的最大值推算局部的最大值

因此无法像完全背包那样优化

二进制优化

例如当s[i] = 19,

我们将其打包为分别有1,2,4,8,4个第i件物品的组,在这些组中用01背包的方式计算最大价值
打包规则为:另k=0,从1,2,4,8,16一直往后累加,直到s[i] - k小于k为止,将s[i] - k作为最后一组打包

例如:s[i] = 100时,打包成以下几组: 1,2,4,8,16,32,37

即在5个组中选,每个物品最多选1次

这个做法和第i件物品,最多选19次,是否等价呢?

  • dp[1][j] = max(dp[0][j], dp[0][j - v[i]] + w[i])

    • 表示第选0个和选1个的最大值
  • dp[2][j] = max(dp[1][j], dp[1][j - 2*v[i]] + 2*w[i])

    • 表示选0到1个的最大值,和选2到3的最大值,即选0到3的最大值
    • 在这里插入图片描述
  • dp[3][j] = max(dp[2][j], dp[2][j - 4*v[i]] + 4*w[i])

    • 表示选0到3的最大值,和选4到7的最大值,即选0到7的最大值

在这里插入图片描述

  • 依次类推,dp[5][j] = max(dp[4][j], dp[4][j - 4*v[i]] + 4*w[i])

    • 表示选0到15的最大值,和选4到19的最大值,即选0到19的最大值

因此将其按照二进制打包成更小的组,在这些组上最01背包,和原始问题是等价的!

实现代码如下:

public  static  int bagMulti(int N,int V,int[] w,int []v, int[] c) {
    // 开字够大的空间
    int[] neww = new  int[N * 32];
    int[] newv = new  int[N * 32];
    int cnt = 0;
    for (int i = 1;i<=N;i++) {
        int k = 1;
        while (k <= c[i]) {
            // 将每个物品按照二进制打包成不同的组,构造成01背包问题
            cnt++;
            neww[cnt] = k * w[i];
            newv[cnt] = k * v[i];
            c[i] -= k;
            k *= 2;
        }

        if (c[i] >= 0) {
            cnt++;
            neww[cnt] = k * w[i];
            newv[cnt] = k * v[i];
        }
    }
    
    // 按照01背包模板计算
    int[] dp = new  int[cnt+1];
    for (int i = 1;i<=cnt;i++) {
        for (int j = V;j>=newv[i];j--) {
            dp[i] = Math.max(dp[i], dp[i - newv[i] + neww[i]);
        }
    }
    
    return dp[cnt];
}

假设c[i]的平均值为C,这种做法的时间复杂度为O(NVlogC)

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

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

相关文章

MATLAB 图像处理大作业

1、基础知识利用 MATLAB 提供的 Image file/IO 函数完成以下处理&#xff1a;&#xff08;a&#xff09;以测试图像中心为圆心&#xff0c;图像长宽中较小值一半为半径画一个红颜色的圆&#xff1b;&#xff08;b&#xff09;将测试图像涂成国际象棋状的‘黑白格’样子&#xf…

华芯片特微 M33内核 KEIL5环境配置不上问题

1 JFLASH连接不上问题 官方手册有说解决这个问题 2 JFLASH能连接上KEIL提示no found sw-dp 在替换keil下载算法后还是提示no found sw-dp 1 怀疑是keil 527版本太高了, 就换了518 还是不行 2 怀疑是keil检测到盗版了就不让下, 替换Jlink为以前老版本还是不行 解决方案: 下…

聊天气泡图片的动态拉伸、适配与镜像

聊天气泡图片的动态拉伸、适配与镜像前情提要创建.9.png格式的图片从资源文件夹加载.9.png图片从本地文件加载“.9.png”图片项目痛点进阶探索iOS中的方式Android中的探索构造chunk数据构造padding数据镜像翻转功能屏幕的适配简单封装演示示例一条线段控制的拉伸两条线段控制的…

Pandas 安装与教程

前言Pandas 是 Python 语言的一个扩展程序库&#xff0c;用于数据分析。Pandas 是一个开放源码、BSD 许可的库&#xff0c;提供高性能、易于使用的数据结构和数据分析工具。Pandas 名字衍生自术语 "panel data"&#xff08;面板数据&#xff09;和 "Python data…

[apidoc]Apidoc-文档生成工具

Apidoc主要是用于生成API文档的工具&#xff0c;可以用于多种语言&#xff0c;包括java、javascript、php等 这里主要是为了写前端的APIDOC&#xff0c;方便交互是双方的使用; 工具的安装 工具包的安装 npm i apidoc [-g|-D]可以-g全局安装&#xff0c;或者-D局部安装,因为…

网盘系统|基于SpringBoot的网盘系统的设计与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

【无功优化】考虑泄流效应的光伏并网点电压系统侧无功优化(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

软考中级,【软件评测师】经验分享

&#xff0c;以下是我的考试成绩&#xff0c;一次通过很是幸运&#xff0c;希望把我的好运传递给大家&#xff0c;大家都能一次通过谈经验之前&#xff0c;先和大家说说考试的题型以及考试的内容&#xff0c;根据往年的考试题目我们可以很容易得知&#xff0c;软件评测师考试分…

Cisco(62)——PBR策略路由案例

场景1-单下一跳: 拓扑: 需求: R1和R2均连接100.100.100.100,R4看做一台PC,当PC访问100.100.100.100的时候优先走左边,当左边down掉之后切换到右边链路,使用PBR操作。 实现: 1.IP地址等基本配置 R4: R4(config)#no ip routingR4(config)#int e0/0 R4(config-if)#ip add…

Typora自动上传文章图片太难折腾?十三行JavaScript代码足矣

前言 Typora是我用过最爽的markdown文本编辑器了。但是有一点很让人难受&#xff0c;就是在写文章的时候&#xff0c;粘贴上的图片是本地路径。这就导致在复制文章到各大博客平台时发表&#xff0c;图片无法显示。然后需要各种办法去处理文章中的图片&#xff0c;不仅要手动上传…

【学习笔记】【Pytorch】十、线性层

【学习笔记】【Pytorch】九、线性层学习地址主要内容一、前言二、Pytorch的线性层三、Linear类的使用1.使用说明2.代码实现学习地址 PyTorch深度学习快速入门教程【小土堆】. 主要内容 一、前言 在神经网络中&#xff0c;我们通常用线性层来完成两层神经元间的线性变换。 …

【C++】面向对象---继承(万字详解)

目录前言一、继承的定义及概念二、继承方式三、基类和派生类之间的转换四、切片五、继承中的作用域六、派生类中的默认成员函数七、继承中的友元与静态成员继承与友元继承中的静态成员八、棱形继承和虚继承棱形继承虚继承总结前言 继承是面向对象的一个重点&#xff0c;而继承…

活动星投票医疗保障案例推介网络评选微信的投票方式线上免费投票

“医疗保障案例推介”网络评选投票_线上免费投票系统_功能齐全的微信投票_在线免费投票用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票…

图形编辑器:标尺功能的实现

大家好&#xff0c;我是前端西瓜哥。今天我们来实现图形编辑器的标尺功能。 项目地址&#xff1a; https://github.com/F-star/suika 线上体验&#xff1a; https://blog.fstars.wang/app/suika/ 标尺指的是画布上边和左边的两个有刻度的尺子&#xff0c;作用让用户知道他正在编…

java 探花交友day2 项目简介,环境搭建 登录验证码

技术方案&#xff1a; 项目结构&#xff1a; 项目概述 通过接口文档&#xff08;API文档&#xff09;定义规范 开发工具安装与配置 Linux虚拟机 YAPI 账号 tanhuaitcast.cn 密码123456 安装个安卓模拟器&#xff0c;然后安装APK 开发环境说明 初始工程搭建 阿里云短…

Leetcode:235. 二叉搜索树的最近公共祖先(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 精简版&#xff1a; 迭代&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先…

1589_AURIX_TC275_PMU_Flash的基本特性以及操作

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 关于这部分&#xff0c;感觉能够看到的比较有实践指导价值的信息不多。这里关于是否支持cache的信息&#xff0c;之前在内核手册等地方其实也看过了。 DFlash不支持buffer命中的功能&#…

21.Isaac教程--GEMS 导航堆栈简介

Isaac GEMS 导航堆栈简介 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 导航堆栈必须执行以下高级功能&#xff1a; Mapping 映射用于自动创建操作环境的地图。 该地图既用于定位&#xff0c;又用于路径规划。 它可以由具有附加功能的人进行注…

deap遗传算法 tirads代码解读

deap遗传算法 tirads代码解读写在最前面Overview 程序概览参考deap框架介绍creator模块创建适应度类Types定义适应度策略创建个体类Toolbox类创建种群&#xff08;个体、策略以及粒子&#xff09;Initialization1. 创建 attr_int 运算符2. 创建 individual_guess() 运算3.创建新…

学会python后:收集每天热点内容信息,并发送到自己的邮箱

嗨害大家好鸭&#xff01;我是小熊猫~ 实现目的 本篇文章内容主要为如何用代码&#xff0c;把你想要的内容&#xff0c;以邮件的形式发送出去 内容可以自己完善&#xff0c;还可以设置一个定时发送&#xff0c;或者开机启动自动运行代码 代理注册与使用 注册账号并登录 生成ap…