关键路径:

通常把计划、施工过程、生产流程、程序流程等都当成一个工程。工程通常分为若干个称为“活动”的子工程。完成了这些“活动”,这个工程就可以完成了。通常用 AOE-网来表示工程。AOE-网是一个带权的有向无环图,其中,顶点表示事件(EVENT),表示活动,权表示活动持续的时间。 AOE-网可以用来估算工程的完成时间。可以使人们了解:

(1)研究某个工程至少需要多少时间?
(2)哪些活动是影响工程进度的关键?

由于 AOE-网中的有些活动可以并行进行,从开始点到各个顶点,以致从开始点到完成点的有向路径可能不止一条,这些路径的长度也可能不同。完成不同路径的活动所需的时间虽然不同,但只有各条路径上所有活动都完成了,这个工程才算完成。因此,完成工程所需的最短时间是从开始点到完成点的最长路径的长度,即在这条路径上的所有活动的持续时间之和.这条路径长度就叫做关键路径(Critical Path)。

例:AOE 图如下:

程序执行结束后应该输出:

关键活动为: a1,a4,a7,a10,a8,a11

关 键 路 径 为 : a1->a4->a7->a10 ( 或 者 V1->V2->V5->V7->V9 ) 和a1->a4->a8->a11(或者 V1->V2->V5->V8->V9

花费的时间为至少为 :18(时间单位)。

算法设计:

1、 用邻接表存储一张带权有向图。
2、 对图进行拓扑排序,并进行事件的最早发生时间Ve[i]的计算。
3、 根据排序结果,判断图中是否存在有向环。
4、 根据逆拓扑序列,计算事件的最晚发生时间Vl[i]。
5、 计算活动的最早、最晚发生时间,判断关键活动,找出关键路径。

流程图:

算法实现:

#include<bits/stdc++.h>
using namespace std;

typedef int Status;
typedef int Boolean; 
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define MAX_NAME 5 
typedef int InfoType;
typedef char VertexType[MAX_NAME];
/* --------------------------------- 图的邻接表存储表示 --------------------------------*/
#define MAX_VERTEX_NUM 20
typedef struct ArcNode{
            //表结点
	int adjvex;                 //该弧所指向的顶点的下标
	struct ArcNode *nextarc;    //指向下一条弧的指针 
	InfoType *info;             //边权值
}ArcNode;
typedef struct{
   
	VertexType data;            //顶点信息
	ArcNode *firstarc;          //第一个表结点的地址,指向第一条依附该顶点的弧的指针
}VNode, AdjList[MAX_VERTEX_NUM];//邻接表
typedef struct{
                    //图
	AdjList vertices;
	int vexnum, arcnum;         //顶点数和边数
	int kind;                   //图的种类
}ALGraph;
/* ----------------------------- 需要用的图的邻接表存储的基本操作 --------------------------*/ 
int LocateVex(ALGraph G, VertexType u){
    
  //若G中存在顶点u,则返回该顶点在图中位置;否则返回-1
	int i;
	for (i = 0; i < G.vexnum; ++i)
		if (strcmp(u, G.vertices[i].data) == 0)
			return i;
	return -1;
}

Status CreateGraph(ALGraph &G){
    //创建有向网
	int i, j, k;
	int w; // 权值 
	VertexType va, vb;
	ArcNode *p;
    G.kind = 1;//有向网
    printf("请输入图的顶点数和边数(空格隔开): ");
	scanf("%d%d", &G.vexnum, &G.arcnum);
	printf("请输入%d个顶点的值(<%d个字符):\n", G.vexnum, MAX_NAME);
	for (i = 0; i < G.vexnum; ++i) {
   
		scanf("%s", G.vertices[i].data);
		G.vertices[i].firstarc = NULL;
	}
	printf("请顺序输入每条弧(边)的权值、弧尾和弧头(以空格作为间隔):\n");
	for (k = 0; k < G.arcnum; ++k) {
   
		scanf("%d%s%s", &w, va, vb);
		i = LocateVex(G, va); // 弧尾 
		j = LocateVex(G, vb); // 弧头 
		p = (ArcNode*)malloc(sizeof(ArcNode));
		p->adjvex = j;
		p->info = (int *)malloc(sizeof(int));
		*(p->info) = w;
		p->nextarc = G.vertices[i].firstarc; // 插在表头 
		G.vertices[i].firstarc = p;
	}
	return OK;
}

void Display(ALGraph G){
    // 输出图的邻接表G 
	int i;
	ArcNode *p;
	printf("有向图\n");
	printf("%d个顶点:\n", G.vexnum);
	for (i = 0; i < G.vexnum; ++i)
		printf("%s ", G.vertices[i].data);
	printf("\n%d条弧(边):\n", G.arcnum);
	for (i = 0; i < G.vexnum; i++){
   
		p = G.vertices[i].firstarc;
		while (p){
   
            printf("%s→%s :%d", G.vertices[i].data, G.vertices[p->adjvex].data, *(p->info));
			p = p->nextarc;
		}
		printf("\n");
	}
}
void FindInDegree(ALGraph G, int indegree[]){
    // 求顶点的入度
	int i;
	ArcNode *p;
	for (i = 0; i < G.vexnum; i++)
		indegree[i] = 0; /* 赋初值 */
	for (i = 0; i < G.vexnum; i++){
   
		p = G.vertices[i].firstarc;
		while (p){
   
			indegree[p->adjvex]++;
			p = p->nextarc;
		}
	}
}
typedef int SElemType; 
/* ----------------------------------- 栈的顺序存储表示 -----------------------------------*/
#define STACK_INIT_SIZE 10
#define STACKINCREMENT 2 
typedef struct SqStack{
   
	SElemType *base; 
	SElemType *top;
	int stacksize;
}SqStack;
Status InitStack(SqStack &S){
    
	S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
	if (!S.base)
		exit(_OVERFLOW);
	S.top = S.base;
	S.stacksize = STACK_INIT_SIZE;
	return OK;
}
 
Status StackEmpty(SqStack S){
   
	if (S.top == S.base)
		return TRUE;
	else
		return FALSE;
}
 
Status GetTop(SqStack s,SElemType &e){
   
    if(s.base==s.top)
        return 0;
    e = *(s.top - 1);
    return 1;
}

Status Push(SqStack &S, SElemType e){
    
	if (S.top - S.base >= S.stacksize) {
   
		S.base = (SElemType *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(SElemType));
		if (!S.base)
			exit(_OVERFLOW);
		S.top = S.base + S.stacksize;
		S.stacksize += STACKINCREMENT;
	}
	*(S.top)++ = e;
	return OK;
}
 
Status Pop(SqStack &S, SElemType &e){
    /* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
	if (S.top == S.base)
		return ERROR;
	e = *--S.top;
	return OK;
}

int ve[MAX_VERTEX_NUM], vl[MAX_VERTEX_NUM];//e[]:最早发生时间 vl[]:最迟发生时间
bool flag = 0;
vector<string> path;
int top,down;
void dfs(ALGraph G, int u){
   
	if (G.vertices[u].data == G.vertices[top].data) {
   //搜索到终点
		if (flag) cout << "\n或者:\n";
		cout << G.vertices[down].data;	//输出起点
		for(auto &it:path)	//遍历输出关键路径
            cout << it;
        flag = 1;			
		return;
	}
	ArcNode* p = G.vertices[u].firstarc;	//初边
	for (; p; p = p->nextarc) {
   				//遍历
		int k = p->adjvex;					//该弧所指向的顶点的下标
		int dut = *(p->info);				//弧的权值
		if (ve[u] == vl[k] - dut) {
   			//是关键活动
            path.push_back("->");			
            path.push_back(G.vertices[k].data);		//记录关键活动
            dfs(G, k);				//继续DFS搜索下一个结点的关键活动
            path.pop_back();		//回溯
            path.pop_back();		//回溯
        }
	}
}

//算法7.13
Status TopologicalOrder(ALGraph G, SqStack &T){
    
	int j, k, count, indegree[MAX_VERTEX_NUM],e;
	SqStack S;
	ArcNode *p;
	FindInDegree(G, indegree);
	InitStack(S); 
	for (j = 0; j < G.vexnum; ++j) 
		if (!indegree[j])
			Push(S, j);
	GetTop(S, down);	//记录起点位置
	InitStack(T); 
	count = 0; 
	for (j = 0; j < G.vexnum; ++j) 
		ve[j] = 0;	//初始化
	while (!StackEmpty(S)){
   
		Pop(S, j);
		Push(T, j);
		++count;	//入栈的顶点数
		for (p = G.vertices[j].firstarc; p; p = p->nextarc){
   	//遍历邻接表
			k = p->adjvex;          //该弧所指向的顶点的下标
			if (--indegree[k] == 0) //入度减为零入栈
				Push(S, k);
			if (ve[j] + *(p->info) > ve[k])//拓扑正序更新max
				ve[k] = ve[j] + *(p->info);
		}
	}
	if (count < G.vexnum){
   
		printf("此有向网有回路\n");
		return ERROR;
	}
	else{
   
        GetTop(T, top);//拓扑排序的最后一个结点为终点
        return OK;
    }
		
}
//算法7.14
Status CriticalPath(ALGraph G){
   
	SqStack T;
	int i, j, k, ee, el;
	ArcNode *p;
	char dut, tag;
	if (!TopologicalOrder(G, T)) // 产生有向环 
		return ERROR;
	j = ve[0];
	for (i = 1; i < G.vexnum; i++) //j=Max(ve[]) 完成点的值 
		if (ve[i] > j)
			j = ve[i];
	int maxn = j;
	for (i = 0; i < G.vexnum; i++) // 初始化顶点事件的最迟发生时间(最大值) 
		vl[i] = j; // 完成点的最早发生时间 
	while (!StackEmpty(T)) // 按拓扑逆序求各顶点的vl值 
		for (Pop(T, j), p = G.vertices[j].firstarc; p; p = p->nextarc){
   
			k = p->adjvex;
			dut = *(p->info); // dut<j,k> 
			if (vl[k] - dut < vl[j])
				vl[j] = vl[k] - dut;
		}
	printf(" j k dut ee el tag\n");
	for (j = 0; j < G.vexnum; ++j)//求ee,el和关键活动 
		for (p = G.vertices[j].firstarc; p; p = p->nextarc){
   
			k = p->adjvex;
			dut = *(p->info);//边权
			ee = ve[j];
			el = vl[k] - dut;
			tag = (ee == el) ? '*' : ' ';
			printf("%2d %2d %3d %3d %3d %c\n", j, k, dut, ee, el, tag);
		}
	printf("关键活动为:\n");
	for (j = 0; j < G.vexnum; ++j) 
		for (p = G.vertices[j].firstarc; p; p = p->nextarc){
   
			k = p->adjvex;
			dut = *(p->info);
			if (ve[j] == vl[k] - dut)
				printf("%s→%s\n", G.vertices[j].data, G.vertices[k].data); // 输出关键活动 
		}
	printf("关键路径为:\n");
    dfs(G,0);
    printf("\n花费时间最少为:%d\n", maxn); 
	return OK;
}
 
int main(){
   
	ALGraph h;
	CreateGraph(h);
	Display(h);
	CriticalPath(h);
}

测试样例:


再加两条:
v1 -> v10(权值为8)
v10 -> v9(权值为10)

测试数据:

请输入图的顶点数和边数(空格隔开): 10 13
请输入10个顶点的值(<5个字符):
v1 v2 v3 v4 v5 v6 v7 v8 v9 v10
请顺序输入每条弧()的权值、弧尾和弧头(以空格作为间隔):
6 v1 v2
4 v1 v3
5 v1 v4
1 v2 v5
1 v3 v5
2 v4 v6
9 v5 v7
7 v5 v8
4 v6 v8
2 v7 v9
4 v8 v9
8 v1 v10
10 v10 v9

运行结果

有向图
10个顶点:
v1 v2 v3 v4 v5 v6 v7 v8 v9 v10
13条弧():
v1→v10 :8v1→v4 :5v1→v3 :4v1→v2 :6
v2→v5 :1
v3→v5 :1
v4→v6 :2
v5→v8 :7v5→v7 :9
v6→v8 :4
v7→v9 :2
v8→v9 :4

v10→v9 :10
 j  k  dut  ee  el  tag
 0  9   8   0   0    *
 0  3   5   0   3
 0  2   4   0   2
 0  1   6   0   0    *
 1  4   1   6   6    *
 2  4   1   4   6
 3  5   2   5   8
 4  7   7   7   7    *
 4  6   9   7   7    *
 5  7   4   7  10
 6  8   2  16  16    *
 7  8   4  14  14    *
 9  8  10   8   8    *
关键活动为:
v1→v10
v1→v2
v2→v5
v5→v8
v5→v7
v7→v9
v8→v9
v10→v9
关键路径为:
v1->v10->v9
或者:
v1->v2->v5->v8->v9
或者:
v1->v2->v5->v7->v9
花费时间最少为:18

心得体会

  1. 在使用邻接表存图过程中再次熟悉链表相关操作。
  2. 复习栈的编写及使用。利用栈求解拓扑序列。
  3. 在进行拓扑排序时,掌握结点度(入度)求取,学会拓扑有序的应用:利用拓扑排序进行最早发生时间的计算。
  4. 复习栈的编写及使用。利用栈存储拓扑有序序列,出栈即得到拓扑逆序序列。并利用拓扑逆序求解最晚发生时间的计算。
  5. 复习图上深度优先搜索。利用DFS输出关键路径。

参考文献

[1] 严蔚敏,吴伟民.数据结构(C语言版)[M]. 北京:清华大学出版社,1997.4

2021-12-30 更新

1. dfs()起点位置不正确:

错误代码:

dfs(G,0);

正确代码:

dfs(G,down);

0并不是起点的下标,down才是。
例如测试样例改为:

请输入图的顶点数和边数(空格隔开): 9 11
请输入9个顶点的值(<5个字符):
v2 v1 v3 v4 v5 v6 v7 v8 v9
请顺序输入每条弧()的权值、弧尾和弧头(以空格作为间隔):
6 v1 v2
4 v1 v3
5 v1 v4
1 v2 v5
1 v3 v5
2 v4 v6
9 v5 v7
7 v5 v8
4 v6 v8
2 v7 v9
4 v8 v9

dfs(G,0)会输出:

有向图
9个顶点:
v2 v1 v3 v4 v5 v6 v7 v8 v9 
11条弧():
v2→v5 :1
v1→v4 :5v1→v3 :4v1→v2 :6   
v3→v5 :1
v4→v6 :2
v5→v8 :7v5→v7 :9
v6→v8 :4
v7→v9 :2
v8→v9 :4

 j  k  dut  ee  el  tag    
 0  4   1   6   6    *     
 1  3   5   0   3          
 1  2   4   0   2          
 1  0   6   0   0    *     
 2  4   1   4   6          
 3  5   2   5   8          
 4  7   7   7   7    *     
 4  6   9   7   7    *     
 5  7   4   7  10     
 6  8   2  16  16    *
 7  8   4  14  14    *
关键活动为:
v2→v5
v1→v2
v5→v8
v5→v7
v7→v9
v8→v9
关键路径为:
v1->v5->v8->v9				//错误
或者:
v1->v5->v7->v9
花费时间最少为:18

dfs(G,down)会输出:

有向图
9个顶点:
v2 v1 v3 v4 v5 v6 v7 v8 v9 
11条弧():
v2→v5 :1
v1→v4 :5v1→v3 :4v1→v2 :6
v3→v5 :1
v4→v6 :2
v5→v8 :7v5→v7 :9
v6→v8 :4
v7→v9 :2
v8→v9 :4

 j  k  dut  ee  el  tag
 0  4   1   6   6    *
 1  3   5   0   3
 1  2   4   0   2
 1  0   6   0   0    *
 2  4   1   4   6
 3  5   2   5   8
 4  7   7   7   7    *
 4  6   9   7   7    *
 5  7   4   7  10
 6  8   2  16  16    *
 7  8   4  14  14    *
关键活动为:
v2→v5
v1→v2
v5→v8
v5→v7
v7→v9
v8→v9
关键路径为:
v1->v2->v5->v8->v9		//正确
或者:
v1->v2->v5->v7->v9
花费时间最少为:18

2. dfs()另一种记录方式:

char* strcmmp(char *a,int n){
   
	char *arr=(char*)malloc(n*sizeof(char));
	for(int i=0;i<n-1;i++){
   
		arr[i]=a[i];
	}
	arr[n-1]='\0';
	return arr;
}
void dfs(ALGraph G,int u,char arry[])
{
   
	if(G.adjlist[u].vex==G.adjlist[top].vex)
	{
   
		//printf("%s",G.adjlist[down].vex);
		for(int i=0;i<strlen(arry);i++)
		{
   
			printf("%c",arry[i]);
			if(i>=0&&i<strlen(arry)-1)
			printf("->");
		}
		printf("\n");
		return ;
	}
	EdgeNode *p=G.adjlist[u].firstedge;
	for(;p;p=p->next)
	{
   
		int k=p->adjvex,w=p->weight;
		if(etv[u]==ltv[k]-w)
		{
   	//printf("%d\n",k);
			arry=strcat(arry,G.adjlist[k].vex);
			dfs(G,k,arry);
			arry=strcmmp(arry,strlen(arry)-1);
		}
	
	}
}

3. C语言完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Max_VERTEX_NUM 20
#define ElemType int
#define Status int
#define MAXVEX 100
#define MAX_NAME 5
int top, down;

int *etv, *ltv; //记录事件最早发生时间和最迟发生时间数组
typedef char VertexType[MAX_NAME];
typedef struct nodee //边表节点
{
   
    int adjvex;         //邻接点域,存储该顶点对应的下标
    int weight;         //权值
    struct nodee *next; //指向下一个顶点
} EdgeNode;

typedef struct vnode //顶点
{
   
    VertexType vex;      //顶点信息
    EdgeNode *firstedge; //指向顶点的第一个邻接顶点
} VertexNode, AdjList[Max_VERTEX_NUM];

typedef struct AlGraph
{
   
    AdjList adjlist;
    int vexnum;  //节点数目
    int edgenum; //边的数目
} ALGraph;
//定义一个栈
typedef struct Node
{
   
    ElemType data;     //存储数据
    struct Node *next; //指向下一个元素
} node, *LinkStackptr;
typedef struct LS
{
   
    LinkStackptr top;
    int count; //记录栈中元素的个数
} LinkStack;

Status InitStack(LinkStack *s) //创建一个栈
{
   
    s->top = (LinkStackptr)malloc(sizeof(node));
    if (s->top == NULL)
        exit(0);
    s->top->next = NULL;
    s->count = 0;
    return 1; //建栈成功返回1
}

Status StackEmpty(LinkStack s) //判断是否为空
{
   
    if (s.count == 0)
        return 1; //若栈为空返回1
    return 0;     //若栈不为空返回 0
}

Status GetTop(LinkStack s, int *e) //返回栈顶元素
{
   
    if (s.top == NULL)
        return 0;
    *e = s.top->data;
    return *e;
}

Status Push(LinkStack *s, ElemType e) //元素入栈(头插)
{
   
    LinkStackptr p = (LinkStackptr)malloc(sizeof(node));
    if (p == NULL)
        return 0;
    p->data = e;
    p->next = s->top;
    s->top = p; //指向第一个元素的指针
    s->count++; //栈中元素个数增加
    return 1;
}

Status pop(LinkStack *s, ElemType *e) //返回栈顶元素 ,并删除 (头删)
{
   
    if (s->top == NULL)
        return 0;
    LinkStackptr p = s->top->next; //指向下一个元素
    *e = s->top->data;             //将首元素的赋给e
    free(s->top);
    s->top = p;              //删除节点
    s->count = s->count - 1; //栈中元素个数减少
    return *e;               //返回e
}

void CreateALG(ALGraph *ALG, int **indegree) //创建一个图(邻接表存储)
{
   
    printf("请输入节点的数目和边的数目 ");
    scanf("%d%d", &ALG->vexnum, &ALG->edgenum);
    int i, j, k, w;
    EdgeNode *e;
    for (i = 0; i < ALG->vexnum; i++)
    {
   
        getchar();
        // printf("请输入第%d个节点的信息",i+1);
        scanf("%s", ALG->adjlist[i].vex); //输入结点信息
        ALG->adjlist[i].firstedge = NULL;
    }
    for (k = 0; k < ALG->edgenum; k++) //边表的建立
    {
   
        getchar();
        //printf("请输入第%d条边<vi,vj>上的顶点序号和权值",k+1);
        scanf("%d%d%d", &w, &i, &j);
        e = (EdgeNode *)malloc(sizeof(EdgeNode));
        e->adjvex = j - 1;
        e->weight = w;
        e->next = ALG->adjlist[i - 1].firstedge;
        ALG->adjlist[i - 1].firstedge = e; //将结点插入
    }
    *indegree = (int *)malloc(ALG->vexnum * sizeof(int)); //创建indegree数组求入度
    for (i = 0; i < ALG->vexnum; i++)
    {
   
        (*indegree)[i] = 0; //(indegree初始化)
    }
}

void FindInDegree(ALGraph ALG, int indegree[]) //求各个结点的入度
{
   
    int i;
    EdgeNode *temp;
    for (i = 0; i < ALG.vexnum; i++) //对每个顶点的边表都进行遍历
    {
   
        temp = ALG.adjlist[i].firstedge;
        while (temp != NULL)
        {
   
            indegree[temp->adjvex]++;
            temp = temp->next;
        }
    }
}
char *strcmmp(char *a, int n)
{
   
    char *arr = (char *)malloc(n * sizeof(char));
    for (int i = 0; i < n - 1; i++)
    {
   
        arr[i] = a[i];
    }
    arr[n - 1] = '\0';
    return arr;
}
void dfs(ALGraph G, int u, char arry[])
{
   
    if (G.adjlist[u].vex == G.adjlist[top].vex)
    {
   
        //printf("%s",G.adjlist[down].vex);
        for (int i = 0; i < strlen(arry); i++)
        {
   
            printf("%c", arry[i]);
            if (i >= 0 && i < strlen(arry) - 1)
                printf("->");
        }
        printf("\n");
        return;
    }
    EdgeNode *p = G.adjlist[u].firstedge;
    for (; p; p = p->next)
    {
   
        int k = p->adjvex, w = p->weight;
        if (etv[u] == ltv[k] - w)
        {
    //printf("%d\n",k);
            arry = strcat(arry, G.adjlist[k].vex);
            dfs(G, k, arry);
            arry = strcmmp(arry, strlen(arry) - 1);
        }
    }
}
Status TopologicalSort(ALGraph ALG, int indegree[], LinkStack *T) //拓扑排序
{
   
    int i, count = 0, k;
    LinkStack s;
    EdgeNode *p;
    InitStack(T);  //创建一个栈 (用于逆序)
    InitStack(&s); //创建一个栈(用于正序)
    etv = (int *)malloc(ALG.vexnum * sizeof(int));
    for (i = 0; i < ALG.vexnum; i++)
    {
   
        etv[i] = 0; // 对记录事件最早发生时间的数组进行初始化
    }

    for (i = 0; i < ALG.vexnum; i++)
    {
   
        if (indegree[i] == 0)
        {
   
            Push(&s, i); //遍历找到入度为0的元素入栈
            GetTop(s, &down);
        }
    }
    while (!StackEmpty(s)) //循环
    {
   
        pop(&s, &i);                       //出栈
        Push(T, i);                        //入栈
        printf("%s ", ALG.adjlist[i].vex); //打印出拓扑正序序列
        count++;                           //记录输出的结点数
        for (p = ALG.adjlist[i].firstedge; p; p = p->next)
        {
   
            k = p->adjvex;
            if (!(--indegree[k]))            //出栈的同时对各个结点的入度进行更新
                Push(&s, k);                 //进行判断,若入度为0便入栈
            if (etv[i] + p->weight > etv[k]) // 求事件的最早发生时间 //前->后
                etv[k] = etv[i] + p->weight;
        }
    }
    if (count < ALG.vexnum) //判断是否有回路
        return 0;           //若有回路返回0
    else
    {
   
        GetTop(*T, &top);
        return 1;
    } //若无回路返回1
}

Status CriticalPath(ALGraph ALG, LinkStack *T, int n) //求关键路径
{
   
    if (n == 0) //判断是否有回路(若有回路程序停止执行)
    {
   
        printf("有回路");
        return 0;
    }
    int i, k, w, j;
    int ete, lte; //活动的最早,最迟开始时间
    EdgeNode *p;
    ltv = (int *)malloc(ALG.vexnum * sizeof(int)); //对求事件最迟开始时间的数组进行初始化
    j = etv[0];
    for (i = 1; i < ALG.vexnum; i++)
    {
   
        if (j < etv[i])
            j = etv[i];
    }
    for (i = 0; i < ALG.vexnum; i++)
    {
   
        ltv[i] = j; //最后一个事件的最迟开始时间等于其最早开始时间
    }
    while (!StackEmpty(*T)) //利用拓扑逆序求事件的最迟开始时间
    {
   
        pop(T, &i);
        for (p = ALG.adjlist[i].firstedge; p; p = p->next)
        {
   
            k = p->adjvex;
            w = p->weight;
            if (ltv[k] - w < ltv[i]) //后->前
                ltv[i] = ltv[k] - w;
        }
    }
    for (i = 0; i < ALG.vexnum; i++)
    {
   
        for (p = ALG.adjlist[i].firstedge; p; p = p->next)
        {
   
            k = p->adjvex;
            w = p->weight;
            ete = etv[i];
            lte = ltv[k] - w;                                                                       //求活动的最迟开始和最早开始时间
            if (ete == lte)                                                                         //若相等便是关建活动
                printf("<%s,%s>length is %d ", ALG.adjlist[i].vex, ALG.adjlist[k].vex, p->weight); //输出关键路径
        }
    }
    printf("\n");
    printf("路径长度为: %d", j);
    printf("\n");
    dfs(ALG, 0, ALG.adjlist[0].vex);
}
int main()
{
   
    ALGraph ALG;
    LinkStack T;
    int *indegree;
    int n;
    CreateALG(&ALG, &indegree);
    FindInDegree(ALG, indegree);
    printf("拓扑正序为:");
    n = TopologicalSort(ALG, indegree, &T);
    printf("\n");
    printf("关键路径为:");
    CriticalPath(ALG, &T, n);
}