在关键路径的实现中利用到了栈、拓扑排序、局部贪心思想,首先需要理解最早发生时间(全程不能休息的活动)和最晚发生时间(最迟能拖到什么时候必须要开始的活动,理解为最晚交作业的时间),路径长度是指各个活动持续时间的总和,从源点到汇点(终点)的最大长度路径叫关键路径,在关键路径上的活动叫关键活动,如果活动最早开始时间和最晚开始时间一致那么说明该活动是关键活动,对应活动间的路径叫关键路径,初学很容易把关键路径误认为是最短路径,其实是最长的路径需要理解,在代码初始化邻接表时,我们需要在邻接表边结点中添加活动的权值,另外邻接表顶点结点中与拓扑排序中的一致,在求最早发生时间时我们同样使用拓扑排序,在原本的拓扑排序中再加入一个数组etv用于存储各活动最早发生时间,思路就是每个活动取Max{当前活动的前驱活动的最早发生时间+当前活动长度},这样我们的最早发生时间就求好了,同时在拓扑排序的时候再加入一个栈stack2用于存储逆向拓扑排序序列,这用于计算最晚发生时间,最晚发生时间的计算是倒着来的,首先先把关键路径长度(活动最早发生时间etv数组的汇点值【最后一个】即关键路径长度)赋值给最晚发生时间长度数组ltv,我们从刚才存储逆向拓扑排序的栈中依次取出每一个活动事件,然后比较Min{当前活动的最晚时间-邻接点的活动长度},与找最早活动发生时间的比较方式相反,前者从源点到汇点计算,后者从汇点到源点计算,最后比较活动的最早开始时间和最晚发生时间,为了代码实现方便,我们利用当前结点的最早发生时间与邻接结点的最晚活动发生时间-邻接结点的活动时间进行比较,如果活动的最早发生时间和最晚发生时间一致就说明找到关键活动也就可以直接输出对应的关键路径,这里你可能想问不是都求出每个活动的最早发生时间和最晚发生时间怎么不直接输出还要与邻接结点比较,这是因为我们需要打印关键路径,单个结点不好判断关键路径,用邻接点辅助判断,就用邻接点结合当前结点一起参与就容易比较,当然如果不想用邻接结点进行辅助计算,那么也可以先把关键路径的结点、对应的活动长度存储下来,然后在依次两两打印就是关键路径,打印路径的实现方法不唯一。
我们将创建下面的图,依然采用邻接表存储,手动头插法
关键路径如下红色标记的路线
A(0)->C(4)->D(4+8)->E(4+8+3)->G(4+8+3+4)->I(4+8+3+4+5)->J(4+8+3+4+5+3)
初始化邻接表结果
寻找D点(下标对应k、指针e)最早活动时间
想法:站在邻接点看前驱结点(下标对应gettop)
寻找最早活动时间核心代码(etv数组记录活动最早发生时间)
/* 求各顶点事件的最早发生时间etv值,gettop为当前点,e、k为邻接点 */
if ((etv[gettop] + e->weight) > etv[k]) etv[k] = etv[gettop] + e->weight;
寻找A(代码中下标对应gettop)点最晚活动时间
想法:站在当前点看邻接结点(下标对应k、指针e)
寻找最晚活动时间核心代码(ltv数组记录活动最晚发生时间)
1.初始化ltv(全部初始值为关键路径长度27)
ltv = (int*)malloc(GL->numNodes * sizeof(int));/* 事件最早发生时间数组 */for (i = 0; i < GL->numNodes; i++)ltv[i] = etv[GL->numNodes - 1]; /* 初始化 */
2.进行比较
if (ltv[k] - e->weight < ltv[gettop]) //gettop当前点,k代表邻接点ltv[gettop] = ltv[k] - e->weight; //更新当前点最晚发生时间
打印关键路径的关键比较(当前点的最早活动时间=?当前点的最晚活动时间(邻接点辅助计算=邻接点活动最晚发发生时间-活动长度))
打印关键路径代码(遍历邻接表来比较寻找)
for (j = 0; j < GL->numNodes; j++) /* 求ete,lte和关键活动 */{for (e = GL->adjList[j].first; e; e = e->next){k = e->adjvex;ete = etv[j]; /* 活动最早发生时间 */lte = ltv[k] - e->weight; /* 活动最迟发生时间 */if (ete == lte) /* 两者相等即在关键路径上 */printf("<v%c - v%c> length: %d \n", GL->adjList[j].data, GL->adjList[k].data, e->weight);}}
完整代码(邻接表、拓扑排序(包含逆拓扑排序)、顺序栈、关键路径计算)
#include<stdio.h>
#include<stdlib.h>#define MAXVEX 10 // 最大顶点数
#define OK 1
#define ERROR 0
typedef int Status;
typedef char VertexType; // 顶点类型,使用字符表示
typedef int EdgeType; // 边上的权值类型,使用整数表示
typedef int Boolean; // 布尔类型
char str[] = "ABCDEFGHIJ"; // 顶点数据
int* etv, * ltv;//用于存储事件最早发生的事件和最迟发生时间
int* stack2;//存储逆向拓扑排序
int top2;//stack2的栈顶指针
// 边表结点
typedef struct EdgeNode {int adjvex; // 顶点下标,表示该边的终点int weight; //边的权值,也代表活动struct EdgeNode* next; // 指向下一条边的指针
} EdgeNode;// 顶点结点
typedef struct VertexNode {int in; VertexType data; // 顶点数据EdgeNode* first; // 指向该顶点的第一条边
} VertexNode, AdjList[MAXVEX];// 图的邻接表表示
typedef struct {AdjList adjList; // 顶点数组int numNodes; // 图的顶点数int numEdges; // 图的边数
} GraphAdjList;//关键路径的邻接表创建
void CreateALGraph(GraphAdjList* G) {int i, j;EdgeNode* e = NULL;// 初始化邻接表for (i = 0; i < G->numNodes; i++) {G->adjList[i].data = str[i]; // 设置顶点数据G->adjList[i].first = NULL; // 边表初始化为空}//初始化入度,顶点信息G->adjList[0].in = 0;G->adjList[0].data = str[0];G->adjList[1].in = 1;G->adjList[1].data = str[1];G->adjList[2].in = 1;G->adjList[2].data = str[2];G->adjList[3].in = 2;G->adjList[3].data = str[3];G->adjList[4].in = 2;G->adjList[4].data = str[4];G->adjList[5].in = 1;G->adjList[5].data = str[5];G->adjList[6].in = 2;G->adjList[6].data = str[6];G->adjList[7].in = 1;G->adjList[7].data = str[7];G->adjList[8].in = 1;G->adjList[8].data = str[8];G->adjList[9].in = 2;G->adjList[9].data = str[9];// 添加边 A->B->Ce = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 2; // 邻接顶点序号为Ce->weight = 4;e->next = G->adjList[0].first; // 插入到邻接表的第一个位置G->adjList[0].first = e;e = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 1; // 邻接顶点序号为Be->weight = 3;e->next = G->adjList[0].first; // 插入到邻接表的第一个位置G->adjList[0].first = e;// 添加边 B->D->Ee = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 4; // 邻接顶点序号为Ee->weight = 6;e->next = G->adjList[1].first;G->adjList[1].first = e;e = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 3; // 邻接顶点序号为De->weight = 5;e->next = G->adjList[1].first;G->adjList[1].first = e;// 添加边 C->D->Fe = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 5; // 邻接顶点序号为Fe->weight = 7;e->next = G->adjList[2].first;G->adjList[2].first = e;e = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 3; // 邻接顶点序号为De->weight = 8;e->next = G->adjList[2].first;G->adjList[2].first = e;// 添加边 D->Ee = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 4; // 邻接顶点序号为Ee->weight = 3;e->next = G->adjList[3].first;G->adjList[3].first = e;// 添加边 E->G->He = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 7; // 邻接顶点序号为He->weight = 9;e->next = G->adjList[4].first;G->adjList[4].first = e;e = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 6; // 邻接顶点序号为Ge->weight = 4;e->next = G->adjList[4].first;G->adjList[4].first = e;// 添加边 F->Ge = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 6; // 邻接顶点序号为Ge->weight = 6;e->next = G->adjList[5].first;G->adjList[5].first = e;// 添加边 G->Ie = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 8; // 邻接顶点序号为Ie->weight = 5;e->next = G->adjList[6].first;G->adjList[6].first = e;// 添加边 H->Je = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 9; // 邻接顶点序号为Je->weight = 2;e->next = G->adjList[7].first;G->adjList[7].first = e;// 添加边 I->Je = (EdgeNode*)malloc(sizeof(EdgeNode));e->adjvex = 9; // 邻接顶点序号为Ee->weight = 3;e->next = G->adjList[8].first;G->adjList[8].first = e;// 打印邻接表(字母)和入度EdgeNode* p = NULL;printf("边结点按邻接顶点字母打印 (入度):\n");for (i = 0; i < G->numNodes; i++) {printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);p = G->adjList[i].first;while (p != NULL) {printf("->%c", G->adjList[p->adjvex].data); // 打印邻接顶点字母p = p->next;}printf("\n");}// 打印邻接表(下标)和入度printf("\n边结点按邻接下标打印 (入度):\n");for (i = 0; i < G->numNodes; i++) {printf("%c (入度: %d)", G->adjList[i].data, G->adjList[i].in);p = G->adjList[i].first;while (p != NULL) {printf("->%d", p->adjvex); // 打印邻接顶点下标p = p->next;}printf("\n");}
}/* 拓扑排序 */
Status TopologicalSort(GraphAdjList *GL)
{ /* 若GL无回路,则输出拓扑排序序列并返回1,若有回路返回0。 */EdgeNode* e;int i, k, gettop;int top = 0; /* 用于栈指针下标 */int count = 0;/* 用于统计输出顶点的个数 */int* stack; /* 建栈将入度为0的顶点入栈 */stack = (int*)malloc(GL->numNodes * sizeof(int));for (i = 0; i < GL->numNodes; i++)if (0 == GL->adjList[i].in) /* 将入度为0的顶点入栈 */stack[++top] = i;top2 = 0;etv = (int*)malloc(GL->numNodes * sizeof(int)); /* 事件最早发生时间数组 */for (i = 0; i < GL->numNodes; i++)etv[i] = 0; /* 初始化 */stack2 = (int*)malloc(GL->numNodes * sizeof(int));/* 初始化拓扑序列栈 */printf("\nTopologicalSort:\n");while (top != 0){gettop = stack[top--];printf("%c -> ", GL->adjList[gettop].data);count++; /* 输出i号顶点,并计数 */stack2[++top2] = gettop; /* 将弹出的顶点序号压入拓扑序列的栈 */for (e = GL->adjList[gettop].first; e; e = e->next){k = e->adjvex;if (!(--GL->adjList[k].in)) /* 将i号顶点的邻接点的入度减1,如果减1后为0,则入栈 */{stack[++top] = k;}if ((etv[gettop] + e->weight) > etv[k]) /* 求各顶点事件的最早发生时间etv值,gettop为当前点,e、k为邻接点 */etv[k] = etv[gettop] + e->weight; }}printf("\n");if (count < GL->numNodes)return ERROR;elsereturn OK;
}/* 求关键路径,GL为有向网,输出G的各项关键活动 */
void CriticalPath(GraphAdjList *GL)
{EdgeNode* e;int i, gettop, k, j;int ete, lte; /* 声明活动最早发生时间和最迟发生时间变量 */TopologicalSort(&(*GL)); /* 求拓扑序列,计算数组etv和stack2的值 */ltv = (int*)malloc(GL->numNodes * sizeof(int));/* 事件最早发生时间数组 */for (i = 0; i < GL->numNodes; i++)ltv[i] = etv[GL->numNodes - 1]; /* 初始化 */printf("\netv(最早发生时间)\ltv(最晚发生时间)数组对应结点:A B C D E F G H I J \n");printf("etv(最早发生时间)\ltv(最晚发生时间)数组对应下标:0 1 2 3 4 5 6 7 8 9 \n");printf("etv:");for (i = 0; i < GL->numNodes; i++)printf("%d -> ", etv[i]);printf("\n");while (top2 != 0) /* 出栈是求ltv */{gettop = stack2[top2--];for (e = GL->adjList[gettop].first; e; e = e->next) /* 求各顶点事件的最迟发生时间ltv值 */{k = e->adjvex;if (ltv[k] - e->weight < ltv[gettop]) //gettop当前点,k代表邻接点ltv[gettop] = ltv[k] - e->weight; //更新当前点最晚发生时间}}printf("ltv:");for (i = 0; i < GL->numNodes; i++)printf("%d -> ", ltv[i]);printf("\n\n");printf("关键路径-活动时间长度\n");for (j = 0; j < GL->numNodes; j++) /* 求ete,lte和关键活动 */{for (e = GL->adjList[j].first; e; e = e->next){k = e->adjvex;ete = etv[j]; /* 活动最早发生时间 */lte = ltv[k] - e->weight; /* 活动最迟发生时间 */if (ete == lte) /* 两者相等即在关键路径上 */printf("<v%c - v%c> length: %d \n", GL->adjList[j].data, GL->adjList[k].data, e->weight);}}
}int main() {GraphAdjList G;G.numNodes = MAXVEX; // 设置顶点数//初始化邻接表CreateALGraph(&G);//关键路径算法CriticalPath(&G);return 0;
}
运行结果