算法導論——全部點對最短路徑:稀疏圖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->
相關文章
相關標籤/搜索