数据结构篇之topo排序
在离散数学角度看待Topo排序,即由集合U上的一个偏序(偏序指集合中仅有部分成员之间可比较)定义得到一个全序(全序指集合中全体成员之间均可比较)的操作就叫Topo排序。
Topo排序的基本思想是这样的:
在无环有向图中;
1. 找出入度为0的结点,记录在topo序列中,结点全部加入该序列后停止;
2. 删除该入度为0的结点以及以它为起始点的边,回到步骤1。
算法思想很容易理解,编程难度也不高。要进行topo排序操作,首先得先有个存储图的数据结构,由于在topo排序算法思想中提到删除结点时同时删除其邻接边,那么选择邻接表就再合适不过了,邻接表结构是点与邻边通过链表直接相连,访问邻边和邻接点很方便,删除邻边的操作只需要遍历该结点的邻接表并逐个删掉即可,选用邻接表作为存储结构也考虑到了在topo排序算法思想中暗藏的每次删除入度为0的结点及其邻边时,其邻接点的入度都会相应减1,那么邻接表结构也可方便我们对邻接点进行操作。
图的邻接表结构体如下:
const int MAXN = 20;
//邻接表结点结构体
typedef struct ArcNode{
int adjvex;//点的编号
struct ArcNode *nextarc;//邻边指针
}ArcNode;
//头结点结构体
typedef struct VNode{
char data[MAXN];//头结点数据信息
int indegree;//头结点的入度
ArcNode *firstarc;//头结点的第一条边(指针)
}VNode, AdjList[MAXN];
//图的邻接表结构体
typedef struct {
AdjList vertices;//头结点数组
int vexnum, arcnum;//结点个数,边个数
}ALGraph;
因为topo排序只能对无环有向图进行操作,但考虑到程序的健壮性,Topo排序程序应该面向所有有向图,包括无环和有环两种,当然,如果待排序的是有环有向图,那么该程序应该提供检测该图无法进行topo排序的功能,否则可以开始topo排序操作了。写一函数ALGraph_Create创建了一个有向图。
有向图创建完毕后,接下来就该topo排序了。
函数名为Topo_Sort,输入一个有向图,输出一个经过topo排序的序列。
我的程序算法过程:
1. 初始化各变量,其中包括topo序列VexSet[],删除标记数组Deleted[],已访问头结点计数器count;
2. 函数Find_Indegree_Zero遍历头结点集,若(count<G.vexnum)&&(点集中无法找到入度为0的结点)则返回-1表示该图为有环有向图,否则返回已找到的结点编号;
3. 检测该图,若为有环图则结束该过程,否则将该点的数据加入到序列VexSet中;
4. 函数Deleting遍历该点的邻接表,将其邻接点的入度都减1,标记该点为已删除,计数器count++;
5. 排序操作结束后输出点集VexSet。(此操作可以改为将输出topo序列替换把数据加入VexSet这一步骤)
程序如下:
#include <stdio.h>
#include <stddef.h>
#include <string.h>
const int MAXN = 20;
typedef struct ArcNode{
int adjvex;
struct ArcNode *nextarc;
}ArcNode;
typedef struct VNode{
char data[MAXN];
int indegree;
ArcNode *firstarc;
}VNode, AdjList[MAXN];
typedef struct {
AdjList vertices;
int vexnum, arcnum;
}ALGraph;
char VexSet[MAXN][MAXN];
int Deleted[MAXN];
void ALGraph_Create(ALGraph &G1);
void TopoSort(ALGraph G);
int Find_Indegree_Zero(ALGraph G);
void Deleting(ALGraph &G, int vex);
int main()
{
ALGraph G;
ALGraph_Create(G);
TopoSort(G);
getchar();
getchar();
return 0;
}
//创建一个有向图
void ALGraph_Create(ALGraph &G1)
{
int vex, nvex, i;
char ans='y';
ArcNode *arcn, *edge;
//输入头结点个数
printf("Enter Vextrix Number:");
scanf("%d", &G1.vexnum);
//给点添加数据信息如v1、v2、v3……
for(i=0; i<G1.vexnum; i++)
{
printf("Enter the %d's data:", i);
scanf("%s", G1.vertices[i].data);
G1.vertices[i].indegree=0;
G1.vertices[i].firstarc=NULL;
}
//输入边格式为:a b
while(ans=='y')
{
printf("Enter edge:");
scanf("%d%d", &vex, &nvex);
G1.vertices[nvex].indegree++;
arcn = new ArcNode;
arcn->adjvex=nvex;
arcn->nextarc=NULL;
edge = G1.vertices[vex].firstarc;
if(!edge)
G1.vertices[vex].firstarc=arcn;
else
{
while(edge->nextarc)
edge=edge->nextarc;
edge->nextarc=arcn;
}
printf("y(es)?");
getchar();
scanf("%c", &ans);
}
}
//拓扑排序
void TopoSort(ALGraph G)
{
int vex, count=0, first=1, i;
memset(Deleted, 0, sizeof(Deleted));
while(count < G.vexnum)
{
//找出入度为0的头结点
vex = Find_Indegree_Zero(G);
//检测是否为有环图
if(vex < 0)
{
printf("Error!A Circle Graph!/n");
return;
}
//将数据加入VexSet序列中
/*strcpy(VexSet[count], G.vertices[vex].data);*/
//直接输出点数据
if(first)
first = 0;
else
printf("->");
printf("%s", G.vertices[vex].data);
Deleted[vex] = 1;
Deleting(G, vex);
count++;
}
//一次输出topo序列所有点信息
/*first = 1;
i=0;
while(i < count)
{
if(first)
first = 0;
else
printf("->");
printf("%s", VexSet[i]);
i ++;
}*/
}
//返回图中未访问过而且入度为0的头结点编号
int Find_Indegree_Zero(ALGraph G)
{
int i;
for(i=0; i<G.vexnum; i++)
{
if(G.vertices[i].indegree==0 && !Deleted[i])
return i;
}
return -1;
}
//删除该点及将其邻接点的入度减1
void Deleting(ALGraph &G, int vex)
{
ArcNode *edge = G.vertices[vex].firstarc;
while(edge)
{
G.vertices[edge->adjvex].indegree --;
edge = edge->nextarc;
}
}
该程序时间复杂度为O(n^2),程序可以再优化吗?答案是肯定。除了第一次寻找入度0结点外其余每次寻找入度为0的结点时,都是由删除前一结点这一变动得到的。那么我们可以缩小寻找范围,只在删除点的邻接表结点中找寻入度为0的结点,但有一个问题就是如果在这些邻接点中没有符合条件的点时,那么找寻点就可能在原本入度为0的点集中。如果两个部分都无法找到入度为0的点,则说明该图为有环图,否则输出该入度为0的点。那么函数就应该分两步找寻入度为0的结点。第一步:遍历全部结点,找出所有原本入度为0的结点,存入某个结构中,然后从第一个找寻出的入度0结点开始,进行第二步;第二步:删除找出的该点及它的邻边,邻接点的入度都减1,进入第三步;第三步:再从剩余的点中找寻入度0点,循环直到找出所有点结束。
这里有个问题——那个存储找寻到点的结构应该选用什么结构来存储呢?
首先这个结构要具备方便存入和拿出的功能,那么这里就想到了栈和队列,如果用队列来存储该点集,那么每次拿出的都是队列头,但是对于从某个点开始进行第二步以后的操作时,得到的topo序列都是由该点变动后得到的连续的点集,当当前点被删除时,邻接点入度减1,该点的邻接点成为候选,如果邻接点有符合入度0的点,那么选出存入该队列,这时问题就来了!在输出时序列的点时点的顺序出差错了,所以不能选择队列;当选择栈时,先是找出原本入度为0的点,存入该栈中,然后栈顶元素出栈,输出该元素数据,由该栈顶元素所代表的点开始进行第二步及以后的操作,候选点集中找寻出符合条件的点后入栈,变为新的栈顶元素,然后在输出时,直接输出新出栈的栈顶元素,因为这个元素是前一栈顶元素变动所产生的,所以它们具有因果关系(在这里可以说有比较关系),所以符合topo排序的算法思想,选用栈作为存储找寻出的点集是合适的。
函数如下:
bool TopoSort(ALGraph G1)
{
int top=-1, count, i, k;
ArcNode *p;
//选出图中原本入度就为0的点
for(i=0; i<G1.vexnum; i++)
if(!G1.vertices[i].indegree)
s[++top]=i;
count=0;
//从栈顶的点开始找寻新的入度为0的点
while(top!=-1)
{
i = s[top--];
printf("number:%d Data:%s/n", i, G1.vertices[i].data);
count++;
//找寻邻接点
for(p=G1.vertices[i].firstarc; p; p=p->nextarc)
{
k=p->adjvex;
if(!(--G1.vertices[k].indegree))
s[++top]=k;
}
}
//判断是否为有环图
if(count<G1.vexnum)
return false;
return true;
}
两程序运行成功,但由于算法实现有所差别,所以找出的topo序列也不尽相同,但只要方法正确,结果也是正确的。
本文详细介绍了拓扑排序的概念,以及如何在无环有向图中进行拓扑排序,包括邻接表的使用和算法流程。程序通过邻接表结构实现,能够检测并处理有环图。此外,还探讨了算法优化方案,提出了使用栈来存储找寻到的入度为0的结点,以保证拓扑序列的正确性。
2594

被折叠的 条评论
为什么被折叠?



