算法分析 | 分支限界法 | (优先队列)01揹包问题

一.问题分析

利用优先队列,以结点的价值上界作为优先值.这里要用到的头文件:<queue>的容器priority_queue.

它的特点是会在内部按特征值自动排序

当优先队列的数据结构是struct时写法如下

struct PState
{
	....
        //成员函数、构造函数、数据成员部分

	friend bool operator <(const PState& a, const PState& b)
	{
		return a.up < b.up;     //up值从小到大--推算-->优先级从小到大
	}
};

priority_queue <PState> q;

 

二.代码实现

分为三部分:全局变量 & 上界函数 & 遍历函数 & 初始化及调用及打印函数

1.全局变量

#include"allh.h"
//采用优先队列的01揹包问题
//Priority_queue是可排序的queue,top()返回最大值元素

//物品结构
vector<int>W2 = { 2,5,4,2 };
vector<int>V2 = { 6,3,5,4 };
int N2 = (int)V2.size();

int bestp2;						//记录最优值
vector<bool>bestx2;		//记录最优解

int Volume2;							//记录购物车总容量
int sumv2, sumw2;			//全部物品总重量和总价值

//性价比结构,用于排序
struct Object
{
	int id;				//表示顺序
	double quo;		//记录性价比
};

2.上界函数Bound(),注意数据类型

//上界函数,需要id,rw,cp
double Bound(PState t)	 
{
	double resValue=0;
	int num = t.id;			//排序后的即将判断的物品序号
	double residue = t.rw;		//当前状态的剩余容量
	while (num < N2 && W2[num] <= residue)
	{
		resValue += V2[num];
		residue -= W2[num];
		num++;
	}
	if (num < N2)
		resValue += double(V2[num]) / W2[num] * residue;		//01揹包问题在计算上界时,分割计算上界

	return resValue + t.cp;		//最大上界=状态当前价值+剩余空间能装的最大价值
}

 

3.遍历函数,改用q.top()获取栈顶元素

int Priority_BFS_01Backpack()
{
	int t;//当前物品序号
	double tup;		//保存价值上界
	priority_queue<PState>q1;
	q1.push(PState(0, sumv2, Volume2, 0));//已有物品总值,剩余物品总值,剩余容量,物品序号

	while (!q1.empty())		//开始循环
	{
		
		PState livenode, lchild, rchild;		//每次循环创建当前结点,左子树,右子树
		livenode = q1.top();					//上一次while循环入队的左右孩子成为了新的livenode
		 q1.pop();
		t = livenode.id;

		//活结点的约束条件(终止循环条件)
		if (t >= N2 || livenode.rw == 0)//物品序号 t=[0,N-1]	||	当前状态的剩余空间==0		
		{
			//多个可行解中得最优值
			if (livenode.cp >= bestp2)
			{
				bestx2 = livenode.x;
				bestp2 = livenode.cp;
			}
			continue;
		}

		//对活结点的限界条件
		if (livenode.up < bestp2)
			continue;

		//左子树
		if (livenode.rw >= W2[t])							//约束条件:剩余容量装得下当前物品
		{
			//创建左孩子
			lchild.cp = livenode.cp + V2[t];
			lchild.rw = livenode.rw - W2[t];
			lchild.id = t + 1;
			lchild.up = Bound(lchild);
			lchild.x = livenode.x; lchild.x[t] = true;
			

			if (lchild.cp > bestp2)				//当前解>最优解才更新
				bestp2 = lchild.cp;

			q1.push(lchild);
		}

		//右子树
		rchild.cp = livenode.cp;
		rchild.rw = livenode.rw;
		rchild.id = t + 1;
		tup = Bound(rchild);
		if (tup >= bestp2)
		{
			rchild.up =Bound(rchild);
			rchild.x = livenode.x;
			rchild.x[t] = false;
			/*rchild = PState(rchild.cp, rchild.up, rchild.rw, rchild.id);*/ //因为构造函数,这一行会变回默认值
			q1.push(rchild);
		}
	}
	return bestp2;
}

4.调用函数

void Prio_backpack()
{
	//初始化
	bestp2 = 0;
	Volume2 = 10;
	bestx2.resize(N2);
	sumw2 = accumulate(W2.begin(), W2.end(), 0);			//记录总重量
	sumv2 = accumulate(V2.begin(), V2.end(), 0);			//记录总价值

	//对性价比排序
	vector<Object>S; Object a;
	for (int i = 0; i < N2; i++)
	{
		a.id = i; a.quo = 1.0*V2[i] / W2[i];
		S.push_back(a);
	}
	sort(S.begin(), S.end(), [](Object a, Object b)
		{
			return a.quo > b.quo;
		});

	//将排序结果传给V2[],W2[]
	vector<int>tempW = W2;
	vector<int>tempV = V2;
	for (int i = 0; i < N2; i++)
	{
		W2[i] = tempW[S[i].id];  
		V2[i] = tempV[S[i].id]; 
	}
	tempV.~vector(); tempW.~vector();
	

	if (sumw2 <= Volume2)
	{
		bestp2 = sumv2;
		cout << "放入购物车的物品最大价值为: " << bestp2 << endl;
		cout << "所有的物品均放入购物车。";
		return;
	}

	//调用函数
	cout << "最优值为:	" << Priority_BFS_01Backpack();
	cout << endl;

	//输出
	cout << "放入的物品为:		";
	for (int i = 0; i < N2; i++)
	{
		if (bestx2[i])
			cout << S[i].id << "	";		//输出原来的序号
	}
	cout << endl;
}

 

三.Bug分析

1.因累赘的构造函数导致的不出现最佳策略.

初次调试:

未出现最优策略

调试发现在左右子树的该语句中,child.x[]继承了livenode.x[]后又被构造函数初始化为{0,0,0....} .这一句完全是多余的

删除后显示成功.

 

2.在Bound()上界函数的定义中, up已经是当前值+剩余所有值,在调用Bound()时, 不应该再加一次cp

 

3.bound()中, res的数据类型写错了.应该是double

 

四.算法优化

先看一段代码

queue<int>q;
q.push(5); q.push(2); q.push(3);
int* p= &q.front();
q.pop();
cout << *p;

输出是5. 

元素5在pop()后仍存在于内存中,仅仅是出队了而已.

可以用指针指向livenode结点的父结点和左右子结点.得到最优解时一路返回.

3个State* 指针所花费的空间 < 一个bool [ n ]解向量  (n比较大时)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章