问题描述:
印刷电路板不限区域划分成n*m个方格阵列。如下图所示
精确的电路布线问题要求确定连接方格a的中点,到连接方格b的中点的最短布线方案。
布线时,电路只能沿直线或直角布线。为了避免线路相交,已布的线的方格做了封锁标记,其他线路不允许穿过被封锁的方格。
分支限界法的解决方案:
首先,从起始位置a开始,将它作为第一个扩展结点。与该节点相邻,并且可达的方格成为可行结点被加入到活节点队列中,并且将这些方格标记为1.
即从起始方格a到这些扩展方格距离为1.
然后,从活节点队列中取出队首结点作为下一个扩展结点,并将于当前扩展结点相邻且为未标记过的方格标记为2,并存入或节点队列。
最后,这个过程一直到算法搜索到目标方格b或活结点队列为空时截止。
实现方案:
初始定义Position,定义变量x,y,显示方格 行 列。
grid[i][j]表示方格阵列的 0 : 开放, 1 :封锁。
2个方格相同,则不必计算,直接返回最小距离。
否则,设置方格围墙,初始化位移矩阵offset。
表示距离时,0,1已经使用,直接从2开始。因此所有距离最后都要减2.
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
#define Maxsize 20
#define N 7
#define M 7
int grid[N+2][M+2];//这个矩阵是N*M的,+2的目的是为了设置边界,每个小方块上面的值,1代表死路,0代表活路,其他数字-2代表到起点的距离
struct Position//定义一个位置结构体,x、y分别为横坐标和纵坐标
{
int x;
int y;
};
struct Queue//定义一个队列,方便进行FIFO操作,注意这里队列里面元素的类型是之前定义的Position类型
{
Position pos[Maxsize];
int front;
int rear;
};
void init(struct Queue &Q)//初始化队列
{
Q.front=Q.rear=0;
}
int isEmpty(struct Queue Q)//判队空
{
return Q.front==Q.rear;
}
void EnQ(struct Queue &Q,Position x)//入队
{
if(Q.front!=(Q.rear+1)%Maxsize)
{
Q.rear=(Q.rear+1)%Maxsize;
Q.pos[Q.rear]=x;
}
}
void DeQ(struct Queue &Q,Position &x)//出队
{
if(Q.front!=Q.rear)
{
Q.front=(Q.front+1)%Maxsize;
x=Q.pos[Q.front];
}
}
int FindPath(Position start,Position finish,Position *&path,int &PathLen)//核心函数,start和finish为两个位置,分别代表起始坐标,path是一个指向Position结构体类型的指针,装的是最终走的路径加引用标志是为了改变实参*path,PathLen是指路径的长度,不包含起点但是包含终点
{
struct Queue Q;//定义一个队列
Position here,nbr,offset[4];//定义两个位置变量,一个是here指当前位置,nbr为下一个探索的位置,here除了初始值以外,here取啥值全靠cbr来探索,offset[4]为四个动作,为别是右下左上
init(Q);//初始化队列
if(start.x==finish.x&&start.y==finish.y)//如果起点就是终点就直接返回1
return 1;
for(int i=0;i<=M+1;i++)//设置上下的围墙
grid[0][i]=grid[N+1][i]=1;
for(int i=0;i<=N+1;i++)//设置左右的围墙
grid[i][0]=grid[i][M+1]=1;
here.x=start.x;//初始化here=start
here.y=start.y;
offset[0].x=0;//定义右
offset[0].y=1;
offset[1].x=1;//定义下
offset[1].y=0;
offset[2].x=0;//定义左
offset[2].y=-1;
offset[3].x=-1;//定义上
offset[3].y=0;
grid[start.x][start.y]=2;//因为1和0都被用了,所以起点在grid[][]上的初始值为2,这个值-2就是到起点的距离
do//开始一轮一轮的循环
{
for(int i=0;i<4;i++)//四个方位探索
{
nbr.x=here.x+offset[i].x;//nbr为下一个探索的值
nbr.y=here.y+offset[i].y;
if(grid[nbr.x][nbr.y]==0)//如果下一个探索的位置是活路
{
grid[nbr.x][nbr.y]=grid[here.x][here.y]+1;//就让这个小方块上面的数字=here上面的数字+1,表示距离又+1
if(nbr.x==finish.x&&nbr.y==finish.y)//再判断这个探索的位置是不是终点,如果是就退出while(1)循环
break;
EnQ(Q,nbr);//如果不是终点的话就让这个位置入队
}
}//四个方向探索完毕
if(nbr.x==finish.x&&nbr.y==finish.y)//探索完毕之后看看刚才是不是探索到终点了,如果刚才break了,说明就到终点啦,那么就赶快退出循环进行下一步回溯操作吧!
break;
if(isEmpty(Q))//如果在四个方向探索之后没有到达终点,并且这个时候队列里空空如也,说明不可能有可行解了,就返回0
return 0;
DeQ(Q,here);//如果在四个方向探索之后没有到达终点,并且这个时候队列里还有东西,就把队头取出来给here继续进行while(1)循环
}while(1);//while(1)结束
/*如果程序执行到这一步,说明有可行解,不然早返回0啦!下面的工作就是要看看到底是怎么样一条路径!*/
PathLen=grid[finish.x][finish.y]-2;//这-2是因为grid[][]上面的数字,除了1和0以外,-2之后就是到起点的距离,说明finish-2就是到起点的距离
path=(Position *)malloc(sizeof(PathLen));//path本来就是一个地址,所以给他开辟PathLen这么长的空间用来存储路径
here=finish;//通过反向回溯的方法来寻找,就先让here=finish
for(int j=PathLen-1;j>=0;j--)//开始反向回溯,j是指path[]的下标,下标从0~PathLen-1
{
path[j]=here;//最后一个位置当然是终点啦,把这句话放在这里的好处是方便循环,直接让path[j]=探索出来的here
for(int i=0;i<4;i++)//就要探索咯,四个方位
{
nbr.x=here.x+offset[i].x;
nbr.y=here.y+offset[i].y;
if(grid[nbr.x][nbr.y]+1==grid[here.x][here.y])//如果这个方位here这个位置的数-1,那么这个数就是我想要的
{
here=nbr;//就把这个数给here
break;//同时结束循环
}
}
}
return 1;
}
int main()
{
grid[N+2][M+2]={0};//首先让grid[][]全部是0
grid[5][1]=grid[6][1]=grid[7][1]=grid[6][2]=grid[7][2]=grid[1][3]=grid[2][3]=grid[6][3]=grid[7][3]=grid[2][4]=grid[4][4]=grid[3][5]=grid[4][5]=grid[5][5]=1;//设置障碍
Position *path;//定义path指针
Position start,finish;//定义起始坐标
int PathLen;//定义路径长度,通过FindPath函数来取值
start.x=3;//初始化起点
start.y=2;
finish.x=4;//初始化终点
finish.y=6;
int ret=FindPath(start,finish,path,PathLen);//ret为返回值
if(!ret)
cout<<"没有可行解!"<<endl;
else//不然就是有解
{
for(int i=0;i<=N+1;i++)//输出grid[][]矩阵
for(int j=0;j<=M+1;j++)
{
if(grid[i][j]>=2)//如果位置上的值既不是0也不是1
cout<<grid[i][j]-2<<" ";//就-2,方便理解
else//不然
cout<<grid[i][j]<<" ";//正常输出就行
if(j==M+1)//如果一行结束就换行
cout<<endl;
}
cout<<"("<<start.x<<","<<start.y<<")"<<endl;//输出起始坐标
for(int i=0;i<PathLen;i++)//输出最短路径
cout<<"("<<path[i].x<<","<<path[i].y<<")"<<endl;
}
return 0;
}