装载问题
该问题是最优装载问题的一个变形。
问题描述
问题分析
算法实现
队列式分支限界法
在算法的循环体中,首先检测当前扩展结点的左儿子结点是否为可行结点。如果是则将其加入到活结点队列中。然后将其右儿子结点加入到活结点队列中(右儿子结点一定是可行结点)。2个儿子结点都产生后,当前扩展结点被舍弃。
活结点队列中的队首元素被取出作为当前扩展结点,由于队列中每一层结点之后都有一个尾部标记-1,故在取队首元素时,活结点队列一定不空。当取出的元素是-1时,再判断当前队列是否为空。如果队列非空,则将尾部标记-1加入活结点队列,算法开始处理下一层的活结点。
节点的左子树表示将此集装箱装上船,右子树表示不将此集装箱装上船。设bestw是当前最优解;ew是当前扩展结点所相应的重量;r是剩余集装箱的重量。则当ew+r<bestw时,可将其右子树剪去,因为此时若要船装最多集装箱,就应该把此箱装上船。另外,为了确保右子树成功剪枝,应该在算法每一次进入左子树的时候更新bestw的值。
为了在算法结束后能方便地构造出与最优值相应的最优解,算法必须存储相应子集树中从活结点到根结点的路径。为此目的,可在每个结点处设置指向其父结点的指针,并设置左、右儿子标志。
找到最优值后,可以根据parent回溯到根节点,找到最优解。
算法实现如下:
#include <iostream> #include <queue> using namespace std; template <class Type> class QNode { friend void EnQueue(queue<QNode<Type>*> &Q, Type wt, int i, int n, Type bestw, QNode<Type> *E, QNode<Type> *&bestE, int bestx[], bool ch); friend Type MaxLoading(Type w[], Type c, int n, int bestx[]); private: QNode *parent; // 指向父结点的指针 bool LChild; // 左儿子标志 Type weight; // 结点所相应的载重量 }; /* 将活结点加入到活结点队列中 * Q: 活结点队列 * wt: 当前船上的载重量 * i: 结点所在的层 * n: n个集装箱 * bestw: 最优载重量 * E: 当前扩展结点 * bestE: 记录最优的叶结点 * bestx: * ch: 左儿子标志 * */ template<class Type> void EnQueue(queue<QNode<Type>*> &Q, Type wt, int i, int n, Type bestw, QNode<Type> *E, QNode<Type> *&bestE, int bestx[], bool ch) { if (i == n) // 可行叶结点 { if (wt == bestw) { // 当前最优载重量 bestE = E; bestx[n] = ch; } return; } // 非叶结点 QNode<Type> *b; b = new QNode<Type>; b->weight = wt; b->parent = E; b->LChild = ch; Q.push(b); } /* 队列式分支限界法 * 返回最优载重量,bestx返回最优解 */ template<class Type> Type MaxLoading(Type w[], Type c, int n, int bestx[]) { // 初始化 queue<QNode<Type>*> Q; // 活结点队列 Q.push(0); // 同层结点尾部标志 int i = 1; // 当前扩展结点所处的层 Type Ew = 0, // 扩展结点所相应的载重量 bestw = 0, // 当前最优载重量 r = 0; // 剩余集装箱重量 for (int j = 2; j <= n; j++) { r += w[j]; } QNode<Type> *E = 0, // 当前扩展结点 *bestE; // 当前最优扩展结点 // 搜索子集空间树 while (true) { // 检查左儿子结点 Type wt = Ew + w[i]; if (wt <= c) // 可行结点 { if (wt>bestw) { bestw = wt; // 提早更新bestw !!! } EnQueue(Q, wt, i, n, bestw, E, bestE, bestx, true); } // 检查右儿子结点 if (Ew + r>bestw) // 剪枝 !!! { EnQueue(Q, Ew, i, n, bestw, E, bestE, bestx, false); } E = Q.front(); // 取下一扩展结点 Q.pop(); if (!E) // 同层结点尾部 { if (Q.empty()) { break; } Q.push(0); // 同层结点尾部标识 E = Q.front(); // 取下一扩展结点 Q.pop(); i++; // 进入下一层 r -= w[i]; // 剩余集装箱重量 } Ew = E->weight; // 新扩展结点所对应的载重量 } // 构造当前最优解 for (int k = n - 1; k>0; k--) { bestx[k] = bestE->LChild; bestE = bestE->parent; } return bestw; } int main() { float c = 70; float w[] = { 0, 20, 10, 26, 15 };//下标从1开始 const int N = 4; int x[N + 1]; float bestw; cout << "轮船载重为:" << c << endl; cout << "待装物品的重量分别为:" << endl; for (int i = 1; i <= N; i++) { cout << w[i] << " "; } cout << endl; bestw = MaxLoading(w, c, N, x); cout << "分支限界选择结果为:" << endl; for (int j = 1; j <= 4; j++) { cout << x[j] << " "; } cout << endl; cout << "最优装载重量为:" << bestw << endl; return 0; }
优先队列式分支限界法
解装载问题的优先队列式分支限界法用最大优先队列存储活结点表。活结点x在优先队列中的优先级定义为从根结点到结点x的路径所相应的载重量再加上剩余集装箱的重量之和。优先队列中优先级最大的活结点成为下一个扩展结点。优先队列中活结点x的优先级为x.uweight。以结点x为根的子树中所有结点相应的路径的载重量不超过x.uweight。子集树中叶结点所相应的载重量与其优先级相同。因此在优先队列式分支限界法中,一旦有一个叶结点成为当前扩展结点,则可以断言该叶结点所相应的解即为最优解,此时可终止算法。
上诉策略可以用两种不同方式来实现。第一种方式在结点优先队列的每一个活结点中保存从解空间树的根节点到该活结点的路径,在算法确定了达到最优值的叶结点时,就在该叶结点处同时得到相应的最优解。第二种方式在算法的搜索进程中保存当前已构造出的部分解空间树,在算法确定了达到最优值的叶结点时,就可以在解空间树中从该叶结点开始向根节点回溯,构造出相应的最优解。在下面的算法中,采用第二种方式。
算法实现如下:
#include <iostream> #include <queue> using namespace std; class HeapNode; /* 子集空间树中的结点类型 */ class bbnode { friend void AddLiveNode(priority_queue<HeapNode> &H, bbnode *E, float wt, bool ch, int lev); friend float MaxLoading(float w[], float c, int n, int bestx[]); private: bbnode *parent; // 指向父结点的指针 bool LChild; // 左儿子结点标志 }; class HeapNode { friend void AddLiveNode(priority_queue<HeapNode> &H, bbnode *E, float wt, bool ch, int lev); friend float MaxLoading(float w[], float c, int n, int bestx[]); public: operator float() const // 重载类型转换运算符 { return uweight; } private: bbnode *ptr; // 指向活结点在子集树中相应结点的指针 float uweight; // 活结点优先级(上界) int level; // 活结点在子集树中所处的层序号 }; /* 将活结点加入到表示活结点优先队列的最大堆H中 */ void AddLiveNode(priority_queue<HeapNode> &H, bbnode *E, float wt, bool ch, int lev) { bbnode *b = new bbnode; b->parent = E; b->LChild = ch; HeapNode N; N.uweight = wt; N.level = lev; N.ptr = b; H.push(N); } /* 优先队列式分支限界法 * 返回最优载重量,bestx返回最优解 */ float MaxLoading(float w[], float c, int n, int bestx[]) { // 定义最大堆 priority_queue<HeapNode> H; // 定义剩余容量数组r float *r = new float[n+1]; r[n] = 0; for(int j=n-1; j>0; j--) { r[j] = r[j+1] + w[j+1]; } // 初始化 int i = 1; // 当前扩展结点所处的层 bbnode *E = 0; // 当前扩展结点 float Ew = 0; // 扩展结点所相应的载重量 // 搜索子集空间树 while(i!=n+1) // 非叶子结点 { // 检查当前扩展结点的儿子结点 if(Ew+w[i]<=c) { AddLiveNode(H, E, Ew+w[i]+r[i], true, i+1); } // 右儿子结点 AddLiveNode(H, E, Ew+r[i], false, i+1); // 取下一扩展结点 HeapNode N = H.top(); H.pop(); i = N.level; E = N.ptr; Ew = N.uweight - r[i-1]; } // 构造当前最优解 for(int k=n; k>0; k--) { bestx[k] = E->LChild; E = E->parent; } return Ew; } int main() { float c = 70; float w[] = {0,20,10,26,15}; // 下标从1开始 const int N = 4; int x[N+1]; float bestw; cout<<"轮船载重为:"<<c<<endl; cout<<"待装物品的重量分别为:"<<endl; for(int i=1; i<=N; i++) { cout<<w[i]<<" "; } cout<<endl; bestw = MaxLoading(w,c,N,x); cout<<"分支限界选择结果为:"<<endl; for(int j=1; j<=4; j++) { cout<<x[j]<<" "; } cout<<endl; cout<<"最优装载重量为:"<<bestw<<endl; return 0; }
程序运行结果: