图-寻找关键路径
前言:图-寻找关键路径的学习和实现笔记
在学习关键路径之前,这里先来学习下关于AOE网的知识点
前面已经学习了关于AOV网的知识点,AOV网的定义就是其中的顶点表示活动,有向边<Vi,Vj>表示活动Vi必须先于活动Vj进行
在AOV网的边上加上权值表示完成该活动所需的时间,则称这样的AOV网为AOE(Activity On Edge)网,不同点就是AOE网是在带权有向图,在AOE网的工程中,其中的顶点表示事件,有向边表示了活动,边的权值表示完成该活动对应的开销(又或者是完成该活动所需要的时间),称之为用边表示活动的网络,如下图所示
AOE网具有以下两个性质:
- 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始
就比如图中的,"开始"了之后才可以进行"打鸡蛋"或者是"洗番茄"
- 只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发生
就比如上面的图中,想要进行"炒菜"这个活动,那么一定需要把"打鸡蛋"和"洗番茄"和"切番茄"这些活动结束之后才能开始
- 另外有些活动是可以并行进行的
这个其实很好理解的,就是比如图中的打鸡蛋的权值为2,洗番茄的权值为1,那么打鸡蛋其中的时间1和洗番茄的权值1可以同时进行
AOE网的概念:
-
在AOE网中仅有一个入度为0的顶点,称为开始顶点,它表示整个工程的开始
-
在AOE网中仅有一个出度为0的顶点,称为结束顶点,它表示整个工程的结束
什么是图的关键路径
关键路径:从源点到汇点的所有路径中,具有最大路径长度的路径;
我个人理解关键路径其实就是在AOE网中从开始顶点到结束顶点中活动耗时最长的一条路径就被称之为关键路径
比如下面这张图中,按道理来说关键路径应该就是 "v1" - "v2" - "v3" - "v4",因为这条路径的耗时最长
图关键路径的实现
我自己代码只实现了这两个"求所有事件的最早发生时间" "求所有事件的最迟发生时间",另外两个"求所有活动的最早发生时间" "求所有活动的最迟发生时间"其实都是差不多的
#include<stdio.h> #include<stdlib.h> #include<string.h> #define OK 1 #define ERROR 0 #define MAX 32767 typedef int Status; typedef struct _Graph { int vexNum; // 顶点数量 int arcNum; // 边长数量 int** arcs; // 边长的0或者1,大小就是arcsNum*arcsNum char* vexs; // 顶点的名称 }Graph, *PGraph; // 图的初始化 Graph* initGraph(int vexNum) { Graph* pGraph = NULL; if (pGraph == NULL) { pGraph = (Graph*)malloc(sizeof(Graph)); memset(pGraph, 0, sizeof(Graph)); if (pGraph == NULL) return NULL; pGraph->vexNum = vexNum; // 顶点的数量 pGraph->vexs = (char*)malloc(sizeof(pGraph->vexNum)); memset(pGraph->vexs, 0, sizeof(pGraph->vexNum)); pGraph->arcNum = 0; // 初始化为0,因为到时候craeteGraph的时候完了之后再进行最终边数的计算 pGraph->arcs = (int**)malloc(sizeof(int*)* pGraph->vexNum); memset(pGraph->arcs, sizeof(int*)* pGraph->vexNum, 0); // 一个图中每个顶点都有对应的n个顶点的边数 for (int i = 0; i < pGraph->vexNum; i++) { pGraph->arcs[i] = malloc(sizeof(int)* pGraph->vexNum); memset(pGraph->arcs[i], 0, sizeof(int)*pGraph->vexNum); } } return pGraph; } // 顶点和边之间的关系初始化 Status createGraph(Graph** pGraph, char* vexs, int* arcs) { if (*pGraph == NULL) return ERROR; for (int i = 0; i<(*pGraph)->vexNum; i++) { *((*pGraph)->vexs + i) = *(vexs + i); for (int j = 0; j<(*pGraph)->vexNum; j++) { (*pGraph)->arcs[i][j] = *(arcs + i*((*pGraph)->vexNum) + j); printf("%d\t", (*pGraph)->arcs[i][j]); if ((*pGraph)->arcs[i][j] == 1) (*pGraph)->arcNum++; } printf("\n"); } // 无向图的边数还需要除以2 (*pGraph)->arcNum /= 2; return OK; } // 深度优先遍历 Status DFS(Graph* pGraph, int* iVisitedArray, int visitedIndex) { if (pGraph == NULL) return ERROR; iVisitedArray[visitedIndex] = 1; printf("%c ", pGraph->vexs[visitedIndex]); for (int i = 0; i<pGraph->vexNum; i++) { if (pGraph->arcs[visitedIndex][i] == 1 && !iVisitedArray[i]) DFS(pGraph, iVisitedArray, i); } return OK; } int* getTopoSort(Graph* pGraph) { int iFlag; // 标识符 int visitedArray[6] = { 0 }; int* pTopoArray = (int*)malloc(7); pTopoArray[7] = '\0'; for (int m = 0; m<pGraph->vexNum; m++) { for (int i = pGraph->vexNum; i >= 0; i--) { iFlag = 1; if (!visitedArray[i]) { for (int j = 0; j < pGraph->vexNum; j++) { if (pGraph->arcs[j][i] != 0 && pGraph->arcs[j][i] != MAX) { iFlag = 0; break; } } if (iFlag) { for (int k = 0; k < pGraph->vexNum; k++){ if (pGraph->arcs[i][k] == 1)pGraph->arcs[i][k]--; } printf("输出顶点%c\n", pGraph->vexs[i]); pTopoArray[m] = i; visitedArray[i] = 1; break; } } } } return pTopoArray; } // 关键路径 - 事件最早发生时间 int* getVexEarliest(Graph* pGraph, int* pTopoArray) { int* iVexPathValue = (int*)malloc(sizeof(int)*pGraph->vexNum); for (int i = 0; i<pGraph->vexNum; i++) iVexPathValue[i] = pGraph->arcs[i][i]; for (int m = 0; m<pGraph->vexNum; m++) { for (int j = 0; j < pGraph->vexNum; j++) { if (pGraph->arcs[pTopoArray[m]][j] != MAX && pGraph->arcs[pTopoArray[m]][j] != 0 && iVexPathValue[j] < (pGraph->arcs[pTopoArray[m]][j] + iVexPathValue[pTopoArray[m]])) iVexPathValue[j] = iVexPathValue[pTopoArray[m]] + pGraph->arcs[pTopoArray[m]][j]; // 获取当前顶点的前驱权值 + 当前权值的边 } printf("ve(%c)=%d\n", pGraph->vexs[pTopoArray[m]], iVexPathValue[pTopoArray[m]]); } return iVexPathValue; } // 计算所有事件最迟完成事件 void getVexLatest(Graph* pGraph, int* pTopoArray, int* pEarlyArray) { // 置换拓扑排序 int* pReverseTopoArray = (int*)malloc(sizeof(int)* pGraph->vexNum); int index = pGraph->vexNum-1; int* iVexPathValue = (int*)malloc(sizeof(int)*pGraph->vexNum); for (int i = 0; i<pGraph->vexNum; i++) iVexPathValue[i] = MAX; iVexPathValue[pGraph->vexNum - 1] = pEarlyArray[pGraph->vexNum - 1]; for (int i = 0; i < pGraph->vexNum; i++) { pReverseTopoArray[i] = pTopoArray[index]; index--; } // 开始进行计算 for (int m = 0;m<pGraph->vexNum;m++) { for (int j = 0;j<pGraph->vexNum;j++) { if (pGraph->arcs[pReverseTopoArray[m]][j] != 0 && pGraph->arcs[pReverseTopoArray[m]][j] != MAX && (pEarlyArray[j] - pGraph->arcs[pReverseTopoArray[m]][j]) < iVexPathValue[pReverseTopoArray[m]]) iVexPathValue[pReverseTopoArray[m]] = pEarlyArray[j] - pGraph->arcs[pReverseTopoArray[m]][j]; } printf("vl(%c)=%d\n", pGraph->vexs[pReverseTopoArray[m]], iVexPathValue[pReverseTopoArray[m]]); } } int main() { // 要初始化的矩阵 int visited[6] = { 0 }; int initArcs1[6][6] = { 0,1,1,MAX,MAX,MAX, MAX,0,MAX,1,1,MAX, MAX,MAX,0,1,MAX,1, MAX,MAX,MAX,0,MAX,1, MAX,MAX,MAX,MAX,0,1, MAX,MAX,MAX,MAX,MAX,0 }; int initArcs2[6][6] = { 0, 3, 2, MAX, MAX, MAX, MAX, 0, MAX, 2, 3, MAX, MAX, MAX, 0, 4, MAX, 3, MAX, MAX, MAX, 0, MAX, 2, MAX, MAX, MAX, MAX, 0, 1, MAX, MAX, MAX, MAX, MAX, 0 }; char initVexs[7] = "123456"; Graph* pGraph = initGraph(6); // 要初始化的顶点的数量 createGraph(&pGraph, initVexs, (int*)initArcs1); int* pTopoSort = getTopoSort(pGraph); memset(pGraph, 0, sizeof(Graph)); pGraph = initGraph(6); // 要初始化的顶点的数量 createGraph(&pGraph, initVexs, (int*)initArcs2); // 所有事件的最早发生时间 int* pVexEarliestArray = getVexEarliest(pGraph, pTopoSort); printf("======================\n"); // 所有时间的最迟发生时间 getVexLatest(pGraph, pTopoSort, pVexEarliestArray); return 0; }
关键路径的总结
-
第一点和第二点同理
-
第二点也很好理解,就是关键路径本来就是最耗时的一条路径,如果该路径的耗时时间减少了,那么整体的耗时时间也就减少了
-
上面的图中的第三点的解释,其实就是如下这种,原本是关键路径中的一个事件,但是该事件的活动耗时变短了,则就有可能不再是一条关键路径了
一个AOE网中可能存在多条关键路径,比如如下所示
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY