关键路径

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)需要分两步进行。

  1. 从ve(0)=0开始向前递推,其中T是所有以第j个顶点为头的弧的集合。

​ ve(j) = Max{ve(i) + dut(<i, j>)}

  1. 从vl(n-1)=ve(n-1)起向后递推,其中S是所有以第i个顶点为尾的弧的集合。

​ vl(i) = Min{vl(j) - dut(<i, j>)}

活动ai的最晚开始时间l[i]

  • 活动ai的最晚开始时间指,在不推迟整个工程完成日期的前提下,必须开始的最晚时间。
  1. 由此得到求关键路径的算法: 输入e条弧<j, k>,创建AOE网的存储结构;
  2. 从源点出发,令ve[0]=0,按拓扑顺序求其余各顶点的最早发生时间ve[i]。如果得到的拓扑有序序列中顶点个数小于网中顶点数n,则说明网中存在环,不能求关键路径,算法终止,否则转到步骤(3);
  3. 从汇点vn出发,令vl[n-1]=ve[n-1],按逆拓扑顺序求其余各顶点的最迟发生时间vl[i];
  4. 根据各顶点的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
}