算法分析 | 分支限界法 | 01揹包問題

紅色代表錯誤或者特別注意

藍色代表修復後的正確代碼

黃色表示變量


一.問題分析

1.問題的性質

回溯法是對樹的深度遍歷,需要用到遞歸.

分支限界法是對樹的廣度遍歷,需要用到<queue>數據結構.而且每個狀態都是一個數據結構實體

狀態應該表示如下幾個屬性:

int cp    //已放入物品總價值

int rp    //剩餘物品的總價值

int rw   //剩餘容量

int id    //物品序號,比如某結點id=0,拓展當前結點時就要檢查物品0  放入/不放入.

int[] x   //當前解向量

運算過程可以描述爲:

將當前狀態結點的符合條件的子結點push到隊尾,並讓當前節點出隊.

2.限界條件&約束條件

01揹包問題中,當前結點和左右子結點的限界條件和約束條件各不一樣

當前結點:

      ①約束條件:什麼情況下,當前結點無法生成左右子結點?

                        只有遍歷完最後一個物品元素,或者容量=0時.看看此時能不能更新最優解. 最後當前結點出隊並檢查下一隊列元素

      ②限界條件:什麼情況下,當前結點沒必要生成左右子節點?

                         當livenode.cp+livebode.rp<bestp時. 最後當前結點出隊並檢查下一隊列元素

左子結點進行約束條件:當前剩餘容量能夠放入第t個物品

                          if(livenode.cp > W[ t ])      擴展lchild,lchild按條件賦值,id++

右子節點進行限界條件:不放入第t個物品,剩餘價值--,計算已有價值和剩餘價值是否大於最優解

                          if( livenode.cp + (livenode.rp - V[ t ]) ) > bestp

二.代碼實現

分爲三部分: 全局變量區  /  廣度遍歷函數  /  初始化&調用&顯示函數

1.全局變量區

①一開始W[]和V[]在一個struct裏,發現寫出來有點繁瑣:Good[i].value.

②每個結點都有cp來表示當前價值,不需要再像回溯法寫在全局變量裏

vector<int>W = { 2,5,4,2 };
vector<int>V = { 6,3,5,4 };
int N1 = (int)V.size();

int bestp1;						//記錄最優值
vector<bool>bestx1;		//記錄最優解

int Volume;							//記錄購物車總容量
int sumv, sumw;			//全部物品總重量和總價值

//狀態結構,記錄下每個狀態
struct State  
{
	int cp;						//當前狀態的放入物品總價
	int rp;						//當前狀態的剩餘物品總價
	int rw;						//當前狀態的剩餘容量
	int id;						//物品號

	vector<bool> x;		//當前策略

	State()
	{
		cp = rp = rw = id = 0;
		x.resize(N1);
	}
	State(int _cp,int _rp,int _rw, int _id)		//構造函數,方便賦值,不必再寫好幾行賦值語句
	{
		cp = _cp;
		rp = _rp;
		rw = _rw;
		id = _id;
		x.resize(N1);
	}
};

 

2.廣度遍歷函數

int BFS_01Backpack()
{
	int t;//當前物品序號
	queue<State>q1;
	q1.push(State(0, sumv, Volume, 0));//已有物品總值,剩餘物品總值,剩餘容量,物品序號

	while (!q1.empty())		//開始循環
	{
		State livenode, lchild, rchild;		//每次循環創建當前結點,左子樹,右子樹
		livenode = q1.front();					//上一次while循環入隊的左右孩子成爲了新的livenode
		q1.pop();
		t = livenode.id;

		//終止條件
		if (t >= N1 || livenode.rw == 0)//物品序號 t=[0,N-1]	||	當前狀態的剩餘空間==0		
		{
			//多個可行解中得最優值
			if (livenode.cp >= bestp1)
			{
				bestx1 = livenode.x;
				bestp1 = livenode.cp;
			}
			continue;
		}

		//對活結點的限界條件
		if (livenode.cp + livenode.rp < bestp1)		
			continue;

		//左子樹
		if (livenode.rw >= W[t])							//約束條件:剩餘容量裝得下當前物品
		{
							      //已有價值+,          剩餘價值-,          剩餘容量-,            序號比父結點+1
			lchild = State(livenode.cp + V[t], livenode.rp-V[t], livenode.rw - W[t], livenode.id + 1);
			lchild.x = livenode.x;
			lchild.x[t] =true;		//表示放入

			if (lchild.cp > bestp1)				//當前解>最優解才更新
				bestp1 = lchild.cp;

			q1.push(lchild);
		}
		
		//右子樹
		if (livenode.cp + (livenode.rp - V[t]) >= bestp1)
		{
			                //已有價值不變,     剩餘價值-,       剩餘容量不變,   序號比父結點+1
			rchild = State(livenode.cp, livenode.rp-V[t], livenode.rw, t + 1);
			rchild.x = livenode.x;
			rchild.x[t] = false;
			q1.push(rchild);
		}
		
	}
	return bestp1;
}

 

3.初始化&調用&打印函數

void backpack()
{
	//初始化
	bestp1 = 0;
	Volume = 10;
	bestx1.resize(N1);
	sumw = accumulate(W.begin(), W.end(), 0);			//記錄總重量
	sumv = accumulate(V.begin(), V.end(), 0);			//記錄總價值
	

	//調用BFS()
	cout<<"最優值爲:	"<<BFS_01Backpack();

	//輸出
	cout << "放入的物品爲:		";
	for (int i = 0; i < N1; i++)
	{
		if (bestx1[i] == true)
			cout << i << "	";
	}
	cout << endl;
	 
}

三.Bug分析

1.vector越界問題

忘記聲明vector<bool> x時預設長度.

 

2.輸出結果錯誤

右子樹的約束條件出了問題,原錯誤代碼:

    if (livenode.cp + livenode.rp >= bestp1)

右子樹表示不放入物品t,剩餘價值也一樣要減少:

    if (livenode.cp + (livenode.rp - V[t]) >= bestp1)

 

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