分支限界算法设计与实现
1.分支限界法求解布线问题
(1)问题描述
印刷电路板不限区域划分成n*m个方格阵列。如下图所示
精确的电路布线问题要求确定连接方格a的中点,到连接方格b的中点的最短布线方案。
(2)问题求解
- 布线问题分析与理解;
布线时,电路只能沿直线或直角布线。为了避免线路相交,已布的线的方格做了封锁标记,其他线路不允许穿过被封锁的方格。
2.分支限界算法设计,确立问题解空间、解的形式,结点约束条件以及剪枝条件,确立采用队列式或优先队列式算法,算法复杂性分析;
①算法设计
用队列式分支限界法来考虑布线问题。
布线问题的解空间是一个图,则从起始位置a开始将它作为第一个扩展结点。与该扩展结点相邻并可达的方格成为可行结点被加入到活结点队列中,并且将这些方格标记为1,即从起始方格a到这些方格的距离为1。接着,从活结点队列中取出队首结点作为下一个扩展结点,并将与当前扩展结点相邻且未标记过的方格标记为2,并存入活结点队列。这个过程一直继续到算法搜索到目标方格b或活结点队列为空时为止。
②算法复杂性分析
由于每个方格成为活结点进入活结点队列最多1次,因此活结点队列中最多只处理O(mn)个活结点。扩展每个结点需O(1)时间,因此算法共耗时O(mn)。构造相应的最短距离要O(1)时间,其中L是最短布线路径的长度。
3.算法代码实现;
#include <iostream>
#include <queue>
using namespace std;
int m=8;
int n=8;
int grid[10][10];
int indexcount=0;
struct Position
{
int row;
int col;
};
void showPath()
{
for(int i=0; i<10; i++)
{
for(int j=0; j<10; j++)
cout<<grid[i][j]<<" ";
cout<<endl;
}
cout<<"------------------"<<endl;
}
bool FindPath(Position start,Position finish,int &PathLen,Position *&path)
{
//计算从起点位置start到目标位置finish的最短布线路径,找到最短布线路//径则返回true,否则返回false
if((start.row==finish.row) && (start.col==finish.col))
{
PathLen=0;
cout<<"start=finish"<<endl;
return true;
} //start=finish
//设置方格阵列“围墙”
//初始化图,-1为未访问
for(int i=1; i<9; i++)
{
for(int j=1; j<9; j++)
grid[i][j]=-1;
}
//添加阻挡点
grid[2][3]=-2;
for(int i=0; i<= m+1; i++)
grid[0][i]=grid[n+1][i]=-2; //顶部和底部
for(int i=0; i<= n+1; i++)
grid[i][0]=grid[i][m+1]=-2; //左翼和右翼
//初始化相对位移
cout<<"完整图"<<endl;
showPath();
Position offset[4];
offset[0].row=0;
offset[0].col=1;//右
offset[1].row=1;
offset[1].col=0;//下
offset[2].row=0;
offset[2].col=-1;//左
offset[3].row=-1;
offset[3].col=0;//上
int NumOfNbrs=4;//相邻方格数
Position here,nbr;
here.row=start.row;
here.col=start.col;
grid[start.row][start.col]=0;
//标记可达方格位置
cout<<"布线前图"<<endl;
showPath();
queue<Position> Q;
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]==-1)
{
//该方格未被标记
//cout<<grid[nbr.row][nbr.col]<<endl;//显示路标值
grid[nbr.row][nbr.col]=grid[here.row][here.col]+1;
//cout<<nbr.col<<" "<<nbr.row<<endl;//显示坐标
}
if((nbr.row==finish.row) &&(nbr.col==finish.col)) break; //完成布线
Q.push(nbr);
}
//是否到达目标位置finish?
if((nbr.row==finish.row)&&(nbr.col==finish.col)) break;//完成布线
//活结点队列是否非空?
if(Q.empty()) return false;//无解
here = Q.front();
//cout<<here.col<<" "<<here.row<<endl;
Q.pop();//取下一个扩展结点
indexcount++;
// cout<<"下一节点"<<indexcount<<endl;
}
while(true);
//构造最短布线路径
PathLen=grid[finish.row][finish.col];
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)
{
// cout<<j<<endl;
break;
}
}
here=nbr;//向前移动
}
return PathLen;
}
int main()
{
Position start;
start.col=1;
start.row=1;
cout<<"布线起点"<<endl;
cout<<start.col<<" "<<start.row<<endl;
Position finish;
finish.row=3;
finish.col=4;
cout<<"布线结束点"<<endl;
cout<<finish.col<<" "<<finish.row<<endl;
int PathLen=0;
Position *path;
FindPath(start,finish,PathLen,path);
cout<<"布线后路径图"<<endl;
showPath();
cout<<"路径"<<endl;
for(int i=0; i<PathLen; i++)
{
cout<<path[i].col<<" "<<path[i].row<<endl;
}
cout << "布线问题完毕!" << endl;
system("pause");
return 0;
}
- 布线问题求解结果分析
三、 实验环境
DEV C++6.0
四、 实验步骤
1.问题的描述
在下图所给的有向图G中,每一边都有一个非负边权。要求图G的从源顶点s到目标顶点t之间的最短路径。
下图是用优先队列式分支限界法解有向图G的单源最短路径问题产生的解空间树。其中,每一个结点旁边的数字表示该结点所对应的当前路长。
找到一条路径:
目前的最短路径是8,一旦发现某个结点的下界不小于这个最短路进,则剪枝:
同一个结点选择最短的到达路径:
2.算法设计思想
解单源最短路径问题的优先队列式分支限界法用一极小堆来存储活结点表。其优先级是结点所对应的当前路长。
算法从图G的源顶点s和空优先队列开始。结点s被扩展后,它的儿子结点被依次插入堆中。此后,算法从堆中取出具有最小当前路长的结点作为当前扩展结点,并依次检查与当前扩展结点相邻的所有顶点。如果从当前扩展结点i到顶点j有边可达,且从源出发,途经顶点i再到顶点j的所相应的路径的长度小于当前最优路径长度,则将该顶点作为活结点插入到活结点优先队列中。这个结点的扩展过程一直继续到活结点优先队列为空时为止。
3.算法代码实现
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
#include <ctime>
using namespace std;
struct Node{
int i;//顶点编号
int length;//当前路长
friend bool operator<(Node a, Node b)
{//不加friend 就会出错
return a.length > b.length;//极小堆,默认情况是极大堆.
}
};
priority_queue < Node > q;
int c[10][10];//图的临街矩阵
int n;
const int INF = 1 << 30;
int dist[10];
int pre[10];
void graphShortestPath(int v){
Node E;
E.i = v;
E.length = 0;
dist[v] = 0;
q.push(E);//一开始没把初始点放进去,导致直接把第二个点pop掉了,没经过第二个点松弛边,所以一直出错.
while (1){
for (int j = 1; j <= n; ++j) {
if ( (c[E.i][j] < INF ) && (E.length + c[E.i][j] < dist[j]) ){
dist[j] = c[E.i][j] + E.length;
pre[j] = E.i;
Node temp;
temp.i = j;//此处一开始没有及时更新
temp.length = dist[j];
//cout << "temp=" << temp.i <<" "<< endl;
q.push(temp);
}
}
if (q.empty())break;
q.pop();
E = q.top();
}
}
int main(){
int m;
n = 6;
m = 9;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (i == j) c[i][j] = 0;
else c[i][j] = INF;
}
}
for (int k = 1; k <= m; ++k) {
int t1,t2,t3;
cin >> t1 >> t2 >>t3;
c[t1][t2] = t3;
}
for (int l = 0; l < 10; ++l) {
dist[l] = INF;
pre[l] = 0;
}
graphShortestPath(1);
for (int i1 = 1; i1 <= n; ++i1) {
cout << dist[i1] <<" " ;
}
return 0;
}
4.数据输入和结果输出
5.算法复杂性分析
时间复杂度:O(n!);
空间复杂度:O(n*n)。
五、 实验总结
通过本次实验,使我充分理解分支限界法的算法思想和布线问题及单源最短路径问题代码的实现和主要算法实现,分支能以最优的方法解出最优答案的方法。这次的报告激发了我对学习的积极性,培养了我独立发现问题,分析问题,解决问题的能力,更增强了我与同学的交流沟通能力和共同合作解决问题的能力。