拓扑排序72.docx
《拓扑排序72.docx》由会员分享,可在线阅读,更多相关《拓扑排序72.docx(29页珍藏版)》请在冰豆网上搜索。
拓扑排序72
附件1:
学号:
课程设计
题目
拓扑排序
学院
计算机科学与技术
专业
计算机科学与技术
班级
姓名
指导教师
2011
年
7
月
1
日
附件2:
课程设计任务书
学生姓名:
专业班级:
班
指导教师:
工作单位:
计算机科学系
题目:
拓扑排序
初始条件:
(1)采用邻接表作为有向图的存储结构;
(2)给出所有可能的拓扑序列。
(3)测试用例见题集p48题7.9图
要求完成的主要任务:
(包括课程设计工作量及其技术要求,以及说明书撰写等具体要求)
课程设计报告按学校规定格式用A4纸打印(书写),并应包含如下内容:
1、问题描述
简述题目要解决的问题是什么。
2、设计
存储结构设计、主要算法设计(用类C语言或用框图描述)、测试用例设计;
3、调试报告
调试过程中遇到的问题是如何解决的;对设计和编码的讨论和分析。
4、经验和体会(包括对算法改进的设想)
5、附源程序清单和运行结果。
源程序要加注释。
如果题目规定了测试数据,则运行结果要包含这些测试数据和运行输出,
6、设计报告、程序不得相互抄袭和拷贝;若有雷同,则所有雷同者成绩均为0分。
时间安排:
1、第19周完成。
2、7月1日14:
00到计算中心检查程序、交课程设计报告、源程序(CD盘)。
指导教师签名:
年月日
系主任(或责任教师)签名:
年月日
目录
课程设计任务书2
目录3
一、问题描述4
二、算法设计4
1.采用邻接表作为有向图的存储结构4
2.操作的结果5
3.主要数据结构5
4.总体设计流程7
5.测试用例设计8
三、调试报告9
1.错误调试9
1.1.输入测试用例邻接表9
1.2.输入完成进行入度计算,发生如下错误,调试中断9
2.新建邻接表指针值传递错误10
2.1.VisualStudio2010的监视窗口查看图G的数据10
2.2.查看第一个点的邻接域10
2.3.分析出错原因及解决办法10
3.指针未初始化导致访问出错14
3.1.指针为空,程序调试中断14
3.2.查看p指针值14
3.3.分析错误原因及解决办法14
四、经验和体会(包括对算法改进的设想)17
1.课程设计体会17
2.算法改进的设想及实现17
五、附源程序清单和运行结果21
本科生课程设计成绩评定表23
一、
问题描述
对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对由某个集合上的一个偏序得到该集合上的一个全序,这个操作就称之为拓扑排序。
偏序集合中仅有部分成员之间颗比较,而全序指集合中全体成员之间均可比较,而由偏序定义得到拓扑有序的操作便是拓扑排序。
一个表示偏序的有向图可用来表示一个流程图,通过抽象出来就是AOV-网,若从顶点i到顶点j有一条有向路径,则i是j的前驱,j是i的后继。
若(i,j)是一条弧,则i是j的直接前驱;j是i的直接后继。
在AOV-网中,不应该出现有向环,用拓扑排序就可以判断网中是否有环,若网中所有顶点都在它的拓扑有序序列中,则该AOV-网必定不存在环。
拓扑排序方法步骤:
1.从有向图上选择一个没有入度的节点并输出。
2.从网中删去改点并删去从该顶点发出的全部有向边。
3.重复上述两步,直到剩余网中不再存在没有前驱的顶点为止。
本次课程设计使用VisualStudio2010旗舰版进行程序的编写和调试。
二、算法设计
1.采用邻接表作为有向图的存储结构
需要编写计算顶点入度的函数FindInDegre0()。
删除入度为零的顶点,及以它为尾的弧的操作,可以换以弧头顶点入度减一来实现。
避免重复检测入度为零的顶点,需另设一栈存储所有入度为零的顶点。
当有向图上某一顶点入度为零则将该顶点入栈,栈不为空时输出栈顶,并将与该元素邻接的顶点的入度减一,若减一后,入度为零,则继续入栈重复上述步骤。
2.操作的结果
(1)全部顶点被输出,网中没有回路。
(2)未输出全部顶点,剩余顶点均有前驱,网中有回路。
3.主要数据结构
(1)图的邻接表存储形式;
对图中每个顶点建立一个单链表第i个单链表中的结点表示依附于顶点vi的边(对有向图是以顶点vi为尾的弧)。
每个结点由三个域组成:
邻接点域adjvex:
指示与顶点vi邻接的点在图中的位置;
链域nextarc:
指示下一条边或弧的结点;
数据域info:
存储和边或弧相关的信息,如权值等。
每个链表附设一个表头结点,有两个域:
数据域data:
存储顶点的名或其它有关信息
链域firstarc:
指向链表中第一个结点。
-----------------图的邻接表存储形式数据结构定义------------------
#defineMAX_VERTEX_NUM20
typedefstructArcNode{
intadjvex;
structArcNode*nextarc;
InfoType*info;
}ArcNode;
typedefstructVNode{
VertexTypedata;
ArcNode*firstarc;
}VNode,AdjList[MAX_VERTEX_NUM];
typedefstruct{
AdjListvertices;
intvexnum,arcnum;
intkind;
}ALGraph;
(2)栈。
栈(Stack)是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为栈顶(Top),另一端为栈底(Bottom)。
当表中没有元素时称为空栈。
假设栈S=(a1,a2,a3,…an),则a1称为栈底元素,an为栈顶元素。
栈中元素按a1,a2,a3,…an的次序进栈,退栈的第一个元素应为栈顶元素。
换句话说,栈的修改是按后进先出的原则进行的。
因此,栈称为后进先出表(LIFO)
顺序栈的类型定义如下:
#defineStack_Init_Size100;
#defineStackIncrement10;
typedefstruct{
SElemType*base;
SElemType*top;
intstacksize;
}SqStack;
链栈的类型说明如下:
typedefstructstacknode{
SElemTypedata
structstacknode*next
}stacknode,*LinkStack;
4.总体设计流程
StatusTopologicalSort(ALGraphG){
//有向图G采用邻接表存储结构。
//若G无回路,则输出G的顶点的一个拓扑序列并返
//回OK,否则ERROR
FindInDegree(G,indegree);//对各顶点求入度
InitStack(S);
for(i=0;iif(!
indegree[i])Push(S,i);//入度为零者进栈
Count=0;//对输出顶点计数
while(!
StackEmpty(S)){
Pop(S,i)p;printf(i,G.vertices[i].data);//输出i号顶点
++count;
for(p=G.vertices[i].firstarc;p!
=null;p=p->nextarc){
k=p->adjvex;//对i号顶点的每个邻接点的入度减1
if(!
(--indegree[k]))Push(S,k);//若入度减为0,则入栈
}
}
If(countelsereturnOK;}
5.
测试用例设计
根据任务书测试用例使用习题册P48题7.9图
邻接表为:
0
1
x
1
2
x
2
3
x
3
x
4
1
5
x
5
3
x
拓扑序列为:
AEBCFD
AEBFCD
AEFBCD
EABCFD
EABFCD
EAFBCD
EFABCD
三、
调试报告
1.错误调试
调试中在确保正确编译通过后输入测试用例进行结果调试,过程如下
1.1.输入测试用例邻接表
1.2.输入完成进行入度计算,发生如下错误,调试中断
2.
新建邻接表指针值传递错误
2.1.VisualStudio2010的监视窗口查看图G的数据
由图可知,图G采用邻接表形式,存储六个点分别为ABCDEF
2.2.查看第一个点的邻接域
如图所示,程序中有向图G的第一个点firstarc域为空值,导致访问p->firstarc时访问空指针而出现错误,调试中断。
2.3.分析出错原因及解决办法
邻接表为:
0
1
x
1
2
x
2
3
x
3
X
4
1
5
x
5
3
x
第一个顶点的firstarc域不应该为空值,而程序中变量G的所有点的firstarc域都为空值,说明在进行邻接表的建立时候出错。
建表的函数为:
voidCreatGraph(ALGraph&G)
{
intpos;
ArcNode*p=G.vertices[0].firstarc;//出错原为ArcNode*p;
cout<<"请输入顶点数目:
";cin>>G.vexnum;
cout<";cin>>G.arcnum;
cout<";cin>>G.kind;
for(inti=0;i<=G.vexnum-1;i++)
{
p=G.vertices[i].firstarc;
cout<";
cout<";
cin>>G.vertices[i].data;
pos=0;//pos赋值使得可以进入while
while(pos>=0&&pos{
cout<";
cin>>pos;//输入实际位置
if(pos>=0&&pos{
p=newArcNode;
p->adjvex=pos;
p=p->nextarc;
}//if
}//while
}//for
}//CreatGraph
分析猜想:
voidCreatGraph(ALGraph&G)中使用
ArcNode*p=G.vertices[0].firstarc;
p=newArcNode;
p->adjvex=pos;
p=p->nextarc;
来新建连接表,并将地址赋给指针firstarc,有可能出现两种错误:
a)由于函数作用域问题导致退出CreatGraph新建空间被释放,导致地址无效。
b)由于没有正确的将地址传给firstarc,导致变量G出错。
通过查阅资料和实际实验排除第一种错误。
查阅资料得知,使用new语句分配的空间不会随着退出函数导致空间的释放。
而且采用一边输数据一遍监视变量G的值时发现在输入firstarc值时,变量G的顶点firstarc值仍为空。
如图:
在输入第一个点的firstarc信息后,但图G的第一个点的firstarc仍为空。
由此可以判断程序出现的是错误b。
针对此错误对函数voidCreatGraph(ALGraph&G)代码进行修改。
修改后的函数代码:
voidCreatGraph(ALGraph&G)
{
intpos,k=0;//pos实际位置k计数头结点的第几条弧
ArcNode*p,*q;//出错原为ArcNode*p;
cout<<"请输入顶点数目:
";cin>>G.vexnum;
cout<";cin>>G.arcnum;
cout<";cin>>G.kind;
for(inti=0;i<=G.vexnum-1;i++)
{
k=0;//每一个顶点的弧数初始化为0
//p=G.vertices[i].firstarc;
cout<";
cout<";
cin>>G.vertices[i].data;
pos=0;//pos赋值使得可以进入while
while(pos>=0&&pos{
cout<";
cin>>pos;//输入实际位置
if(pos>=0&&pos{
k++;//对弧数计数
p=newArcNode;
p->adjvex=pos;
if(k==1)
G.vertices[i].firstarc=p;//k==1表示头结点的第一条弧
if(k>1)
q->nextarc=p;//将后续弧连接到上一条弧的nexarc指针域
q=p;//存储上一条弧
}//if
}//while
}//for
}//CreatGraph
修改了newArcNode处指针传递问题,解决了firstarc域不能存储新建空间地址的错误。
3.
指针未初始化导致访问出错
3.1.指针为空,程序调试中断
3.2.查看p指针值
上图显示p指针为空,但while未退出,访问p->adjvex出错在++indegree[p->adjvex];处中断。
3.3.分析错误原因及解决办法
单步执行发现
while(p)
{
++indegree[p->adjvex];
p=p->nextarc;
}//while
在第一次进入while循环时可以正常访问,而再执行p=p->nextarc;后本应该退出while循环程序却没有退出反而出现访问错误。
原始代码为:
voidFindInDegree(ALGraphG,int*indegree)
{
ArcNode*p;
for(inti=G.vexnum-1;i>=0;--i)
{
p=G.vertices[i].firstarc;
while(p)
{
++indegree[p->adjvex];
p=p->nextarc;
}//while
}//for
}
原本想使用while(p)判断p是否为空,为空则自动跳出循环,但很显然没有达到这个目的。
查阅相关资料得知,C++指针在使用时要初始化,我们知道指针是用来保存内存地址的变量,因此定义一个指针后一定要用它来保存一个内存地址,假如我们不那么做,那么该指针就是一个失控指针,它可以指向任何地址,并且对该地址的数值进行修改或者删除,带来非常可怕的后果。
在这里我并没有对指针进行初始化,导致在判断是否为空时出错。
因此解决这个错误的办法就是将所有指针在使用之前初始化。
将代码修改如下:
voidCreatGraph(ALGraph&G)
{
intpos,k=0;//pos实际位置k计数头结点的第几条弧
ArcNode*p,*q;//出错原为ArcNode*p;
cout<<"请输入顶点数目:
";cin>>G.vexnum;
cout<";cin>>G.arcnum;
cout<";cin>>G.kind;
for(inti=0;i<=G.vexnum-1;i++)
{
G.vertices[i].firstarc=NULL;//初始化为空,以便利于后续判断
k=0;//每一个顶点的弧数初始化为0
//p=G.vertices[i].firstarc;
cout<";
cout<";
cin>>G.vertices[i].data;
pos=0;//pos赋值使得可以进入while
while(pos>=0&&pos{
cout<";
cin>>pos;//输入实际位置
if(pos>=0&&pos{
k++;//对弧数计数
p=newArcNode;
p->nextarc=NULL;//初始化为空,以便利于后续判断
p->adjvex=pos;
if(k==1)
G.vertices[i].firstarc=p;//k==1表示头结点的第一条弧
if(k>1)
q->nextarc=p;//将后续弧连接到上一条弧的nexarc指针域
q=p;//存储弧
}//if
}//while
}//for
}//CreatGraph
添加了语句G.vertices[i].firstarc=NULL;和p->nextarc=NULL;来初始化指针。
将while(p)改为while(p!
=NULL)是否为空
voidFindInDegree(ALGraphG,int*indegree)
{
ArcNode*p;
for(inti=G.vexnum-1;i>=0;--i)
{
p=G.vertices[i].firstarc;
while(p!
=NULL)//判断是否为空
{
indegree[p->adjvex]++;
p=p->nextarc;
}//while
}//for
}
再次运行测试程序正常,没有出错。
四、
经验和体会(包括对算法改进的设想)
1.课程设计体会
通过本次数据结构的课程设计,对数据结构这么课程的内涵有了一个更加深入的理解。
我的收获如下:
a)巩固和加深了对数据结构的理解,提高综合运用本课程所学知识的能力。
b)培养了我选用参考书,查阅手册及文献资料的能力。
培养独立思考,深入研究,分析问题、解决问题的能力。
c)通过实际编译系统的分析设计、编程调试,掌握应用软件的分析方法和工程设计方法。
d)通过课程设计,逐步建立正确的算法设计、程序调试和全局观念。
根据我在实习中遇到得问题,我将在以后的学习过程中注意以下几点:
a)认真上好专业实验课,多在实践中锻炼自己。
b)写程序的过程中要考虑周到,严密。
c)在做设计的时候要有信心,有耐心,切勿浮躁。
d)认真的学习课本知识,掌握课本中的知识点,并在此基础上学会灵活运用。
e)在课余时间里多写程序,熟练掌握在调试程序的过程中所遇到的常见错误,以便能节省调试程序的时间。
此外,这次课程设计对C++指针的理解又有了一个深入。
C++指针在使用时要初始化,我们知道指针是用来保存内存地址的变量,因此定义一个指针后一定要用它来保存一个内存地址,假如我们不那么做,那么该指针就是一个失控指针,它可以指向任何地址,并且对该地址的数值进行修改或者删除,带来非常可怕的后果。
在程序调试过程中对算法和C++语言的理解为我积累了宝贵的经验。
2.算法改进的设想及实现
此次课程设计的程序仅仅只输出了一个拓扑排序,而没有将有向图的所有可能拓扑排序输出,因此应该对排序算法进行改进,使得可以输出有向图的所有拓扑排序序列。
改进算法如下:
给出一个非连通的有向图,要求输出所有拓扑排序的序列,可以有多种方法,这里提供一种方法,采用一个队列记录所有输出过的结果,其实就是回溯法,大概思路如下:
voidtopsort(Graph&G,inti)//正在对图G中第i个结点进行排序
{
if(G为空)
输出队列中的排序结果
else
{
if(G不存在入度为0的节点)
{
图G存在回路,无法拓扑排序
}
else
{
for(G中每个入度为0的节点v)
{
先将v插入拓扑排序的队列中的第i个位置上
然后将v从图G中删除,但是把v和它的边保存下来,因为后面回溯需要这些数据
topsort(G,i+1);
将刚才删除的v及其所有的边恢复
}
}
}
}
改进的算法参考了网络资源,参考了网络上给出的基本思想,自己具体将算法实现。
为了实现此算法,将图的存储结构进行了一些改动:
------------------------------------------改进算法后图的数据结构----------------------------
typedefstructArcNode{
intadjvex;
structArcNode*nextarc;
InfoType*info;
}ArcNode;
typedefstructVNode{
VertexTypedata;
ArcNode*firstarc;
boolisEx;//标识点是否删除
intindegree;//点的入度
}VNode,AdjList[MAX_VERTEX_NUM];
typedefstruct{
AdjListvertices;
intvexnum,arcnum;
intkind;
}ALGraph;
为了适应改进算法的需要,节点信息增加了boolisEx;和intindegree;分别用来标识节点是否删除,存储节点的入度。
//------------------------改进后的拓扑排序算法------------------
queuecharQueue;
stackintStack;
intii=0;
voidTopologicalSort(ALGraph&G)
{
ii++;
#ifdefDEBUG