上一次写了拓扑排序,这一次就写关键路径,因为关键路径需要以拓扑排序为基础。
提出问题:为什么会出现关键路径这个东西?
答:加入你是一个包工头,你承包了一个工程,“一年内帮政府建立一座现代化的养老院” ,这下问题就来了,怎么建呢?总不能从天上掉下来一个养老院把?我们需要计划,需要流程,需要环环相扣的步骤,从买土地,到买建筑材料,到雇佣工人,到着盖楼,到装修,到交付。一步一步,环环相扣,实际的程序其实比我描述的要复杂的多,我们需要有严谨的流程,那么问题来了?建楼是需要时间的,每一个步骤的完成都是需要时间的,有些步骤一刻也不能耽误,一旦耽误,工程的完成时间就会被推迟。但有些步骤你无论是用一个月还是三个月,都不会对工程的完成时间造成影响。我们就将那些一刻也不能延误的步骤称为关键路径。我们在承包工程的时候需要找到关键路径,并且特别留心这写步骤,因为这些步骤一旦延期就会直接导致整个工程无法按时交付,就要赔钱了兄弟!!!
总结:关键路径是为了告诉我们在流程执行的过程中需要特别关注的步骤。
我们再来说一下关键路径的应用背景:
在有向无环的AOE网中可以应用关键路径(AOE就是说每一个顶点代表一个时间,而边上的权值代表一个活动也可以说是步骤持续的时间);
下面让我们通过一道来深入了解以下关键路径:
下面为初始化代码:
/**
* AOE网中的关键路径问题
*
* AOE:Activity On Edge(活动边缘)
* AOE图就是说每一个定点代表一个事件,而每条边代表一个活动,也就是说一个有向无回路的网。
* AOV的意思就是说边是不带权的,顶点表示一个活动。
*
*
* @author king
*
*/
//这个数组就是一个邻接表的感觉,它的里面存储的时一个链表的头节点,
这个链表标识的时当前数组的下标所对应的节点可以到达的对应的数组下表的节点。
private node[] map= {null,null,null,null,null,null,null};
//这个二维数组标表网中的所有有向边及权值,需要将其转存储到邻接表中
private int[][] path= {{0,1,2},{0,2,3},{0,3,2},{1,5,8},{1,2,2},{2,5,5},{2,4,3},{2,3,1},{3,4,5},{4,5,3},{4,6,9},{5,6,5}};
//记录每一个点的入度
private int[] sign= {0,0,0,0,0,0,0};
//创建一个队列来存储入度为零的顶点
private LinkedList<Integer> queue=new LinkedList<>();
//创建一个数组记录每个点的最早开始的时间
private int[] early= {0,0,0,0,0,0,0};
//创建一个数组记录每个点的最晚开始时间
private int[] slowly={0,0,0,0,0,0,0};
接下来问为创建邻接表的代码块:
//创建邻接链表
@Test
public void create_adj() {
//遍历path数组
for(int i=0;i<path.length;i++) {
if(map[path[i][0]]==null) {
map[path[i][0]]=new node(path[i][1],path[i][2],null);
sign[path[i][1]]++;
}else {
node temp=map[path[i][0]];
while(temp.getNext()!=null) {
temp=temp.getNext();
}
//将新加入的节点添加到出发点所对应的链表的尾部
temp.setNext(new node(path[i][1],path[i][2],null));
sign[path[i][1]]++;
}
}
//将入度为零的节点直接推入队列中
for(int i=0;i<sign.length;i++) {
if(sign[i]==0) {
queue.addLast(i);
}
}
// Topological_Sorting();
}
接下来是稍稍升级版的拓扑排序,它可以求出early数组:
public void Topological_Sorting() {
while(queue.size()!=0) {
int index=queue.pollFirst();
node temp=map[index];
while(temp!=null) {
//这部很重要,我们将当前点作为出发点,该点所有可达的点与early数组中已经存储的该点的值进行比较,如果小于,则替换
if (early[temp.getData()]<temp.getPower()+early[index]) {
early[temp.getData()] = temp.getPower()+early[index];
}
//如果入度为零,推入队列
if(--sign[temp.getData()]==0) {
queue.addLast(temp.getData());
}
temp=temp.getNext();
}
}
// init_slowly();
}
然后依照early数组和path求出slowly数组:
public void init_slowly() {
//因为最后一个很节点上对应的活动的最早的开始时间和最晚的开始时间是一致的,因为最后一个节点是汇点,
//所以我们不需要判断最后一个节点,直接是slowly[最后一个节点]=early[最后一个节点]
slowly[early.length-1]=early[early.length-1];
for(int i=early.length-2;i>=0;i--) {
//初始化min为汇点对应的权值
int min=slowly[early.length-1];
node temp=map[i];
while(temp!=null) {
//某点活动的最晚开始时间等于该点所能到达的所有点的最晚开始时间减去该条边上的权值的最小值
if(slowly[temp.getData()]-temp.getPower()<min) {
min=slowly[temp.getData()]-temp.getPower();
}
temp=temp.getNext();
}
slowly[i]=min;
}
// show_main_path();
}
接下来,我们根据early数组,slowly数组,path数组求出所有关键路径:
public void show_main_path() {
//判断某条路径是不是关键路径如果是则输出这条路径的起始点和终止点
for(int i=0;i<path.length;i++) {
if((slowly[path[i][1]]-slowly[path[i][0]])-(early[path[i][1]]-early[path[i][0]])==0) {
System.out.println("点"+path[i][0]+"至"+"点"+path[i][1]+"为关键路径");
}
}
}
运行结果为:
点0至点1为关键路径
点0至点2为关键路径
点0至点3为关键路径
点1至点2为关键路径
点2至点4为关键路径
点2至点3为关键路径
点3至点4为关键路径
点4至点6为关键路径