回溯演算法 --- 例題1.裝載問題
阿新 • • 發佈:2021-12-21
一.問題描述
n個集裝箱裝到2艘載重量分別為c1,c2的貨輪,其中集裝箱i的重量為wi且Σwi <= c1 + c2 , 1<=i<=n
問題要求找到一個合理的裝載方案可將這n個貨箱裝上這2艘輪船。
例如 當n=3, c1=c2=50, w=[10, 40, 40], 可將貨箱1和2裝到第一艘船上;貨箱3裝到第二艘船上; 若w=[20, 40, 40], 則無法將全部貨箱裝船.
二.解題思路
簡單分析可知:
- 當Σwi = c1+c2, 1<=i<=n問題等價於子集和問題
- 當c1 = c2, 且Σwi = 2c1, 1<=i<=n, 問題等價於劃分問題
若裝載問題有解, 採用如下策略可得一個最優裝載方案:
(1)將第一艘輪船儘可能裝滿; (2)將剩餘的貨箱裝到第二艘輪船上。
將第一艘船儘可能裝滿等價於如下0-l揹包問題:
用回溯法解裝載問題,用子集樹(從n個元素的集合S中找出滿足某種性質的子集)表示其解空間顯然是最合適的.
可行性約束函式可剪枝去不滿足約束條件ΣWiXi <= C1(1<=i<=n)的子樹.在子集樹的第i+1層的節點Z處,用cw記為當前的裝載重量,即cw = ΣWiXi (1<=i<=j).當cw>c1時,以結點Z為根的子樹中所有節點都不滿足約束條件,因而該子樹中的解都為不可行解,故將該子樹減去.
演算法還有一個限界條件就是,如果右子樹中剩餘所有還未考察的集裝箱的總重量 加上 當前重量 還是小於目前最優裝載量,那麼右子樹也不用考慮了,故直接回溯一層!思路非常簡單,直接看程式碼就好了.
具體程式碼如下:
// 最大裝載問題回溯演算法 #include<bits/stdc++.h> using namespace std; // 一.初始版本,無限接近窮舉(僅僅有一個顯性約束條件 (cw+w[i] > c)限制一下U•ェ•*U) // template<class Type> // class Loading // { // friend Type MaxLoading(Type [], Type, int); // private: // void Backtrack(int i); // int n; //集裝箱數目 // Type *w, //集裝箱重量陣列 // c, //第一艘輪船的載重量 // cw, //當前載重量 // bestw; //當前最優載重量 // }; // template<class Type> // void Loading<Type>::Backtrack(int i) //回溯搜尋 // { // // 搜尋第i層節點 // if(i>n) //到達葉節點 // { // if(cw > bestw) // bestw = cw; // return ; // } // if(cw+w[i] <= c) //搜尋子樹 // { // cw += w[i]; //當前載重量增加正考慮物件的重量 // Backtrack(i+1); //x[i] = 1; // cw -= w[i]; // Backtrack(i+1); //x[i] = 0; 無腦進入右子樹 // } // } // template<class Type> // Type MaxLoading(Type w[], Type c, int n) //返回最優載重量 // { // Loading<Type> x; //初始化 // x.w = w; // x.c = c; // x.n = n; // x.bestw = 0; //當前最優載重量的初值賦為0 // x.cw = 0; // x.Backtrack(1); //計算最有載重量 --- 呼叫遞迴函式,實現回溯搜尋 // return x.bestw; // } // 二.演算法改進 --- 引入剪枝函式 // template<class Type> // class Loading // { // friend Type MaxLoading(Type[], Type, int); // private: // void Backtrack(int i); // int n; // Type *w, // c, // cw, // bestw, // r; //剩餘集裝箱重量 --- 未考察過的集裝箱重量,並非沒有裝載的集裝箱重量 // }; // template<class Type> // void Loading<Type>::Backtrack(int i) // { // if(i>n) // { // bestw = cw; //直接更新(這是因為上界函式是演算法搜尋到的每個葉節點都是當前找到的最優解) // return; // } // //搜尋子樹 // r -= w[i]; //剩餘集裝箱重量 // if(cw + w[i] <= c) //x[i] = 1; //無腦左子樹,一直左; // { // cw += w[i]; // Backtrack(i+1); // cw -= w[i]; // } // if(cw+r > bestw) //x[i] = 0;限界函式 // Backtrack(i+1); // r += w[i]; //左邊走不了,右邊也走不下去(直接被剪枝),回退一格 // } // template<class Type> // Type MaxLoading(Type w[], Type c, int n) //返回最優載重量 // { // Loading<Type> X; // // 初始化X // x.w = w; // x.c = c; // x.n = n; // x.bestw = 0; // x.cw = 0; // x.r = 0; //初始化r // for(int i=0; i<n; i++) r+=w[i]; // X.Backtrack(1); //計算最優載重量 // return X.bestw; // } // 三.最優版本 --- 引入變數來構造最優解 // template<class Type> (為啥函式模板出錯,去掉就沒事了.暈(((φ(◎ロ◎;)φ)))) class Loading { friend int MaxLoading(int [], int , int , int *); public: void Init(); private: void Backtrack(int i); int n, *x, //當前解 *bestx; //當前最優解 int* w; int c; int cw, bestw, r; //剩餘集裝箱重量 }; // template<class Type> void Loading::Backtrack(int i) { // 搜尋第i層節點 if(i>n) { if(cw > bestw) { for(int j=1; j<=n; j++) bestx[j] = x[j]; bestw = cw; } return; } r -= w[i]; if(cw + w[i] <= c) { x[i] = 1; cw += w[i]; Backtrack(i+1); cw -= w[i]; x[i] = 0; } if(cw + r > bestw) { x[i] = 0; Backtrack(i+1); } r += w[i]; //左邊走不了,右邊也走不下去(直接被剪枝),回退一格 } // template<class Type> int MaxLoading(int *w, int c, int n, int bestx[]) { Loading X; //private:可以被該類中的函式、友元函式訪問,但不可以由子類的函式、該類的物件、訪問 // 初始化X X.x = new int[n+1]; X.w = w; X.n = n; X.c = c; X.bestx = bestx; X.bestw = 0; X.cw = 0; X.r = 0; for(int i=1; i<=n; i++) X.r += w[i]; X.Backtrack(1); delete[] X.x; return X.bestw; } int main() { cout<<"請輸入一個多少個集裝箱: "; int n; cin>>n; cout<<"請輸入每個集裝箱的重量: "; int *w = new int[n+1]; w[0] = 0; for(int i=1; i<=n; i++) cin>>w[i]; cout<<"請輸入輪船1的載重量: "; int c; cin>>c; int *bestx = new int[n+1]; for(int i=0; i<=n; i++) bestx[i] = 0; cout<<MaxLoading(w, c, n, bestx)<<endl; system("pause"); return 0; }
執行結果如下:
參考畢方明老師《演算法設計與分析》課件.
歡迎大家訪問個人部落格網站---喬治的程式設計小屋,和我一起加油吧!