一、基本概念
类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法,但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
二、一般过程
由于求解目标不同,导致分支限界法与回溯法在解空间树T上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。
分支限界法的搜索策略:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被子加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。
三、回溯法和分支限界法的区别
回溯法深度优先搜索堆栈活结点的所有可行子结点被遍历后才被从栈中弹出找出满足约束条件的所有解。
分支限界法广度优先或最小消耗优先搜索队列、优先队列每个结点只有一次成为活结点的机会找出满足约束条件的一个解或特定意义下的最优解。
注:深度优先搜索一般用栈,广度优先搜索一般用队列实现!
四、分支限界法分为如下两种类型
1、队列式(FIFI,先进先出)分支限界法。队列式分支限界法将活结点表组织成一个队列,并按队列的先进先出原则选择下一个结点作为扩展节点。
2、优先队列式分支限界法。优先队列式分支限界法将活结点表组织成一个优先队列,并按优先队列中规定的节点优先级选取下一个结点作为扩展节点。
五、电路布线问题—分支限界法求解
1、问题描述:
布线问题:印刷电路板将布线区域划分成n×m个方格阵列,要求确定连接方格阵列中的方格a的中点到方格b的中点的最短布线方案。在布线时,电路只能沿直线或直角布线,为了避免线路相交,已布了线的方格做了封锁标记,其他线路不允许穿过被封锁的方格。
2、算法应用:
用分支限界法解此布线问题。分支限界法类似回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但分支限界法只找出满足约束条件的一个最优解,并且以广度优先或最小耗费优先的方式搜索解空间树T。树T是一棵子集树或排列树。在搜索时,每个结点只有一次机会成为扩展结点,并且一次性产生其所有儿子结点。从活结点表中选择下一扩展结点有两种方式:(1)队列式(FIFO) (2)优先队列式。分支限界法广泛应用于单源最短路径问题,最大团问题,布线问题,电路板排列问题等。
3、求解思路:
用队列式分支限界法来考虑布线问题。布线问题的解空间是一个图,则从起始位置a开始将它作为第一个扩展结点。与该扩展结点相邻并可达的方格成为可行结点被加入到活结点队列中,并且将这些方格标记为1,即从起始方格a到这些方格的距离为1。接着,从活结点队列中取出队首结点作为下一个扩展结点,并将与当前扩展结点相邻且未标记过的方格标记为2,并存入活结点队列。这个过程一直继续到算法搜索到目标方格b或活结点队列为空时为止。
4、算法思路:
在实现上述算法时,
(1)定义一个表示电路板上方格位置的类Position。
它的2个成员row和col分别表示方格所在的行和列。在方格处,布线可沿右、下、左、上4个方向进行。沿这4个方向的移动分别记为0,1,2,3。下表中,offset[i].row和offset[i].col(i= 0,1,2,3)分别给出沿这4个方向前进1步相对于当前方格的相对位移。
(2)用二维数组grid表示所给的方格阵列。
初始时,grid[i][j]= 0, 表示该方格允许布线,而grid[i][j]= 1表示该方格被封锁,不允许布线。
5、举例说明:
一个7×7方格阵列布线如下:
起始位置是a =(3,2),目标位置是b =(4,6),阴影方格表示被封锁的方格。当算法搜索到目标方格b时,将目标方格b标记为从起始位置a到b的最短距离。此例中, a到b的最短距离是9。
6、Java代码实现
代码来源:https://blog.csdn.net/lican19911221/article/details/26507855
public class WireRouter {
public int[][] grid;//方格阵列;=0表示该方格运行布线;=1表示被封锁,不允许布线
public int size;//方格阵列大小
public int pathLen;//最短线路长度
public LinkedList<Position> q;//扩展结点队列,用list存储
public Position start;//起始位置
public Position finish;//终点
public Position[] path;//最短路径
public WireRouter(int[][] grid,int size,Position start,Position finish){
this.grid=grid;
this.size=size;
this.start=start;
this.finish=finish;
}
/**
* 方格所在位置
* @author Lican
*
*/
public static class Position{
public int row;//行
public int col;//列
public Position(int r ,int c){
row=r;
col=c;
}
}
/**
*计算从起始位置start到目标位置finish的最短布线路径
* @return 找到最短布线路径则返回true,否则返回false
*/
public boolean findPath(){
if(start.row==finish.row&&start.col==finish.col){//start==finish,最短路径为0
pathLen=0;
return true;
}
//初始化相对位移
Position[] offset=new Position[4];
offset[0]=new Position(0,1);//右
offset[1]=new Position(1,0);//下
offset[2]=new Position(0,-1);//左
offset[3]=new Position(-1,0);//上
//设置方格阵列“围墙”,方便处理方格边界的情况
for(int i=0;i<=size+1;i++){
grid[0][i]=grid[size+1][i]=1;//顶部和底部
grid[i][0]=grid[i][size+1]=1;//左边和右边
}
Position here=new Position(start.row,start.col);
grid[start.row][start.col]=2;//数字0,1表示方格的开放或封锁所以,表示距离时,让所有距离都加2;起始位置的距离为0+2=2
int numOfNbrs=4;//相邻方格数
//以下为标记可达的方格位置
q=new LinkedList<Position>();
Position nbr=new Position(0,0);
do{
//标记可达的相邻方格(每个方格有四个相邻方格)
for(int i=0;i<numOfNbrs;i++){
nbr.row=here.row+offset[i].row;
nbr.col=here.col+offset[i].col;
if(grid[nbr.row][nbr.col]==0){//该方格未被标记,且该方格允许布线
grid[nbr.row][nbr.col]=grid[here.row][here.col]+1;
if(nbr.row==finish.row&&nbr.col==finish.col)
break;
q.add(new Position(nbr.row,nbr.col));
}
}
//检测是否到达目标位置finish
if(nbr.row==finish.row&&nbr.col==finish.col)
break;
if(q.isEmpty())
return false;
here=q.poll();
}while(true);
//构造最短布线路径
pathLen=grid[finish.row][finish.col]-2;
path=new Position[pathLen];
//从目标位置finish开始向起始位置回溯
here=finish;
for(int j=pathLen-1;j>=0;j--){
path[j]=here;
//找前驱位置
for(int i=0;i<numOfNbrs;i++){
nbr.row=here.row+offset[i].row;
nbr.col=here.col+offset[i].col;
if(grid[nbr.row][nbr.col]==j+2)
break;
}
here=new Position(nbr.row,nbr.col);
}
System.out.println("最短路线为:");
for(int j=0;j<pathLen-1;j++){
System.out.println("点"+(j+1)+"位置: 行-"+path[j].row+" 列-"+path[j].col);
}
System.out.println("布线长度为:"+pathLen);
return true;
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入方格阵列大小:");
String s1=sc.nextLine();
Integer size=Integer.parseInt(s1);
System.out.println("请输入起始点的行和列,用空格隔开:");
String s2=sc.nextLine();
String[] s3=s2.split(" ");
int startRow=Integer.parseInt(s3[0]);
int startCol=Integer.parseInt(s3[1]);
Position start=new Position(startRow,startCol);
System.out.println("请输入结束点的行和列,用空格隔开:");
String s4=sc.nextLine();
String[] s5=s4.split(" ");
int finishRow=Integer.parseInt(s5[0]);
int finishCol=Integer.parseInt(s5[1]);
Position finish=new Position(finishRow,finishCol);
int[][] grid=new int[size+2][size+2];
System.out.println("请输入方格阵列:");
for(int i=1;i<=size;i++){
String str=sc.nextLine();
String[] strs=str.split(" ");
for(int j=0;j<strs.length;j++){
grid[i][j+1]=Integer.parseInt(strs[j]);
}
}
WireRouter w=new WireRouter(grid,size,start,finish);
w.findPath();
}
}
运行输出如下:
请输入方格阵列大小:
7
请输入起始点的行和列,用空格隔开:
2 3
请输入结束点的行和列,用空格隔开:
4 6
请输入方格阵列:
0 0 1 0 0 0 0
0 0 1 1 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 0 0
1 0 0 0 1 0 0
1 1 1 0 0 0 0
1 1 1 0 0 0 0
最短路线为:
点1位置: 行-3 列-3
点2位置: 行-4 列-3
点3位置: 行-5 列-3
点4位置: 行-5 列-4
点5位置: 行-6 列-4
点6位置: 行-6 列-5
点7位置: 行-6 列-6
点8位置: 行-5 列-6
布线长度为:9
完成!