优先队列分支限界法求01背包——手写堆140行——priority_queue 100行

这篇博客介绍了如何使用优先队列分支限界法解决01背包问题,对比了这种方法与普通DFS、BFS的时间复杂度,并详细阐述了求解过程,包括如何计算上界和进行BFS搜索。博主还分享了手动实现堆和使用`priority_queue`的代码版本,强调了正确实现堆的重要性。
摘要由CSDN通过智能技术生成

算法设计与分析的作业,老实说,这个方法挺强的,但是网上的参考基本都是指针+课设,而且解释不清晰,真心难参考····,这里我尽可能简单的重述思路和代码。
01背包是个老问题,n个物品,每个物品装或者不装,每个物品都有自己的价值。问在背包容量范围内能装入的物品的最大总价值。
正常做法是DP。当然,dfs,bfs也能写,问题就是太慢了,时间复杂度O(2^n),这谁顶得住啊。
开始正题

与其他方法的比较
考虑二叉树解决01背包,取和不取两种状态分别用左孩子和右孩子表示,所有状况表示构成一棵完全二叉树。
普通bfs就是逐层处理,每一层代表一个物品,然后比较所有2^n种情况(不考虑剪枝)的最终价值。
优先队列分支限界法本质还是bfs,和普通的bfs不同的是,我们对于每一个点求一个bound,根据bound的大小来确定bfs的顺序。
也就是,普通的bfs是逐层把节点加入queue,优先队列分支限界法则是根据bound的大小,先把bound比较大的节点加入queue。
虽然最坏情况下复杂度也是o(2^n),但是平均时间复杂度非常好,对于随机数据,这个写法和普通的dp写法时间不相上下。(洛谷p1048,此写法38ms,dp写法31ms).当然,这个跟数据有关。
根本原因就是优先队列分支限界法求01背包和普通bfs相比,bfs要搜索所有情况,但是这个方法处理到的第一个叶子节点,必然就是答案,可以直接跳过其他所有情况。

具体求法
bound是什么?答:一个上界 具体来说就是当前节点之后可能达到的最大值
只要能够确定bound,接下来就是把bfs的queue换成priority_queue,这个做法剩下的东西就和普通的bfs没有任何区别。

我们提到了上界是当前节点之后可能达到的最大值,求法也简单,自然就是:
已经有的价值 + 所有没取过的物品能得到的最大价值
假设当前节点为cur,cur.v就是当前节点含有的物品价值,cur所在层和更深的层所代表的物品,就是我们求上界要考虑的对象。
注意:上界是可能达到的最大值,也就是说我们最后的结果可以不达到上界。那就简单了,按照分数背包处理,即一个物品我可以只取走一部分,那我取物品的顺序就是单位价值最大的优先取,如果背包容量不够了,就尽可能取走。
那cur.bound = cur.v + 尽可能取的按单位价值排序后的物品的价值

double cal_bound(int x,int v){
   //当前物品编号和剩余体积
	double res=0;// 尽可能取的按单位价值排序后的物品的价值
	for(int i=x;i<=n;i++){
   //剩下的所有物品 能装就装
		if(a[i].w<=v){
   
			v-=a[i].w;
			res+=a[i].v;
		}
		else{
   //装不下  拆成一部分装
			res+=a[i].f*v; v=0;
			break;
		}
	}
	return res;
}

那么,上界处理完了。
说说bfs的过程。
我的做法是对于一棵二叉树,高度从上到下依次为0~n,高度0不表示物品,高度1到n分别表示物品1到n,bfs开始时,手动插入高度0的节点first, 然后while(!empty()),对于每一个节点,分别求其左孩子节点(取)和右孩子(不取)然后加入优先队列,优先队列自动排序即可。
然后就是一个层数为n+1,记录结果,跳出,结束。

整理思路

  1. 读入数据,所有物品按照单位价值排序
  2. bfs,每个节点求bound,子节点加入队列后继续搜索。
  3. 是否搜索到了最后
  4. 输出

比较麻烦的地方就是要记录的东西太多了:
每个物品,记录原序号id, 重量w, 价值v, 单位价值v/w(排序用),上界bound,排序后的序号level(对应树高),已装入物品path(vector一波)
然后是结构体的比较通过重载<和>实现了,省的每次用内部变量麻烦。
最后就是手写堆真容易出锅·····建议好好研究手写堆,我大数据WA了就是因为堆模板有问题,一度自闭,不知道哪里错了。。。

贴两个版本

代码:(手写堆实现版本)

#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
//分支限界法求01背包
const int N=1005;
struct item{
   
	int w,v,id,level;//已装入物品的重量 价值  id表示当前物品的原编号 level表示现编号(高度)
	double f,bound;// f 单位价值  bound上界
	vector<int>path;//记录当前节点的到达方式(选取的物品)
	item(){
   //构造函数
		w=0; v=0; id=0;level=0; f=0; bound=0;
		path.clear();
	}
}a[N];
int total,n,res=0;//背包总容量  物品总数量 题目求的最大价值
vector<int> respath;//记录物品选取情况
bool cmp1(item A,item B){
   
	return A.f>B.f;//按照单位价值排序用
}
bool operator <(const item &A, const item &B){
   //重载运算符 让item类型可以直接比较大小
		return A.bound<B.bound;
}
bool operator >(const item &A, const item &B){
   
		return A.bound>B.bound;
}
	//手写大根堆
item q[N*10];int cnt=0;//队列开大,队列存点可能很多
void push(item x){
   //存入数字
	q[++cnt
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值