有向无环图下的关键路径

一.什么是AOE-网络和关键路径?

AOE-网(Activity On Edge)即边表示活动的网。AOE-网是一个带权的有向无环图,其中顶点表示事件(Event),弧表示活动,权代表活动持续的时间。通常AOE-网可以用来估算工程的完成时间。
正常情况下,网中只有一个入度为0的点(称作源点)和一个出度为0的点(叫做汇点)。
有向无环图下的关键路径_第1张图片
由于AOE-网中有些活动可以并行的进行,所以完成工程的最短时间是从开始点到完成点的最长路径的长度(这里说的路径长度是指路径上各活动持续时间之和,不是路径上弧的数目)。路径长度最长的叫做关键路径(Critical Path)。

二.怎么求关键路径?

首先我们需要明确,由关键活动组成的路径就是关键路径
1.那么什么又是关键活动呢? 该活动的早发生时间和最晚发生时间相同

2.那如何求一个活动的最晚开始时间和最早开始时间呢?
活动的最早开始时间 = 该活动的始点对应的事件发生的最早时间
活动的最晚开始时间 = 该活动的终点对应的事件发生的最晚时间 - 该活动花费的时间

3.那如何求事件发生的最早开始时间和最晚开始时间呢?
这里我们需要知道源点默认事件的最早开始时间和最晚开始时间为0,汇点的最早开始时间和最晚开始时间为关键路径的长度。在一个AOE-网中,一个活动连接两个事件。
事件的最早发生时间 = MAX{以该事件作为终点的边的始点的事件的最早发生时间 + 边的权值}
事件的最晚发生时间 = MIN{以该事件作为始点的边的终点的事件的最晚发生时间 - 边的权值}

4.这里我们以ve[i]表示某事件的最早发生时间,ve[j]表示以i为始点的边对应的终点,vl[i]表示某事件的最晚发生时间,ee[i]表示某活动的最早发生时间,el[i]表示某活动的最晚发生时间,dut表示以i为始点,j为终点的边的权值。故以上公式可以简写为:
ve[j] = MAX{ve[i] + dut}
vl[i] = MIN{vl[j] - dut}
ee[i] = ve[i]
el[i] = vl[j] - dut

5.算法的具体实现思想
(1)从源点出发,按拓扑排序求其余各顶点的最早发生时间。其次拓扑排序还可以检查该该图是否存在环,若该有向图存在环则无法求解关键路径。
(2)从汇点出发,按逆拓扑有序求其余各顶点的最迟发生时间。
(3)根据各顶点的ve和vl值求各个活动发生的最早发生时间和最晚发生时间,若该活动的最晚发生时间和最早发生时间相同,则该活动为关键活动。

三.java代码实现该算法

import java.util.Scanner;
import java.util.Stack;

/*
领接表存储AOE-网
9 11
1 2 1 6
1 3 2 4
1 4 3 5
2 5 4 1
3 5 5 1
4 6 6 2
5 7 7 9
5 8 8 7
6 8 9 4
7 9 10 2
8 9 11 4
*/
public class AdjacencyList {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int numberOfNode = sc.nextInt();   //点的总数
		int numberOfEdge = sc.nextInt();   //边的总数
		
		VNode[] v = new VNode[numberOfNode];    //创建领接表的数组部分,事件的名字的起始点从1开始
		int[] indegree = new int[numberOfNode]; //创建一个入度数组,数组同样是从下标为0开始计数
		for(int i=0;i<numberOfNode;i++) {
			v[i] = new VNode(i+1,null);  //初始化事件的标号,从下标为1开始
		}
		
		//例样输入:1 2 1 6  表示起始点为1,终止点为2,活动名为1,活动持续时间为6天
		for(int i=1;i<=numberOfEdge;i++) {   //录入各条边
			 int start = sc.nextInt();
			 int end = sc.nextInt();
			 int name = sc.nextInt();
			 int weight = sc.nextInt();
			 
			 ArcNode tempArc = new ArcNode(weight,name,end-1,null);  //初始化边结点
			 if(v[start-1].fistEdge != null) {
				 tempArc.nextArc = v[start-1].fistEdge;
				 v[start-1].fistEdge = tempArc;
			 } else {
				 v[start-1].fistEdge = tempArc;
			 }
			 
			 indegree[end-1]++;
		}
		
		sc.close();
		
