原文链接
前言:首先关键路径是针对DAG图来说的,我们通常用AOE网来表示一个工程的进行过程,AOV网可以转换为AOE网,AOE网是没有环的,通常关键路径求解需要弄清楚以下四个概念:
事件最早发生时间Ve[u]、事件最晚发生时间Vl[u]
活动最早发生时间e[r]、活动最晚发生时间l[r]
在AOE网(Activity On Edge,用带权的边表示活动,用顶点表示事件的有向图)中,【其实一个事件(顶点)仅表示一个中介状态】首先,我们要求出每个事件(顶点)的最早发生时间Ve[u]和最晚发生时间Vl[u],“事件最早发生时间”可用拓扑排序来进行求解;而事件的最晚发生时间可以用逆拓扑来求解。以上两个求解出来后,就可以计算活动最早发生时间e[r]和活动最晚发生时间l[r]了,他们的关系如下:
e[r]=Ve[u]
l[r]+length[r]=Vl[v]
代码如下:
-
#include"stdafx.h"
-
#include<cstdio>
-
#include<stack>
-
#include<algorithm>
-
#include<cmath>
-
#include<iostream>
-
#include<vector>
-
#include<queue>
-
using
namespace
std;
-
const
int maxn =
500;
-
int Vertexnum, Edgenum;
-
struct node {
-
int v;
-
int weight;
-
};
-
vector<node> adj[maxn];
//邻接表存储DAG图
-
int innode[maxn] = {
0 };
//记录每个结点的入度
-
queue<
int> q;
-
stack<
int> s;
//运用一个栈,到时候实现逆拓扑
-
int Ve[maxn];
//事件发生的最早时间
-
int Vl[maxn];
//事件发生的最晚时间
-
bool topologicalsort() {
//拓扑排序
-
for (
int i =
0; i < Vertexnum; i++) {
//将所有入度为0的点加入到队列中
-
if (innode[i]==
0) {
-
q.push(i);
-
}
-
}
-
while (!q.empty()) {
-
int u = q.front();
//取队首元素
-
q.pop();
-
s.push(u);
//进入栈
-
for (
int i =
0; i < adj[u].size(); i++) {
//进行入度更新操作
-
int v = adj[u][i].v;
-
innode[v]--;
-
if (innode[v] ==
0) {
//如果入度为0的点,则入队
-
q.push(v);
-
}
-
if (Ve[u] + adj[u][i].weight > Ve[v]) {
//计算事件最早发生时间
-
Ve[v] = Ve[u] + adj[u][i].weight;
-
}
-
}
-
}
-
if (s.size() == Vertexnum)
return
true;
-
else
return
false;
//否则拓扑失败,说明此图是存在环,不是DAG图
-
}
-
void antitopologicalsort() {
//逆拓扑求事件最晚发生时间
-
while (!s.empty()) {
-
int u = s.top();
-
s.pop();
-
for (
int i =
0; i < adj[u].size(); i++) {
//计算事件最晚发生时间
-
int v = adj[u][i].v;
-
if (Vl[v] - adj[u][i].weight<Vl[u]) {
-
Vl[u] = Vl[v] - adj[u][i].weight;
-
}
-
}
-
}
-
}
-
void criticalpath() {
//关键路径
-
fill(Ve, Ve + maxn,
0);
//事件最早发生时间初始化
-
if (topologicalsort() ==
false)
return;
//拓扑失败
-
fill(Vl, Vl + maxn, Ve[Vertexnum -
1]);
//事件最晚发生时间初始化
-
antitopologicalsort();
-
cout <<
"关键路径长度:" << Ve[Vertexnum -
1] <<
"\n";
//输出关键路径长度
-
for (
int u =
0; u < Vertexnum; u++) {
//遍历所有的边
-
for (
int i =
0; i < adj[u].size(); i++) {
-
int v = adj[u][i].v;
//边的两个端点分别是u和v
-
int e = Ve[u];
//活动最早发生时间
-
int l = Vl[v] - adj[u][i].weight;
//活动最晚发生时间
-
if (e == l) {
-
cout << u <<
"->" << v <<
"\n";
//输出关键路径
-
}
-
}
-
}
-
}
-
int main() {
-
cin >> Vertexnum >> Edgenum;
-
int start, end, weight;
-
for (
int i =
0; i < Edgenum; i++) {
-
cin >> start >> end >> weight;
-
node N;
-
N.v = end;
-
N.weight = weight;
-
innode[end]++;
//入度加1
-
adj[start].push_back(N);
-
}
-
criticalpath();
//关键路径
-
return
0;
-
}
运行结果如下:
补充:其实求AOE网中的关键路径就是求DAG最长路路径,所以对于以上求DAG最长路还有一种更简单的办法:利用动态规划思想去解决,令dp[i]表示以i为源点的最长路,如果要求dp[i],就必须求出以源点i出发能够到达的顶点j的dp[j],而dp[j]的求解又是一样的思路,其中DAG图中出度为0的点v,他的dp[v]就是为0了,很显然,求解dp数组可以利用递归去求解。当求出所有的dp[i],其中dp值最大的就是DAG图的最长路径长度,代码如下:
以下算法实现的功能是:求出最长路径长度和输出最长路径序列
-
#include"stdafx.h"
-
#include<cstdio>
-
#include<stack>
-
#include<algorithm>
-
#include<cmath>
-
#include<iostream>
-
#include<vector>
-
#include<queue>
-
using
namespace
std;
-
const
int maxn =
500;
-
int Vertexnum, Edgenum;
-
struct node {
-
int v;
-
int weight;
-
};
-
vector<node> adj[maxn];
//邻接表存储DAG图
-
int outnode[maxn] = {
0 };
//记录每个顶点的出度
-
int dp[maxn];
-
void init() {
//dp数组初始化
-
fill(dp, dp + maxn,
-1);
-
for (
int i =
0; i < Vertexnum; i++) {
-
if (outnode[i] ==
0) {
//初始化出度为0的dp为0
-
dp[i] =
0;
-
}
-
}
-
}
-
vector<
int> sumpath[maxn];
//sumpath[i]数组表示:以i为源点的DAG最长路路径序列
-
int path[maxn];
//存放DAG最长路路径序列
-
int DFS(int index) {
//递归求解dp数组
-
if (dp[index] ==
0)
return dp[index];
//到达递归边界
-
for (
int i =
0; i < adj[index].size(); i++) {
-
int v = adj[index][i].v;
-
int weight = adj[index][i].weight;
-
int distv = DFS(v);
-
if (dp[index] < distv + weight) {
-
dp[index] = distv + weight;
-
path[index] = v;
//顶点index的后继结点是v(和Dijkstra算法记录前驱结点的思路差不多)
-
}
-
}
-
return dp[index];
-
}
-
int main() {
-
cin >> Vertexnum >> Edgenum;
-
int start, end, weight;
-
for (
int i =
0; i < Edgenum; i++) {
-
cin >> start >> end >> weight;
-
node N;
-
N.v = end;
-
N.weight = weight;
-
outnode[start]++;
//更新结点的出度
-
adj[start].push_back(N);
-
}
-
init();
//初始化
-
for (
int i =
0; i < Vertexnum; i++) {
-
path[i] = i;
//初始化每个结点的后继结点为自身
-
}
-
int maxdp =
-1;
//记录最大的dp[i]的值
-
int longestindex;
//表示DAG最长路路径是以longestindex为源点
-
for (
int i =
0; i < Vertexnum; i++) {
-
int temp = DFS(i);
-
sumpath[i].push_back(i);
-
int j = i;
-
while (path[j] != j) {
-
j = path[j];
-
sumpath[i].push_back(j);
-
}
-
if (maxdp < temp) {
-
maxdp = temp;
-
longestindex = i;
-
}
-
}
-
cout <<
"关键路径长度:" << maxdp <<
"\n";
-
cout <<
"关键路径序列为:";
-
for (
int i =
0; i < sumpath[longestindex].size(); i++) {
//输出以longestindex为源点的DAG最长路
-
cout << sumpath[longestindex][i];
-
}
-
return
0;
-
}
运行结果如下: