前言:首先关键路径是针对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;
}
运行结果如下: