数据结构课程设计最小生成树
成绩评定表
课程设计任务书
摘要
“计算机算法设计与分析”着重介绍了计算机算法设计领域的基本原则和根本原理。深入分析了一些计算机模型上的算法,介绍了一些和设计有效算法有关的数据结构和编程技术,为读者提供了有关递归方法、分治方法和动态规划方面的详细实例和实际应用,并致力于更有效算法的设计和开发。同时,对NP完全等问题能否有效求解进行了分析,并探索了应用启发式算法解决问题的途径。
本文第一问,随着信息技术的发展和嵌入式设备的广泛使用.电路布线问题越来越受到人们的重视.要使电子电路获得最佳性能.不仅元器件的布置占有着重要的地位.而且导线的布设也是非常重要的.特别是在涉及到线路成本的布线方案中.一个好的布线算法就显得尤为重要本文首先对一类电路板低成本布线问题进行了分析.进而给出了解决这一问题的分支限界解法.并对所提出算法的时间复杂度进行了分析和讨论。
本文第二问,掌握动态规划法的原理,并能够按其原理编程实现求两个序列数据的最长公共子系列,以加深对其的理解。
关键字:分支限界;布线问题;动态规划
目录
1 分支限界解决布线问题 (1)
1.1问题重述 (1)
1.2问题分析 (1)
1.3算法分析与设计 (2)
1.4算法的实现与结果 (3)
2 动态规划解决最长公共子序列问题 (9)
2.1问题重述 (9)
2.2问题分析 (9)
2.3算法分析与设计 (9)
2.4算法实现与结果 (10)
参考文献 (15)
1 分支限界解决布线问题
1.1问题重述
`布线问题:如图1所示,印刷电路板将布线区域划分成n*m个方格。精确的电路布线问题要求确定连接方格a的中点到b的中点的最短布线方案。在布线时,电路只能沿直线或直角布线,如图1所示。为了避免线路相交,已经布线的方格做了封锁标记(如图1中阴影部分),其他线路不允许穿过被封锁的方格。
1.2问题分析
题目的要求是找到最短的布线方案,从图1的情况看,可以用贪婪算法解决问题,也就是从a开始朝着b的方向垂直布线即可。实际上,再看一下图2,就知道贪婪算法策略是行不通的。因为已布线的放个没有规律的所以直观上说只能用搜索方法去找问题的解。
根据布线方法的要求,除边界或已布线处,每个E-结点分支扩充的方向有4个:上、下、左、右,也就是说,一个E-结点扩充后最多产生4个活结点。以图2的情况为例,图的搜索过程如图3所示。
搜索以a为第一个E-结点,以后不断扩充新的活结点,直到b结束(当然反之也可以)。反过来从b到a,按序号8-7-6-5-4-3-2-1就可以找到最短的布线方案。从图3中也可以发现最短的布线方案是不唯一的。且由此可以看出,此问题适合
用分支限界搜索。
1.3 算法分析与设计
算法分析:
分支限界搜索法是一种在问题解空间上进行搜索尝试的算法。所谓“分支”是采用广度优先的策略,依次搜索E-结点的所有分支,也就是所有的相邻结点。和回溯法一样,在生成的结点中,抛弃那些不满足约束条件(或者说不可能到处最优可行解)的结点,其余结点加入活结点表。然后从表中选择一个节点作为下一个E-结点,继续搜索。选择下一个E-结点的方式不同,会出现几种不同的分值搜索方式。
FIFO搜索
先进先出(FIFO)搜索算法要依赖“队”做基本的数据结构。一开始,根节点是唯一的活结点,根节点入队。从活结点队中取出根节点后,作为当前扩展结点。对当前扩展结点,先从左到右地产生它的所有儿子,用约束条件检查,把所有满足约束函数的儿子加入或节点队列中。再从活结点表中取出队首结点(对中最先进来的结点)为当前结点,……,直到找到一个解或活结点队列为空为止。
LIFO搜索
后进先出(LIFO)搜索算法要依赖“栈”做基本的数据结构。一开始,根结点入栈,从栈中弹出一个结点为当前扩展结点。对当前扩展结点,从左到右地产生它的所有儿子,用约束条件检查,把所有满足约束函数的儿子入栈,再从栈中弹出一个结点(栈中最后进来的结点)为当前扩展结点,……,直到找到一个解或栈为空为止。
优先队列式搜索
为了加速搜索的进程,应采用有效的方式选择E-结点进行扩展。优先队列搜索,对每一个活结点计算一个优先级(某些信息的函数值),并根据这些优先级,从当前结点表中优先选择一个优先级最高(最有利)的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
算法设计:
开辟m*n的二维数组模拟布线区域,初始值均为0,表示没有被使用。已使用
的位置,通过键盘输入其下标,将对应值置为-1.输入方格a、b的下标,存储在变量中。
①一开始,唯一的活结点是a。
②从活结点表中取出后为当前扩展结点。
③对当前扩展结点,按上、下、左、右的顺序,找出可布线的位置,加入活结点队列中,
④再从活结点队列中取出后为当前扩展结点。
⑤依此类推,直到搜索到达b结点,然后按序号8-7-6-5-4-3-2-1输出最短布线方案,算法结束。或活结点队列已为空,表示无解,算法结束。
1.4算法的实现与结果
通过算法的分析与设计,计算如图所示4所示的布线问题
代码:
#include
#include
typedef struct Position
{
int row;
int col;
}Position;
typedef struct team
{
int x;
int y;
struct team *next;
}team,*TEAM;
Position start,end,path[100];
TEAM team_l=NULL;
int a[100][100];
int m,n,path_len;
void output()
{
int i,j;
printf("\n|-------------------布线区域图-------------------|\n");
for(i=0;i { for(j=0;j { printf("%5d",a[i][j]); } printf("\n"); } printf("|------------------------------------------------|\n"); return; } void input_data() { char yes; int x,y; printf("创建布线区域...\n\n"); printf("请输入区域大小(行列的个数): "); scanf("%d,%d",&m,&n); printf("请输入开始点坐标(x,y): "); scanf("%d,%d",&start.row,&start.col); printf("请输入结束点坐标(x,y): "); scanf("%d,%d",&end.row,&end.col); printf("区域内是否有被占用点? (y/n) "); fflush(stdin); scanf("%c",&yes); fflush(stdin); while(yes=='y') { printf("请输入占用点的坐标(x,y): "); scanf("%d,%d",&x,&y); fflush(stdin); if(x<0 || x>m+1 || y<0 || y>n+1 || (x==start.row && y==start.col) || (x==end.row && y==end.col)) { printf("输入错误,请重新输入!!!\n"); continue; } else { a[x][y]=-1; } printf("是否还有被占用点? (y/n) "); scanf("%c",&yes); fflush(stdin); } for(x=0;x { a[0][x]=-1; a[m+1][x]=-1; } for(x=0;x { a[x][0]=-1; a[x][n+1]=-1; } return; } void inq(Position p) { TEAM t,q; q=team_l; t=(TEAM)malloc(sizeof(TEAM)); t->x=p.row; t->y=p.col; t->next=NULL; if(team_l==NULL) { team_l=t; return ; } while(q->next!=NULL) { q=q->next; } q->next=t; return; } Position outq() { Position out; out.row=team_l->x; out.col=team_l->y; team_l=team_l->next; return out; } void find_path() { Position offset[4]; Position here={start.row,start.col}; Position nbr={0,0}; int num_of_nbrs=4; int i,j; 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;//上 printf("\n开始搜索路径...\n"); if((start.row == end.row)&&(start.col == end.col)){ path_len = 0; return; } while(1) { for(i=0;i { nbr.row=here.row+offset[i].row; nbr.col=here.col+offset[i].col; if(a[nbr.row][nbr.col]==0) { a[nbr.row][nbr.col]=a[here.row][here.col] + 1; if((nbr.row == end.row) && (nbr.col == end.col)) break; inq(nbr); //nbr入队 } } //是否到达目标位置finish if((nbr.row == end.row) && (nbr.col == end.col)) break; //或节点队列是否为空 if(team_l==NULL) { printf("\n没有结果!!!\n"); return ; } here=outq(); } path_len=a[end.row][end.col]; here=end; for(j=path_len-1;j>=0;j--) { path[j] = here; for(i = 0;i < num_of_nbrs;i++) { nbr.row = here.row + offset[i].row; nbr.col = here.col + offset[i].col; if(a[nbr.row][nbr.col] == j) //+ 2) break; } here=nbr; } return; } void out_path() { int i; printf("\n路径为:\n"); printf("(%d,%d) ",start.row,start.col); for(i=0;i { printf("(%d,%d) ",path[i].row,path[i].col); } printf("\n"); return; } void main() { input_data(); output(); find_path(); out_path(); output(); } 运行结果与分析: 根据运行的结果我们看到,首先要给布线区域加一道“围墙”,从开始点进行 标记。直达到达结束点。并且可以看到a 到b 的路径不是唯一的,及最优解不是唯一的。 2 动态规划解决最长公共子序列问题 2.1 问题重述 若给定序列X ={1x ,2x ,…,n x },则另一序列Z ={1z ,2z ,…,k z },是X 的子序列是指存在一个严格递增下标序列{1i , 2i ,…, k i }使得对于所有j=1,2,…,k 有:j z =ij x 。例如,序列Z ={B ,C ,D ,B}是序列X ={A ,B ,C ,B ,D ,A ,B}的子序列,相应的递增下标序列为{2,3,5,7}。给定2个序列X 和Y ,当另一序列Z 既是X 的子序列又是Y 的子序列时,称Z 是序列X 和Y 的公共子序列。请使用C 语言编程,设计一个有效的算法解决下述问题:给定2个序列X={1x ,2x ,…,n x },和Y={ 1y ,2y ,…,n y },找出X 和Y 的最长公共子序列。 2.2问题分析 设序列X={1x ,2x ,…,n x }和Y={ 1y ,2y ,…,n y }的最长公共子序列为 Z ={1z ,2z ,…,k z },则 (1)若n n y x =,则n n k y x z ==,且1-k z 是1-n x 和1-n y 的最长公共子序列。 (2)若n n y x ≠且n k x z ≠,则Z 是1-n x 和Y 的最长公共子序列。 (3)若n n y x ≠且n k y z ≠,则 Z 是X 和1-n y 的最长公共子序列。 由此可见,2个序列的最长公共子序列包含了这2个序列的前缀的最长公共子序列。 2.3算法分析与设计 算法分析: 动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中,这就是动态规划法的基本思路。 算法设计: 由最长公共子序列问题的最优子结构性质建立子问题最优值的递归关系。用c[i][j]记录序列和的最长公共子序列的长度。其中,i X ={1x ,2x ,…,xi } j Y ={ 1y ,2y ,…,j y }。当i=0或j=0时,空序列是i X 和j Y 的最长公共子序列。 故此时C[i][j]=0。程序的设计思路是:调用函数void Initialize (),输入两个字符串,对两个串的存储数组b ,c 进行动态分配。调用函数void LCSLength (int m,int n,string x,string y,int**c,int**b ),将长度较小的字符串作为第一参数,将长度较大的字符串作为第二个参数。调用函数void LCS (inti ,int j,string x,int**b )构造最长公共子序列调用函数。调用函数ReadCommand 进行系统操作屏幕指示,然后利用函数void Interpret (char&cmd )对函数进行总体操作,最后得出最长公共子序列。 2.4算法实现与结果 代码如下: #include string x,y;//x,y 用来存放字符序列 int **c,**b,m,n; /*m,n分别存储字符串x,y的长度,数组c[i][j]存储Xi和Yj得最长公共子序列的长度, b[i][j]记录c[i][j]的值是有哪一个子问题的解得到的*/ void LCSLength(int m,int n,string x,string y,int **c,int**b); void LCS(int i,int j,string x,int **b); void Initialize();//对数组b,c动态分配空间以及对其进行初始化 void ReadCommand(char &cmd); void Interpret(char &cmd); void Realese();//释放指针 void Display(); int main() { char cmd; do { ReadCommand(cmd); Interpret(cmd); }while(cmd!='q'&&cmd!='Q'); return 0; } void ReadCommand(char &cmd) { system("cls"); //清屏 cout<<"\n--------------------------------------------------------------------------\n"; cout<<"\n\t\t\t\t操作提示"; cout<<"\n--------------------------------------------------------------------------\n"; cout<<"\tquit--q/Q \t\t continue---c/C\n"; do{ cout<<"\n\n\t请选择操作:"; cin>>cmd; cout<<"\n--------------------------------------------------------------------------\n"; }while(cmd!='c'&&cmd!='C'&&cmd!='q'&&cmd!='Q'); } void Initialize() { cout<<"分别输入两个字符串(每个字符串以回车结束)\n"; cin>>x; cin>>y; m=x.length(); n=y.length(); c=new int*[m+1]; b=new int*[m+1]; for(int i=0;i<=m;i++) { c[i]=new int[n+1]; b[i]=new int[n+1]; } } void Realese()//释放指针board { for(int i=0;i<=m;i++) { delete c[i]; delete b[i]; } delete[] c; delete[] b; } void Interpret(char &cmd) { switch(cmd) { case 'c': case 'C': Initialize(); LCSLength(m,n,x,y,c,b); Display(); Realese(); break; } } void LCSLength(int m,int n,string x,string y,int **c,int**b) { int i,j; for(i=0;i<=m;i++)c[i][0]=0; for(i=1;i<=n;i++)c[0][i]=0; for(i=1;i<=m;i++) for(j=1;j<=n;j++) { if(x[i-1]==y[j-1]) { c[i][j]=c[i-1][j-1]+1; b[i][j]=1; } else if(c[i-1][j]>=c[i][j-1]) { c[i][j]=c[i-1][j]; b[i][j]=2; } else { c[i][j]=c[i][j-1]; b[i][j]=3; } } } //构造最长公共子序列 void LCS(int i,int j,string x,int **b) { if(i==0||j==0)return; if(b[i][j]==1) { LCS(i-1,j-1,x,b); cout< } else if(b[i][j]==2)LCS(i-1,j,x,b); else LCS(i,j-1,x,b); } void Display() { LCS(m,n,x,b); cout<<"\n请按回车键继续!\n"; cin.get(); cin.get(); } 结果分析: 通过代码和运行结果,我们能够看到,首先对二个序列进行动态分配,将长度较小的字符串作为第一参数,将长度较大的字符串作为第二个参数。然后调用函数进行分析,最长公共子序列可能不是唯一的,但是本文的结果只能输出一个, 所以本文的算法还需要改进。 参考文献 【1】《算法设计与分析》王晓东编著电子工业出版社。