分支限界法——装载问题

队列式分支限界法

思路

  1. 解装载问题的队列式分支限界法仅求出所要求的最优值。
  2. 首先检测当前扩展结点的左儿子结点是否为可行结点。如果是,则将其加入到活结点队列Q中。
  3. 然后,将其右儿子结点加入到活结点队列中(右儿子结点一定是可行结点)。2个儿子结点都产生后,当前扩展结点被舍弃。
  4. 活结点队列中,队首元素被取出作为当前扩展结点。
  5. 活结点队列已空,算法终止。

代码

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Queue1 {
    //节点类
    private static class QNode {
        QNode parent;  //父节点
        boolean leftChild;
        int weight;
        //构造方法

        public QNode(QNode parent, boolean leftChild, int weight) {
            this.parent = parent;
            this.leftChild = leftChild;
            this.weight = weight;
        }
    }

    private static Scanner scanner = new Scanner(System.in);
    static int n;
    static QNode bestE;  //当前最优扩展节点
    static int bestW;  //当前最优载重量
    static int[] bestX;  //当前最优解
    static Queue<QNode> queue = new LinkedList<>();

    //队列分支界限法,返回最优装载重量
    static int maxLoading(int[] w, int c) {
        //初始化
        n = w.length - 1;
        bestW = 0;
        queue.offer(null);  //同层节点尾部标志 -1
        QNode e = null;
        bestE = null;
        int i = 1;  //当前扩展结点所处的层
        int ew = 0;  //扩展结点所相应的载重量

        int r = 0;  //剩余集装箱的载重量
        for (int j = 2; j <= n; j++) {
            r += w[j];
        }

        //搜索自己空间树
        while (true) {
            //检查左儿子节点,当前物品可以装在第一艘船
            int wt = ew + w[i];  //左儿子节点的重量
            if (wt <= c) {  //可行节点
                if (wt > bestW) bestW = wt;
                //加入活结点队列
                enQueue(wt, i, e, true);
            }
            //检查右儿子节点
            if (ew + r > bestW) enQueue(ew, i, e, false);
            e = queue.poll();  //取下一扩展节点
            if (e == null) {  //同层节点尾部1
                if (queue.isEmpty()) break;
                queue.offer(null);  //同层节点尾部标志
                e = queue.poll();  //取下一扩展节点
                i++;  //进入下一层
                r -= w[i];
            }
            ew = e.weight;
        }

        //构造最优解
        for (int j = n - 1; j > 0; j--) {
            bestX[j] = bestE.leftChild ? 1 : 0;
            bestE = bestE.parent;
        }
        return bestW;
    }

    //将活结点加入到活结点队列Q中
    static void enQueue(int wt, int i, QNode parent, boolean leftChild) {
        if (i == n) {  //叶子节点
            if (wt == bestW) {//当前最优解
                bestE = parent;
                bestX[n] = leftChild ? 1 : 0;
            }
            return;
        } else {  //非叶子节点
            QNode b = new QNode(parent, leftChild, wt);
            queue.offer(b);
        }
    }

    public static void main(String[] args) {
        int c = scanner.nextInt();
        n = scanner.nextInt();
        bestX = new int[n + 1];
        int[] w = new int[n + 1];
        for (int i = 1; i < n + 1; i++) {
            w[i] = scanner.nextInt();
        }
        int ans = maxLoading(w, c);
        for (int i = n; i > 0; i--) {
            System.out.print(bestX[i] + " ");
        }
        System.out.println();
        System.out.println(ans);

    }
}

image.png

优先队列式分支限界法

思路

  1. 解装载问题的优先队列式分支限界法用最大优先队列存储活结点表。
  2. 活结点x在优先队列中的优先级定义为从根结点到结点x的路径所相应的载重量Ew(即:当前扩展结点船的载重量Ew)再加上剩余集装箱的重量r之和(即:将上界Ew+r定义为结点优先级)。
  3. 优先队列中优先级最大的活结点成为下一个扩展结点。
  4. 子集树中叶结点所相应的载重量与其优先级(上界值)相同,即:该叶子结点的上界值等于当前叶子结点处船的重量Ew。
  5. 在优先队列式分支限界法中,一旦有一个叶结点成为当前扩展结点,则可以断言该叶结点所相应的解即为最优解。此时可终止算法

代码

#include <bits/stdc++.h>
using namespace std;
class MaxHeapQNode {
	public:
		MaxHeapQNode *parent;  //父节点
		int lchild;    //左节点:1; 右节点"0
		int weight;    //总重量
		int lev;       //层次
};
struct cmp {
	bool operator()(MaxHeapQNode *&a, MaxHeapQNode *&b) const {
		return a->weight < b->weight;
	}
};
int n;
int c;
int bestw;
int w[100];
int bestx[100];
void InPut() {
	scanf("%d %d", &c, &n);
	for(int i = 1; i <= n; ++i)
		scanf("%d", &w[i]);
}
void AddAliveNode(priority_queue<MaxHeapQNode *, vector<MaxHeapQNode *>, cmp> &q, MaxHeapQNode *E,  int wt, int i, int ch) {
	MaxHeapQNode *p = new MaxHeapQNode;
	p->parent = E;
	p->lchild = ch;
	p->weight = wt;
	p->lev = i + 1;
	q.push(p);
}
void MaxLoading() {
	priority_queue<MaxHeapQNode *, vector<MaxHeapQNode *>, cmp > q; // 大顶堆
	//定义剩余重量数组r
	int r[n + 1];
	r[n] = 0;
	for(int j = n - 1; j > 0; --j)
		r[j] = r[j + 1] + w[j + 1];
	int i = 1;
	MaxHeapQNode *E;
	int Ew = 0;
	while(i != n + 1) {
		if(Ew + w[i] <= c) {
			AddAliveNode(q, E, Ew + w[i] + r[i], i, 1);
		}
		AddAliveNode(q, E, Ew + r[i], i, 0);

		//取下一节点
		E = q.top();
		q.pop();
		i = E->lev;
		Ew = E->weight - r[i - 1];
	}
	bestw = Ew;
	for(int j = n; j > 0; --j) {
		bestx[j] = E->lchild;
		E = E->parent;
	}
}
void OutPut() {
	printf("最优装载量为 %d\n", bestw);
	printf("装载的物品为 \n");
	for(int i = 1; i <= n; ++i)
		printf("%d ", i);
}
int main() {
	InPut();
	MaxLoading();
	OutPut();
}

运行结果

image.png

总结

对比回溯法

回溯法的求解目标是找出解空间中满足约束条件的所有解,想必之下,分支限界法的求解目标则是找出满足约束条件的一个解,或是满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
另外还有一个非常大的不同点就是,回溯法以深度优先的方式搜索解空间,而分支界限法则以广度优先的方式或以最小耗费优先的方式搜索解空间。

分支限界法的搜索策略

在当前节点(扩展节点)处,先生成其所有的儿子节点(分支),然后再从当前的活节点(当前节点的子节点)表中选择下一个扩展节点。为了有效地选择下一个扩展节点,加速搜索的进程,在每一个活节点处,计算一个函数值(限界),并根据函数值,从当前活节点表中选择一个最有利的节点作为扩展节点,使搜索朝着解空间上有最优解的分支推进,以便尽快地找出一个最优解。分支限界法解决了大量离散最优化的问题。

选择方法

1.队列式(FIFO)分支限界法

队列式分支限界法将活节点表组织成一个队列,并将队列的先进先出原则选取下一个节点为当前扩展节点。

2.优先队列式分支限界法

优先队列式分支限界法将活节点表组织成一个优先队列,并将优先队列中规定的节点优先级选取优先级最高的下一个节点成为当前扩展节点。如果选择这种选择方式,往往将数据排成最大堆或者最小堆来实现。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦码城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值