关键路径
1. Description
1.1 AOE网
在谈论关键路径这个问题之前,我们先了解一下AOE网这种数据结构。
AOE网(Activity On Edge)即边表示活动的网,是一个带权的有向无环图,其中顶点表示事件(Event),每个事件表示在它之前的活动已经完成,在它之后的活动可以开始,弧上的数字表示权值。在工程上,弧表示活动,权表示活动持续的时间。因此,AOE网可用来表示工程上面的完成时间问题。
其中,求最早时间是基于拓扑排序,最晚时间是基于逆拓扑排序。
然后这就引出了我们今天的问题——关键路径。
所谓关键路径,就是从源点到汇点的所有路径中具有最大路径长度的路径。这条路径上面的所有活动都称为关键活动。
注意:关键路径虽然看起来是求最大路径长度,但其实求得是工程的最少时间
因为工程上面有的事件必须要等前置事件结束才能进行,所以求工程完成的最少时间就是求最长的路径。
1.2 节点最早发生时间和最迟开始时间
假设开始点是v1,从v1到vi的最长路径叫做最早发生时间,这个时间决定了所有以vi为尾的弧所表示的活动的最早开始时间。如果用e(i)表示活动ai的最早开始时间,I(i)为一个活动的最迟开始时间,这是在不推迟整个工程完成的前提下,活动ai最迟必须开始进行的时间。两者之差I(i) - e(i)意味着完成活动ai的时间余量。I(i) = e(i)的活动叫做关键活动。
关键路径上面的所有活动都是关键活动。提前完成非关键活动并不能加快工程进度。
求最早开始时间e(i)和最迟开始时间I(i),需要先求得时间的最早发生时间ve(j)和最迟发生时间vl(j)。如果活动ai由弧<j,k>表示,其持续时间记为dut(<j, k>),则有e(i) = ve(j), l(j) = vl(k) - dut(<j, k>)。求ve(j)和vl(j)需要分两步进行。
- 从ve(0)=0开始向前递推,其中T是所有以第j个顶点为头的弧的集合。
ve(j) = Max{ve(i) + dut(<i, j>)}
- 从vl(n-1)=ve(n-1)起向后递推,其中S是所有以第i个顶点为尾的弧的集合。
vl(i) = Min{vl(j) - dut(<i, j>)}
活动ai的最晚开始时间l[i]
- 活动ai的最晚开始时间指,在不推迟整个工程完成日期的前提下,必须开始的最晚时间。
- 由此得到求关键路径的算法: 输入e条弧<j, k>,创建AOE网的存储结构;
- 从源点出发,令ve[0]=0,按拓扑顺序求其余各顶点的最早发生时间ve[i]。如果得到的拓扑有序序列中顶点个数小于网中顶点数n,则说明网中存在环,不能求关键路径,算法终止,否则转到步骤(3);
- 从汇点vn出发,令vl[n-1]=ve[n-1],按逆拓扑顺序求其余各顶点的最迟发生时间vl[i];
- 根据各顶点的ve和vl值,求每条弧s的最早开始时间e(s)和最迟开始时间l(s)。若某弧满足条件e(s)=l(s),则为关键活动。
3. Code
package datastructure.graph;
import java.util.Arrays;
import matrix.IntMatrix;
public class Net {
// 距离的最大值
public static final int MAX_DISTANCE = 10000;
// 节点数。
int numNodes;
// 权值矩阵。
IntMatrix weightMatrix;
/**
*********************
* The first constructor.
*
* @param paraNumNodes The number of nodes in the graph.
*********************
*/
public Net(int paraNumNodes) {
numNodes = paraNumNodes;
weightMatrix = new IntMatrix(numNodes, numNodes);
for (int i = 0; i < numNodes; i++) {
Arrays.fill(weightMatrix.getData()[i], MAX_DISTANCE);
} // Of for i
}// Of the first constructor
/**
*********************
* The second constructor.
*
* @param paraMatrix The data matrix.
*********************
*/
public Net(int[][] paraMatrix) {
weightMatrix = new IntMatrix(paraMatrix);
numNodes = weightMatrix.getRows();
}// Of the second constructor
// 一个简单的toString方法。
public String toString() {
String resultString = "This is the weight matrix of the graph.\r\n" + weightMatrix;
return resultString;
}// Of toString
/**
*********************
* The Dijkstra algorithm: shortest path from the source to all nodes.
*
* @param paraSource The source node.
* @return The distances to all nodes.
*********************
*/
public int[] dijkstra(int paraSource) {
// Step 1. Initialize.
int[] tempDistanceArray = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
tempDistanceArray[i] = weightMatrix.getValue(paraSource, i);
} // Of for i
int[] tempParentArray = new int[numNodes];
Arrays.fill(tempParentArray, paraSource);
// -1 for no parent.
tempParentArray[paraSource] = -1;
// 略过已处理过的节点。
boolean[] tempVisitedArray = new boolean[numNodes];
tempVisitedArray[paraSource] = true;
// Step 2. 迪杰斯特拉算法的主要内容。
int tempMinDistance;
int tempBestNode = -1;
for (int i = 0; i < numNodes - 1; i++) {
tempMinDistance = Integer.MAX_VALUE;
for (int j = 0; j < numNodes; j++) {
if (tempVisitedArray[j]) {
continue;
} // Of if
if (tempMinDistance > tempDistanceArray[j]) {
tempMinDistance = tempDistanceArray[j];
tempBestNode = j;
} // Of if
} // Of for j
tempVisitedArray[tempBestNode] = true;
// 为下一轮做准备。
for (int j = 0; j < numNodes; j++) {
if (tempVisitedArray[j]) {
continue;
} // Of if
if (weightMatrix.getValue(tempBestNode, j) >= MAX_DISTANCE) {
continue;
} // Of if
if (tempDistanceArray[j] > tempDistanceArray[tempBestNode] + weightMatrix.getValue(tempBestNode, j)) {
tempDistanceArray[j] = tempDistanceArray[tempBestNode] + weightMatrix.getValue(tempBestNode, j);
tempParentArray[j] = tempBestNode;
} // Of if
} // Of for j
// For test
System.out.println("The distance to each node: " + Arrays.toString(tempDistanceArray));
System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
} // Of for i
// Step 3. 输出。
System.out.println("Finally");
System.out.println("The distance to each node: " + Arrays.toString(tempDistanceArray));
System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
return tempDistanceArray;
}// Of dijkstra
/**
*********************
* The minimal spanning tree.
*
* @return The total cost of the tree.
*********************
*/
public int prim() {
int tempSource = 0;
int[] tempDistanceArray = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
tempDistanceArray[i] = weightMatrix.getValue(tempSource, i);
} // Of for i
int[] tempParentArray = new int[numNodes];
Arrays.fill(tempParentArray, tempSource);
tempParentArray[tempSource] = -1;
// 略过已访问的节点
boolean[] tempVisitedArray = new boolean[numNodes];
tempVisitedArray[tempSource] = true;
// prim算法的主要内容。
int tempMinDistance;
int tempBestNode = -1;
for (int i = 0; i < numNodes - 1; i++) {
// 找到那个最佳的节点
tempMinDistance = Integer.MAX_VALUE;
for (int j = 0; j < numNodes; j++) {
if (tempVisitedArray[j]) {
continue;
} // Of if
if (tempMinDistance > tempDistanceArray[j]) {
tempMinDistance = tempDistanceArray[j];
tempBestNode = j;
} // Of if
} // Of for j
tempVisitedArray[tempBestNode] = true;
// 为下一轮做准备。
for (int j = 0; j < numNodes; j++) {
if (tempVisitedArray[j]) {
continue;
} // Of if
// 无法找到的节点
if (weightMatrix.getValue(tempBestNode, j) >= MAX_DISTANCE) {
continue;
} // Of if
if (tempDistanceArray[j] > weightMatrix.getValue(tempBestNode, j)) {
// Change the distance.
tempDistanceArray[j] = weightMatrix.getValue(tempBestNode, j);
// Change the parent.
tempParentArray[j] = tempBestNode;
} // Of if
} // Of for j
// For test
System.out.println("The selected distance for each node: " + Arrays.toString(tempDistanceArray));
System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
} // Of for i
int resultCost = 0;
for (int i = 0; i < numNodes; i++) {
resultCost += tempDistanceArray[i];
} // Of for i
// 输出。
System.out.println("Finally");
System.out.println("The parent of each node: " + Arrays.toString(tempParentArray));
System.out.println("The total cost: " + resultCost);
return resultCost;
}// Of prim
public boolean[] criticalPath() {
int tempValue;
// Step 1. 找到每个节点的入度.
int[] tempInDegrees = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
for (int j = 0; j < numNodes; j++) {
if (weightMatrix.getValue(i, j) != -1) {
tempInDegrees[j]++;
} // Of if
} // Of for j
} // Of for i
System.out.println("In-degree of nodes: " + Arrays.toString(tempInDegrees));
// Step 2. 拓扑排序开始.
int[] tempEarliestTimeArray = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
if (tempInDegrees[i] > 0) {
continue;
} // Of if
System.out.println("Removing " + i);
for (int j = 0; j < numNodes; j++) {
if (weightMatrix.getValue(i, j) != -1) {
tempValue = tempEarliestTimeArray[i] + weightMatrix.getValue(i, j);
if (tempEarliestTimeArray[j] < tempValue) {
tempEarliestTimeArray[j] = tempValue;
} // Of if
tempInDegrees[j]--;
} // Of if
} // Of for j
} // Of for i
System.out.println("Earlest start time: " + Arrays.toString(tempEarliestTimeArray));
// Step 3. 每个节点的出度。
int[] tempOutDegrees = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
for (int j = 0; j < numNodes; j++) {
if (weightMatrix.getValue(i, j) != -1) {
tempOutDegrees[i]++;
} // Of if
} // Of for j
} // Of for i
System.out.println("Out-degree of nodes: " + Arrays.toString(tempOutDegrees));
// Step 4. 反向拓扑排序
int[] tempLatestTimeArray = new int[numNodes];
for (int i = 0; i < numNodes; i++) {
tempLatestTimeArray[i] = tempEarliestTimeArray[numNodes - 1];
} // Of for i
for (int i = numNodes - 1; i >= 0; i--) {
if (tempOutDegrees[i] > 0) {
continue;
} // Of if
System.out.println("Removing " + i);
for (int j = 0; j < numNodes; j++) {
if (weightMatrix.getValue(j, i) != -1) {
tempValue = tempLatestTimeArray[i] - weightMatrix.getValue(j, i);
if (tempLatestTimeArray[j] > tempValue) {
tempLatestTimeArray[j] = tempValue;
} // Of if
tempOutDegrees[j]--;
System.out.println("The out-degree of " + j + " decreases by 1.");
} // Of if
} // Of for j
} // Of for i
System.out.println("Latest start time: " + Arrays.toString(tempLatestTimeArray));
boolean[] resultCriticalArray = new boolean[numNodes];
for (int i = 0; i < numNodes; i++) {
if (tempEarliestTimeArray[i] == tempLatestTimeArray[i]) {
resultCriticalArray[i] = true;
} // Of if
} // Of for i
System.out.println("Critical array: " + Arrays.toString(resultCriticalArray));
System.out.print("Critical nodes: ");
for (int i = 0; i < numNodes; i++) {
if (resultCriticalArray[i]) {
System.out.print(" " + i);
} // Of if
} // Of for i
System.out.println();
return resultCriticalArray;
}// Of criticalPath
/**
*********************
* The entrance of the program.
*
* @param args Not used now.
*********************
*/
public static void main(String args[]) {
Net tempNet0 = new Net(3);
System.out.println(tempNet0);
int[][] tempMatrix1 = { { 0, 9, 3, 6 }, { 5, 0, 2, 4 }, { 3, 2, 0, 1 }, { 2, 8, 7, 0 } };
Net tempNet1 = new Net(tempMatrix1);
System.out.println(tempNet1);
tempNet1.dijkstra(1);
System.out.println();
// 建立一个无向网。
int[][] tempMatrix2 = { { 0, 7, MAX_DISTANCE, 5, MAX_DISTANCE }, { 7, 0, 8, 9, 7 },
{ MAX_DISTANCE, 8, 0, MAX_DISTANCE, 5 }, { 5, 9, MAX_DISTANCE, 0, 15 },
{ MAX_DISTANCE, 7, 5, 15, 0 } };
Net tempNet2 = new Net(tempMatrix2);
tempNet2.prim();
// A directed net without loop is required.
// Node cannot reach itself. It is indicated by -1.
int[][] tempMatrix3 = { { -1, 3, 2, -1, -1, -1 }, { -1, -1, -1, 2, 3, -1 },
{ -1, -1, -1, 4, -1, 3 }, { -1, -1, -1, -1, -1, 2 }, { -1, -1, -1, -1, -1, 1 },
{ -1, -1, -1, -1, -1, -1 } };
Net tempNet3 = new Net(tempMatrix3);
System.out.println("-------critical path");
tempNet3.criticalPath();
}// Of main
}
好的,一个逐渐完善的过程。我博客写的少,还需要向各位大佬多多学习
GPT说的没错,多解释解释就好了,加几个应用场景就好了……我都不知道这个有嘛用……
这篇博客主要包含了一个网络的实现,包括最短路径算法(Dijkstra)、最小生成树算法(Prim)以及关键路径算法。作者通过Java代码详细介绍了这些算法的实现过程,同时还包括了详细的注释和测试代码,方便读者理解和运行。
我觉得这篇博客的优点在于,作者对于算法的理解深入,代码实现清晰明了,且注释充足,让读者可以更好地理解算法的实现过程。特别是在实现关键路径算法时,作者详细地介绍了拓扑排序的过程,让读者可以更好地理解关键路径算法的原理。
然而,这篇博客也存在一些可以改进的地方。首先,博客中的代码块没有进行适当的排版,这可能会让读者在阅读时感到困扰。其次,博客中的代码直接放在文章中,没有使用代码高亮,也没有提供代码的下载链接,这可能会影响读者的阅读体验。
建议作者可以在博客中加入更多的理论解释,比如对于每个算法的原理和应用场景进行详细的介绍,这样可以帮助读者更好地理解这些算法。此外,作者还可以在博客中加入更多的示例,通过实例来展示这些算法的应用,这将使博客内容更加丰富和实用。
总的来说,这是一篇非常实用的博客,对于学习和理解这些网络算法有很大的帮助。期待作者在未来的博客中持续分享更多的知识和经验。