您的位置:首页 > 理论基础 > 数据结构算法

数据结构 JAVA描述(八) 最短路径+拓扑排序+关键路径

2015-12-31 12:12 751 查看

最短路径

迪杰斯特拉算法

原文分析思路(理解不了这里就pass掉):在有向网中,从某一源点到其余各点都有一条最短路径。首先在这些最短路径中,长度最短的必定只有一条弧,且它的权值是从源点出发的所有弧上权的最小值;其次,第二条长度次短的最短路径只可能有两种情况:

从源点出发的一条弧,权值大于已求的最短路径的那条弧,但小于其他的从源点出发的弧的权值

是一条经过已求得最短路径的路径

该算法需要引入辅助数组D,每个分量D[i]存放当前所找到的从源点到各个终点vi的最短路径的长度。过程是:

(1)令S = {v},其中v为源点,并设定D[i] 的初始值为:D[i] = |v,vi|

(2)选择顶点vj使得:D[j] = min{D[i]}(i∈V-S),并将vj并入到S中

(3)对集合V-S的所有顶点vk,若D[j] + |vj,vk| < D[k],则修改D[k]的值

(4)重复(2)、(3)的操作共n-1次,由此求得的所有最短路径是按路径长度递增的序列

ShortestPath_DIJ

package Graph;

/**
* @description 求最短路径的问题 迪杰斯特拉  算法
*              (1)令S = {v},其中v为源点,并设定D[i] 的初始值为:D[i] = |v,vi|
*              (2)选择顶点vj使得:D[j] = min{D[i]}(i∈V-S),并将vj并入到S中
*              (3)对集合V-S的所有顶点vk,若D[j] + |vj,vk| < D[k],则修改D[k]的值
*              (4)重复(2)、(3)的操作共n-1次,由此求得的所有最短路径是按路径长度递增的序列
* @date  2015年12月31日
*/
public class ShortestPath_DIJ {
// v0到其余顶点的最短路径,若p[v][w]为true,则w是从v0到v当前求得最短路径上的顶点
private boolean[][] P;

// v0到其余顶点的最小带权长度 (是变化的,一旦发现更小的,就重新赋值,到最后就是最短路径长度了)
private int[] D;

private final static int INFINITY = Integer.MAX_VALUE;

public boolean[][] getP() {
return P;
}

public int[] getD() {
return D;
}

/**
* @description 用迪杰斯特拉算法求有向网的v0到其余顶点的最短路径p[v]及其权值D[v]
* @date  2015年12月31日
*/
public void DIJ(MGraph G, int v0){
int vexNum = G.getVexNum(); // 顶点数
this.P = new boolean[vexNum][vexNum];
this.D = new int[vexNum];
//finish[v]为true当且仅当v属于S,即已经求得从v0到v的最短路径
boolean[] finish = new boolean[vexNum];

//初始化所有数据
for(int v = 0; v < vexNum; v++){
finish[v] = false;
D[v] = G.getArcs()[v0][v];
for(int w = 0; w < vexNum; w++){
P[v][w] = false;
}
if(D[v] < INFINITY){
P[v][v0] = true;
P[v][v] = true;
}
}

D[v0] = 0; //从v0开始,并入S集
finish[v0] = true;

int v = -1 ; // 这里的赋值没有什么实际意义,只是为了保证编译正确
//开始主循环,每次求得v0到某个v顶点的最短路径,并将v加入到S集.循环n-1次
for(int i = 1; i < vexNum; i++){
int min = INFINITY; //当前所知离v0最近的距离
for(int w = 0; w < vexNum; w++){
if( !finish[w]){
if(D[w] < min){
v = w;
min = D[w];
}
}
}
finish[v] = true; //离v0最近的v并入S

//更新当前最短路径和距离
for(int w = 0; w < vexNum; w++){
if( !finish[w] && G.getArcs()[v][w] < INFINITY && (min + G.getArcs()[v][w] < D[w])){
D[w] = min + G.getArcs()[v][w];
//下面两句这么理解,现在路径是v0-v-w,所以经过了v点,那么v0到v的最小路径自然要给w,同时再加上w点(P[W][W] = true)
System.arraycopy(P[v], 0, P[w], 0, P[v].length);
P[w][w] = true;
}
}

}
}

// 测试
public static void main(String[] args) throws Exception {
Object[] vexs = { "v0", "v1", "v2", "v3", "v4", "v5" };
int[][] arcs = { { INFINITY, INFINITY, 10, INFINITY, 30, 100 },
{ INFINITY, INFINITY, 5, INFINITY, INFINITY, INFINITY },
{ INFINITY, INFINITY, INFINITY, 50, INFINITY, INFINITY },
{ INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, 10 },
{ INFINITY, INFINITY, INFINITY, 20, INFINITY, 60 },
{ INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY } };
MGraph G = new MGraph(GraphKind.DG, 6, 8, vexs, arcs);
ShortestPath_DIJ dij = new ShortestPath_DIJ();
dij.DIJ(G, 0);

for(int i = 0; i < vexs.length; i++){
System.out.println("从v0出发,到" + G.getVex(i) + "的最短路径长度是:" + (dij.getD()[i] == INFINITY ? "∞" : dij.getD()[i]) );
System.out.print("经过结点有:");
for(int j = 0; j < vexs.length; j++){
if(dij.getP()[i][j]) //即从v0到i位置的顶点经过了j位置的顶点
System.out.print(G.getVex(j) + " ");
}
System.out.println();
}
}

}


