算法分析 | 分支限界法 | (優先隊列)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比較大時)

 

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