一、核心前提
- AOE 网 :有向无环、带权边,边代表活动,顶点代表事件;源点(起点:入度为 0)、汇点(终点:出度为 0)。
- 关键路径 :从源点 → 汇点 的最长路径 ;决定工程最短完成时间 ,路径上活动为关键活动。
二、四大必背参数(考试核心)
设:顶点为事件,边为活动
- ve[i] :事件 i 的最早发生时间
- vl[i] :事件 i 的最晚发生时间
- e :活动最早开始时间
- l :活动最晚开始时间 l−e=0 → 该活动为关键活动
计算公式
-
最早发生时间(正向拓扑)源点权
-
最晚发生时间(逆拓扑)汇点汇点权
-
活动时间
- 活动 <u,v>:e=ve[u],l=vl[v]−w
三、算法步骤
- 对 AOE 网做拓扑排序
- 按拓扑序正向遍历,求数组 ve
- 按逆拓扑序反向遍历,求数组 vl
- 遍历所有边,计算 、
- l−e=0 的边 → 关键活动,串联即为关键路径
四、完整 C 语言代码(邻接表・带详细注释)
#include <stdio.h>
#include <string.h>
#define MAXN 105
#define INF 0x3f3f3f3f
// 边结点:终点、权值、后继边
typedef struct Edge{
int to,w;
struct Edge *next;
}Edge;
Edge* G[MAXN];
int n,m;
int in[MAXN]; // 入度
int topo[MAXN],cnt; // 拓扑序列
int ve[MAXN],vl[MAXN];
// 加边
void addEdge(int u,int v,int w){
Edge *p=(Edge*)malloc(sizeof(Edge));
p->to=v; p->w=w; p->next=G[u];
G[u]=p;
in[v]++;
}
// 1.拓扑排序,得到topo数组
void TopSort(){
int stk[MAXN],top=0;
cnt=0;
for(int i=1;i<=n;i++)
if(in[i]==0) stk[top++]=i;
while(top>0){
int u=stk[--top];
topo[++cnt]=u;
for(Edge *p=G[u];p;p=p->next){
int v=p->to;
in[v]--;
if(in[v]==0) stk[top++]=v;
}
}
}
// 2.求ve:正向拓扑
void getVE(){
memset(ve,0,sizeof(ve));
for(int i=1;i<=cnt;i++){
int u=topo[i];
for(Edge *p=G[u];p;p=p->next){
int v=p->to;
if(ve[v] < ve[u]+p->w)
ve[v] = ve[u]+p->w;
}
}
}
// 3.求vl:逆拓扑
void getVL(){
for(int i=1;i<=n;i++) vl[i]=ve[topo[cnt]];
for(int i=cnt;i>=1;i--){
int u=topo[i];
for(Edge *p=G[u];p;p=p->next){
int v=p->to;
if(vl[u] > vl[v]-p->w)
vl[u] = vl[v]-p->w;
}
}
}
// 4.查找并输出关键活动、关键路径
void KeyPath(){
printf("=====关键活动=====\n");
for(int u=1;u<=n;u++){
for(Edge *p=G[u];p;p=p->next){
int v=p->to;
int e = ve[u];
int l = vl[v] - p->w;
if(e == l){
printf("关键活动:%d -> %d 权=%d\n",u,v,p->w);
}
}
}
printf("工程最短工期:%d\n",ve[topo[cnt]]);
}
int main(){
scanf("%d%d",&n,&m);
memset(G,0,sizeof(G));
memset(in,0,sizeof(in));
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addEdge(u,v,w);
}
TopSort();
getVE();
getVL();
KeyPath();
return 0;
}
五、考点必背
- 关键路径仅存在于 AOE 有向无环图
- 关键路径 = 源点到汇点最长路径
- 关键活动:l−e=0
- 关键活动推迟→整体工期延长 ;非关键活动有时间余量,可适当延迟
- 拓扑求 ve,逆拓扑求 vl
- 关键路径可能多条