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

01背包问题-队列分支限界法-C++

0-1背包问题-队列分支限界法

问题描述:

给定n种物品和一个背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包中的物品,使得装入背包中物品的总价值最大?对于给定的n种物品的重量和价值,以及背包的容量,计算可装入背包的最大价值。

算法设计过程:

考虑使用队列分支限界法来解决来解决0-1背包问题,对于每一个物品,我们都有选或者不选两种情况,因此该问题的解空间为一颗子集树。

要采用队列来求解该问题,对于队列中的每一个节点,都要维护当前节点所选择的物品集合,同时维护当前节点的总价值和容量,为剪去不可行解和优化搜索过程做准备。

对于该问题,我们在使用队列分支限界的搜索过程,在处理当前节点时,可以考虑对左右儿子进行约束和限界:
对于该节点的左儿子,我们要考虑其是否满足约束,具体的,我们要考虑当前背包剩余空间能否装下这个物品,也就是当前的总重加上该物品的总量要小于背包总容量时我们才考虑进入左儿子节点。否则左儿子就是一个不可行解,直接跳过即可。
对于该节点的右儿子,我们考虑设计一个限界函数bound来限制进入右儿子的条件,在搜索过程我们维护一个当前的最优解,我们要通过计算进入右子树后有没有可能得到更优的解,只有右儿子包含最优解时我们才选择进入。具体的,设cv是当前的价值,我们考虑将该物品跳过后的最优价值为r,对于当前的最优解bstv,若是\cv+r<=bstv时,我们便考虑剪去右子树,为了更好的实现,我们需要在搜索之前对物品按单位重量价值进行排序,然后依次装入物品,直到装不下时,再装入该物品的一部分而装满背包,由此得到右子树的上界bound。

对于该问题我们的具体算法流程:
1.对所有物品按单位重量价值从大到小进行排序。
2.初始化队列,加入初始的节点,这里以第一个物品作为根节点加入。
3.当队列不为空时,每次从队列中取出一个节点的进行扩展。
4.如果左儿子是可行节点,即满足约束条件时,如果该结点的价值大于最优解,则更新最优解,同时将该节点加入队列。
5.对于扩展节点的右儿子,当其满足限界函数时才将其加队列中。
6.当队列为空时,说明搜索过程已经结束,直接返回最优解即可。

流程图:
在这里插入图片描述

代码:

#include <bits/stdc++.h>
using namespace std;struct Obj{ // 物品int id; int w;int val;bool operator<(Obj &obj){return val * obj.w > obj.val * w;}
};struct Node {int dep; // 深度,第几层就是处理第几个物品int cv; /// 当前价值int cw; // 当前容量vector<int>x; // 解向量Node(int dep,int cv, int cw, vector<int>x):dep(dep), cv(cv), cw(cw), x(x) {} friend ostream& operator<<(ostream& os, const Node& p){cout << "dep:" << p.dep << " cv:" << p.cv << " cw:" << p.cw;cout << " x:";for(int i:p.x)cout << i << ' ';return os;};  };struct Whopxx{int n; // 物品数量vector<Obj>vt; // 物品int sum; // 背包大小vector<int>bstx; // 最优解int bstv;Whopxx(int n,int sum, vector<int>w, vector<int>p):n(n), sum(sum) {bstx.resize(n + 1);bstv = 0;vt.resize(n + 1);for(int i = 1; i <= n; i++){vt[i] = {i, w[i], p[i]};}sort(vt.begin() + 1, vt.end()); // 按单价从大到小排序}void work(){queue<Node> q;q.push(Node(1, 0, 0, {})); // 第一个物品开始做选择while(q.size()){Node node = q.front(); q.pop();cout << node << " bound:" << Bound(node) << " bstv:" << bstv << endl;int i = node.dep;if(i > n || node.cw == sum){ // 到达叶子结点或背包已满if(node.cv > bstv) update(node); // 更新最大值continue;}auto [id, w, val] = vt[i];if(node.cw + w <= sum){ // 左儿子满足约束Node lnode = add(node, vt[i]);q.push(lnode);if(lnode.cv > bstv) update(lnode);}if(Bound(node) > 1.0 * bstv){ // 右儿子满足限界Node rnode = uadd(node);q.push(rnode);}}}double Bound(Node node){ // 限界函数int rw = sum - node.cw; // 剩余容量int i = node.dep + 1;double b = node.cv;while(i <= n && vt[i].w <= rw){rw -= vt[i].w;b += vt[i].val;i++;}if(i <= n){b += vt[i].val * 1.0 / vt[i].w * rw;}return b;}Node add(Node node, Obj obj){node.cv += obj.val;node.cw  += obj.w;node.x.push_back(obj.id);node.dep += 1;return node;}Node uadd(Node node){node.dep += 1;return node;}void update(Node node){bstx = node.x;bstv = node.cv;}
};int main(){ freopen("input.txt","r", stdin);freopen("output.txt", "w", stdout);int n, c; // 物品数量,背包容量cin >> n >> c;vector<int>w(n + 1);vector<int>p(n + 1);for(int i = 1; i <= n; i++) cin >> p[i];for(int i = 1; i <= n; i++) cin >> w[i];Whopxx wx(n, c, w, p);wx.work();vector<int>ans(n + 1);for(int i:wx.bstx) ans[i] = 1;cout << wx.bstv << endl;for(int i = 1; i <= n; i++){cout << ans[i] << ' ';}fclose(stdin);fclose(stdout);return 0;
}
/*
4 7
9 10 7 4
3 5 2 13 30
45 25 25
16 15 154 15
10 10 12 18
2 4 6 95 10
2 2 6 5 4
6 3 5 4 64 10
40 42 25 12
4 7 5 3
*/

实验测试结果及分析:
测试数据:
input.txt
在这里插入图片描述

通过运行程序得到:
output.txt
在这里插入图片描述

在该输出结果中,cv是当前节点的物品价值总和,cw是当前总重,bound是进入右子树后的最大值,bstv是进行到当前节点的全局最优解得值,形象的可以用如下图来表示该搜索过程:
在这里插入图片描述

最终我们得到最优解即选择物品1和物品3装入背包,得到的最优总价值为65。

复杂度分析:限界函数的时间复杂度为O(n),在最坏的情况下有2^n个右儿子结点需要计算上界,所以计算需要的最坏情况下的时间复杂度为O(n2 ^n)。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 数据结构之“栈”(全方位认识)
  • C++初学者指南-4.诊断---基础:警告和测试
  • 宿舍报修小程序的设计
  • 从入门到深入,Docker新手学习教程
  • 网络-calico问题分析
  • Java面试八股之MySQL存储货币数据,用什么类型合适
  • 24.6.30
  • C++笔试强训2
  • c_各个unsigned int 和 int的取值范围
  • Exploting an API endpoiint using documentation
  • 011 多线程问题
  • 【刷题汇总--大数加法、 链表相加(二)、大数乘法】
  • 在原有的iconfont.css文件中加入新的字体图标
  • 【OnlyOffice】桌面应用编辑器,插件开发大赛,等你来挑战
  • Qt提升控件失败的解决办法
  • Android开源项目规范总结
  • canvas 绘制双线技巧
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • JavaScript函数式编程(一)
  • Js基础知识(四) - js运行原理与机制
  • js如何打印object对象
  • mac修复ab及siege安装
  • PAT A1017 优先队列
  • SQL 难点解决:记录的引用
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 自制字幕遮挡器
  • 你对linux中grep命令知道多少?
  • ​MySQL主从复制一致性检测
  • ​香农与信息论三大定律
  • # C++之functional库用法整理
  • #1014 : Trie树
  • #Datawhale X 李宏毅苹果书 AI夏令营#3.13.2局部极小值与鞍点批量和动量
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • (23)Linux的软硬连接
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (汇总)os模块以及shutil模块对文件的操作
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (数据结构)顺序表的定义
  • (转)人的集合论——移山之道
  • (转载)Google Chrome调试JS
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • (自适应手机端)行业协会机构网站模板
  • .htaccess配置重写url引擎
  • .jks文件(JAVA KeyStore)
  • .Net CF下精确的计时器
  • .net dataexcel winform控件 更新 日志
  • .net 桌面开发 运行一阵子就自动关闭_聊城旋转门家用价格大约是多少,全自动旋转门,期待合作...
  • .Net(C#)自定义WinForm控件之小结篇
  • .NET8使用VS2022打包Docker镜像
  • .NetCore实践篇:分布式监控Zipkin持久化之殇
  • .net反混淆脱壳工具de4dot的使用
  • .NET框架设计—常被忽视的C#设计技巧
  • .net项目IIS、VS 附加进程调试
  • .net中调用windows performance记录性能信息