关键路径详解🔥

181 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

算法简介

关键路径算法是一种用来解决 事件最短时间问题 的算法

现实案例:

小莹周末想跟对象出去玩,但是老师留了一堆作业,所以她在出去玩之前要先将作业完成,写完作业之后立马去约会肯定是不行滴,她还需要化妆,然后再美美地约对象去玩,然后等玩到深夜,回来还要卸妆,洗澡,洗衣服,如果玩的太晚,这些事情可能就会耽搁她睡觉。于是她得出结论, 是时候该分手了。。。。

由上可见,小莹在出去玩之前需要完成一些前置事件,之后才能进行 跟对象出去玩 这个事件,因此

跟对象出去玩 这个事件的 最早发生时间 取决于所有前置事件的完成时间。 然后,小莹在回家后为了不推迟睡觉时间,她就需要在睡前把各种杂务事做完,所以 杂务事件 的 最晚发生时间 是睡觉时间往前减去做完杂务所需的时间。

那么 关键路径就是 最早发生时间等于最晚发生时间的路径。 在这条路径上的活动集合,我们不能拖延,一旦这些活动的前置事件完成,我们就要立马去做这些事,否则就会耽误这个活动集合后面的活动。

在生活中,每件事的限定时间一般都会超出我们需要花的时间很多, 也就是说,当我们做这件事之前可以

拖延一段时间准备一段时间。所以经常就会有人在时间截止时间(DDL)前不久才将事情做完。 在日常生活中,适当增加关键路径活动,可以提高我们的工作效率。

算法分析

在关键路径算法中我们使用的图是------ AOE(Activity On Edge)网:活动在边上,边有权重链接.

算法思路

在这个算法中我们先求出每个事件的最早发生时间和最晚发生时间,然后再从起点开始遍历整张图,查看哪些点的最早发生时间等于最晚发生时间,这些点连接成的路径就是关键路径。

算法步骤

求解事件最早发生时间

对图做拓扑排序,接着将起点事件的最早发生时间设置为0。然后按照拓扑正序,我们从起点出发,设当前点为u,u的后置事件为v。

v的最早发生事件为(v的最早发生时间 与 u点的最早发生时间加上u事件的消耗时间 )取最大值,遵循了 事件的 最早发生时间 取决于所有前置事件的完成时间。代码如下:

for (int j = bian[0][x]; j; j = e[j].last) {
    int y = e[j].to;
    early[y] = max(early[y], early[x] + e[j].d);
}
求解事件最晚发生时间

我们将终点的最晚发生时间设置为终点的最早发生时间。然后按照拓扑反序,我们从汇点(终点)出发,设当前点为u,u的前置事件为v。

v的最晚发生时间等于 (v的最晚发生时间 与 u点最晚发生时间减去该事件的消耗时间)取最小值,遵循了 事件的 最晚发生时间 取决于后置事件的完成时间- 后置事件消耗的时间。代码如下

for (int j = bian[1][x]; j; j = e[j].last) {
    int y = e[j].to;
    last[y] = min(last[y], last[x] - e[j].d);
}

最后我们用dfs从起点遍历图,寻找最早发生时间等于最晚发生时间的点,他们连接起来的路径就是关键路径啦

详细代码:

代码默认起点为0,终点为n-1

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int n = 2333;
int n, m, u, v, d, deg[n], early[n], last[n],bian[2][n],cnt,top[n];
vector<int>path;
vector<vector<int>>ans;
struct edge {
	int to, last, d;
}e[n<<1];
void addd(int u, int v, int d,int bian[]) {
	e[++cnt].d = d;
	e[cnt].to = v;
	e[cnt].last = bian[u];
	bian[u] = cnt;
}
void topsort() {
	int head = 0, tail = 0;
	top[0] = 0;
	while (head <= tail) {
		int t = top[head++];
		for (int i = bian[0][t]; i; i = e[i].last) {
			int j = e[i].to;
			if (--deg[j] == 0)
				top[++tail] = j;
		}
	}
}
void critical_path() {
	topsort();
	for (int i = 0; i <= n; i++) {//队列遍历顺序
		int x = top[i];
		for (int j = bian[0][x]; j; j = e[j].last) {
			int y = e[j].to;
			early[y] = max(early[y], early[x] + e[j].d);
		}
	}
	memset(last, 0x3f, sizeof(last));
	last[n] = early[n];
	for (int i = n; i >= 0; i--) {
		int x = top[i];
		for (int j = bian[1][x]; j; j = e[j].last) {
			int y = e[j].to;
			last[y] = min(last[y], last[x] - e[j].d);
		}
	}
}
void dfs(int u) {
	path.push_back(u);
	if (!bian[0][u])ans.push_back(path);
	for (int i = bian[0][u]; i; i = e[i].last) {
		int y = e[i].to;
		if(early[y]==last[y])
			dfs(y);
	}path.pop_back();
}
int main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
		cin >> u >> v >> d, addd(u, v, d, bian[0]), addd(v, u,d, bian[1]),deg[v]++;
	critical_path();
	dfs(0);
	printf("关键路径为\n");
	for (auto &t : ans) {
		for (int i = 0; i < t.size(); i++)
			cout << t[i] << ' ';
		cout << endl;
	}
	return 0;
}