拓扑排序和关键路径是基于无环的有向图。

主要用来表示工程进度中各个事件之间的关系。

 拓扑排序和关键路径 使用邻接表存储数据,最小生成树和最短路径用 邻接矩阵 存储数据。

1、拓扑排序

AOV网:在一个表示工程的有向图中,用固定点表示活动,用弧表示活动之间的优先级关系,这样的有向图为顶点表示活动的网,我们称之为AOV网(Activity On Vertex Network)

拓扑排序:是将一个AOV网的各个顶点按一定顺序排列,要求满足若存在,则排序中的顶点Vi必在顶点Vj之前。对于同一幅图,拓扑排序可有多个拓扑排序。

在显示生活中,用到的拓扑排序的例子:

        学校课程布置图,要先修完一些基础课,才可以继续修专业课,以计算机软件专业为例,在《程序设计基础》和《离散数学》课程学完之前就不能开始学习《数据结构》,这些先决条件定义了课程之间的优先(领先)关系。

算法:

(1)从有向图中选择一个无前驱(入度为0)的顶点输出。

(2)删除此顶点,并删除已此顶点为为尾的弧。

(3)重复(1),(2)步骤,知道输出全部顶点或者AOV网中不存在入度为0的顶点。

具体实现代码:

    用栈来保存入度为0的顶点,和更新后入度为0的顶点

#include 
#define MAXVEX 100
typedef struct EdgeNode   /*边表节点*/
{
int adjvex;       /*邻接点域,存储该顶点对应的下表*/
int weight;       /*用于存储权值,对于非网图可以不需要*/
struct EdgeNode *next;  /*链域,指向下一个邻节点*/
}EdgeNode;
typedef struct VertexNode   /*顶点表节点*/
{
int in;          /*顶点入度*/
int data;       /*顶点域,存储顶点信心*/
EdgeNode *firstedge; /*边表头指针*/
}VertexNode, Adjlist[MAXVEX];
typedef struct
{
Adjlist adjList;
int numVertexes;
int numEdges;  /*图中当前顶点数和边数*/
}graphAdjList, *GraphAdjList;
/*拓扑排序,若GL无回路,则输出拓扑排序序列并返回OK,若有回路返回ERROR*/
int TopoLogicalSort(GraphAdjList GL)
{
EdgeNode *e;
int i,k,gettop;
int top = 0;   /*用于栈指针下表*/
int count = 0; /*用于统计输出顶点的个数*/
int *stack;  /*建栈用于存储入度为0的顶点*/
stack = (int*)malloc(GL->numVertexes *sizeof(int));  //动态分配内存,大小为n个顶点(最多有n个顶点入度为0,因为图总共有n个顶点)
for(i = 0; inumVertexes; i++)
if(GL->adjList[i].in == 0)  /*将入度为0的顶点入栈*/
stack[++top] = i;
while(top != 0)
{
gettop = stack[top--];  /*出栈*/
printf("%d -> ",GL->adjList[gettop].data); /*打印此顶点*/
count++; /*统计输出顶点数*/
for(e=GL->adjList[gettop].firstedge; e!; e=e->next)
{ /*对此顶点弧表遍历*/
k = e->adjvex;
if(--GL->adjList[k].in == 0)  //将k号顶点邻接点的入度减1,且减1后,入度为0的顶点需要存到栈中
stack[++top] = k;
}
}
if(count < GL->numVertexes)  /*如果count小于顶点数,说明存在环*/
return -1;
else
return 0;
}

 

分析整个算法,对一个具有n个顶点e条弧的AOV网来说,将入度为0的顶点入栈的时间复杂度为O(n),而之后的while循环中,每个顶点入一次栈,出一次栈,入度减1的操作执行了e次,所以整个算法时间复杂度为O(n+e)。

2、关键路径

拓扑排序是解决一个工程能否顺序进行的问题,但有时还需解决工程完成需要的最短时间。

AOE网:在一个表示工程带权的有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种用有向图的边表示活动的网,我们称之为AOE网(Activity On Edge Network)

用ve(i)表示事件(即顶点)i的最早开始时间,用vl(i)表示事件i的最开始时间,如果活动k由弧表示,用dut()表示活动的持续时间,则有:

e(k) = ve(m)

l(k) = vl(n) - dut()

求解关键路径的具体算法(假设图中有n个顶点)

(1) 从开始顶点V0出发,假设ve(0)=0,然后按照拓扑有序求出其他各顶点i的最早开始时间ve(i),如果得到拓扑序列中顶点数目小于图中的顶点数,则表示图中存在回路,算法结束,否则继续执行。

(2)从结束顶点Vn出发,假设vl(n-1) = ve(n-1);然后求出各顶点i的最晚发生时间。

