標籤:
用優先隊列式分支限界法解決0-1背包問題的演算法思想:
1.分支限界法常以廣度優先或最小耗費優先(最大效益優先)方式搜尋問題的解空間樹, 對於0-1背包問題的解空間樹是一個顆子集樹。
2.在分支限界法中有一個活結點表,活結點表中的每個活結點只有一次機會成為擴充結點,一旦成為 擴充結點就一次性產生所有兒子結點,在這些兒子結點中,導致不可行解或導致非最優解的兒子 結點被捨棄,其餘兒子結點被加入到活結點表中。對於0-1背包問題中的每個活結點只有兩個兒子 結點,分別表示對物品i的選取和對物品i的捨去;在判斷兒子結點是否能加入到活結點表中,有兩個 函數需要滿足,第一個稱為約束函數,判斷能否滿足背包容量約束,第二個稱為限界函數,判斷 是否可能有最優解。
3.為了儘快找到0-1背包問題的解,每次選取下一個活結點成為擴充結點的判斷依據是當前情況下 最有可能找到最優解的下一個結點。因此,每次選擇擴充結點的方法:當前情況下,在活結點表中 選擇活結點的上界uprofit(通過限界函數Bound求出)最大的活結點成為當前的擴充結點。 這一過程一直持續到找到所需的解或活結點表為空白時為止。這個過程體現出分支限界法以“最大 效益優先”方式進行。
4.為了在活結點表中選擇擁有最大的上界uprofit的活結點,在活結點表上實現優先隊列。
5.通過上述第3點,可以求出0-1背包問題的最優值。為了求出0-1背包問題的最優解,對於每一個在 活結點表中的活結點建立一個樹結點,樹節點需要反映該結點的父節點和是否有左孩子(有左孩子 表示物品i選取了,沒有左孩子表示物品i捨去了)。因此,可以構造一顆子集樹,最優解就是從樹根 到葉子結點的路徑,子集樹的第i層的所有結點就是在不同情況下對物品i的取捨結點。構造最優解的 順序是從葉子結點到根結點的過程。
從上述演算法思想中,得出必須解決的問題:
1.優先隊列式的活結點表
2.活結點表對應的子集樹
演算法涉及的函數功能:
1.建立一個最大堆、初始化最大堆、在最大堆中插入一個元素和在最大堆中取出最大元素
2.求解0-1背包問題的主函數Knapsack
3.向子集樹和最大堆中插入結點函數AddLiveNode
4.計算結點價值上界函數Bound,為了方便,需要對物品以單位價值量排序
5.負責求解0-1背包問題的最優值和最優解函數MaxKnapsack
演算法涉及的類:
1.樹結點類,用於構造子集樹,以便計算最優解
2.堆結點類,用於定義堆元素類型,便於MaxKnapsack函數使用
3.最大堆類,用於實現優先隊列
4.物品類,用於儲存物品編號和物品的單位重量價值
5.解決0-1背包問題的主類
以下是具體的代碼:
#include "stdafx.h"#include <iostream>using namespace std; typedef int Typew;typedef int Typep;//物品類class Object{friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);public:int operator <= (Object a) const{return (d >= a.d);}private:int ID; //物品編號float d; //單位重量價值};//樹結點類class bbnode{friend class Knap;friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);private:bbnode *parent; //指向父節點的指標int LChild; //如果是左兒子結點取1,也即說明該物品已裝進背包};//堆結點類class HeapNode{friend class Knap;friend class MaxHeap;public:operator Typep()const{return uprofit;};private:Typep uprofit, //結點的價值上界 profit; //結點所相應的價值Typew weight; //結點所相應的重量int level; //活結點在子集樹中所處的層序號bbnode *elemPtr; //指向該活結點在子集樹中相應結點的指標};//最大堆類class MaxHeap{public:MaxHeap(int maxElem){HeapElem = new HeapNode* [maxElem+1]; //下標為0的保留capacity = maxElem;size = 0;}void InsertMax(HeapNode *newNode);HeapNode DeleteMax(HeapNode* &N);private:int capacity; int size; HeapNode **HeapElem; };//0-1背包問題的主類class Knap{//Knapsack主函數功能:解決初始化、求解最優值和最優解、回收記憶體friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);public:Typep MaxKnapsack();private:MaxHeap *H;//Bound輔助Maxknapsack函數:計算結點價值上界Typep Bound(int i);//AddLiveNode輔助Maxknapsack函數:將活結點插入子集樹和優先隊列中void AddLiveNode(Typep up, Typep cp, Typew cw, int ch, int level);bbnode *E; //指向擴充結點的指標Typew c; //背包容量int n; //物品總數Typew *w; //物品重量數組(以單位重量價值降序)Typep *p; //物品價值數組(以單位重量價值降序)Typew cw; //當前裝包重量Typep cp; //當前裝包價值int *bestx; //最優解};void MaxHeap::InsertMax(HeapNode *newNode){//極端情況下暫未考慮,比如堆容量已滿等等int i = 1;for (i = ++size; i/2 > 0 && HeapElem[i/2]->uprofit < newNode->uprofit; i /= 2){HeapElem[i] = HeapElem[i/2];}HeapElem[i] = newNode;}HeapNode MaxHeap::DeleteMax(HeapNode *&N){//極端情況下暫未考慮if(size >0 ){N = HeapElem[1];//從堆頂開始調整int i = 1;while(i < size){if(((i*2 +1) <= size) && HeapElem[i*2]->uprofit > HeapElem[i*2 +1]->uprofit){HeapElem[i] = HeapElem[i*2];i = i*2;}else{ if(i*2 <= size){HeapElem[i] = HeapElem[i*2];i = i*2;}elsebreak;}}if(i < size) HeapElem[i] = HeapElem[size];}size--;return *N;}Typep Knap::MaxKnapsack(){H = new MaxHeap(1000);bestx = new int [n+1];//初始化,為處理子集樹中的第一層做準備,物品i處於子集樹中的第i層int i = 1; //產生子集樹中的第一層的結點E = 0; //將首個擴充點設定為null,也就是物品1的父節點cw = 0;cp = 0;Typep bestp = 0; //當前最優值 Typep up = Bound(1); // 選取物品1之後的價值上界//當選擇左兒子結點時,上界約束up不用關心,重量約束wt需要考慮。因為上界約束跟父節點相同。//當選擇右兒子結點時,上界約束up需要考慮,重量約束不需要考慮。因為父節點和該結點重量相同。while (i != n+1){//檢查當前擴充結點的左兒子結點Typew wt = cw + w[i]; //當前選擇物品i之後的總重量wtif(wt <= c) //背包能將物品i裝下,也即當前擴充結點的左兒子結點可行{if(cp + p[i] > bestp)bestp = cp + p[i];AddLiveNode(up, cp + p[i], cw + w[i], 1, i);}//檢查當前擴充結點的右兒子結點up = Bound(i + 1); //未選擇物品i之後的價值上界if(up >= bestp)AddLiveNode(up, cp, cw, 0, i);//從優先隊列中選擇價值上界最大的結點成為擴充結點HeapNode* N;H->DeleteMax(N);E = N->elemPtr;cw = N->weight;cp = N->profit;up = N->uprofit;i = N->level + 1; //準備產生N.level+1層的子集樹結點}//從子集樹中的某葉子結點開始構造當前最優解for (int i = n; i > 0; i--){bestx[i] = E->LChild;E = E->parent;}return cp;}Typep Knap::Bound(int i){Typew cleft = c - cw;Typep b = cp;while (i<=n && w[i] <= cleft){cleft -= w[i];b += p[i];i++;}if(i<=n) b += p[i]/w[i] * cleft;return b;}void Knap::AddLiveNode(Typep up, Typep cp, Typew cw, int ch, int level){bbnode *b=new bbnode; b->parent=E; b->LChild=ch; HeapNode *N = new HeapNode; N->uprofit=up; N->profit=cp; N->weight=cw; N->level=level; N->elemPtr=b; H->InsertMax(N); }//Knapsack返回最大價值,最優值儲存在bestxTypep Knapsack(Typew *w, Typep *p, Typew c, int n, int *bestx){//數組w、p和bestx中下標為0的元素保留不用//初始化Typew W = 0;Typep P = 0;Object *Q = new Object[n];for(int i =1; i<=n; i++){Q[i-1].ID = i;Q[i-1].d = 1.0*p[i]/w[i];P += p[i];W += w[i];}//所有物品的總重量小於等於背包容量cif (W <= c) {for(int i =1; i<=n; i++){bestx[i] = p[i];}return P;}//所有物品的總重量大於背包容量c,存在最佳裝包方案//sort(Q,n);對物品以單位重量價值降序排序//採用簡單冒泡排序for(int i = 1; i<n; i++)for(int j = 1; j<= n-i; j++){if(Q[j-1].d < Q[j].d){Object temp = Q[j-1];Q[j-1] = Q[j];Q[j] = temp;}}Knap K;K.p = new Typep [n+1];K.w = new Typew [n+1];for(int i = 1; i<=n; i++){K.p[i] = p[Q[i-1].ID];//(以單位重量價值降序排序)K.w[i] = w[Q[i-1].ID];//(以單位重量價值降序排序)}K.cp = 0;K.cw = 0;K.c = c;K.n = n;Typep bestp = K.MaxKnapsack();for(int i = 1; i<=n; i++){bestx[Q[i-1].ID] = K.bestx[i];}delete [] Q;delete [] K.w;delete [] K.p;delete [] K.bestx;delete [] K.H;return bestp;}int _tmain(int argc, _TCHAR* argv[]){const int N = 4; Typew c=8; //背包容量 int bestx[N+1]; //最優解int bestp; //最優值//需要說明的是,這裡的數組p[]和w[]的第一個元素是-1,這是因為我在操作過程中//都是從數組元素的1開始的,而我們知道數組中第一個元素是0號元素,所以我這裡用-1填上 Typep p[]={-1,6,4,7,4};//物品價值 Typew w[]={-1,2,3,5,2};//物品重量 bestp = Knapsack(w, p, c, N, bestx);cout<<"物品總數N = "<< N<<",背包容量C = "<< c<<endl;for (int i = 1; i <= N; i++){if(i ==1 ) cout<<"重量數組:";cout<<w[i];if(i != N) cout<<",";elsecout<<endl;}for (int i = 1; i <= N; i++){if(i ==1 ) cout<<"價值數組:";cout<<p[i];if(i != N) cout<<",";elsecout<<endl;}for (int i = 1; i <= N; i++){if(i ==1 ) cout<<"是否選取:";cout<<bestx[i];if(i != N) cout<<",";elsecout<<endl;}cout<<"背包最優價值:"<<bestp<<endl;system("pause");return 0;}運行結果如:
用優先隊列式分支限界法解決0-1背包問題