问题描述
关键路径
通常把计划、施工过程、生产流程、程序流程等都当成一个工程。工程通常分为若干个称为“活动”的子工程。完成了这些“活动”,这个工程就可以完成了。通常用AOE-网来表示工程。AOE-网是一个带权的有向无环图,其中,顶点表示事件(EVENT),弧表示活动,权表示活动持续的时间。
AOE-网可以用来估算工程的完成时间。可以使人们了解:
(1)研究某个工程至少需要多少时间?
(2)哪些活动是影响工程进度的关键?
由于AOE-网中的有些活动可以并行进行,从开始点到各个顶点,以致从开始点到完成点的有向路径可能不止一条,这些路径的长度也可能不同。完成不同路径的活动所需的时间虽然不同,但只有各条路径上所有活动都完成了,这个工程才算完成。因此,完成工程所需的最短时间是从开始点到完成点的最长路径的长度,即在这条路径上的所有活动的持续时间之和.这条路径长度就叫做关键路径(Critical Path)。
程序执行结束后应该输出:
关键活动为a1,a4,a7,a10,a8,a11
关键路径为: a1->a4->a7->a10(或者V1->V2->V5->V7->V9)和a1->a4->a8->a11(或者V1->V2->V5->V8->V9)
花费的时间为至少为18(时间单位)。
算法设计
拓扑排序的求解关键在于首先应该判断该无向图是否存在环,若不存在,则利用栈的操作,需要没有前驱的顶点选中并输出,然后删除该顶点和所有以他为尾的弧,之后再重复上述两步,直到全部的顶点均已输出。同时在计算出事件的最早发生时间。再将拓扑排序的逆序事件存入到另一个栈中。
在拓扑排序的基础上再进行关键路径的求解。主要通过计算各个活动结点的最早发生时间和最晚发生时间。若某个活动的最早和最晚发生时间相等则说明这是关键活动,由关键活动及其之间的关系组成的从原点到汇点的路径即为关键路径,一个工程不止一条关键路径。同时在根据拓扑排序的逆序计算出事件的最晚发生时间。
在实现该算法的过程中首先定义事件类和活动类,分别赋予它们不同的成员变量和成员方法,在关键路径类中实现其余算法。
AOE网的构造过程中,使用邻接矩阵存储AOE网中的关系和权值。在关键路径方法中实现计算活动的最早发生时间和最晚发生时间。将两者相等的活动放入一个数组中,利用DFS算法回溯,在遍历以前将关键路径用标记过的活动,在该标记为0时则为关键结点,然后遍历此节点进入递归。之后进入下一个结点,遇见终点就先遍历并输出栈中的元素,即栈中的元素知道分叉点,退回上一次分岔点,然后递归进入下一条路,直到遍历结束输出关键路径。
源码如下
//结点类
public class Event {
private String name;//节点的名字
private int in; //入度
private int data;//储存顶点数组的下标
private Activity ActOE;//边上的活动
public int getIn() {
return in;
}
public int getData() {
return data;
}
public Activity getActOE() {
return ActOE;
}
public String getName() {
return name;
}
public void setIn(int in) {
this.in = in;
}
public void setData(int data) {
this.data = data;
}
public void setActOE(Activity next) {
this.ActOE = next;
}
public void setName(String name) {
this.name = name;
}
public Event(int data, Activity ActOE, String name) {
this.data = data;
this.ActOE = ActOE;
this.name = name;
}
public Event() {
}
}
public class CriticalPath {
private Event[] vexList;//节点数组
private int etv[], ltv[];//etv 事件最早发生时间(即顶点) ltv 事件最晚发生时间
//拓扑排序所用到的栈
private final Stack<Event> NodeStack = new Stack<>();
private final Stack<Event> NodeStack1 = new Stack<>();
//标明关键路径的关键节点
static int MAX_VERTEXNUM = 100;
static int[] visited = new int[MAX_VERTEXNUM];
//关键路径用到的栈,数据结构.数组
private final Stack<Activity> activityStack = new Stack<>();
private static int finall;
//使用键盘输入来构建图构建图
public void CreateGraphByYou() {
Event pl1 = new Event();
Activity pl = new Activity();
int n;
Scanner in = new Scanner(System.in);
System.out.println("请输入共有多少个节点:");
n = in.nextInt();
vexList = new Event[n];
for (int i = 0; i < n; i++) {
int p;//构建节点
System.out.println("节点" + (i + 1) + " 节点存在数组中的位置 " + "节点名字");
Event v = new Event(in.nextInt(), null, in.nextLine());
pl1 = v;
pl = v.getActOE();
System.out.println("请输入此节点后有多少个边:");
p = in.nextInt();
for (int j = 0; j < p; j++) {
//构建活动即事件
System.out.println("边" + (j + 1) + " 弧头位置 " + "弧尾位置 " + "权值 " + "活动的名字");
Activity a = new Activity(in.nextInt(), in.nextInt(), in.nextInt(), null, in.next(), false);
if (j == 0) {
pl1.setActOE(a);
pl = pl1.getActOE();
} else {
pl.setNext(a);
pl = pl.getNext();
}
}
vexList[i] = v;
}
}
//构建一个默认图
public void CreateGraphByMe() {
//所有节点即事件
Event v1 = new Event(0, null, "v1");
Event v2 = new Event(1, null, "v2");
Event v3 = new Event(2, null, "v3");
Event v4 = new Event(3, null, "v4");
Event v5 = new Event(4, null, "v5");
Event v6 = new Event(5, null, "v6");
Event v7 = new Event(6, null, "v7");
Event v8 = new Event(7, null, "v8");
Event v9 = new Event(8, null, "v9");
//v1节点 a1活动,a2活动,a3活动
Activity a1 = new Activity(0, 1, 6, null, "a1", false);
Activity a2 = new Activity(0, 2, 4, null, "a2", false);
Activity a3 = new Activity(0, 3, 5, null, "a3", false);
v1.setActOE(a1);
a1.setNext(a2);
a2.setNext(a3);
//v2节点 a4活动
Activity a4 = new Activity(1, 4, 1, null, "a4", false);
v2.setActOE(a4);
//v3节点 a5活动
Activity a5 = new Activity(2, 4, 1, null, "a5", false);
v3.setActOE(a5);
//v4节点 a6活动
Activity a6 = new Activity(3, 5, 2, null, "a6", false);
v4.setActOE(a6);
//v5节点 a7活动 a8活动
Activity a7 = new Activity(4, 6, 9, null, "a7", false);
Activity a8 = new Activity(4, 7, 7, null, "a8", false);
v5.setActOE(a7);
a7.setNext(a8);
//v6节点 a9活动
Activity a9 = new Activity(5, 7, 4, null, "a9", false);
v6.setActOE(a9);
//v7节点 a10活动
Activity a10 = new Activity(6, 8, 2, null, "a10", false);
v7.setActOE(a10);
//v8节点 a11活动
Activity a11 = new Activity(7, 8, 4, null, "a11", false);
v8.setActOE(a11);
//对象数组:vexList,保存节点构建了图
vexList = new Event[9];
vexList[0] = v1;
vexList[1] = v2;
vexList[2] = v3;
vexList[3] = v4;
vexList[4] = v5;
vexList[5] = v6;
vexList[6] = v7;
vexList[7] = v8;
vexList[8] = v9;
}
//拓扑排序
public boolean topologicalSort() {
//计算入度:初始化所有节点的入度
for (Event node : vexList) {
node.setIn(0);
}
//遍历每个节点后面的路径,然后就给弧尾顶点加一
for (Event node : vexList) {
Activity p = new Activity();
p = node.getActOE();
while (p != null) {
Event Event = new Event();
Event = vexList[p.getIndex()];
Event.setIn(Event.getIn() + 1);
p = p.getNext();
}
}
int count = 0;
Event p = new Event();
Activity p1 = new Activity();
System.out.println("拓扑排序:");
//对事件最早发生时间数组初始化
etv = new int[vexList.length];
Arrays.fill(etv, 0);
//将度为0结点的入栈
for (Event Event : vexList) {
if (Event.getIn() == 0) {
NodeStack.push(Event);
}
}
//遍历领接表里面边结点
while (!NodeStack.empty()) {
p = NodeStack.pop();
count++;
// 拓扑排序的逆序加入栈2中
NodeStack1.push(p);
System.out.print(p.getName() + " ");
if (p.getActOE() != null) {
p1 = p.getActOE();
}
//顶点最早发生时间
//遍历到入度就减一
while (p1 != null) {
vexList[p1.getIndex()].setIn(vexList[p1.getIndex()].getIn() - 1);
if (vexList[p1.getIndex()].getIn() == 0) {
NodeStack.push(vexList[p1.getIndex()]);
}
if (etv[p.getData()] + p1.getWight() > etv[p1.getIndex()]) {
etv[p1.getIndex()] = etv[p.getData()] + p1.getWight();
}
p1 = p1.getNext();
}
}
//计数等于结点数就无环
if (count != vexList.length) {
System.out.println();
System.out.println("图有环");
return true;
} else {
System.out.println();
System.out.println("图无环");
}
return false;
}
//关键路径
public void criticalPath() {
// 活动的最早发生时间 et 活动发生的最晚时间 lt p 指针扫描事件节点 pa 扫描活动节点
int et, lt;
Event p = new Event();
Activity pa = new Activity();
if (topologicalSort()) {
return;
}
finall = NodeStack1.peek().getData();
ltv = new int[vexList.length];
for (int i = 0; i < vexList.length; i++) {
ltv[i] = etv[finall];
}
//事件最晚发生时间
while (!NodeStack1.empty()) {
p = NodeStack1.pop();
if (p.getActOE() != null) {
pa = p.getActOE();
}
while (pa != null) {
if (ltv[pa.getIndex()] - pa.getWight() < ltv[p.getData()]) {
ltv[p.getData()] = ltv[pa.getIndex()] - pa.getWight();
}
pa = pa.getNext();
}
}
Arrays.fill(visited, 1);
System.out.print("关键活动为:");
//求et,lt和关键路径
for (int i = 0; i < vexList.length; i++) {
Activity pn = new Activity();
pn = vexList[i].getActOE();
while (pn != null) {
et = etv[i];
lt = ltv[pn.getIndex()] - pn.getWight();
if (et == lt) {
visited[vexList[i].getData()] = 0;
System.out.print(pn.getName() + " ");
}
pn = pn.getNext();
}
}
System.out.println();
System.out.println("花费时间至少为:" + ltv[finall]);
System.out.println();
}
//输出每条关键路径
public void DFS(int k) {
visited[k] = 1;
Activity p = new Activity();
p = vexList[k].getActOE();
//终点
visited[finall] = 0;
if (k == finall) {
for (Activity activity : activityStack) {
System.out.print(activity.getName() + "->");
}
}
// 遍历节点后面的链表
while (p != null) {
if (visited[p.getIndex()] != 1) {
//是关键路径就入栈
activityStack.push(p);
DFS(p.getIndex());
//遇见死路回退的时候出栈
activityStack.pop();
}
System.out.println();
p = p.getNext();
}
}
public static void main(String[] args) {
CriticalPath a=new CriticalPath();
a.CreateGraphByMe();
//a.CreateGraphByYou();
a.criticalPath();
a.DFS(0);
}
}
//活动类
public class Activity {
private int index;//存储该顶点对应的下标
private int wight;//权值
private int head;//弧头
private Activity next;//弧尾
private String name;//活动的名字
private boolean mark; //标识位:标识有没有遍历过
public boolean isMark() {
return mark;
}
public void setMark(boolean mark) {
this.mark = mark;
}
public int getHead() {
return head;
}
public void setHead(int head) {
this.head = head;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Activity(int head, int index, int wight, Activity next, String name, boolean mark) {
this.index = index;
this.wight = wight;
this.next = next;
this.name=name;
this.mark=mark;
this.head=head;
}
public Activity() {
}
public int getIndex() {
return index;
}
public void SetIndex(int index) {
this.index = index;
}
public int getWight() {
return wight;
}
public void SetWight(int wight) {
this.wight = wight;
}
public Activity getNext() {
return next;
}
public void setNext(Activity next) {
this.next = next;
}
}