结束
j=j+1
输入节点数i,边数n,j=0
输入确定弧的两点
N
Y
图2.2建立有向无环图
2.如图2.3所示,判断有向图是否为有向无环图。
按照输入的有向图建立一个邻接表,将图储存在邻接表中,并将邻接表打印,然后对该邻接表进行拓扑排序,依次取入度为零的节点,入栈,并删除该节点和该节点有关的所边,若入栈节点个数与节点数相同,则全部入栈,该图为无环图,可以进行拓扑排序,若该图节点数大于入栈节点数,则该图为有环图。
开始
建立邻接表并打印
取入度为零的节点入栈,删除该节点,继续遍历其他节点
入栈节点数等于节点总数
该图为无环图
该图为有环图
结束
N
Y
图2.3判断是否为无环图
3.此时该图为有向无环图,可进行拓扑排序,在上一过程中,所有节点已经入栈,将栈顶弹出进入另一个空栈,,然后依次输出栈顶,拓扑排序成功。
如图2.4所示。
开始
将栈顶依次输出
拓扑排序成功
结束
将弹出的栈顶进入新的空栈
输出栈顶元素
图2.4输出拓扑排序结果
4.具体内容
-
开始
符合条件?
根据输入信息建立邻接表
建栈
寻找入度为零的节点入栈
弹出栈顶,打印,将与栈顶元素邻接的节点入度减一
栈是否为空
结束
输入节点及弧的信息
-图2.5拓扑排序
3数据结构分析
3.1存储结构
一个无环的有向图叫做有向无环图,简称DAG图。
本算法首先要建立一个有向无环图,即通过输入各边的数据,搭建图的结构。
对于图的存储,用到邻接表,是一种链式存储结构。
弧结点结构类型:
typedefstructArcNode
{
intadjvex;/*该弧指向的顶点的位置*/
structArcNode*nextarc;/*指向下一条弧的指针*/
}ArcNode;
邻接表头结点类型:
typedefstructVNode
{
intdata;/*顶点信息*/
ArcNode*firstarc;/*指向第一条依附于该点的弧的指针*/
}VNode,AdjList[MAX_VEXTEX_NUM];
3.2算法描述
1.邻接表的构建
创建一个邻接表,首先要输入节点数和边数,然后输入确定一条边的两个节点,通过输入顺序来确定边的方向,构成有向图,具体方法如下:
初始化头结点
for(i=1;i<=G->vexnum;i++)
{
G->vertices[i].data=i;/*编写顶点的位置序号*/
G->vertices[i].firstarc=NULL;
}
先将头结点清零,编写顶点位置序号。
输入确定弧的两点,如果输入数字大于节点数或者小于零,则输出错误信息,并重新输入一组节点,申请新的节点来储存新的节点信息,该弧指向位置编号为m的节点,下一条弧指向的是依附于n的第一条弧,最后打印生成的邻接表。
for(i=1;i<=G->arcnum;i++)
{
printf("\n输入确定弧的两个顶点u,v:
");
scanf("%d%d",&n,&m);
while(n<0||n>G->vexnum||m<0||m>G->vexnum)
{
printf("输入的顶点序号不正确请重新输入:
");
scanf("%d%d",&n,&m);
}
p=(ArcNode*)malloc(sizeof(ArcNode));/*开辟新的弧结点*/
if(p==NULL)
{
printf("ERROR!
");
exit
(1);
}
p->adjvex=m;/*该弧指向位置编号为m的结点*/
p->nextarc=G->vertices[n].firstarc;
G->vertices[n].firstarc=p;
}
printf("\n建立的邻接表为:
\n");/*打印生成的邻接表(以一定的格式)*/
for(i=1;i<=G->vexnum;i++)
{
printf("%d",G->vertices[i].data);
for(p=G->vertices[i].firstarc;p;p=p->nextarc)
printf("-->%d",p->adjvex);
printf("\n");}
邻接表构建完成。
2.入栈操作
在本算法中栈主要用来构造拓扑排序序列。
由于栈具有先入后出的特点,所以,将每次选择入度为零的结点入栈,这样当结点都入栈的时候,再依次出栈,进入另一个新栈,这样,排序序列显而易见。
它将图这样的非线性结构转化为栈这样的线性结构。
建立空栈
typedefstruct
{
int*base;/*栈底指针*/
int*top;/*栈顶指针*/
intstacksize;
}SqStack;
初始化栈
voidInitStack(SqStack*S)
{
S->base=(int*)malloc(STACK_INIT_SIZE*sizeof(int));
if(!
S->base)/*存储分配失败*/
{
printf("ERROR!
");
exit
(1);
}
S->top=S->base;
S->stacksize=STACK_INIT_SIZE;
}
入栈操作,压入新的元素为栈顶,e为新的栈顶元素。
voidPush(SqStack*S,inte)/*压入新的元素为栈顶*/
{
if(S->top-S->base>=S->stacksize)
{
S->base=(int*)realloc(S->base,(S->stacksize+STACKINCREMENT)*sizeof(int));
if(!
S->base)
{
printf("ERROR!
");
exit
(1);
}
S->top=S->base+S->stacksize;
S->stacksize+=STACKINCREMENT;
}
*S->top++=e;
}
3.拓扑排序
本程序的拓扑排序,必须在图的邻接表已知的情况下。
它还有另外一个功能:
判断一个图是不是无环图。
确切的说,不能单纯的叫拓扑排序,但考虑它主要的作用,在不引起误解的情况下就叫拓扑排序算法。
判断一个图是否为有向无环图并进行拓扑排序,对于本题目来说,由于本题要求是对有向无环图进行拓扑排序,其主要方法是将入度为零的结点依次输出出来,知道图的所有定点全部输出为止。
那么若图为有环图,在环上的结点在其他结点都选择出来后,入度都不为零,即无法被输出出来。
那么就可以认为按照拓扑排序的方法输出结点后,若不是将节点全部输出出来的,则此图为有环图。
输出有向图有环,拓扑排序失败。
若无环,则进行拓扑排序。
通过入栈出栈的操作来完成拓扑排序。
主要算法如下:
voidTopoSort(ALGraphG)
{intindegree[M];
inti,k,n;
intcount=0;/*初始化输出计数器*/
ArcNode*p;
SqStackS;
FindInDegree(G,indegree);
InitStack(&S);
printf("\n");
for(i=1;i<=G.vexnum;i++)/*入度为0的入栈*/
{if(!
indegree[i])
Push(&S,i);
}
printf("\n拓扑排序序列为:
");
while(!
StackEmpty(&S))/*栈不为空*/
{
Pop(&S,&n);/*弹出栈顶*/
printf("%4d",G.vertices[n].data);/*输出栈顶并计数*/
count++;
for(p=G.vertices[n].firstarc;p!
=NULL;p=p->nextarc)
{
k=p->adjvex;
if(!
(--indegree[k]))/*若入度减为零,则再入栈*/
{
Push(&S,k);
}
}
}
if(countelse{printf("排序成功!
");}
}
4调试与分析
4.1调试过程
在调试程序是主要遇到一下几类问题:
1.数组的数据容易出现混乱,比如节点用数字标识,数组结点的位置是从0开始,而标识符往往从1开始,这在程序的开始就应该注意到;
2.各函数的形参和实参的区别,全局变量,局部变量的区别,特别是在做大型程