分支限界法求解装载问题实验总结(114)
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
return 0; } while (true) {
int wt = Ew + w[i]; // 检查左孩子结点
if (wt <= c) {
if(wt>bestw) bestw = wt; EnQueue(Ew + w[i], bestw,i,n,E,bestE,1); } // 右孩子总是可行的 EnQueue(Ew, bestw, i, n, E, bestE, 0); Delete(E); // 取下一个扩展结点 if (E!=NULL&&E->weight == -1) { // 到达层的尾部 if (IsEmpty()) break; if(i<n) Add(-1, NULL, 0); // 同层结点的尾部 Delete(E); // 取下一扩展结点 i++; // 进入下一层 r -= w[i]; } Ew = E->weight; } //构造当前最优解 for(j = n -1; j>0; j--) { bestx[j] = bestE->LChild;
{ if(wt>bestw) bestw = wt; EnQueue(Ew + w[i], bestw,i,n,E,bestE,1);
} // 右孩子总是可行的 EnQueue(Ew, bestw, i, n, E, bestE, 0); Delete(E); // 取下一个扩展结点 if (E!=NULL&&E->weight == -1) { // 到达层的尾部
if (IsEmpty()) break; if(i<n) Add(-1, NULL, 0); // 同层结点的尾部 Delete(E); // 取下一扩展结点 i++; // 进入下一层 r -= w[i]; } Ew = E->weight; } //构造当前最优解 for(j = n -1; j>0; j--) { bestx[j] = bestE->LChild; bestE = bestE ->parent; } return 0; }
二、描述问题
有一批共 n
n
个集装箱要装上
2
艘载重量分别为
c1
和
c2 的轮船,其中集装箱
i
的重量为
wi ,且 wi c1 c2 ,要求确定是否有一个合理的装载方案可将这 n 个集装箱装上这 2
艘轮船。i如1 果有,请给出该方案。
wk.baidu.com
三、由原理得到的算法、算法的复杂度、改进
1、 可得算法 算法 enquence 将活结点加入队列。若 i=n,则表示当前为叶节点,此时只要检查对应 的可行解是否为最优解。i<n 时,当期结点为内部节点,加入队列中。 在算法的 while 循环中,首先检测当前扩展结点的左儿子结点是否为可行结点。如果是则将 其加入到活结点队列中。然后将其右儿子结点加入到活结点队列中(右儿子结点一定是可行 结点)。2 个儿子结点都产生后,当前扩展结点被舍弃。 活结点队列中的队首元素被取出作为当前扩展结点,由于队列中每一层结点之后都有一个尾 部标记-1,故在取队首元素时,活结点队列一定不空。当取出的元素是-1 时,再判断当前队 列是否为空。如果队列非空,则将尾部标记-1 加入活结点队列,算法开始处理下一层的活 结点。
//当前最优载重量 bestE = E; bestx[n] = ch; } return; } Add(wt, E, ch); // 不是叶子 }
int MaxLoading(int w[], int c, int n) { // 返回最优装载值 // 为层次 1 初始化
int err; //返回值 int i = 1; // 当前扩展结点的层
分支限界法求解装载问题
一、方法一般原理
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。 在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点, 就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结 点被舍弃,其余儿子结点被加入活结点表中。 此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一 直持续到找到所需的解或活结点表为空时为止。 两种分支限界法 (1)队列式(FIFO)分支限界法 按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。 (2)优先队列式分支限界法 按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
(1)求解目标:回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分 支 限 界 法 的 求 解 目 标 则 是 找 出 满 足 约 束 条 件 的 一 个 解 ,或 是 在 满 足 约 束 条 件 的 解 中 找 出在某种意义下的最优解。 (2)搜索方式的不同:回溯法以深度优先的方式搜索解空间树,而分支限界法则以 广度优先或以最小耗费优先的方式搜索解空间树。
void EnQueue(int wt,int& bestw, int i, int n, Queue*E,Queue*&bestE, bool ch) // 该函数负责加入活结点 { // 如果不是叶结点,则将结点权值 wt 加入队列 Q
if (i == n) {
if(wt == bestw) {
while(true)
{ //检查左儿子结点 Type wt =Ew + w[i];//左儿子结点的重量 if(wt <= c) { //可行结点 if(wt>bestw) bestw = wt; //加入活结点队列 if(i<n) Q.push(wt); } //检查右儿子结点 if(Ew+r>bestw&&i<n) Q.push(Ew);//可能含最优解 Ew = Q.front(); Q.pop(); //取下一扩展结点 if(Ew == -1) { //同层结点尾部 if(Q.empty()) return bestw; Q.push(-1);//同层结点尾部标志 Ew = Q.front(); Q.pop();//取下一扩展结点 i++;//进入下一层 r-=w[i]; }
2、时间复杂度 Maxloading 算法计算时间和空间复杂性均为 O(2n)。
3、可能的改进 节点的左子树表示将此集装箱装上船,右子树表示不将此集装箱装上船。设 bestw 是当前最 优解;wt 是当前扩展结点所相应的重量;r 是剩余集装箱的重量。则当 wt+rbestw 时,可 将其右子树剪去,因为此时若要船装最多集装箱,就应该把此箱装上船。 另外,为了确保 右子树成功剪枝,应该在算法每一次进入左子树的时候更新 bestw 的值。
friend Type MaxLoading_three(Type *,Type,int,int *); private:
QNode* parent;// 指向父结点的指针 bool LChild; //左儿子标志 Type weight; //结点所相应的载重量 };
四、算法实现
void EnQueue(int wt,int& bestw, int i, int n, Queue*E,Queue*&bestE, bool ch) // 该函数负责加入活结点 { // 如果不是叶结点,则将结点权值 wt 加入队列 Q
int Ew = 0; // 当前扩展结点的权值 int r = 0 ; //剩余集装箱重量 Queue *E=0 ; //当前扩展结点 //*bestE 当前最优扩展结点 for(int j =2; j<=n; j++)
r += w[j]; bestw = 0; // 目前的最优值 Q =new Queue; Q->next = NULL; Q->weight = -1; err = Add(-1, NULL, 0); //标记本层的尾部 if(err) {
}
int IsEmpty() {
if(Q->next==NULL) return 1; return 0; }
int Delete(Queue* &E) {
Queue* tmp = NULL; tmp = fq; E = fq; Q->next = fq->next; /*一定不能丢了链表头*/ fq = fq->next; return 0; }
} 构造最优解: 为了在算法结束后能方便地构造出与最优值相应的最优解,算法必须存储相应子集树中从活 结点到根结点的路径。为此目的,可在每个结点处设置指向其父结点的指针,并设置左儿子 标志。
template<class Type> class QNode {
friend void EnQueue(queue<QNode <Type> * >&,Type,int,int,Type,QNode <Type> *,QNode <Type> * &,int*,bool);
六、附录(源码)
#include<iostream> using namespace std;
class Queue
{public:
Queue* parent; // 指向父结点的指针
bool LChild;
// 左儿子标志
int weight;
// 结点所相应的载重量
Queue* next;
};
int *bestx; int bestw=0; // 目前的最优值 Queue* Q; // 活结点队列 Queue* bestE; //当前最优扩展结点 Queue* lq = NULL; Queue* fq = NULL;
if (i == n) {
if(wt == bestw) {
//当前最优载重量 bestE = E; bestx[n] = ch; } return; } Add(wt, E, ch); // 不是叶子 } int MaxLoading(int w[], int c, int n) { // 返回最优装载值 // 为层次 1 初始化 int err; //返回值 int i = 1; // 当前扩展结点的层 int Ew = 0; // 当前扩展结点的权值 int r = 0 ; //剩余集装箱重量 Queue *E=0 ; //当前扩展结点 //*bestE 当前最优扩展结点 for(int j =2; j<=n; j++) r += w[j]; bestw = 0; // 目前的最优值 Q =new Queue; Q->next = NULL; Q->weight = -1; err = Add(-1, NULL, 0); //标记本层的尾部 if(err) { return 0; } while (true) { int wt = Ew + w[i]; // 检查左孩子结点 if (wt <= c)
int Add(int w, Queue* E, bool l) {
Queue* q; q = new Queue; q->next = NULL; q->weight = w; q->parent = E; q->LChild = l; if(Q->next == NULL) {
Q->next = q; fq = lq = Q->next; //一定要使元素放到链中 } else { lq->next = q; lq = q; } return 0;
五、总结
由此,我们可以总结出分 支 限 界 法 的 设 计 思 路 :
设求解最大化问题,解向量为 X=(x1,…,xn),xi 的取值范围为 Si,|Si|=ri。在使用 分支限界搜索问题的解空间树时,先根据限界函数估算目标函数的界[down, up],然 后从根结点出发,扩展根结点的 r1 个孩子结点,从而构成分量 x1 的 r1 种可能的取 值方式。 对这 r1 个孩子结点分别估算可能的目标函数 bound(x1),其含义:以该结点为根的子 树所有可能的取值不大于 bound(x1),即: bound(x1)≥bound(x1,x2)≥…≥ bound(x1,…,xn) 若 某 孩 子 结 点 的 目 标 函 数 值 超 出 目 标 函 数 的 下 界 ,则 将 该 孩 子 结 点 丢 弃 ;否 则 ,将 该 孩子结点保存在待处理结点表 PT 中。 再取 PT 表中目标函数极大值结点作为扩展的根结点,重复上述。 直到一个叶子结点 时的可行解 X=(x1,…,xn),及目标函数值 bound(x1,…,xn)。 分支限界法与回溯法的不同:
int wt = Ew + w[i]; // 检查左孩子结点
if (wt <= c) {
if(wt>bestw) bestw = wt; EnQueue(Ew + w[i], bestw,i,n,E,bestE,1); } // 右孩子总是可行的 EnQueue(Ew, bestw, i, n, E, bestE, 0); Delete(E); // 取下一个扩展结点 if (E!=NULL&&E->weight == -1) { // 到达层的尾部 if (IsEmpty()) break; if(i<n) Add(-1, NULL, 0); // 同层结点的尾部 Delete(E); // 取下一扩展结点 i++; // 进入下一层 r -= w[i]; } Ew = E->weight; } //构造当前最优解 for(j = n -1; j>0; j--) { bestx[j] = bestE->LChild;
{ if(wt>bestw) bestw = wt; EnQueue(Ew + w[i], bestw,i,n,E,bestE,1);
} // 右孩子总是可行的 EnQueue(Ew, bestw, i, n, E, bestE, 0); Delete(E); // 取下一个扩展结点 if (E!=NULL&&E->weight == -1) { // 到达层的尾部
if (IsEmpty()) break; if(i<n) Add(-1, NULL, 0); // 同层结点的尾部 Delete(E); // 取下一扩展结点 i++; // 进入下一层 r -= w[i]; } Ew = E->weight; } //构造当前最优解 for(j = n -1; j>0; j--) { bestx[j] = bestE->LChild; bestE = bestE ->parent; } return 0; }
二、描述问题
有一批共 n
n
个集装箱要装上
2
艘载重量分别为
c1
和
c2 的轮船,其中集装箱
i
的重量为
wi ,且 wi c1 c2 ,要求确定是否有一个合理的装载方案可将这 n 个集装箱装上这 2
艘轮船。i如1 果有,请给出该方案。
wk.baidu.com
三、由原理得到的算法、算法的复杂度、改进
1、 可得算法 算法 enquence 将活结点加入队列。若 i=n,则表示当前为叶节点,此时只要检查对应 的可行解是否为最优解。i<n 时,当期结点为内部节点,加入队列中。 在算法的 while 循环中,首先检测当前扩展结点的左儿子结点是否为可行结点。如果是则将 其加入到活结点队列中。然后将其右儿子结点加入到活结点队列中(右儿子结点一定是可行 结点)。2 个儿子结点都产生后,当前扩展结点被舍弃。 活结点队列中的队首元素被取出作为当前扩展结点,由于队列中每一层结点之后都有一个尾 部标记-1,故在取队首元素时,活结点队列一定不空。当取出的元素是-1 时,再判断当前队 列是否为空。如果队列非空,则将尾部标记-1 加入活结点队列,算法开始处理下一层的活 结点。
//当前最优载重量 bestE = E; bestx[n] = ch; } return; } Add(wt, E, ch); // 不是叶子 }
int MaxLoading(int w[], int c, int n) { // 返回最优装载值 // 为层次 1 初始化
int err; //返回值 int i = 1; // 当前扩展结点的层
分支限界法求解装载问题
一、方法一般原理
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。 在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点, 就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结 点被舍弃,其余儿子结点被加入活结点表中。 此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一 直持续到找到所需的解或活结点表为空时为止。 两种分支限界法 (1)队列式(FIFO)分支限界法 按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。 (2)优先队列式分支限界法 按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
(1)求解目标:回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分 支 限 界 法 的 求 解 目 标 则 是 找 出 满 足 约 束 条 件 的 一 个 解 ,或 是 在 满 足 约 束 条 件 的 解 中 找 出在某种意义下的最优解。 (2)搜索方式的不同:回溯法以深度优先的方式搜索解空间树,而分支限界法则以 广度优先或以最小耗费优先的方式搜索解空间树。
void EnQueue(int wt,int& bestw, int i, int n, Queue*E,Queue*&bestE, bool ch) // 该函数负责加入活结点 { // 如果不是叶结点,则将结点权值 wt 加入队列 Q
if (i == n) {
if(wt == bestw) {
while(true)
{ //检查左儿子结点 Type wt =Ew + w[i];//左儿子结点的重量 if(wt <= c) { //可行结点 if(wt>bestw) bestw = wt; //加入活结点队列 if(i<n) Q.push(wt); } //检查右儿子结点 if(Ew+r>bestw&&i<n) Q.push(Ew);//可能含最优解 Ew = Q.front(); Q.pop(); //取下一扩展结点 if(Ew == -1) { //同层结点尾部 if(Q.empty()) return bestw; Q.push(-1);//同层结点尾部标志 Ew = Q.front(); Q.pop();//取下一扩展结点 i++;//进入下一层 r-=w[i]; }
2、时间复杂度 Maxloading 算法计算时间和空间复杂性均为 O(2n)。
3、可能的改进 节点的左子树表示将此集装箱装上船,右子树表示不将此集装箱装上船。设 bestw 是当前最 优解;wt 是当前扩展结点所相应的重量;r 是剩余集装箱的重量。则当 wt+rbestw 时,可 将其右子树剪去,因为此时若要船装最多集装箱,就应该把此箱装上船。 另外,为了确保 右子树成功剪枝,应该在算法每一次进入左子树的时候更新 bestw 的值。
friend Type MaxLoading_three(Type *,Type,int,int *); private:
QNode* parent;// 指向父结点的指针 bool LChild; //左儿子标志 Type weight; //结点所相应的载重量 };
四、算法实现
void EnQueue(int wt,int& bestw, int i, int n, Queue*E,Queue*&bestE, bool ch) // 该函数负责加入活结点 { // 如果不是叶结点,则将结点权值 wt 加入队列 Q
int Ew = 0; // 当前扩展结点的权值 int r = 0 ; //剩余集装箱重量 Queue *E=0 ; //当前扩展结点 //*bestE 当前最优扩展结点 for(int j =2; j<=n; j++)
r += w[j]; bestw = 0; // 目前的最优值 Q =new Queue; Q->next = NULL; Q->weight = -1; err = Add(-1, NULL, 0); //标记本层的尾部 if(err) {
}
int IsEmpty() {
if(Q->next==NULL) return 1; return 0; }
int Delete(Queue* &E) {
Queue* tmp = NULL; tmp = fq; E = fq; Q->next = fq->next; /*一定不能丢了链表头*/ fq = fq->next; return 0; }
} 构造最优解: 为了在算法结束后能方便地构造出与最优值相应的最优解,算法必须存储相应子集树中从活 结点到根结点的路径。为此目的,可在每个结点处设置指向其父结点的指针,并设置左儿子 标志。
template<class Type> class QNode {
friend void EnQueue(queue<QNode <Type> * >&,Type,int,int,Type,QNode <Type> *,QNode <Type> * &,int*,bool);
六、附录(源码)
#include<iostream> using namespace std;
class Queue
{public:
Queue* parent; // 指向父结点的指针
bool LChild;
// 左儿子标志
int weight;
// 结点所相应的载重量
Queue* next;
};
int *bestx; int bestw=0; // 目前的最优值 Queue* Q; // 活结点队列 Queue* bestE; //当前最优扩展结点 Queue* lq = NULL; Queue* fq = NULL;
if (i == n) {
if(wt == bestw) {
//当前最优载重量 bestE = E; bestx[n] = ch; } return; } Add(wt, E, ch); // 不是叶子 } int MaxLoading(int w[], int c, int n) { // 返回最优装载值 // 为层次 1 初始化 int err; //返回值 int i = 1; // 当前扩展结点的层 int Ew = 0; // 当前扩展结点的权值 int r = 0 ; //剩余集装箱重量 Queue *E=0 ; //当前扩展结点 //*bestE 当前最优扩展结点 for(int j =2; j<=n; j++) r += w[j]; bestw = 0; // 目前的最优值 Q =new Queue; Q->next = NULL; Q->weight = -1; err = Add(-1, NULL, 0); //标记本层的尾部 if(err) { return 0; } while (true) { int wt = Ew + w[i]; // 检查左孩子结点 if (wt <= c)
int Add(int w, Queue* E, bool l) {
Queue* q; q = new Queue; q->next = NULL; q->weight = w; q->parent = E; q->LChild = l; if(Q->next == NULL) {
Q->next = q; fq = lq = Q->next; //一定要使元素放到链中 } else { lq->next = q; lq = q; } return 0;
五、总结
由此,我们可以总结出分 支 限 界 法 的 设 计 思 路 :
设求解最大化问题,解向量为 X=(x1,…,xn),xi 的取值范围为 Si,|Si|=ri。在使用 分支限界搜索问题的解空间树时,先根据限界函数估算目标函数的界[down, up],然 后从根结点出发,扩展根结点的 r1 个孩子结点,从而构成分量 x1 的 r1 种可能的取 值方式。 对这 r1 个孩子结点分别估算可能的目标函数 bound(x1),其含义:以该结点为根的子 树所有可能的取值不大于 bound(x1),即: bound(x1)≥bound(x1,x2)≥…≥ bound(x1,…,xn) 若 某 孩 子 结 点 的 目 标 函 数 值 超 出 目 标 函 数 的 下 界 ,则 将 该 孩 子 结 点 丢 弃 ;否 则 ,将 该 孩子结点保存在待处理结点表 PT 中。 再取 PT 表中目标函数极大值结点作为扩展的根结点,重复上述。 直到一个叶子结点 时的可行解 X=(x1,…,xn),及目标函数值 bound(x1,…,xn)。 分支限界法与回溯法的不同: