算法导论——全部点对最短路径:稀疏图Johnson算法

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();
			}
		}

	}
}

若是采用负环图,打印结果为: java

有负环,不存在最短路径...
该有向加权图含有负权重环,不存在最短路径

若是采用非负环图,打印结果为: 算法

从原点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->
相关文章
相关标签/搜索