前言:首先关键路径是针对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]了,他们的关系如下:

JAVA关键路径法算法_关键路径

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;
}

运行结果如下:

JAVA关键路径法算法_#include_02


补充:其实求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;
}

运行结果如下:

JAVA关键路径法算法_i++_03