AOV->拓扑序列->拓扑排序
AOV
在某个工程中,可以把顶点当做是一个活动事件,把顶点中的弧当做活动事件的优先顺序(优先级)。这就组成了一个有向网。在数据结构中我们通常叫为AOE网(Activity On Vertex Network)。
此处借用百度百科中一个列表,百度百科:拓扑排序
| 编号 | 课程名称 | 学前基础 |
|---|---|---|
| C1 | 高等数学 | 无 |
| C2 | 程序设计基础 | 无 |
| C3 | 离散数学 | C1,C2 |
| C4 | 数据结构 | C3,C5 |
| C5 | 算法语言 | C2 |
| C6 | 编译技术 | C4,C5 |
| C7 | 操作系统 | C4,C9 |
| C8 | 普通物理 | C1 |
| C9 | 计算机原理 | C8 |

拓扑序列&&拓扑排序
对一个有向无环图G进行拓扑排序,是将G进行线性化,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
构造拓扑序列的过程会有两种结果
- 如果此⽹中的全部顶点被输出,则说明它不存在环(回路)的AOV⽹;
- 如果输出的顶点数少了,哪怕仅少了⼀个,也说明这个⽹存在环(回路),不是AOV⽹
拓扑排序算法解析
示例
用下面的例子进行算法解析

图的存储方式
邻接表的方式进行存储图

思路
- 用栈来存放入度为0的顶点
- 顶点出栈时将顶点的关联顶点的入度-1
- 再讲入度为0的顶点加入到栈中
- 循环上述操作
代码
#include <stdio.h>
#include "stdlib.h"
#define MAXVEX 14
#define MAXEDGE 20
#define OK 1
#define ERROR 0
typedef int Status;
//图 邻接矩阵
typedef struct {
int vex[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGragp;
//图 邻接表 边结构
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, numEdges;
}GraphList, *GraphAdjList;
//创建图 邻接矩阵
void CreateMGraph(MGragp *g) {
g->numVertexes = MAXVEX;
g->numEdges = MAXEDGE;
for (int i = 0; i < g->numVertexes; i++) {
g->vex[i] = i;
for (int j = 0; j < g->numVertexes; j++) {
g->arc[i][j] = 0;
}
}
g->arc[0][4]=1;
g->arc[0][5]=1;
g->arc[0][11]=1;
g->arc[1][2]=1;
g->arc[1][4]=1;
g->arc[1][8]=1;
g->arc[2][5]=1;
g->arc[2][6]=1;
g->arc[2][9]=1;
g->arc[3][2]=1;
g->arc[3][13]=1;
g->arc[4][7]=1;
g->arc[5][8]=1;
g->arc[5][12]=1;
g->arc[6][5]=1;
g->arc[8][7]=1;
g->arc[9][10]=1;
g->arc[9][11]=1;
g->arc[10][13]=1;
g->arc[12][9]=1;
}
//转换成邻接表
void CreateGraphList(MGragp g, GraphAdjList *gl) {
*gl = (GraphAdjList)malloc(sizeof(GraphList));
(*gl)->numVertexes = g.numVertexes;
(*gl)->numEdges = g.numEdges;
for (int i = 0; i < g.numVertexes; i++) {
(*gl)->adjList[i].in = 0;
(*gl)->adjList[i].data = g.vex[i];
(*gl)->adjList[i].firstEdge = NULL;
}
for (int i = 0; i < g.numVertexes; i++) {
for (int j = 0; j < g.numVertexes; j++) {
if (g.arc[i][j] == 1) {
EdgeNode *edge = (EdgeNode*)malloc(sizeof(EdgeNode));
edge->adjvex = j;
edge->next = (*gl)->adjList[i].firstEdge;
(*gl)->adjList[i].firstEdge = edge;
(*gl)->adjList[j].in ++;
}
}
}
}
//拓扑排序
Status TopologicalSort(GraphAdjList gl) {
//栈 存放顶点下标
int *stack = (int*)malloc(sizeof(int));
int top = 0;
//记录顶点个数
int count = 0;
//将入度为0的顶点入栈
for (int i = 0; i < gl->numVertexes; i++) {
if (gl->adjList[i].in == 0) {
stack[++top] = i;
}
}
printf("top = %d\n",top);
//开始循环 条件栈不为空
while (top != 0) {
//出栈 && 打印
int getTop = stack[top--];
printf("%d -> ", gl->adjList[getTop].data);
//记录
count ++;
//循环处理与当前顶点有联系的其他顶点的入度-1
for (EdgeNode *edge = gl->adjList[getTop].firstEdge; edge; edge = edge->next) {
//当入度为0 入栈
if (--gl->adjList[edge->adjvex].in == 0) {
stack[++top] = edge->adjvex;
}
}
}
printf("\n");
//记录的顶点数量与总数量相等,证明没有回路
if (count == gl->numVertexes) {
return OK;
} else {
//有回路 报错
return ERROR;
}
}
运行
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, 拓扑排序!\n");
MGragp g;
GraphAdjList gl;
CreateMGraph(&g);
CreateGraphList(g, &gl);
int result = TopologicalSort(gl);
printf("result: %d\n", result);
return 0;
}

