数据结构之关键路径

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;
}
相关推荐
hansang_IR8 小时前
【题解】LC:倍增 / 区间并查集(Range Parallel Unionfind)
c++·算法·并查集
Tisfy10 小时前
LeetCode 3731.找出缺失的元素:哈希 / 排序
算法·leetcode·哈希算法·排序·哈希表
lucas_AI10 小时前
Q-CueGraph:你的多模态大模型会 zoom,但真的知道该看哪儿吗?
人工智能·算法
kaixin_啊啊10 小时前
test_机器学习算法学习
学习·算法·机器学习
liulilittle10 小时前
MOE路由:路由(logits: top-k/8)
c++·人工智能·算法·机器学习·llm
旖旎夜光10 小时前
LeetCode 11:盛最多水的容器(双指针问题) —— 题解
数据结构·c++·算法·leetcode·双指针
一只齐刘海的猫11 小时前
【Leetcode】乘积最大子数组
算法·leetcode·职场和发展
余生皆假期-12 小时前
傅里叶变换和拉普拉斯变换
人工智能·算法·机器学习
不一样的故事12612 小时前
芯片设计是一个高度复杂、分工精细的系统工程
数据结构·经验分享
数模竞赛Paid answer13 小时前
2026年五一杯数学建模C题边坡预警问题求解全过程文档及程序
算法·数学建模·五一杯