算法小讲堂之关键路径

前言

AOE网

在带权有向图中, 以顶点表示 事件 ,以 有向边 表示 活动 ,以 边上的权值 表示完成该活动的开销(如完成活动所需的时间),称之为用边表示活动的网络,简称 A O E AOE AOE

A O E AOE AOE 网和 A O V AOV AOV 网都是有向无环图,不同之处在于, A O E AOE AOE 网的边有权值,而 A O V AOV AOV 网无权值,仅表示顶点之间的前后关系, A O E AOE AOE 网的两个性质如下:

  • ①只有在 某顶点所代表的事件发生后 ,从该顶点出发的各有向边所代表的活动才能开始
  • ②只有在进入某顶点的各 有向边 所代表的 活动 都已 结束 时,该顶点所代表的事件才能发生。

注意

  • A O E AOE AOE 网中,有些活动是可以并行进行
  • 从源点到汇点的有向路径可能有多条, 并且这些 路径长度 可能不同,因此从源点到汇点中 最大路径长度(即最大边权和) 的路径称为 关键路径 ,关键路径上的活动称为 关键活动 ,显然这个关键路径可能有多条
  • 只有 所有路径的活动 都完成,整个工程才算结束

事件 V k V_k Vk 的最早发生时间 V e ( K ) Ve(K) Ve(K)

它是指从源点 V 1 V_1 V1 到顶点 V k V_k Vk 的最长路径长度。事件 V k V_k Vk 的最早发生时间决定了所有从 V k V_k Vk 开始的 活动能够开工的 最早时间 。 可用下面的边推公式来计算:

  • V e ( 源点 ) = 0 Ve(源点)=0 Ve(源点)=0
  • V e ( k ) = M a x V e ( j ) + W e i g h t ( V j , V k ) Ve(k) = Max{Ve(j)+Weight(V_j,V_k)} Ve(k)=MaxVe(j)+Weight(Vj,Vk) ,其中的 V k V_k Vk V j V_j Vj 的任意后继结点, W e i g h t ( V j , V k ) Weight(V_j,V_k) Weight(Vj,Vk) 表示 < V j , V k > <V_j,V_k> <Vj,Vk> 边上的权值

简单理解一下,假设我们当前走到了点 j j j ,然后我们将点 j j j 的所有出度点 k k k V e ( k ) Ve(k) Ve(k) 值和当前 j j j 点的 V e ( j ) Ve(j) Ve(j)加边权 < j , k > <j,k> <j,k> 进行比较,如果后者大就更新为后者(就是尽可能让 V e ( k ) Ve(k) Ve(k) 更大),看到这个我们很自然想起了拓扑排序,我们可以再拓扑排序的基础上进行计算

这里说一下为什么称为 最早发生时间 ,毕竟刚接触怎么看都是 “最迟” ,想象一下,这个 A O E AOE AOE 网是从源点出发,可能会存在多个活动并行进行的情况,但目的都是到达汇点,那么在这个过程中,可能通过某些活动(弧)早到,有的活动(弧)晚到,其中最晚到的活动(弧)就是关键活动,对于那些能 “早到” 的路径来说就会存在两个情况,是先完成活动再等待那个所谓的关键活动完成,还是先等待关键活动,然后再完成这个活动,这里先完成当前的活动就是最早发生时间,而等待后再来完成就是一个活动开始最迟时间,很显然 在关键路径上的最早发生时间和最迟发生时间是一样的

事件 V k V_k Vk 的最迟发生事件 V l ( K ) Vl(K) Vl(K)

它是指在不推迟整个工程完成的前提下, 即保证它的后继事件 V j V_j Vj 在其最迟发生时间 V l ( j ) Vl(j) Vl(j) 能够 发生时,该事件最迟必须发生的时间 。 可用下面的递推公式来计算:

  • V l ( 汇点 ) = V e ( 汇点 ) Vl(汇点)=Ve(汇点) Vl(汇点)=Ve(汇点)
  • V l ( k ) = M i n V l ( j ) + W e i g h t ( V k , V j ) Vl(k) = Min{Vl(j) + Weight(V_k,V_j)} Vl(k)=MinVl(j)+Weight(Vk,Vj) ,其中点 V k V_k Vk V j V_j Vj 的任意前驱结点, W e i g h t ( V k , V j ) Weight(V_k,V_j) Weight(Vk,Vj) 表示 < V k , V j > <V_k,V_j> <Vk,Vj> 边上的权值

这里其实和上面流程其实是类似的,只不过需要从汇点出发一直到源点,那么这个很显然就是一个逆拓扑排序的操作,然后不断地尽可能最小化 V l ( k ) Vl(k) Vl(k) 的值,当然对于关键路径上的活动的 V l ( k ) Vl(k) Vl(k) V e ( k ) Ve(k) Ve(k) 是相同的

活动 a i a_i ai 的最早开始时间 e ( i ) e(i) e(i)

它是指该活动弧的起点所表示的事件的最早发生时间。若边 < V k , V j > <V_k, V_j> Vk,Vj> 表示活动 a i a_i ai ,则有 e ( i ) = V e ( k ) e(i) =Ve(k) e(i)=Ve(k)

