目录
1.关键路径
1.1关键路径的产生:
关键路径法是采用网络技术来组织生产、节约时间和资源的科学管理方法。它起源于美国,1957年美国杜邦公司的沃尔克在制定化学工厂的建设计划与保全计划时,在兰德公司克里的协助下,设计出的一种计划、管理方法。说白了,就是在现实生产问题中,为减少总体生产时间,提高生产目的的情况下,提出的一种在过程中缩短必要时间,从而减少整体时间的方法。
1.2实际问题的转化
在现实生活中,为减少时间做出科学有效的判断,我们将实际问题可以转化为等价的两个参数,一个是事件,一个是活动。将其事件作为节点,活动所需时间作为边,其中边带有权值,这就形成了图论中的关键路径问题。
1.3关键路径AOE网的概念以及基础
在带权有向图中,以顶点为事件,以有向边表示活动,以边上的权值表示完成该活动的时间,称之为用边表示活动的网络,简称AOE网。AOE网和AOV网都是有向无环图,不同之处在于他们的边和顶点所表示的含义不同,AOE网的边是有权值,AOV网中的边没有权值,仅代表顶点之间的前后关系。
两个性质:
- 只有在某顶点所代表的事件发⽣后,从该顶点出发的各有向边所代表的活动才能开始;
- 只有在进⼊某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发⽣。 另外,有些活动是可以并⾏进⾏的
在AOE网中仅有一个入度为0的顶点,称为开始顶点(源点),它表示整个工程的开始。网中也仅有一个出度为0的点,称为结束顶点(汇点)。
1.3.1几个参数的定义:
① 事件
- 事件v的最早发⽣时间ve(k)——决定了所有从vk开始的活动能够开⼯的最早时间。
- 事件v的最迟发⽣时间vl(k)——它是指不推迟整个⼯程完成的前提下,该事件最迟必须发⽣的时间。
② 活动
- 活动a的最早开始时间e(i)——指该活动弧的起点所表⽰的事件的最早发⽣时间
- 活动a的最迟开始时间e(i)——指该活动弧的终点所表⽰的事件的最迟发⽣时间
- 活动a的最迟开始时间l(i)与其最早开始时间的差——即一个活动时间的余量
活动a的时间余量d(i)=l(i)-e(i),表⽰在不增加完成整个⼯程所需总时间的情况下,活动a可以拖延的时间 若⼀个活动的时间余量为零,则说明该活动必须要如期完成,d(i)=0,即l(i) = e(i)的活动a是关键活动。由关键活动组成的路径就是关键路径。
该图为根据手动创建邻接矩阵画出来的路径图
2.具体实现过程
Step 1:计算每个节点的入度
// 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));
在这段计算入度的算法当中,判断条件是矩阵的权值不等于-1,那就意味着在手动输入带权图的时候,未相连的两条边的值赋为了-1。需要注意的是,在Step 1中所使用的行数,tempInDegrees数组中用的是j,不是i,列数代表出度。
Step 2 正向拓扑排序
int[] tempEarliestTimeArray = new int[numNodes];
//找到入度为0的点
for (int i = 0; i < numNodes; i++) {
// This node cannot be removed.
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));
在这一段中,是通过拓扑排序的方法,来寻找最早开始时间。最开始在第一个for循环中,不断遍历直至找出入度为0的点,然后对其边的权值选出最大值。此处为什么要选出最大值,因为在AOE网中,事件开始之前入度必须为0,就代表着在开始之前,前面所有的事要完成才行,简单来说就是必须等最慢的那件事发生了,自己才能够开始。其中在创建tempEarliestTimeArray这个数组时候,系统会自动将全部元素分配为0。因为在Step1中就说过,如果连通,则weightMatrix中的值必定大于0,不连通则为-1,如果tempEarliestTimeArray-1的话,说明不是连通的,也不会满足下面if的判断条件,所以不会更新最大值。就这样依次遍历就可找出事件的最早开始时间。
Step 3 :计算每个节点的出度
// 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 1 不再赘述。需要注意的是,在Step3中所使用的行数,tempOutDegrees数组中用的是i,不是j,行数代表出度。
Step 4:通过逆拓扑排序来计算出活动最迟开始时间
// 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--) {
// This node cannot be removed.
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));
在Step2中,计算事件最早开始发生事件的时候,是要计算最大值。而在Step4中计算活动最迟开始时间要最早开始时间减去前面活动所需时间的最小值。首先要注意的一个点是,在最开始对tempLatestTimeArray(最迟开始时间)数组初始化时,要将其所有值赋为Step3中最早开始时间的最大值,也就是最后一个节点的最早开始时间。完成更新tempOutDegrees后,出度-1,然后进行下一轮检查更替。
Step 5:计算最终关键路径
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();
一个活动最迟开始时间减去最早开始时间的差值,是指的是不增加整个工程所需总时间的情况下,活动可以拖延的时间。若一个活动的时间余量为0,则说明该活动必须要如期完成,否则将拖延整个进度。则在Step5中判断条件写的是最迟=最早,也就相当于差值为0,则将布尔类型的resultCriticalArray数组赋为真,在下面输出的时候,只要为真,则属于关键路径的节点。
Step 6:最后填入一个活动权值的邻接矩阵,进行验证
public static void main(String args[]) {
// 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
输出结果为:
3总结:
在关键路径的解决中,路径长度实际上就是最大路径的长度,这与迪杰斯特拉中求解最小路径可形成对偶问题,但是需要注意的是,关键路径的求解中,必须要在节点入度为0的情况下才能进行计算,而改变迪杰斯特拉核心算法去求解最大值的话,是否能避免或者说在节点入度这一方面不产生影响的问题上,我暂时还没有想清楚其中具体步骤的逻辑问题。