算法设计与分析——分支限界法——装载问题
有一批共個集裝箱要裝上2艘載重量分別為C1和C2的輪船,其中集裝箱i的重量為Wi,且裝載問題要求確定是否有一個合理的裝載方案可將這個集裝箱裝上這2艘輪船。如果有,找出一種裝載方案。
容易證明:如果一個給定裝載問題有解,則采用下面的策略可得到最優裝載方案。
(1)首先將第一艘輪船盡可能裝滿;
(2)將剩余的集裝箱裝上第二艘輪船。
思想:
在算法的循環體中,首先檢測當前擴展結點的左兒子結點是否為可行結點。如果是則將其加入到活結點隊列中。然后將其右兒子結點加入到活結點隊列中(右兒子結點一定是可行結點)。2個兒子結點都產生后,當前擴展結點被舍棄。
活結點隊列中的隊首元素被取出作為當前擴展結點,由于隊列中每一層結點之后都有一個尾部標記-1,故在取隊首元素時,活結點隊列一定不空。當取出的元素是-1時,再判斷當前隊列是否為空。如果隊列非空,則將尾部標記-1加入活結點隊列,算法開始處理下一層的活結點。
節點的左子樹表示將此集裝箱裝上船,右子樹表示不將此集裝箱裝上船。設bestw是當前最優解;ew是當前擴展結點所相應的重量;r是剩余集裝箱的重量。則當ew+r<bestw時,可將其右子樹剪去,因為此時若要船裝最多集裝箱,就應該把此箱裝上船。另外,為了確保右子樹成功剪枝,應該在算法每一次進入左子樹的時候更新bestw的值。
為了在算法結束后能方便地構造出與最優值相應的最優解,算法必須存儲相應子集樹中從活結點到根結點的路徑。為此目的,可在每個結點處設置指向其父結點的指針,并設置左、右兒子標志。
找到最優值后,可以根據parent回溯到根節點,找到最優解。優先隊列寫法:
#include<iostream> #include<queue>//優先隊列的頭文件 using namespace std; //子集樹中節點的定義 class bbnode{ public:bbnode *parent;//指向父節點的指針 bool Lchild;//是否是左孩子結點的標記 }; //優先隊列中節點的定義 class HeapNode{public:bbnode *ptr; //指向活結點在子集樹中相應節點的指針int uweight; //活結點的優先級(上界) //優先級的定義:從根節點到節點x的路徑相應的載重量加上剩余集裝箱的重量之和int level; //活結點在子集樹中所處的層次 }; struct compare{bool const operator()(HeapNode *&a,HeapNode *&b){return (a->uweight) < (b->uweight);//最大堆 } }; //該函數是將新產生的活結點加入到子集樹中,并將這個節點插入到表示活結點優先隊列的最大堆中 void AddLiveNode(priority_queue<HeapNode*,vector<HeapNode*>,compare> &Q,bbnode *parent,bool isLeft,int uWeight,int level) {//先在子集樹中建立這個節點bbnode *b = new bbnode;b->parent = parent;b->Lchild = isLeft;//再在優先隊列中新建一個節點HeapNode *h = new HeapNode;h->ptr = b;//將剛在添加到子集樹中的新節點b再賦值給將要添加到優先隊列中的節點h h->uweight = uWeight;h->level = level; //將新建的節點添加到優先隊列Q.push(h); }int MaxLoading(int *weight,int n,int *bestx,int c) {priority_queue<HeapNode*,vector<HeapNode*>,compare> Heap;int i=0;// 表示當前所在子集樹的層次int now_weight=0;//表示當前已裝載的重量int node_priority=0; //優先級————裝載的最大上界=當前裝載量+剩余集裝箱的重量HeapNode *H;//用于保存從優先隊列出來的節點 bbnode *B;//子集樹上的擴展節點int *remains; //剩余集裝箱 , 記載未裝的貨物remains = new int[n];remains[n-1]=0;//當到達最后的葉子節點時,沒有剩余的貨物 for(int j=n-2;j>=0;j--){ //計算當到達指定層時的剩余數組 remains[j]=remains[j+1]+weight[j+1];}while(i!=n)//沒有到達葉子節點時 ,到達第i層 {if(now_weight+weight[i]<=c)//即當前的重量能夠裝的下 =進入左子樹 左孩子 {node_priority=(now_weight+weight[i]) +remains[i];//優先級 =當前裝載量+剩余集裝箱的重量AddLiveNode(Heap,B,true,node_priority,i+1);}// 進入右子樹 右孩子 node_priority=(now_weight) +remains[i];//優先級 =當前裝載量+剩余集裝箱的重量AddLiveNode(Heap,B,false,node_priority,i+1);H = Heap.top(); //查詢優先隊列隊頭結點 Heap.pop(); //隊頭結點出隊 i=H->level;B=H->ptr; //記錄當前到達的結點 now_weight=H->uweight - remains[i-1]; //計算當前已經裝載量 }// 記錄已裝載貨物 for(int j=n-1;j>=0;j--){bestx[j]=B->Lchild? 1:0;B=B->parent;}return now_weight; //返回最優裝載量 } int main() {int n; //貨物數 int shipNum; //貨船數量 int bestw; //最優裝載量 int weight[n]; //貨物總重量 int bestx[n]; //最優的裝載序列 int c1=0; //第一艘船的裝載重量 int c2=0; //第二艘船的裝載重量 cout<<"請輸入貨物數:";cin>>n;cout<<"請輸入貨船的數量:";cin>>shipNum;cout<<"請輸入貨物的重量序列:";for(int i=0;i<n;i++){cin>>weight[i];}cout<<"請輸入兩艘船的載重量:";cin>>c1>>c2;int maxc=0;//船的最大裝載重量 int sumc=c1+c2;//船的裝載重和 if(c1>c2)maxc=c1;else maxc=c2;int maxweight=0;//貨物中的最大重量int sumweight=0; //貨船總重量 for(int i=0;i<n;i++){if(weight[i]>maxweight){maxweight=weight[i];}sumweight+=weight[i];}if(maxweight>maxc){printf("貨物重量超過貨船的載重量,無法裝載!");return 0;} if(sumweight>sumc){printf("貨物總重量超過貨船的總載重量,無法裝載!");return 0;}MaxLoading(weight,n,bestx,c1);cout<<"裝載序列為:"; for(int i=0;i<n;i++){cout<<bestx[i]<<" ";}cout<<endl;cout<<"第1艘船的最優裝載量為:"<<MaxLoading(weight,n,bestx,c1)<<endl;cout<<"裝載的貨物為:";for(int i=0;i<n;i++){if(bestx[i]!=0)cout<<weight[i]<<" ";}cout<<endl;cout<<"第2艘船的最優裝載量為:"<<sumweight-MaxLoading(weight,n,bestx,c1)<<endl;cout<<"裝載的貨物為:";for(int i=0;i<n;i++){if(bestx[i]!=1)cout<<weight[i]<<" ";}}
堆寫法:
總結
以上是生活随笔為你收集整理的算法设计与分析——分支限界法——装载问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在电脑上怎么充值DNF点券?DNF点券充
- 下一篇: 算法设计与分析——动态规划——最大字段和