原文链接
前言
这次我准备做关于AOV网与拓扑排序的介绍。不光给读者看,也是对自己的一种“费曼学习法”(说来惭愧,自己比较笨,这块学的不好)。
什么是AOV网?
可能不少同学看到这个名字就被吓着了, AOV网的英文名叫“activity on vertex”,中文意思是指“顶点活动”网。AOV网本身是一张【有向图】,但它有具体的意义,处理与现实活动有关的问题。AOV网的顶点即表示具体【活动】,边表示活动之间的【先后关系】。
什么是拓扑排序?
而拓扑排序基于之前所讲的AOV网,即将AOV网的各个顶点(活动)排成一个【线性】序列,而且必须保证一个要求:序列必须保持AOV网中【原有】的优先关系。那为了形成一个线性序列,没有优先关系的顶点被【人为】地赋予优先关系。
举例如下,像v1和v2必须保持着原来的前后,而v2和v3原来没有谁前谁后,但可以人为地安排v3在前、v2在后。
拓扑排序的性质
1. 拓扑排序有唯一性的条件为:有向图必须要有【全序】关系(假设存在不满足全序关系的两点,则对其互换顺序使拓扑排序不唯一)。
2. 若将图中顶点按拓扑次序排成一行,则图中所有的有向边均是从左指向右的(线性排列从左向右,先后顺序即边的方向也就是从左向右了)。
3. 若图中存在有向环,则不可能使顶点满足拓扑次序(例如:a可以在b的前面,也可以在b的后面,那就构不成遵循先后顺序的序列)。
拓扑排序的步骤
我们编写好了有向图的数据结构之后,如果想对其输出拓扑排序,可以用以下步骤进行操作:
1. 从有向图中选择一个没有前驱(即入读为0)的节点,并输出它。
2. 在图中删除刚才的节点,并抹掉从该节点出发的所有边。
3. 重复第1、2步,直到剩余的有向图中没有无前驱的节点为止。
例如:
拓扑排序的算法
首先我们用如下函数求每个点的入度:
void FindInDegree(AL_Graph G, int indegree[])
{
int i;
AL_AdjNode *p;
for(i=0;i<G.VexNum;i++)
indegree[i]=0;
for(i=0;i<G.VexNum;i++)
{
p=G.VexList[i].link;
while(p)
{
indegree[p->adjvex]++;
p=p->next;
}
}
}
其次我们进行拓扑排序:
int TopologicalSort(AL_Graph G, int v[])
{
int i,k,count=0;
int indegree[VERTEX_NUM]; //入度数组
SeqStack S; //顶点栈
AL_AdjNode *p; //邻接结点指针
FindInDegree(G,indegree); //求顶点求入度
initialize_SqStack(&S); //初始化栈
//入度为0的顶点进栈S
for(i=0;i<G.VexNum; ++i)
if(!indegree[i]) Push_SqStack(&S,i); //S栈非空
while(!StackEmpty_SqStack(&S))
{ Pop_SqStack(&S,&i);
v[count]=i; ++count; //记录度为0顶点编号
//扫描邻接链表,入度为0的顶点进栈
for(p=G.VexList[i].link; p; p=p->next)
{ k=p->adjvex;
if(!(--indegree[k]))
Push_SqStack(&S,k);
}
}
if(count<G.VexNum) return FALSE;
else return TRUE;
}
求每个点的入度的算法不用多说,我重点说一说拓扑排序算法的实现吧。
1. 算法中,找出图中【所有】入读为0的节点,以遍历顺序入栈。
2. 循环判断栈是否为空,若非空,则取出栈顶节点并输出。
3. 遍历此节点的边,删除边,同时检查边指向的节点是否入读为0,若为0,则将此节点入栈。
注:这样就保证了对任意一个节点,如果它要被输出,那么它要么没有前驱节点,要么前驱节点已经全被输出,即拓扑排序的先后关系成立。
4. 倒数第二行的“if(count<G.VexNum)”,就是检查输出的节点是否小于总节点数。
注:换句话说,就是判断是否存在【有向环】:如果有有向环,则有向环中的节点会永远地至少地保持着环结构的一条出边和一条入边,即一开始不会被选中为【入读为0的节点】,而后随着外面被输出的节点去除出边,也不会使环中的节点的入读变为0(要是为0,那么它环中前驱节点必须被输出过,但环中的前驱节点以此归纳,也不会被输出)。所以直到栈空,即入读为0或之后变为0的节点依次输出,而环中的节点【岿然不动】,这就导致了输出的节点数小于总数。