package org.loda.graph;
import org.loda.structure.Stack;
import org.loda.util.In;
/**
*
* @ClassName: Johnson 时间复杂度:EVlgV
* @Description: 稀疏图上的johnson算法,由于稀疏图的数据结构推荐使用邻接链表,所以这里也采用邻接链表,该算法也是给稀疏图使用的,如果是密集图,推荐使用实现较为简单的FloydWashall算法,可以保证V^3的时间复杂度
*
* Johnson算法使用的方式相当于给每个边都加了一个权重,使得所有边都为非负数,这样就能对每个边使用较为高效的Dijkstra算法。
* 注意的是不能简单的给每个边加相同的值然后使得所有边都变成非负数,原因为假设从a->b有两条路径,一条权重为1+1,一条为2,本应权重和相等;如果都加1,则变成了2+2和3,不一致了,就会导致更新了不该更新的边
*
* Johnson比较巧妙的引入了h函数来解决这个问题,通过这个函数进行每个边的重新赋值权重
*
* @author minjun
* @date 2015年6月2日 下午5:49:13
*
*/
public class Johnson {
/**
* 权重函数h 用来重新赋予权重的函数,利用数组存储每个i对应的权重函数值
*/
private double[] h;
/**
* 距离
*/
private double[][] dist;
/**
* 前驱节点
*/
private int[][] prev;
/**
* 是否含有负环
*/
private boolean negativeCycle;
@SuppressWarnings("unchecked")
public Johnson(WeightDigraph g) {
int v = g.v();
h = new double[v];
dist = new double[v][v];
prev = new int[v][v];
// 先进行一次BellmanFord算法,用来判断是否存在负环,如果存在则停止计算最短路径
BellmanFord bf = new BellmanFord(g, 0);
if (bf.hasNegCycle()) {
negativeCycle = true;
System.out.println("有负环,不存在最短路径...");
return;
}
/**
* 利用Johnson算法计算最短路径
*/
computeShortestPath(g);
}
public void computeShortestPath(WeightDigraph g) {
int v = g.v();
// 创建一个新的图G,G含有一个多余的顶点s'作为原点计算h函数(目前将s'设为索引为v的顶点)
WeightDigraph G = new WeightDigraph(v + 1);
for (int i = 0; i < v; i++) {
// 将新的顶点v和其他节点相连,并将他们的距离设为0
G.add(v, i, 0);
for (Edge e : g.adj(i)) {
int j = e.otherSide(i);
G.add(i, j, e.weight());
}
}
int V = G.v();
BellmanFord bf = new BellmanFord(G, V - 1);
// 为权重函数赋值
for (int i = 0; i < v; i++) {
h[i] = bf.distTo(i);
}
WeightDigraph wg = new WeightDigraph(v);
//创建一个新的图,将所有更新权重后的节点和边加到该图中,这时的图为非负权重图,可以对他使用Dijkstra算法
for (int i = 0; i < v; i++) {
for (Edge e : g.adj(i)) {
int j = e.otherSide(i);
wg.add(i, j, e.weight() + h[i] - h[j]);
}
}
for (int i = 0; i < v; i++) {
//对每个节点使用Dijkstra算法
Dijkstra d = new Dijkstra(wg, i);
for (int j = 0; j < v; j++) {
//由于每个边都加了一定的权重,这里为了还原到原始距离,就将增加的权重减去
dist[i][j] = d.distTo(j) + h[j] - h[i];
d.pathTo(i);
prev[i]=d.prev;
}
}
}
/**
*
* @Title: hasNegtiveCycle
* @Description: 是否含有负环
* @param @return 设定文件
* @return boolean 返回类型
* @throws
*/
public boolean hasNegtiveCycle() {
return negativeCycle;
}
/**
*
* @Title: distTo
* @Description: i->j的距离
* @param @param i
* @param @param j
* @param @return 设定文件
* @return double 返回类型
* @throws
*/
public double distTo(int i, int j) {
return dist[i][j];
}
public Iterable<Integer> pathTo(int i,int j){
int[] p=prev[i];
Stack<Integer> path=new Stack<Integer>();
for(int m=j;m!=i;m=p[m]){
path.push(m);
}
path.push(i);
return path;
}
public static void main(String[] args) {
// 不含负权重环的文本数据
String text1 = "F:\\算法\\attach\\tinyEWDn.txt";
// 含有负权重环的文本数据
String text2 = "F:\\算法\\attach\\tinyEWDnc.txt";
WeightDigraph g = new WeightDigraph(new In(text1));
Johnson d = new Johnson(g);
if (d.hasNegtiveCycle()) {
System.out.println("该有向加权图含有负权重环,不存在最短路径");
} else {
int s = 0;
for (int i = 0; i < g.v(); i++) {
System.out
.println("从原点" + s + "到" + i + "距离为" + d.distTo(s, i)+",路径为:");
for(int m:d.pathTo(s, i)){
System.out.print(m+"->");
}
System.out.println();
}
}
}
}
如果采用负环图,打印结果为:
有负环,不存在最短路径...
该有向加权图含有负权重环,不存在最短路径
如果采用非负环图,打印结果为:
从原点0到0距离为0.0,路径为:
0->
从原点0到1距离为0.93,路径为:
0->2->7->3->6->4->5->1->
从原点0到2距离为0.26,路径为:
0->2->
从原点0到3距离为0.9900000000000001,路径为:
0->2->7->3->
从原点0到4距离为0.26000000000000023,路径为:
0->2->7->3->6->4->
从原点0到5距离为0.6100000000000001,路径为:
0->2->7->3->6->4->5->
从原点0到6距离为1.5100000000000002,路径为:
0->2->7->3->6->
从原点0到7距离为0.6000000000000001,路径为:
0->2->7->