拓扑排序和关键路径是基于无环的有向图。
主要用来表示工程进度中各个事件之间的关系。
拓扑排序和关键路径 使用邻接表存储数据,最小生成树和最短路径用 邻接矩阵 存储数据。
1、拓扑排序
AOV网:在一个表示工程的有向图中,用固定点表示活动,用弧表示活动之间的优先级关系,这样的有向图为顶点表示活动的网,我们称之为AOV网(Activity On Vertex Network)
拓扑排序:是将一个AOV网的各个顶点按一定顺序排列,要求满足若存在<Vi,Vj>,则排序中的顶点Vi必在顶点Vj之前。对于同一幅图,拓扑排序可有多个拓扑排序。
在显示生活中,用到的拓扑排序的例子:
学校课程布置图,要先修完一些基础课,才可以继续修专业课,以计算机软件专业为例,在《程序设计基础》和《离散数学》课程学完之前就不能开始学习《数据结构》,这些先决条件定义了课程之间的优先(领先)关系。
算法:
(1)从有向图中选择一个无前驱(入度为0)的顶点输出。
(2)删除此顶点,并删除已此顶点为为尾的弧。
(3)重复(1),(2)步骤,知道输出全部顶点或者AOV网中不存在入度为0的顶点。
具体实现代码:
用栈来保存入度为0的顶点,和更新后入度为0的顶点
#include <stdio.h> #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; i<GL->numVertexes; 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由弧<m,n>表示,用dut(<m,n>)表示活动的持续时间,则有:
e(k) = ve(m)
l(k) = vl(n) - dut(<m,n>)
求解关键路径的具体算法(假设图中有n个顶点)
(1) 从开始顶点V0出发,假设ve(0)=0,然后按照拓扑有序求出其他各顶点i的最早开始时间ve(i),如果得到拓扑序列中顶点数目小于图中的顶点数,则表示图中存在回路,算法结束,否则继续执行。
(2)从结束顶点Vn出发,假设vl(n-1) = ve(n-1);然后求出各顶点i的最晚发生时间。
(3)根据顶点的最早发生时间,和最晚发生时间,依次求出出每条弧的最早开始时间和最晚开始时间,如果两只相等,则为关键活动。关键活动组成的路径则为关键路径。
具体实现代码:
#include <stdio.h> #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; i<GL->numVertexes; i++) if(GL->adjList[i].in == 0) /*将入度为0的顶点入栈*/ stack[++top] = i; top2 = 0; etv = (int*) malloc(GL->numVertexes*sizeof(int)); /*事件的最早发生时间*/ for(i=0;i<GL->numVertexes; 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; i<GL->numVertexes;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->weight<ltv[gettop]) ltv[gettop]= ltv[k]-e->weight; /*求最晚发生时间,是从拓扑序列的最后一个顶点逆着推导*/ } } for(j=0;j<GL->numVertexes;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("<v%d,v%d> length: %d, ",GL->adjList[j].data,GL->adjList[k].data,e->weight); } } }