活动 a i a_i ai 的最迟开始时间 l ( i ) l(i) l(i)

它是指该活动弧的终点所表示 事件的最迟发生时间 与该 活动所需时间 之差。若边 < V k , V j > <V_k,V_j> VkVj 表示活动 a i a_i ai ,则有 l ( i ) = v l ( j ) − W e i g h t ( V k , V j ) l(i) = vl(j) - Weight(V_k,V_j) l(i)=vl(j)Weight(Vk,Vj)

活动的差额d

它是指该活动完成的时间余量,即在 不增加完成整个工程所需总时间 的情况下,活动 a i a_i ai 可以拖延的时间,若一个活动的时间余量为 0 0 0 则说明这个活动必须要如期完成,否则会延长整个工期,换句话说就是 关键活动
d ( i ) = l ( i ) − e ( i ) d(i) = l(i) - e(i) d(i)=l(i)e(i)

原理

求解关键路径的算法步骤大体为五步:

  • ①从源点出友,令 V e ( 源点 ) = 0 Ve(源点) = 0 Ve(源点)=0,按拓扑有序求其余顶点的最早发生时间 V e ( i ) Ve(i) Ve(i)
  • ②从汇点出友,令 V i ( 汇点 ) = V e ( 汇点 ) Vi(汇点) = Ve(汇点) Vi(汇点)=Ve(汇点) , 按逆拓拓扑有序求其余顶点的最迟发生时间 V l ( i ) Vl(i) Vl(i)
  • ③根据各顶点的 v e ( i ) ve(i) ve(i) 值求所有孤的最早开始时间 e ( i ) e(i) e(i)
  • ④根据各顶点的 v l ( i ) vl(i) vl(i) 值求所有孤的最早开始时间 l ( i ) l(i) l(i)
  • ⑤求 A O E AOE AOE 网 中所有活动的差额 d ( ) d() d(),找出所有 d ( ) = 0 d() = 0 d()=0 的活动构成关键路径(可能不唯一)

举例:
在这里插入图片描述

注意:
因为 A O E AOE AOE 网中的关键路径可能不唯一,提高某一个活动的效率可能并不能加快整体工程的效率
关键路径的难点在于理解事件的 最早和最迟时间

代码实现

思路较为简单,且未碰到编程类的题目,博主这里就不做实现了,可以参考另一位大佬的实现代码:
博客链接:https://blog.csdn.net/weixin_42638946/article/details/120154312

/*
输出关键路径
input:
9 11
1 2 6
1 3 4
1 4 5
2 5 1
3 5 1
4 6 2
5 7 9
5 8 7
6 8 4
7 9 2
8 9 4
output:
1 2 5 8 9 
1 2 5 7 9 


*/

#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

const int N = 1010, M = 100010;

int n, m;
int hs[N], ht[N], e[M], w[M], ne[M], idx;  // 顶点表示事件,边表示活动

int ve[N], vl[N];  // 事件允许的最早发生时间, 事件允许的最晚发生时间
// int ee[M], el[M];  // 活动的最早发生时间, 活动的最晚发生时间

int d[N];  // 每个点的入度
int q[N];  // 拓扑排序数组

vector<int> path;
vector<vector<int>> res;

void add(int h[], int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void topsort() {

    int hh = 0, tt = 0;
    q[0] = 1;
    while (hh <= tt) {
        int t = q[hh++];
        for (int i = hs[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (--d[j] == 0)
                q[++tt] = j;
        }
    }
}

void critical_path() {

    topsort();

    // 求解事件允许的最早发生时间
    ve[1] = 0;
    for (int k = 0; k < n; k++) {
        int ver = q[k];
        for (int i = hs[ver]; i != -1; i = ne[i]) {
            int j = e[i];
            ve[j] = max(ve[j], ve[ver] + w[i]);
        }
    }

    // 求解事件允许的最晚发生时间
    memset(vl, 0x3f, sizeof vl);
    vl[n] = ve[n];
    for (int k = n - 1; k >= 0; k--) {
        int ver = q[k];
        for (int i = ht[ver]; i != -1; i = ne[i]) {
            int j = e[i];
            vl[j] = min(vl[j], vl[ver] - w[i]);
        }
    }
}

void dfs(int u) {
    
    path.push_back(u);
    
    if (hs[u] == -1) {
        res.push_back(path);
    }
    
    for (int i = hs[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (vl[j] - ve[j] == 0)
            dfs(j);
    }
    
    path.pop_back();
}

int main() {

    cin >> n >> m;
    memset(hs, -1, sizeof hs);
    memset(ht, -1, sizeof ht);
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        add(hs, a, b, c);
        add(ht, b, a, c);
        d[b]++;
    }

    // 输入满足1号点是源点,n号点是汇点
    critical_path();
    
    dfs(1);
    
    for (auto &t : res) {
        for (int i = 0; i < t.size(); i++)
            cout << t[i] << ' ';
        cout << endl;
    }

    return 0;
}

这个 A O E AOE AOE 图大概长这样:
在这里插入图片描述

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MangataTS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值