(3)根据顶点的最早发生时间,和最晚发生时间,依次求出出每条弧的最早开始时间和最晚开始时间,如果两只相等,则为关键活动。关键活动组成的路径则为关键路径。

具体实现代码:

 

#include 
#define MAXVEX 100
/*首先声明几个全局变量*/
int *etv,*ltv;  /*时间的最早发生时间和最迟发生时间*/
int *stack2;  /*用于存储拓扑序列的栈*/
int top2;    /*用于stack2的指针*/
typedef struct EdgeNode   /*边表节点*/
{
int adjvex;       /*邻接点域,存储该顶点对应的下表*/
int weight;       /*用于存储权值,对于非网图可以不需要*/
struct EdgeNode *next;  /*链域,指向下一个邻节点*/
}EdgeNode;
typedef struct VertexNode   /*顶点表节点*/
{
int in;          /*顶点入度*/
int data;       /*顶点域,存储顶点信心*/
EdgeNode *firstedge; /*边表头指针*/
}VertexNode, Adjlist[MAXVEX];
typedef struct
{
Adjlist adjList;
int numVertexes;
int numEdges;  /*图中当前顶点数和边数*/
}graphAdjList, *GraphAdjList;
/*拓扑排序,若GL无回路,则输出拓扑排序序列并返回OK,若有回路返回ERROR*/
int TopoLogicalSort(GraphAdjList GL)
{
EdgeNode *e;
int i,k,gettop;
int top = 0;   /*用于栈指针下表*/
int count = 0; /*用于统计输出顶点的个数*/
int *stack;  /*建栈用于存储入度为0的顶点*/
stack = (int*)malloc(GL->numVertexes *sizeof(int));  //动态分配内存,大小为n个顶点(最多有n个顶点入度为0,因为图总共有n个顶点)
for(i = 0; inumVertexes; i++)
if(GL->adjList[i].in == 0)  /*将入度为0的顶点入栈*/
stack[++top] = i;
top2 = 0;
etv = (int*) malloc(GL->numVertexes*sizeof(int));  /*事件的最早发生时间*/
for(i=0;inumVertexes; i++)
etv[i] = 0;
stack2 = (int*) malloc(GL->numVertexes*sizeof(int)); /*初始化*/
while(top != 0)
{
gettop = stack[top--];  /*出栈*/
printf("%d -> ",GL->adjList[gettop].data); /*打印此顶点*/
stack2[++top2] = gettop;  /*将弹出的顶点序列号压入拓扑序列的栈中*/
count++; /*统计输出顶点数*/
for(e=GL->adjList[gettop].firstedge; e; e=e->next)
{ /*对此顶点弧表遍历*/
k = e->adjvex;
if(--GL->adjList[k].in == 0)  //将k号顶点邻接点的入度减1,且减1后,入度为0的顶点需要存到栈中
stack[++top] = k;
if((etv[gettop]+e->weight)>etv[k]) /*求各顶点时间最早发生时间*/
etv[k] = etv[gettop] + e->weight;  /*某个顶点的最早发生时间= 和他相关的活动必须全部完成*/
}
}
if(count < GL->numVertexes)  /*如果count小于顶点数,说明存在环*/
return -1;
else
return 0;
}
/*求关键路径,GL为有向网,输出GL的各项关键活动*/
void CriticalPath(GraphAdjList GL)
{
EdgeNode *e;
int i,gettop,k,j;
int ete,lte;    /*声明活动最早发生时间和最迟发生时间*/
TopoLogicalSort(GL);  /*求拓扑序列,计算数组etv和stack2的值*/
ltv = (int*) malloc(GL->numVertexes*sizeof(int)); /*时间的最晚发生时间*/
for(i= 0; inumVertexes;i++)
ltv[i]=etv[GL->numVertexes-1];  /*初始化ltv[i] 为工程完成的最早时间,etv[i]初始化为0*/
while(top2!=0)  /*计算ltv*/
{
gettop = stack2[top2--];
for(e=GL->adjList[gettop].firstedge;e!=NUll;e=e->next)
{/*求各定点事件的最迟发生时间ltv值*/
k=e->adjvex;
if(ltv[k]-e->weightweight;   /*求最晚发生时间,是从拓扑序列的最后一个顶点逆着推导*/
}
}
for(j=0;jnumVertexes;j++)  /*求关键活动*/
{
for(e=GL->adjList[j].firstedge;e!=NULL;e=e->next)
{
k=e->adjvex;
ete = etv[j];   /*活动最早开始时间*/
lte = ltv[k] - e->weight;/*活动最晚发生时间*/
if(ete ==lte)
printf(" length: %d, ",GL->adjList[j].data,GL->adjList[k].data,e->weight);
}
}
}