		ToPoLogicSort(indegree,v,numberOfEdge);
		
	}
	
	
	/*拓扑排序判断该有向图是否存在环,传入参数为入度数组和图的领接表,同时完成我们ve[]数组
	 *为了完成整个关键路径的编写,我们必须引入4个数据结构
	 *Ve[] 表示各个事件最早开始的时间
	 *Vl[] 表示各个事件最晚开始的时间
	 *Ee[] 表示各个活动最早开始的时间
	 *El[] 表示各个活动最晚开始的时间
	 */
	public static boolean ToPoLogicSort(int[] indegree,VNode[] v,int numberOfEdge) {
		Stack<VNode> s = new Stack<>();       //为了避免重复检测入度为零的顶点,我们另设一栈来存储入度为0的顶点
		Stack<Integer> si = new Stack<>();    //此栈的创建完全是为了求Vl数组的值
		int[] ve = new int[indegree.length];  //存储各个事件最早可以开始的时间,并完成初始化
		for(int i=0;i<ve.length;i++) {
			ve[i] = 0;
		}
		
		for(int i=0;i<indegree.length;i++) {  //将入度为零的点入栈
			if(indegree[i] == 0) {
				s.push(v[i]);
			}
		}
		
		int count = 0;
		while(!s.isEmpty()) {
			VNode tempN = s.pop();
			System.out.print(tempN.name+" ");
			si.push(tempN.name);  //将拓扑排序序列保存到栈中,注意这里保存的是事件的名字
			count++;
			
			ArcNode p = tempN.fistEdge;
			while(p != null) {
				//完成入度数组的更新
				int index = p.nextVode;
				indegree[index]--;
				if(indegree[index]==0) {
					s.push(v[index]);
				}
				
				//完成ve数组的填写
				if(ve[tempN.name-1]+p.weight > ve[p.nextVode]) {
					ve[p.nextVode] = ve[tempN.name-1]+p.weight;
				}
				
				p = p.nextArc;
			}			
		}
		
		//如果count的值小于顶点的个数,说明该图存在环,不可以求关键路径
		if(count<indegree.length) {
			return false;
		}		
		
		//开始求vl数组的值
		int[] vl = new int[indegree.length];
		for(int i=0;i<vl.length;i++) {
			vl[i] = ve[ve.length-1];  //初始化vl数组,将其初始化为汇点的值,在AOE-网中汇点的值明显最大
		}
		si.pop();                     //首先弹出汇点,因为汇点的最早和最晚时间相同
		
		while(!si.isEmpty()) {
			int tempN = si.pop()-1;   //由于栈中存储的是事件的名字,将其转化为数组的下标
			
			ArcNode tempArc = v[tempN].fistEdge;
			while(tempArc != null) {
				if(vl[tempArc.nextVode]-tempArc.weight < vl[tempN]) {
					vl[tempN] = vl[tempArc.nextVode]-tempArc.weight;
				}
				tempArc = tempArc.nextArc;
			}			
		}
		
		System.out.println();
		
		//下面开始求活动的最早开始时间和活动的最晚开始时间
		int[] ee = new int[numberOfEdge];   //活动有11个,为11条边
		int[] el = new int[numberOfEdge];
		
		System.out.println("该图的关键路径为:");
		for(int i=0;i<v.length;i++) {
			ArcNode tempArc = new ArcNode();
			tempArc = v[i].fistEdge;
			while(tempArc != null) {
				ee[tempArc.name-1] = ve[i];                                //活动的最早开始时间等于时间开始的最早时间
				el[tempArc.name-1] = vl[tempArc.nextVode]-tempArc.weight;  //活动的最晚开始时间,等于其终点的最晚开始时间减去边的权值
				
				//输出关键活动
				if(ee[tempArc.name-1] == el[tempArc.name-1]) {
					System.out.print(tempArc.name+"<"+v[i].name+","+(tempArc.nextVode+1)+">  ");
				}
				
				tempArc = tempArc.nextArc;
			}
		}

		return true;
	}
	
	//展示领接表函数,观察图是否存储正确
	public static void display(VNode[] v) {
		for(int i=0;i<v.length;i++) {
			System.out.print("事件"+(i+1)+"  ");
			ArcNode Arc = v[i].fistEdge;
			while(Arc != null) {
				System.out.print("活动"+Arc.name+"  ");
				Arc = Arc.nextArc; 
			}
			System.out.println();
		}
	}
}


//顶点的结构体
class VNode{
	int name;   //事件的名字
	ArcNode fistEdge;  //第一条边
	
	public VNode() {
		super();
	}

	public VNode(int name, ArcNode fistEdge) {
		super();
		this.name = name;
		this.fistEdge = fistEdge;
	}
	
}


//弧的结构体
class ArcNode{
	int weight;
	int name;      //该活动的编号
	int nextVode;  //该活动的另一个端点
	ArcNode nextArc;
	
	public ArcNode() {
		super();
	}

	public ArcNode(int weight,int name,int nextVode,ArcNode nextArc) {
		this.weight = weight;
		this.name = name;
		this.nextVode = nextVode;
		this.nextArc = nextArc;
	}
}

有向无环图下的关键路径_第2张图片

你可能感兴趣的:(算法和数据结构)