当前位置: 首页 > java >正文

动态规划之背包问题:组合优化中的经典NP挑战

背包问题概念:

背包问题是一种经典的组合优化的NP问题,在计算机科学、运筹学等领域有着广泛的应用。

问题可以简单的描述为:

假设有一个容量为C的背包和n个物品,每个物品i都有重量w[i]和价值v[i]。目标是选择一些物品放入背包,使得放入背包的物品总价值最大,同时背包中物品的总重量不能超过背包的容量。

 

 这里先简单介绍两种背包问题:

1.01背包:也就是你物品的个数是1个,你拿了就剩0个,没拿就剩1个

2.完全背包:物品个数无数个,可以拿0/1/2/3/4至无穷多个

背包也可以分两种:1.背包不需要装满。2.背包必须装满

这样两两组合就有4种问题

其实背包问题还有很多种情况,个数有2/3/4/5等等,每个在X2(不必装满||必须装满)

这里重在了解01背包和完全背包问题(其他可能更多出现在竞赛中)

01背包问题是所有问题的基础(基本上所有的背包问题都衍生于01背包)

以牛客网例题为例(leetcode没有较好的入门例题)

题目解析: 

第1小问:背包不必装满问题

第2小问:背包必装满问题

建议画一个表格,而且最好上面标号,就有点像我们之间处理初始化那里一样,多加一行多加一列,有利于我们后面填表

算法原理 :

其实这里的背包问题就是一个线性dp问题,也就是你挑物品时可以从左往右依次选还是不选

类似于我们之前讲解线性dp问题一样去分析即可

1.状态表示:经验+题目要求

经验:以i位置为结尾巴拉巴拉

题目要求:dp[i]:表示以i位置为结尾(从前i个物品中)所有的选法中价值最大的

我们可以发现这个状态表示推导不出来状态转移方程,因为我们在考虑第i个物品的时候,需要考虑这个能不能放进背包,我连总的重量和剩余容量都不知道

所以我们需要改一下状态表示,既然一维表示不了,那我们就二维

dp[i][j]:从前i个物品挑选,总体积不超过j,所有选法中,能挑选出来的最大价值

为什么这里是不超过?因为我们做第一小问,问题是不需要装满

2.状态转移方程:以最近的一步状况,划分情况

1.不选i物品,不选的话是不是最大价值就在i-1前,回归我们的状态表示

dp[i-1][j]:从前i-1个物品挑选,总体积不超过j,所有选法中,能挑选出来的最大价值 

那这种选法中dp[i][j]=dp[i-1][j]

2.选i物品,选的时候是不是要考虑能不能装进背包,所以我们需要判断背包剩余容量

剩余容量就是j-v[i]

如果剩余容量小于0,那这个i物品肯定是不能选的

如果剩余容量大于等于0,这个i物品就可以选,怎么选,回归状态表示

是不是你自身的价值加上i-1的最大价值

所以综上,最大价值就是这两种选法中最大的那个

第2小问讲解:

这里只需要稍加修改一下状态表示即可

原: dp[i][j]:从前i个物品挑选,总体积不超过j,所有选法中,能挑选出来的最大价值

现:dp[i][j]:从前i个物品挑选,总体积正好等于j,所有选法中,能挑选出来的最大价值

基本是一样的,但我们需要特别注意:我们用dp[i][j]=-1,表示没有这种情况,就是所有的选法无法凑到刚好总体积等于j的情况

那为什么不等于0呢?因为如果等于0,我们就无法区分dp[i][j]=0时表示什么情况,我们之前做第一问的时候就有初始化为0,为了区分这两种情况,我们把凑不出总体积为j的情况设为-1

第一种情况不选i物品,可以不用判断dp[i-1][j]!=-1,因为我不选i物品,dp[i-1][j]都等于-1,那证明你凑不出来,那dp[i][j]也凑不出来,所以这时候的dp[i][j]=dp[i-1][j];

第二种情况,选i物品,就必须要判断dp[i-1][j-v[i]] :也就是你必须要判断你前面的必须要凑出来j-v[i]的体积,因为你dp[i][j]这个位置选i物品要加体积v[i]的,所以你前面要能凑出来,这时候在加上v[i]的体积就刚刚好

初始化:明确第一行第一列表示啥,第一行我从0个物品中选还是不选凑出体积为0/1/2/3

第一列我选不选0/1/2/3/4物品,凑0体积,那就说明都不选,价值就是0,为了方便,我们只要创建dp表的时候初始化第一行为-1即可

后面的都和第一小问一模一样了

 

优化 :

第一种方法:滚动数组的方法,我们可以发现我们的状态转移方程仅仅需要两行的数组

也就是我们在填这一行的数据时,仅仅需要上一行的数据

例如我们在填写第一行数据时,仅仅需要第0行的数据,那我们填第2行时,就可以把第0行滚动下来充当第2行

 

如果你还觉得两行数组很麻烦,当然也可以用一行数组,

但需要注意你的填表顺序要从右往左

如果你是左往右,你填表的时候需要借助左上角的值,那左往右就是覆盖,填右边的时候就出错

这里运用的原理就是覆盖,你的数组原来是有数据的,也就是上一行的数据,你填这一行时就可以用到这个数据,注意填表从右往左就行(因为你只有一行数组) 

http://www.xdnf.cn/news/4912.html

相关文章:

  • CCDO|企业数字化转型:机制革新与人才培育的双重引擎
  • 在 Ubuntu 上安装并运行 ddns-go 教程
  • 量化交易策略的运行
  • StreamRL:弹性、可扩展、异构的RLHF架构
  • Rust中记录日志:fast_log
  • 第一天——贪心算法——分饼干
  • 【软件设计师:软件】20.软件设计概述
  • Oracle链接服务器导致SQL Server异常终止
  • PHP会话技术
  • 机器学习与深度学习的区别与联系:多角度详细分析
  • Java 模板引擎 Thymeleaf JSP FreeMarker
  • 【物联网】基于树莓派的物联网开发【1】——初识树莓派
  • 塔能工业互联节能方案:数据驱动工业制造绿色转型
  • 遗传算法(GA)
  • MiM: Mask in Mask Self-SupervisedPre-Training for 3D Medical Image Analysis
  • 基于公共卫生大数据收集与智能整合AI平台构建测试:从概念到实践
  • 【C++进阶】第2课—多态
  • 数据统计的意义:钱包余额变动
  • C++八股 —— map/unordered_map
  • 发那科机器人5(异常事件和程序备份加载+ROBOGUIDE离线仿真)
  • 服务器多客户端连接核心要点(1)
  • 计算机视觉】OpenCV项目实战:eye_mouse_movement:基于opencv实战眼睛控制鼠标
  • 【Python】Pycharm中安装库可靠的方法
  • 从AI到新能源:猎板PCB的HDI技术如何定义高端制造新标准?
  • Java设计模式之单例模式:从入门到精通
  • 大数据狙击金融欺诈——技术如何守护交易安全?
  • c++:双向链表容器(std::list)
  • C语言—指针3
  • 集群/微服务/分布式
  • 地平线rdk x5部署yolo11