最近算法课在讲解回溯法,回溯法是一种利用树结构,遍历全部可能结果,并输出最优解的算法,它能够保证每一种结果都考虑到,不会出现漏掉的情况。
在用回溯法时,重要的一步操作是画出解空间树,之后按照深度优先便利的操作,搜索每一个节点,直到搜索至叶子结点。在搜索过程中,我们要考虑这个节点是否能够满足问题的限制,如果不满足,则尽早进行剪枝操作,以减少不必要的遍历。
具体问题描述如下: 有一批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且
,装载问题要求确定是否有一个合理的装载方案可将这些集装箱装上这2艘轮船。如果有,找出一种装载方案。
这个问题的基本策略非常清晰, (1)首先将第一艘轮船尽可能装满;
(2)将剩余的集装箱装上第二艘轮船。
将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近C1。由此可知,装载问题等价于以下特殊的0-1背包问题。
具体代码如下:
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
/**
* Created by Amiedon on 2016/10/24.
*/
public class JiZhuangXiang {
private static JiZhuangXiang jzx = new JiZhuangXiang();
private static LNode root = jzx.new LNode(0,0,""); //初始化一个根节点
private static double c1 = 40,c2 = 50;
public static void main(String[] args) {
double[] container = new double[]{4.1,5.2,6.3,7.4,8.5,9.6,10.7,11.8,12.9,3.2};
double sumWeight = 0; //集装箱的总重量
for (double d : container)
sumWeight += d;
jzx.initialTree(container); //初始化集装箱的解空间,即一棵二叉树
LNode lNode = jzx.backTrack(); //进行二叉树的遍历并找出尽量使C1装满的方案
System.out.println("船C1载重:"+lNode.weight+" 船C2载重:"+(sumWeight-lNode.weight));
System.out.println("船C1的装载方案为:"+lNode.trace+"(ps:其中数字表示第几个集装箱)");
}
/**
* 采用先序遍历二叉树,返回相应方案的叶子节点
* @return
*/
public LNode backTrack() {
Stack<LNode> stack = new Stack<LNode>();
LNode node = root;
double weight = 0;
LNode returnNode = root;
while (node != null || stack.size() > 0) {
while (node != null) {
if(node.lChild == null && weight < node.weight && node.weight < c1){
weight = node.weight;
returnNode = node;
}
stack.push(node);
node = node.lChild;
}
if (stack.size() > 0) {
node = stack.pop();
node = node.rChild;
}
}
return returnNode;
}
/**
* 初始化二叉树
* @param container
*/
public void initialTree(double[] container) {
Queue<LNode> queue = new LinkedList<LNode>();
queue.offer(root);
LNode temp;
LNode lChild;
LNode rChild;
int i = 1;
int deep;
while (!queue.isEmpty()){
deep = new Double(Math.log(i)/Math.log(2)).intValue();
if(deep >= container.length)
break;
temp = queue.poll();
lChild = new LNode(i,container[deep]+temp.weight,temp.trace+" "+(deep+1));
rChild = new LNode(i+1,temp.weight,temp.trace);
temp.setlChild(lChild);
temp.setrChild(rChild);
queue.offer(lChild);
queue.offer(rChild);
i++;
}
}
/**
* 定义了一个节点的内部类,用于存放左子树、右子树,以及节点的相关信息
*/
public class LNode{
int num; //节点编号
double weight; //到该节点为止,船C1所装载的重量
LNode lChild;
LNode rChild;
String trace; //用于跟踪船C1集装箱的装载情况
LNode(int num,double weight,String trace){
this.num = num;
this.weight = weight;
this.trace = trace;
}
public void setlChild(LNode lChild) {
this.lChild = lChild;
}
public void setrChild(LNode rChild) {
this.rChild = rChild;
}
}
}
在此,我们采用队列实现对树的遍历,在便利过程中检查每一个节点,进行剪枝操作。
本文介绍了如何运用回溯法来解决最优装载问题。通过建立解空间树,按照深度优先搜索策略,对每个节点进行判断并适时剪枝,确保找到所有可能的解决方案。问题背景是将n个不同重量的集装箱合理装载到两艘船中,目标是找到最佳装载方案。回溯法在此问题中表现为先尽可能填满第一艘船,再处理剩余集装箱。这个问题可以转换为0-1背包问题的特殊形式。
2468

被折叠的 条评论
为什么被折叠?



