基本思路:
最早发生时间是越早越好,但由于前面路径的消耗不得不晚,早了前面就还没有完成
最晚发生时间是越晚越好,但由于后面路径的消耗不得不早,晚了后面的路径消耗来不及完成。
初始化最早发生时间数组为0,找到排序的下一顶点,用该顶点去更新最早发生时间并入栈,方法是用自身的最早发生时间+路径代价,如果大于连接的下一顶点的最早发生时间就更新一下下一顶点的最早发生时间。一直做到最后一个顶点,最后一个顶点不和任何顶点相连而什么都做,最后就可以得到最早发生时间数组和堆栈中的逆拓扑顺序的顶点序列。
初始化最晚发生时间数组为终点最早发生时间的值,逆拓扑顺序逐个出栈,用出栈的该顶点的最晚发生时间-路径代价更新相连的顶点,如果小于相连顶点的最晚发生时间,就更新下。这样一直做到最前面的顶点,最前面的顶点因为没有任何入度而不作任何操作。最后得到的就是最晚发生时间。
最后,一个顶点的最早发生时间等于最晚发生时间,就是关键顶点(不能有任何延迟),连起来就是关键路径。
邻接矩阵实现方法:
找到拓扑排序顶点,方法是循环列如果该列没有被访问过,则进入循环该列的行,如果全部为0则表明没有入度则记下下标并设置该行已经被访问过,跳出寻找的循环,否则就把全0计数器清0.然后用该下标找到该顶点入栈(为了形成逆拓扑序列),同时更新它相连的顶点的最早发生时间,如果该顶点的最早发生时间+路径消耗大于原本的值就更新下。循环做到最后。
得到的逆拓扑序列分别出栈得到顶点,更新它相连的前面一个顶点的最晚发生时间。一直到最后。
然后判断是否是关键顶点并形成critical path。
邻接表实现方法:
1 和矩阵类似,唯一的不同是,求最晚发生时间,顶点由堆栈pop出来,因此节点必须加一个属性number才能知道自己在顶点数组中的位置(不然就要遍历节点数组找到位置,空间换时间)。
2 用1知道的位置找到附表,用附表节点的最晚发生时间减去路径消耗去更新自己的最晚发生时间
3 判断关键顶点并形成critical path
代码:
package nuaa.ds;
//拓扑排序,排到了某个顶点就等于可以访问那个顶点
//计算该顶点的最早发生时间并入栈
//得到栈内的逆拓扑序列出栈分别计算顶点最晚发生时间
//顶点的最早发生时间等于最晚发生时间则该顶点为关键路径上的点
public class CPofGraph {
private CreateGraph g;
private String[] vertexes;//邻接矩阵法所
private int[][] vr; //使用的成员变量
private VertexNode[] vertexNodes;//邻接表所使用的成员变量
/**
* 手动生成有向无环图测试算法
* A->B->E-->F-->H
* ^ ^\ ^
* \ | v /
* C---> D->G
*/
public CPofGraph(CreateGraph g){
this.g = g;
switch(g){
case AdjacencyMatrix://邻接矩阵
this.vertexes = new String[]{"A","B","C","D","E","F","G","H"};
vr = new int[8][8];
//arcs都有权重
vr[0] = new int[]{0,7,0,0,0,0,0,0};//好
vr[1] = new int[]{0,0,0,0,3,0,0,0};
vr[2] = new int[]{0,0,0,4,0,0,0,0};
vr[3] = new int[]{0,2,0,0,1,0,0,0};//多
vr[4] = new int[]{0,0,0,0,0,5,6,0};
vr[5] = new int[]{0,0,0,0,0,0,0,2};//浪
vr[6] = new int[]{0,0,0,0,0,0,0,4};
vr[7] = new int[]{0,0,0,0,0,0,0,0};//费
break;
case AdjacencyTable:
vertexNodes = new VertexNode[8];
String[] temp = {"A","B","C","D","E","F","G","H"};
for(int i=0;i<vertexNodes.length;i++){
vertexNodes[i] = new VertexNode(temp[i]);
vertexNodes[i].loc = i;
}
vertexNodes[0].arcNode = new ArcNode(1,7);
vertexNodes[1].arcNode = new ArcNode(4,3);
vertexNodes[2].arcNode = new ArcNode(3,4);
vertexNodes[3].arcNode = new ArcNode(1,2);
vertexNodes[3].arcNode.arcNode = new ArcNode(4,1);
vertexNodes[4].arcNode = new ArcNode(5,5);
vertexNodes[4].arcNode.arcNode = new ArcNode(6,6);
vertexNodes[5].arcNode = new ArcNode(7,2);
vertexNodes[6].arcNode = new ArcNode(7,4);
}
}
/**
* AOE(activity on edge)网,顶点表示事件,arc表示活动
*
*/
public void criticalPath(){//拓扑排序改改得到的关键路径
this.criticalPath(this.g);
};
private void criticalPath(CreateGraph g){
switch(g){
case AdjacencyMatrix:
tCriticalPath();
break;
case AdjacencyTable:
mCriticalPath();
}
}
//该方法破坏了原来了邻接矩阵,应该拷贝一份再调用更合适
private void tCriticalPath(){
int[][] vrcopy = new int[vr.length][vr.length];
for(int i=0;i<vrcopy.length;i++)
for(int j=0;j<vrcopy.length;j++)
vrcopy[i][j] = vr[i][j];
int[] ve = new int[vertexes.length];//顶点最早发生时间
int[] vl = new int[vertexes.length];//顶点最晚发生时间
Stack<Integer> stack = new Stack<Integer>();
boolean[] visited = new boolean[vr.length];
int zeroNumber = 0;//判断某列是否全为0
int chosen = 0;//找到序列下一个节点就把下标记录下来
while(true){
//寻找拓扑序列下一节点
for(int i=0;i<vr.length;i++){//i为列下标
if(!visited[i]){//如果该列代表元素没有被塞入拓扑序列
for(int j=0;j<vr.length;j++){//j为行下标,循环判断某列是否为全0
if(vr[j][i]!=0){//有非0元素表示有前序节点
break;//跳出来
}
zeroNumber++;//不然就++自增
}
}
if(zeroNumber==vr.length){//一列都为0
chosen = i;//记录下这个拓扑节点
visited[i] = true;//这个元素要被塞入拓扑序列
break;
}else{
zeroNumber = 0;
}
}
if(zeroNumber==vr.length){//如果找到了拓扑序列下一个节点
stack.push(chosen);
for(int i=0;i<vr.length;i++){//循环更新最早发生时间
if(vr[chosen][i]!=0&&ve[chosen]+vr[chosen][i]>ve[i]){
ve[i] = ve[chosen]+vr[chosen][i];
}
vr[chosen][i] = 0;//该行全部置为0
}
}else{//找不到下一拓扑节点了
break;
}
zeroNumber = 0;//计数器归0
}
//计算最晚发生时间
for(int i=0;i<vl.length;i++){//初始化,将最早发生时间当成最晚发生时间
vl[i] = ve[ve.length-1];
}
while(!stack.isEmpty()){
int iterator = stack.pop();
for(int i=0;i<vrcopy.length;i++){
if(vrcopy[i][iterator]!=0&&vl[iterator]-vrcopy[i][iterator]<vl[i]){
vl[i] = vl[iterator]-vrcopy[i][iterator];
}
}
}
//找到关键节点
for(int i=0;i<ve.length;i++){
if(ve[i]==vl[i])
System.out.print(vertexes[i]+" ");
}
}
private void mCriticalPath(){
int[] ve = new int[vertexNodes.length];//顶点最早发生时间
int[] vl = new int[vertexNodes.length];//顶点最晚发生时间
int[] indegree = this.getIndegree();
Stack<VertexNode> stack = new Stack<VertexNode>();//要拓扑序列用Queue更合适,只是
Stack<VertexNode> saveStack = new Stack<VertexNode>();//为了后面的关键路径算法
for(int i=0;i<indegree.length;i++){
if(indegree[i]==0){
stack.push(vertexNodes[i]);
}
}
while(!stack.isEmpty()){
VertexNode vertexNode = stack.pop();
saveStack.push(vertexNode);
ArcNode p = vertexNode.arcNode;
while(p!=null){
indegree[p.serialNumber]--;
if(indegree[p.serialNumber]==0){
stack.push(vertexNodes[p.serialNumber]);
}
if(ve[vertexNode.loc]+p.weight>ve[p.serialNumber]){//最早发生时间有更大的值
ve[p.serialNumber] = ve[vertexNode.loc]+p.weight;
}
p = p.arcNode;
}
}
for(int i=0;i<vl.length;i++){
vl[i] = ve[ve.length-1];//初始化最晚发生时间
}
while(!saveStack.isEmpty()){
VertexNode v = saveStack.pop();
ArcNode p = v.arcNode;
while(p!=null){
if(vl[p.serialNumber]-p.weight<vl[v.loc]){
vl[v.loc] = vl[p.serialNumber]-p.weight;
}
p = p.arcNode;
}
}
//找到关键节点
for(int i=0;i<ve.length;i++){
if(ve[i]==vl[i])
visit(vertexNodes[i]);
}
}
//邻接表求入度,入度in English 怎么说。。。。。。。。。
private int[] getIndegree(){
int[] indegree = new int[this.vertexNodes.length];
for(int i=0;i<vertexNodes.length;i++){
ArcNode p = vertexNodes[i].arcNode;
while(p!=null){
indegree[p.serialNumber]++;//序号表示节点在入度数组的位置
p = p.arcNode;
}
}
return indegree;
}
private void visit(VertexNode node){
System.out.print(node.vertex+" ");
}
class VertexNode{
String vertex;
ArcNode arcNode;
int loc;//又加的一个字段,堆栈里找到vertexNode必须要知道下标
//才能用下标定位到自己的最早发生时间+弧长度更新节点的最早发生时间
public VertexNode(String vertex){
this.vertex = vertex;
}
}
class ArcNode{
int serialNumber;//存放边连着的节点在vertexNode数组里面的位置
int weight;//权重在求最小生成树时用到,拓扑排序没有
ArcNode arcNode;
public ArcNode(int serialNumber,int weight){
this.serialNumber = serialNumber;
this.weight = weight;
}
}
}