基本思想
有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
解空间树:
例如,对于 n=3 的 01-背包问题,可以用完全二叉树表示其解空间:
解空间树的类型
- 子集树:当所给问题是从 n 个元素的集合 S 中找出 S 满足某种性质的子集时,相应的解空间树称为子集树。
void backtrack(int t){
if(t>n)
output(x);
else{
for(int i=0;i<=1;i++){
x[t] = i;
if(constraint(t)&&bount(t))
backtrack(t+1);
}
}
}
- 排列树:当所给问题是确定 n 个元素满足某种性质的排列时,相应的解空间被称为排列树。
void backtrack(int t){
if(t>n)
output(x);
else{
for(int i=t;i<=n;i++){
swap(x[t],x[i]);
if(constraint(t)&&bount(t))
backtrack(t+1);
swap(x[t],x[i]);
}
}
}
回溯的过程:
- 回溯法从根节点出发,以深度优先方式搜索整个解空间。
- 这个开始节点成为活结点,也成为当前的扩展结点。在当前扩展结点处,搜索向纵深方向移至一个新结点,则这个新结点成为新的活结点,并成为当前的扩展结点。
- 如果当前扩展结点不能再移动,则当前扩展结点成为死结点,此时应该回溯到最近的活结点处。
- 直到在解空间中已无活结点则搜索结束。
解题的步骤:
- 针对所给问题,定义问题的解空间
- 确定易于搜索的解空间结构
- 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索
批处理作业调度
问题:给定 n 个作业的集合 J={J1,J2,…,Jn},每个作业 Ji都有两项任务分别在两台机器上完成,每个作业必须先有机器1处理,然后由机器2处理。Fji 是作业 i 在机器 j 上完成处理的时间。所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和,批处理作业调度即给出最优调度使得完成时间和最小。
解题思路:
可以确定解空间是一颗排列树,设 x=[1,2,…,n] 是所给的 n 个作业,则相应的排列树由 x[1:n] 的所有排列构成。
- 当i>n时,算法搜索至叶子结点,得到一个新的作业调度方案。此时算法适时更新当前最优值和相应的当前最佳调度。
- 当i<n时,当前扩展结点位于排列树的第(i-1)层,此时算法选择下一个要安排的作业,以深度优先方式递归的对相应的子树进行搜索,对不满足上界约束的结点,则剪去相应的子树。
代码:
public class FlowShop{
static int n,f1,f,bestf;
//作业数,机器1完成时间,完成时间和,最优值
static int[