- 邻接表:
- 顶点下标查找函数(LocateVex)
- 创建有向图的邻接表(CreateDG)
- 邻接表打印函数(print)
- 拓扑排序(TopologicalSort)
AOV网与AOE网:
-
AOV网(Activity On Vertex NetWork):用顶点表示活动,边表示活动发生的 先后关系。
-
AOE网(Activity On Edge NetWork):在带权有向图中若以顶点表示事件,有向边表示活动,边上的权值表示该活动持续的时间,AOE网通常用于 估算事件/工程完成的工期(时间)。
-
关键路径:是从开始点到完成点的 最长路径的长度。路径的长度是边上活动耗费的时间。
-
AOV网络与AOE网络的关系
从定义上来看,很容易看出两种网的不同,AOV网的活动以顶点表示,而AOE网的活动以有向边来表示,AOV网的有向边仅仅表示活动的先后次序。纵观这两种网图,其实它们总体网络结构是一样的,仅仅是活动所表示的方式不同,因此可以猜想从AOV网转换成AOE网应该是可行的。
通常AOE网都是和关键路径联系在一起的,在AOE网中我们可以通过关键路径法来计算影响整个工期的关键路径,达到缩短工期的目的。在传统的AOV网中是没有表示活动时间的权值的,因此传统的AOV网无法估算工期,但是如果我们在AOV网中的活动结点上都标上时间属性,那么AOV网就可以完全转换为AOE网。
什么是拓扑排序?
- 拓扑序列:在AOV网(无环图)中,由顶点Vi到顶点Vj有一条路径,则在该线性序列中的顶点Vi必定在顶点Vj之前。
- 拓扑排序:将AOV网中的顶点序列排成拓扑序列就叫拓扑排序。
- 拓扑排序条件:必须是有向无环图(Directed Acycline Graph),AOV网满足有向无环图条件,若是有向成环图,则会进入死循环。
拓扑排序算法思路
- 拓扑排序的执行过程相当于每次 删去入度为0的顶点和这个顶点发射出去的边,那么我们每次删去一个顶点和其发射边,就会生成一个新图,在这个新图上继续执行删去入度为0的顶点和这个顶点发射出去的边,直到所有顶点都被删完。删除每个顶点的顺序,就是拓扑序列。
拓扑排序如何实现?
- 对一个顶点而言:拓扑排序主要是对入度为0的顶点做处理,处理的内容包括:1.顶点本身;2.顶点发出的边,去掉顶点的同时要删去这些有向边,也就是要更新当前顶点的邻接顶点的入度。
- 对所有顶点而言:重复执行上述操作,直到把所有入度为0的顶点处理完毕,即拓扑排序完成。
- 我们首先找到入度为0的顶点,然后对其进行输出,接着根据邻接表的邻接关系,找到与其邻接的其他顶点,再去对其他顶点进行相同的处理,由此往复。
- 我们需要一个 临时存取空间space,我们每次把入度为0的顶点放入space中,然后按顺序(顺序可以从头开始、从尾开始、甚至可以任意取)从space中取出,然后进行步骤1的处理,再将更新后入度为0的顶点放入space中,直到space中的元素被取空,即拓扑排序结束。
- 临时存取空间space的存取顺序可以是任意的,所以,这个space可以是栈结构,队列结构,也可以是一个数组,甚至可以是其他(源代码用数组模拟栈结构)。由此可见,存取顺序的不同,直接导致了拓扑排序结果不唯一。
- 总结一下拓扑排序在程序中的执行流程:首先我们搞了一个临时存储空间space,然后将入度为0的顶点放入space,再按顺序取出,每去取出一次,就根据邻接表的邻接关系,查找当前取出的顶点的邻接点,对每个邻接点入度-1,更新完入度后,看看有没有出现新的入度为0的结点,将其放入space中,直到space为空时,即所有顶点处理完毕,输出的序列就是拓扑序列。
可能产生的疑问
- 问题1:什么原因会导致拓扑序列产生不同的结果?
- 临时存取空间space的结构、存取方式会直接影响对拓扑排序结果
- (我给忘了…)
- 问题2:一个顶点会不会被重复放入临时存取空间space中,会不会对一个结点重复访问,是否需要像DFS/BFS那样设置visited数组记录结点访问状态?
- 在拓扑排序算法中,没有顶点会被多次访问,也不需要visited数组对访问状态记录,每个顶点只访问一次。原因是:拓扑排序的前提是在 有向无环图 中不会存在顶点之间有多分支回路的情况,如果注入水流,水流方向是只能向前的,无法回头,因为不存在环。
- 问题3:待补充
完整源代码:
#include <stdio.h>
#include <stdlib.h>
#define VertexType char //顶点的数据类型(char)
#define VertexMax 20 //最大顶点个数
typedef struct ArcNode//边表
{
int adjvex;//存储的是该顶点在顶点数组即AdjList[]中的位置
struct ArcNode *next;
}ArcNode;
typedef struct VNode //顶单个点
{
VertexType vertex;
struct ArcNode *firstarc;
}VNode;
typedef struct //顶点表
{
VNode AdjList[VertexMax];//由顶点构成的结构体数组
int vexnum,arcnum; //顶点数和边数
}ALGraph;
int LocateVex(ALGraph *G,VertexType v)
{
int i;
for(i=0;i<G->vexnum;i++)
{
if(v==G->AdjList[i].vertex)
{
return i;
}
}
printf("No Such Vertex!\n");
return -1;
}
//有向图
void CreateDG(ALGraph *G)
{
int i,j;
//1.输入顶点数和边数
printf("输入顶点个数和边数:\n");
printf("顶点数 n=");
scanf("%d",&G->vexnum);
printf("边 数 e=");
scanf("%d",&G->arcnum);
printf("\n");
printf("\n");
//2.顶点表数据域填值初始化顶点表指针域
printf("输入顶点元素(用空格隔开):");
for(i=0;i<G->vexnum;i++)
{
scanf(" %c",&G->AdjList[i].vertex);
G->AdjList[i].firstarc=NULL;
}
printf("\n");
//3.输入边信息构造邻接表
int n,m;
VertexType v1,v2;
ArcNode *p1,*p2;
printf("请输入边的信息:\n\n");
for(i=0;i<G->arcnum;i++)
{ //输入边信息,并确定v1和v2在G中的位置,即顶点在AdjList[]数组中的位置(下标)
printf("输入第%d条边信息:",i+1);
scanf(" %c%c",&v1,&v2);
n=LocateVex(G,v1);
m=LocateVex(G,v2);
if(n==-1||m==-1)
{
printf("NO This Vertex!\n");
return;
}
p1=(ArcNode *)malloc(sizeof(ArcNode));
p1->adjvex=m;//填上坐标
p1->next=G->AdjList[n].firstarc;//改链(头插法)
G->AdjList[n].firstarc=p1;
}//for
}
void print(ALGraph G)
{
int i;
ArcNode *p;
printf("\n-------------------------------");
printf("\n图的邻接表表示:\n");
for(i=0;i<G.vexnum;i++)
{
printf("\n AdjList[%d]%4c",i,G.AdjList[i].vertex);
p=G.AdjList[i].firstarc;
while(p!=NULL)
{
printf("-->%d",p->adjvex);
p=p->next;
}
}
printf("\n");
}
void TopologicalSort(ALGraph *G)
{//文中说的space就是此处的栈结构
//此处的栈结构我用的是数组来模拟的(因为我懒)
int i;
int top=-1;//栈顶指针
int Gettop;//用于存储/获取栈的栈顶元素
int count=0;//用于统计拓扑排序生成的结点数(若生成结点数 < 图的结点数,则代表图中有环,拓扑排序不成功)
int stack[VertexMax]={0};//栈
int indegree[VertexMax]={0};//入度数组
struct ArcNode *p;//临时变量
//1.计算顶点入度,并存入indegree数组中
for(i=0;i<G->vexnum;i++)
{
if(G->AdjList[i].firstarc!=NULL)
{
p=G->AdjList[i].firstarc;
while(p!=NULL)
{
indegree[p->adjvex]++;
p=p->next;
}
}
}
//2.初始化部分:将初始入度为0的顶点入栈
for(i=0;i<G->vexnum;i++)
{
if(indegree[i]==0)
{
stack[++top]=i;//先将指针加一在进行存储
}
}
//3.拓扑排序
while(top!=-1)//栈不为空
{
Gettop=stack[top--];//获取栈顶元素,并且栈顶指针减一
printf(" %c",G->AdjList[Gettop].vertex);//输出栈顶元素
count++;
p=G->AdjList[Gettop].firstarc;
while(p!=NULL)
{
indegree[p->adjvex]--;
if(indegree[p->adjvex]==0)
{
stack[++top]=p->adjvex;
}
p=p->next;
}
}
//4.判断拓扑排序是否成功(生成结点数 < 图的结点数,则代表图中有环,拓扑排序不成功)
if(count<G->vexnum)
printf("TopologicalSort Failed!\n");
else return;
}
int main()
{
ALGraph G;
CreateDG(&G);
print(G);
printf("\n拓扑排序结果:");
TopologicalSort(&G);
return 0;
}
执行结果
参考:
- AOV网与AOE网部分内容参考自:AOV网络与AOE网络——lx青萍之末
- 参考教材:严蔚敏数据结构(C语言版||第二版)