1、AOE网
AOE网是⼀个带权的有向无环图,其中顶点表示事件(Event),弧表示活动,权表示活动持续的时间。
2、源点和汇点
AOE网中入度为零的点称为源点,将出度为零的点称为汇点。
3、关键路径
完成工程的最短时间是从源点到汇点的最长路径的长度。路径长度最长的路径就叫做关键路径。
ETV(Earliest Time Of Vertex):事件最早发生时间,就是顶点的最早发生时间。LTV(Latest Time Of Vertex):事件最晚发生时间,就是每个顶点对应的事件最晚需要开始的时间,如果超出此时间将会延误整个工期。ETE(Earliest Time Of Edge):活动的最早开工时间,就是弧的最早发⽣时间。LTE(Lastest Time of Edge):活动的最晚开工时间,就是不推迟工期的最晚开工时间。
4、关键路径算法
关键路径(Critical Path)针对有向无环加权图(AOE网,工程活动网络),是从源点(入度为0)到汇点(出度为0)的最长路径。路径上的活动称为关键活动,关键路径长度决定整个工程的最短完成时间,关键活动延时会直接导致整体工程延时。
步骤1:拓扑排序
对AOE网进行拓扑排序,得到有序节点序列,若图存在环则无关键路径,工程无法正常进行。
步骤2:计算最早发生时间 ve\[\]
vei:节点i(事件)的最早发生时间,从源点正向推导。
公式:源点 vestart = 0;遍历拓扑序列,对当前节点u的所有出边 u→v(权重w),执行 vev = max(vev, veu + w)。
步骤3:计算最晚发生时间 vl\[\]
vli:节点i(事件)的最晚发生时间,不推迟整体工程工期,从汇点逆向推导。
公式:汇点 vlend = veend;逆序遍历拓扑序列,对当前节点v的所有入边 u→v(权重w),执行 vlu = min(vlu, vlv - w)。
步骤4:筛选关键节点与关键路径
若节点满足 vei == vli,该节点为关键节点;由相邻关键节点组成的路径,即为关键路径。
cpp
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 105;
const int INF = 0x3f3f3f3f;
// 邻接表存储: to目标节点, weight边权, next下一条边
struct Edge {
int to, w;
Edge(int t, int ww) : to(t), w(ww) {}
};
vector<Edge> adj[MAXN]; // 正向邻接表(拓扑、求ve)
vector<Edge> reverseAdj[MAXN]; // 逆向邻接表(求vl)
int inDegree[MAXN]; // 入度数组
vector<int> topoSeq; // 存储拓扑排序序列
int ve[MAXN], vl[MAXN]; // 最早、最晚发生时间
// 拓扑排序
bool topoSort(int n) {
queue<int> q;
// 初始化入度为0的节点入队
for (int i = 1; i <= n; i++) {
if (inDegree[i] == 0)
q.push(i);
}
while (!q.empty()) {
int u = q.front();
q.pop();
topoSeq.push_back(u);
// 遍历出边,更新后继节点入度
for (Edge e : adj[u]) {
int v = e.to;
inDegree[v]--;
if (inDegree[v] == 0)
q.push(v);
}
}
// 拓扑序列长度不等于节点数,说明有环,无关键路径
return topoSeq.size() == n;
}
// 计算关键路径
void criticalPath(int n) {
// 1. 初始化ve数组,正向求最早发生时间
memset(ve, 0, sizeof(ve));
for (int u : topoSeq) {
for (Edge e : adj[u]) {
int v = e.to, w = e.w;
if (ve[v] < ve[u] + w) {
ve[v] = ve[u] + w;
}
}
}
// 2. 初始化vl数组,逆向求最晚发生时间
int endNode = topoSeq.back();
fill(vl, vl + n + 1, INF);
vl[endNode] = ve[endNode];
// 逆序遍历拓扑序列
for (int i = topoSeq.size() - 1; i >= 0; i--) {
int v = topoSeq[i];
for (Edge e : reverseAdj[v]) {
int u = e.to, w = e.w;
if (vl[u] > vl[v] - w) {
vl[u] = vl[v] - w;
}
}
}
// 3. 输出关键节点和关键路径
cout << "===== 关键路径计算结果 =====" << endl;
cout << "工程最短总工期:" << ve[endNode] << endl;
cout << "关键节点:";
for (int i = 1; i <= n; i++) {
if (ve[i] == vl[i]) {
cout << i << " ";
}
}
cout << endl;
cout << "关键活动(边):" << endl;
for (int u = 1; u <= n; u++) {
// 筛选关键节点之间的边
if (ve[u] != vl[u]) continue;
for (Edge e : adj[u]) {
int v = e.to, w = e.w;
if (ve[v] == vl[v] && ve[v] == ve[u] + w) {
cout << "节点" << u << " -> 节点" << v << " (工期:" << w << ")" << endl;
}
}
}
}
int main() {
// 初始化
memset(inDegree, 0, sizeof(inDegree));
int n, m;
cout << "请输入节点数、边数:";
cin >> n >> m;
// 构建正向、逆向邻接表
for (int i = 0; i < m; i++) {
int u, v, w;
cout << "请输入第" << i + 1 << "条边(起点 终点 权重):";
cin >> u >> v >> w;
adj[u].emplace_back(v, w);
reverseAdj[v].emplace_back(u, w);
inDegree[v]++;
}
// 拓扑排序判环
if (!topoSort(n)) {
cout << "图中存在环路,无关键路径!" << endl;
return 0;
}
// 计算并输出关键路径
criticalPath(n);
return 0;
}