算法设计-分支界限法——装载问题

算法介绍

分支界限法:

分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。

与回溯法的区别:

(1)求解目标:回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。
(2)搜索方式的不同:回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。

常见的两种分支限界法:

(1)队列式(FIFO)分支限界法
  按照队列先进先出(FIFO)原则选取下一个结点为扩展结点。
(2)优先队列式分支限界法
  按照优先队列中规定的优先级选取优先级最高的结点成为当前扩展结点。

问题实例

问题描述:

题目:
请用队列式分支限界法求解装载问题,设7个集装箱的重量分别为:
90 80 40 30 20 12 10
船的重量限制为:152

问题分析:

题目要求使用队列分支界限法求解该题目,首先我们需要找到向队列中放的元素是什么,在本题中我们以比较轮船装入的集装箱重量是否超出载重量为判断依据,所以选择轮船当前的载重量为队列中的元素。
当装入该集装箱符合边界条件时,生成新的结点加入队列,并用一个特定的字符记录层数(即逐步判断7个集装箱)。当从队列中取出该字符时进入下一层,并再将新的特定字符加入队列,以便再次进入下一层。

伪代码:
①先将特定字符 NULL 加入队列,通过构建二叉树求解最优装载,设一个根节点,无装载量,代表此时轮船为空。
②判断第一个集装箱能否装的下,如果装的下,生成一个新的结点,该结点的重量为此时轮船的载重量,并通过指针让新结点的父亲指针指向根节点,记录此时装入,记为1。
③如果装不下,判断此时未放该集装箱的轮船重量加上剩余的集装箱重量是否大于此时的最优载重量。若大于,则有可能存在不放该集装箱仍能达到最优载重,生成新的结点,加入队列并记录此时不装入,记为0;若小于,则这条路行不通,修剪。
④完成第一个集装箱的判断后,从队首取第一个元素,如果第一个元素为 NULL ,则进入下一层(再取队首元素)进行下一个集装箱的判断(此时使用 Java 的 queue.remove 方法,取出队首元素时,该元素已出队列),并向队列中再加入 NULL 以便再进入下一层。
⑤循环步骤④,直到队列为空。
⑥利用构建的最优装载二叉树,输出每个节点记录的选择结果,对应输出选择的集装箱。
如下:
算法设计-分支界限法——装载问题_第1张图片
代码:

package 分支限界法;

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



public class Zhuangzai {
	private class Node {
		Node father; //父亲结点
		Boolean fathersleftchild; // 父亲结点的左子树,1 代表选择,0代表不选择
		int weight; // 达到该结点的重量
		
		public Node(Node father,int weight,Boolean fathersleftchild) {
			this.father = father;
			this.weight = weight;
			this.fathersleftchild = fathersleftchild;
		}
		
	}
	int n;
	int w[];
	int c;
	//输入集装箱个数和重量以及轮船1最大载重量
	public Zhuangzai (int n, int w[], int c)
	{
		this.n=n;
		this.c=c;
		this.w=w;
		this.bestx=new int[n+1];
	}
	
	
	Node bestE=null; //最优叶子结点的父亲结点
	int bestW=0;  //最优载重量
	int wt=0;   //当前的载重量
	int bestx[];//最优解
	// 创建队列
	Queue<Node> queue= new LinkedList();
	// 构建解空间(树)
	private void EnQueue (int i,Node father,int weight,Boolean fathersleftchild)
	{
		//如果达到叶子节点
		if(i==n)
		{
			//如果此时达到最优载重量
			if(weight==bestW)
			{
				bestE = father;
				bestx[n]=(fathersleftchild)?1:0;
			}
		}
		else
		{
			//创建新的结点
			Node b = new Node(father, weight, fathersleftchild);
			//加入队列
			queue.add(b);
			
		}
	}
	// 判断是否装入
	private void forQueue ()
	{
		int i=1;
		Node A = new Node(null, 0, true);
		Node e = A;
		bestE = A;
		queue.add(null);
		int remainder =0;
		for (int j=2;j<=n;j++)
		{
			remainder=remainder+w[j];
		}
		int ew = 0;// 当前重量
		while(true)
		{
			wt = ew + w[i];
			// 判断是否加入左子树
			if(wt<=c)
			{
				if(wt>bestW){
                    bestW=wt;
                 
                }
				
				// 扩展结点
				
				EnQueue(i, e, wt, true);
			}
			//判断是否进入右子树
			if(ew+remainder>bestW)
			{
				EnQueue(i, e, ew, false);
			}
			//取队列的第一个结点并出队列
			e = queue.poll();
			if(e==null)
			{
				if(queue.isEmpty())
					break;
				queue.add(null);
				//取第一个结点并出队列 进入下一层
				e = queue.remove();
				i++;
				remainder=remainder-w[i];
				
			}
			ew = e.weight; 
		}
		for(int j=n-1;j >0; j--)
		{
			bestx[j] = (bestE.fathersleftchild)?1:0;
			bestE = bestE.father;
			
			
		}
		
		
	}
	
	 
	
	public static void main(String[] args) {
		 int n=7;
	     int c=152;//船1承载重量
	     int[] w= {0,90 ,80 ,40 ,30 ,20 ,12 ,10};//集中箱i的重量
	     Zhuangzai fifoLoading=new Zhuangzai(n,w,c);
	     fifoLoading.forQueue();
	     System.out.println("轮船1的最大载重量为:"+fifoLoading.bestW);
	     System.out.println("装载的集装箱重量为:");
	     for(int i=1;i<=n;i++)
	     {
	    	 if(fifoLoading.bestx[i]==1)
	    	 {
	    		 System.out.print(w[i]+" ");
	    	 }
	     }

	}

}

结果:
算法设计-分支界限法——装载问题_第2张图片
解决该问题的关键是设计一个特定的字符,以此来判断是否可以进入下一层,在每次从队首提取出该特定字符时,再将其加入队列中,这样就解决了逐个判断集装箱是否可以装入轮船问题。

记录整理一些学习中的问题,如果有不恰当和错误的地方,欢迎批评指正~

你可能感兴趣的:(算法设计,队列,java,算法,分支界限法,装载问题)