测试分析图



从源点v0到各个终点的D值和最短路径的求解过程:

终点12345
v1∞无
v210(v0,v2)
v360(v0,v2,v3)50(v0,v4,v3)
v430(v0,v4)30(v0,v4)
v5100(v0,v5)100(v0,v5)90(v0,v4,v5)60(v0,v4,v3,v5)
vjv2v4v3v5
S{v0,v2}{v0,v2,v4}{v0,v2,v3,v4}{v0,v2,v3,v4,v5}

弗洛伊德算法

基本思想是:求得一个n阶方阵序列:{D(-¹),D(º),D(¹) …… D(n-1)},其中D(-¹)[i][j]表示从vi出发,不经过其他顶点直接到达vj的路径长度,D(k)则表示从vi到vj的中间只可能经过v0,v1……,vk,而不可能经过v k+1,v k+2等顶点的最短路径长度。

ShortestPath_FLOYD

package Graph;

/**
* @description 求最短路径的问题 弗洛伊德  算法   注意其成员变量的含义和迪杰斯特拉算法的完全不一样
* @date  2015年12月31日
*/
public class ShortestPath_FLOYD {
//顶点v和w之间的最短路径P[v][w],若P[v][w][u]为true,则u是从v到w当前求得最短路径上的顶点
private boolean[][][] P;

//顶点v和w之间的最短路径的带权长度D[v][w]
private int[][] D;

public final static int INFINITY = Integer.MAX_VALUE;

public boolean[][][] getP() {
return P;
}

public int[][] getD() {
return D;
}

public void FLOYD(MGraph G){
int vexNum = G.getVexNum();
this.P = new boolean[vexNum][vexNum][vexNum];
this.D = new int[vexNum][vexNum];

//初始化所有数据
for(int v = 0; v < vexNum; v++){
for(int w = 0; w < vexNum; w++){
D[v][w] = G.getArcs()[v][w];
for(int u = 0; u < vexNum; u++)
P[v][w][u] = false;

if(D[v][w] < INFINITY) {  //从v到w有直接路径
P[v][w][v] = true;
P[v][w][w] = true;
}
}
}

for(int u = 0; u < vexNum; u++)
for(int v = 0; v < vexNum; v++)
for(int w = 1; w < vexNum; w++)
if(D[v][u] < INFINITY && D[u][w] < INFINITY && D[v][u] + D[u][w] < D[v][w]){
D[v][w] = D[v][u] + D[u][w];
for(int i = 0; i< vexNum; i++)
P[v][w][i] = P[v][u][i] || P[u][w][i];
}
}

public static void main(String[] args) throws Exception {
Object[] vexs = { "A", "B", "C", "D"};
int[][] arcs = { { 0, 15, 3, INFINITY},
{ 10, 0, 2, INFINITY},
{ INFINITY, INFINITY, 0, 2},
{ INFINITY, 8, 4, 0} };
MGraph G = new MGraph(GraphKind.DG, 4, 7, vexs, arcs);
ShortestPath_FLOYD floyd = new ShortestPath_FLOYD();
floyd.FLOYD(G);

for(int i = 0; i < vexs.length; i++){
System.out.println("从" + G.getVex(i) + "出发:");
for(int j = 0; j < vexs.length; j++){
System.out.println("到" + G.getVex(j) + "的最短路径长度是:" + (floyd.getD()[i][j] == INFINITY ? "∞" : floyd.getD()[i][j]) );
System.out.print("经过结点有:");
for(int k = 0; k < vexs.length; k++){
if(floyd.getP()[i][j][k])  //即从vi到vj经过了k位置的顶点
System.out.print(G.getVex(k) + " ");
}
System.out.println();
}
}
}

}


测试分析图



弗洛伊德算法求各点间最短路径长度过程



拓扑排序

判断有向网中是否存在有向环的一个办法是:针对AOV(活动顶点图,Activity On Vertex Network)网进行拓扑排序

在AOV网中选择一个没有前驱的结点并输出