AOE->关键路径
首先图的关键路径,需要在拓扑排序的基础上进行,所以对拓扑排序理解的不透彻的,先把上面的知识点多巩固一段时间,再继续阅读下面的内容。
AOE
在⼀个表示⼯程的带权有向图中,⽤顶点表示事件,⽤有向边表示活动,⽤边上的权值表示活动的持续时间,这种有向图的边表表示活动的⽹,我们称之为AOE ⽹(Activity On Edge Network)
- 没有入边的顶点称为始点或源点
- 没有出边的顶点称为终点或汇点
- AOE网只有一个源点和一个汇点
生活中的造车例子,权值单位是天:

等这些零部件造好后,几种起来需要0.5天,最后需要2天时间组装。
邻接表


其他相关名词解释
- 事件最早发⽣的时间etv(earliest time of vertex):即顶点Vk的最早发⽣时间;
- 事件最晚发⽣时间ltv(latest time of vertex):即顶点Vk的最晚发⽣时间,也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个⼯期;
- 活动的最早开⼯时间ete(earliest time of edge):即弧Ak的最早发⽣时间;
- 活动的最晚开⼯时间lte(latest time of edge):即弧Ak的最晚发⽣时间,也就 逻辑教育 是不推迟⼯期的最晚开⼯时间.
etv
etv:事件“最早”发生时间。用一个数组表示,元素是 当前顶点的开始时间,也就是当前结点“最晚”开始时间。这个地方理解起来会有点歧义。
- “最早”:是所有事件的总时间当中的,当前事件发生的最早时间。
- “最晚”:是当前事件(顶点),的所有前驱事件都满足的时间。代码中的实现是求Max值。

其实etv求得过程就是拓扑序列过程。另外etv数组下标不是事件(顶点)下标,所以还需要一个栈来表示顶点信息。 用栈的原因是为了求得ltv时逆序方便
初始化栈变量
int *etv;
int *stack2;
int top2;
拓扑排序部分代码
Status TopologicalSort(GraphAdjList GL) {
......
//初始化栈
top2 = 0;
stack2 = (int*)malloc(sizeof(int) *GL->numVertexes);
//初始化etv数组
etv = (int*)malloc(sizeof(GL->numVertexes * sizeof(int)));
for (int i = 0; i < GL->numVertexes; i++) {
etv[i] = 0;
}
printf("TopologicSort:\t");
while (top != 0) {
......
for (EdgeNode *e = GL->adjList[gettop].firstedge; e; e = e->next) {
......
//获取到开始事件的最大值
if ((etv[gettop] + e->weight) > etv[k]) {
etv[k] = etv[gettop] + e->weight;
}
}
}
......
}
结果:

ltv
ltv:就是事件(顶点)最晚发生的时间,如果超出这个时间,就会延期工程。和etv一样,用一个数组表示,数组元素是最晚发生的时间。初始值为整个工程的时间,也就是栈顶元素V9的时间27

更新逻辑
- 获取栈顶元素,找到该顶点的所有后继顶点
- 在所有的后继顶点的对应的ltv中的值减去后继顶点中权值,在这些商中取最小的值当做当前顶点的对应的ltv的值
例如:下标8的顶点


在举一个多个后继顶点的例子,例如下标4的顶点


最终结果:

求ltv代码:
//初始化
ltv = (int*)malloc(sizeof(int) * GL->numVertexes);
for (int i = 0; i < GL->numVertexes; i++) {
ltv[i] = etv[GL->numVertexes - 1];
}
while (top2 != 0) {
//取栈顶元素
int gettop = stack2[top2--];
for (EdgeNode *e = GL->adjList[gettop].firstedge; e; e = e->next) {
int k = e->adjvex;
//min操作
if (ltv[k] - e->weight < ltv[gettop]) {
ltv[gettop] = ltv[k] - e->weight;
}
}
}
ete && lte
ete:最早开工时间,其实就是获取到etv中的元素
lte:最晚开工时间,也就是不推迟工期的最晚开工时间。
ete与lte相等,活动之间没有空闲时间,也就是最关键活动。
例如求得V0->V1和V0->V2哪个是关键活动,如图:


- V0->V1:ete = etv[0] = 0, lte = ltv[1]-3 = 7-3 = 4,ete不等于lte,说明此路径不是关键路径
- V0->V2:ete = etv[0] = 0, lte = ltv[2]-4 = 4-4 = 0,ete等于lte,说明是关键路径
for (int i = 0; i < GL->numVertexes; i++) {
for (EdgeNode *e = GL->adjList[i].firstedge; e; e = e->next) {
int k = e->adjvex;
int ete = etv[i];
int lte = ltv[k] - e->weight;
if (ete == lte) {
printf("<%d-%d> length:%d\n", GL->adjList[i].data, GL->adjList[k].data, e->weight);
}
}
}
关键路径的完整代码
#include <stdio.h>
#include "stdlib.h"
#define OK 1
#define ERROR 0
#define MAXEDGE 30
#define MAXVEX 30
#define INFINITYC 65535
typedef int Status;
typedef struct {
int vex[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph;
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, numEdges;
}graphAdjList, *GraphAdjList;
void CreateMGraph(MGraph *G) {
G->numEdges = 13;
G->numVertexes = 10;
for (int i = 0; i < G->numVertexes; i++) {
G->vex[i] = i;
for (int j = 0; j < G->numVertexes; j++) {
if (i == j) {
G->arc[i][j] = 0;
} else {
G->arc[i][j] = INFINITYC;
}
}
}
G->arc[0][1]=3;
G->arc[0][2]=4;
G->arc[1][3]=5;
G->arc[1][4]=6;
G->arc[2][3]=8;
G->arc[2][5]=7;
G->arc[3][4]=3;
G->arc[4][6]=9;
G->arc[4][7]=4;
G->arc[5][7]=6;
G->arc[6][9]=2;
G->arc[7][8]=5;
G->arc[8][9]=3;
}
void CreateALGraph(MGraph G, GraphAdjList *GL) {
*GL = (GraphAdjList)malloc(sizeof(graphAdjList));
(*GL)->numVertexes = G.numVertexes;
(*GL)->numEdges = G.numEdges;
for (int i = 0; i < G.numVertexes; i++) {
(*GL)->adjList[i].in = 0;
(*GL)->adjList[i].data = G.vex[i];
(*GL)->adjList[i].firstedge = NULL;
}
for (int i = 0; i < G.numVertexes; i++) {
for (int j = 0; j < G.numVertexes; j++) {
if (G.arc[i][j] != 0 && G.arc[i][j] < INFINITYC) {
EdgeNode *e = (EdgeNode*)malloc(sizeof(EdgeNode));
e->adjvex = j;
e->weight = G.arc[i][j];
e->next = (*GL)->adjList[i].firstedge;
(*GL)->adjList[i].firstedge = e;
(*GL)->adjList[j].in++;
}
}
}
}
int *etv, *ltv;
int *stack2;
int top2;
Status TopologicalSort(GraphAdjList GL) {
int *stack = (int *)malloc(sizeof(int) * GL->numVertexes);
int count = 0;
int top = 0;
for (int i = 0; i < GL->numVertexes; i++) {
if (0 == GL->adjList[i].in) {
stack[++top] = i;
}
}
//初始化栈
top2 = 0;
stack2 = (int*)malloc(sizeof(int) *GL->numVertexes);
//初始化etv数组
etv = (int*)malloc(sizeof(GL->numVertexes * sizeof(int)));
for (int i = 0; i < GL->numVertexes; i++) {
etv[i] = 0;
}
printf("TopologicSort:\t");
while (top != 0) {
int gettop = stack[top--];
printf("%d -> ", GL->adjList[gettop].data);
count ++;
stack2[++top2] = gettop;
for (EdgeNode *e = GL->adjList[gettop].firstedge; e; e = e->next) {
int k = e->adjvex;
if (--GL->adjList[k].in == 0) {
stack[++top] = k;
}
//获取到开始事件的最大值
if ((etv[gettop] + e->weight) > etv[k]) {
etv[k] = etv[gettop] + e->weight;
}
}
}
printf("\n");
if (count < GL->numVertexes) {
return ERROR;
} else {
return OK;
}
}
void CriticalPath(GraphAdjList GL) {
TopologicalSort(GL);
printf("etv:\n");
for (int i = 0; i < GL->numVertexes; i++) {
printf("etv[%d] = %d \n", i , etv[i]);
}
printf("\n");
//初始化
ltv = (int*)malloc(sizeof(int) * GL->numVertexes);
for (int i = 0; i < GL->numVertexes; i++) {
ltv[i] = etv[GL->numVertexes - 1];
}
while (top2 != 0) {
//取栈顶元素
int gettop = stack2[top2--];
for (EdgeNode *e = GL->adjList[gettop].firstedge; e; e = e->next) {
int k = e->adjvex;
//min操作
if (ltv[k] - e->weight < ltv[gettop]) {
ltv[gettop] = ltv[k] - e->weight;
}
}
}
printf("ltv:\n");
for (int i = 0; i < GL->numVertexes; i++) {
printf("ltv[%d] = %d\n", i , ltv[i]);
}
printf("\n");
for (int i = 0; i < GL->numVertexes; i++) {
for (EdgeNode *e = GL->adjList[i].firstedge; e; e = e->next) {
int k = e->adjvex;
int ete = etv[i];
int lte = ltv[k] - e->weight;
if (ete == lte) {
printf("<%d-%d> length:%d\n", GL->adjList[i].data, GL->adjList[k].data, e->weight);
}
}
}
}
运行:
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, 关键路径!\n");
MGraph G;
CreateMGraph(&G);
GraphAdjList GL;
CreateALGraph(G, &GL);
CriticalPath(GL);
return 0;
}