从AOV网中删除该结点以及从它出发的弧

重复以上过程,直至AOV网为空,或者剩余子图中不存在没有前驱的顶点(说明该AOV网中存在有向环)

Topological

package Graph;

import Stack.LinkStack;

/**
* @Description 拓扑排序算法
* @author liuquan
* @time 2016年1月1日 下午3:58:13
*/
public class Topological {

/**
* @description 求各个顶点入度的算法
* @param G
* @return
* @throws Exception
* @time 2016年1月1日 下午4:02:49
*/
public static int[] findInDegree(ALGraph G) throws Exception{
int[] indegree  = new int[G.getVexNum()];
for(int i = 0; i < G.getVexNum(); i++)
for(ArcNode arc = G.getVexs()[i].getFirstArc(); arc != null; arc = arc.getNextArc())
++ indegree[arc.getAdjVex()]; //入度增1

return indegree;
}

/**
* @description 若G无回路,则输出G的顶点的一个拓扑排序序列并返回true
* @param G
* @return
* @throws Exception
* @time 2016年1月1日 下午4:13:04
*/
public static boolean topologicalSort(ALGraph G) throws Exception{
int count = 0; //输出顶点计数
int[] indegree = findInDegree(G); //求各个顶点的入度
LinkStack S = new LinkStack();  //零入度的 顶点栈
for(int i = 0; i < G.getVexNum(); i++)
if(indegree[i] == 0)
S.push(i);  //入度为0的进栈
while( !S.isEmpty()){
int i = (Integer) S.pop();
System.out.print(G.getVex(i) + " "); //输出v号顶点并计数
++count;
for(ArcNode arc = G.getVexs()[i].getFirstArc(); arc != null; arc = arc.getNextArc()){
int k = arc.getAdjVex();
if(--indegree[k] == 0) // 对k号顶点的每个邻接点的入度减1
S.push(k);
}
}
if(count < G.getVexNum())
return false;  //该有向图有回路
else
return true;
}

// 测试
public static void main(String[] args) throws Exception {
ArcNode ab = new ArcNode(1);
VNode A = new VNode("A", ab);

ArcNode bc = new ArcNode(2);
ArcNode be = new ArcNode(4, 0, bc);
VNode B = new VNode("B", be);

ArcNode cd = new ArcNode(3);
VNode C = new VNode("C", cd);

VNode D = new VNode("D");

ArcNode ed = new ArcNode(3);
VNode E = new VNode("E", ed);

ArcNode fa = new ArcNode(0);
ArcNode fb = new ArcNode(1, 0, fa);
ArcNode fe = new ArcNode(4, 0, fb);
VNode F = new VNode("F", fb);

VNode[] vexs = {A, B, C, D, E, F};
ALGraph G = new ALGraph(GraphKind.DG, 6, 8, vexs);
System.out.println(topologicalSort(G));
}
}


测试分析图:



关键路径:

以弧表示活动,弧上的权值表示进行该项活动所需的时间,以顶点表示“事件”,称这种有向图为活动网,简称AOE(Activity On Edge)。

从源点v0出发,令ve0=0,按拓扑排序序列,求其余各顶点的ve(j)=max{ve(i) + |i,j|},i,j∈T,T是所有以第j个顶点为头的弧的集合

从汇点vn-1出发,令vl(n-1) = ve(n-1),按逆拓扑排序求其余顶点允许的最迟开始时间为:vl(i)= min{vl(j)-|i,j|},i,j∈S,S是所有以第j个顶点为尾的弧的集合

求每一项活动ai的最早开始时间e(i)=ve(j)和最迟开始时间l(i)=vl(j)-|i,j|。若满足e(i)=l(i),则它是关键路径。

测试分析图:



事件vevl
v000
v166
v246
v358
v477
v5710
v61616
v71414
v81818
活动ell-e
a10(ve0)0(vl1-/v0v1/)0
a20(ve0)2(vl2-/v0v2/)2
a30(ve0)3(vl3-/v0v3/)3
a46(ve1)6(vl4-/v1v4/)0
a54(ve2)6(vl4-/v2v4/)2
a65(ve3)8(vl5-/v3v5/)3
a77(ve4)7(vl6-/v4v6/)0
a87(ve4)7(vl7-/v4v7/)0
a97(ve5)10(vl7-/v5v7/)3
a1016(ve10)16(vl8-/v6v8/)0
a1114(ve11)14(vl8-/v7v8/)0

CriticalPath :

package Graph;

import Stack.LinkStack;

/**
* @Description AOE网的关键路径
* @time 2016年1月2日 下午10:24:51
*/
public class CriticalPath {

private LinkStack T = new LinkStack(); //拓扑排序的顶点栈
private int[] ve, vl; //各顶点的最早发生时间和最迟发生时间

/**
* @description 求各个顶点入度的算法
* @param G
* @return
* @throws Exception
* @time 2016年1月1日 下午4:02:49
*/
public  int[] findInDegree(ALGraph G) throws Exception{
int[] indegree  = new int[G.getVexNum()];
for(int i = 0; i < G.getVexNum(); i++)
for(ArcNode arc = G.getVexs()[i].getFirstArc(); arc != null; arc = arc.getNextArc())
++ indegree[arc.getAdjVex()]; //入度增1

return indegree;
}

/**
* @description 求各个顶点的最早发生时间ve,若G无回路,用栈T存放一个拓扑排序,且函数返回true
* @param G
* @return
* @throws Exception
* @time 2016年1月1日 下午4:13:04
*/
public  boolean topologicalOrder(ALGraph G) throws Exception{
int count = 0; //输出顶点计数
int[] indegree = findInDegree(G); //求各个顶点的入度
LinkStack S = new LinkStack();  //零入度的 顶点栈
for(int i = 0; i < G.getVexNum(); i++)
if(indegree[i] == 0)
S.push(i);  //入度为0的进栈
this.ve = new int[G.getVexNum()]; //初始化

while( !S.isEmpty()){
int i = (Integer) S.pop();
this.T.push(i); //i号顶点入T栈并计数
++count;
for(ArcNode arc = G.getVexs()[i].getFirstArc(); arc != null; arc = arc.getNextArc()){
int k = arc.getAdjVex();
if(--indegree[k] == 0) // 对k号顶点的每个邻接点的入度减1
S.push(k);
if(ve[i] + arc.getValue() > ve[k])
ve[k] = ve[i] + arc.getValue();
}
}
if(count < G.getVexNum())
return false;  //该有向图有回路
else
return true;
}

/**
* @description 输出G的各项关键活动
* @param G
* @return
* @throws Exception
* @time 2016年1月2日 下午10:53:20
*/
public boolean criticalPath(ALGraph G) throws Exception{
if( !topologicalOrder(G))
return false;
vl = new int[G.getVexNum()];
// 初始化各顶点事件的最迟发生时间
for(int i = 0; i < G.getVexNum(); i++){
vl[i] = ve[G.getVexNum() - 1];
}
while( !T.isEmpty()){ // 按拓扑序列 逆序 求各顶点的vl值
int i = (int) T.pop();
for(ArcNode arc = G.getVexs()[i].getFirstArc(); arc != null; arc = arc.getNextArc()){
int k = arc.getAdjVex();
int value = arc.getValue();
if(vl[k] - value < vl[i])
vl[i] = vl[k] - value;
}
}

for(int i = 0; i < G.getVexNum(); i++){
//求ee,el和关键路径
for(ArcNode arc = G.getVexs()[i].getFirstArc(); arc != null; arc = arc.getNextArc()){
int k = arc.getAdjVex();
int value = arc.getValue();
int ee = ve[i];
int el = vl[k] - value;
System.out.println(G.getVex(i) + "->" + G.getVex(k) + "\t最早开始时间:" + ee + ",最迟开始时间:"+ el +"。" + (ee == el ? "YES" : "NO"));
}
}
return true;
}

public static void main(String[] args) throws Exception {
ArcNode v01 = new ArcNode(1, 6);
ArcNode v02 = new ArcNode(2, 4, v01);
ArcNode v03 = new ArcNode(3, 5, v02);
VNode v0 = new VNode("v0", v03);

ArcNode v14 = new ArcNode(4, 1);
VNode v1 = new VNode("v1", v14);

ArcNode v24 = new ArcNode(4, 1);
VNode v2 = new VNode("v2", v24);

ArcNode v35 = new ArcNode(5, 2);
VNode v3 = new VNode("v3", v35);

ArcNode v46 = new ArcNode(6, 9);
ArcNode v47 = new ArcNode(7, 7, v46);
VNode v4 = new VNode("v4", v47);

ArcNode v57 = new ArcNode(7, 4);
VNode v5 = new VNode("v5", v57);

ArcNode v68 = new ArcNode(8, 2);
VNode v6 = new VNode("v6", v68);

ArcNode v78 = new ArcNode(8, 4);
VNode v7 = new VNode("v7", v78);

VNode v8 = new VNode("v8");

VNode[] vexs = {v0, v1, v2, v3, v4, v5, v6, v7, v8};
ALGraph G = new ALGraph(GraphKind.DG, 9, 11, vexs);
new CriticalPath().criticalPath(G);

}

}


需要注意的是:并不是加快任何一个关键路径活动都可以缩短整个工程完成的时间,只有在不改变AOE网的关键路径的前提下,加快包含在关键路径上的活动才可以缩短整个工程的完成时间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息