咱們實現兩個類,權重邊類和有向圖類。有向圖類中組合權重邊類來實現加權有向圖。java
public class DirectedEdge { private int v; private int w; private double weight; public DirectedEdge(int v,int w,double weight) { this.v = v;this.w = w;this.weight = weight; } public double weight(){ return weight; } public int form(){ return v; } public int to(){ return w; } public String toString(){ return String.format("%d->%d %.2f", v,w,weight); } }
public class EdgeWeightedDigraph { private int V;//頂點數 private int E;//邊數 private Bag<DirectedEdge>[] adj;//鄰接表 public EdgeWeightedDigraph(int V) { this.V = V; this.E = 0; adj = (Bag<DirectedEdge>[]) new Bag[V]; for(int v=0;v< V;v++) { adj[v] = new Bag<DirectedEdge>(); } } public int V() {return V;} public int E() {return E;} public void addEdge(DirectedEdge e) { adj[e.form()].add(e); E++; } //從v指出的邊 public Iterable<DirectedEdge> adj(int v){return adj[v];} //該有向圖中全部的邊 public Iterable<DirectedEdge> edges(){ Bag<DirectedEdge> bag = new Bag<DirectedEdge>(); for(int v = 0; v<V; v++) for(DirectedEdge e : adj[v]) bag.add(e); return bag; } }
Dijkstra算法能夠解決邊的權重非負的最短路徑問題,沒法判斷含負權邊的圖的最短路徑,但Bellman-Ford算法能夠。算法
在實現Dijkstra算法以前,必須先了解邊的鬆弛:鬆弛邊v->w意味着檢查從s到w的最短路徑是不是先從s到v,再從v到w。若是是,則根據這個狀況更新數據。下面的代碼實現了放鬆一個從給定頂點的指出的全部的邊:數組
private void relax(EdgeWeightedDigraph G,int v) { for(DirectedEdge e: G.adj(v)) { int w = e.to(); if(distTo[w]>distTo[v]+e.weight()) { distTo[w] = distTo[v]+e.weight(); edgeTo[w] = e; } } }
其中,distTo[]是一個頂點索引數組,保存的是G中路徑的長度。對於從s可達的全部頂點w,distTo[v]的值是從s到w的某條路徑長度,對於s不可達的全部頂點w,該值爲無窮大。數據結構
public class DijkstraSP { private DirectedEdge[] edgeTo; //edgeTo用來逆推最短路徑 private double[] distTo; //distTo[]用來計算最短路徑 private IndexMinPQ<Double> pq; //用來保存須要被放鬆的頂點並確認下一個被放鬆的頂點 public DijkstraSP(EdgeWeightedDigraph G,int s) { //初始化 edgeTo = new DirectedEdge[G.V()]; distTo = new double[G.V()]; pq = new IndexMinPQ<Double>(G.V()); for(int v = 0;v<G.V();v++) distTo[v] = Double.POSITIVE_INFINITY; distTo[s] = 0.0; //從起始頂點s開始 pq.insert(s,0.0); //調用優先權隊列的insert()方法 while(!pq.isEmpty()) relax(G,pq.delMin()); } private void relax(EdgeWeightedDigraph G,int v) { for(DirectedEdge e: G.adj(v)) { int w = e.to(); if(distTo[w]>distTo[v]+e.weight()) { distTo[w] = distTo[v]+e.weight(); edgeTo[w] = e; if(pq.contains(w)) pq.change(w, distTo[w]); else pq.insert(w, distTo[w]); } } } public double distTo(int v) { return distTo[v]; } public boolean hasPathTo(int v) { return distTo[v]<Double.POSITIVE_INFINITY; } public Iterable<DirectedEdge> pathTo(int v){ if(!hasPathTo(v)) return null; Stack<DirectedEdge> path = new Stack<DirectedEdge>(); for(DirectedEdge e = edgeTo[v];e!=null;e = edgeTo[e.form()]) path.push(e); return path; } }
簡單的在上述算法外添加一層循環便可實現任意頂點對之間的最短路徑(權重非負)。this
若是加權有向圖不含有向環,則下面要實現的算法比Dijkstra算法更快更簡單。它有如下特色:spa
該方法將頂點的放鬆與拓撲排序結合起來,首先將distTo[s]初始化爲0,其餘distTo[]初始化爲無窮大,而後一個個地按照拓撲排序放鬆全部頂點。.net
按照拓撲排序放鬆頂點,就能在和V+E成正比的時間內解決無環加權有向圖的單點最短路徑問題。code
public class AcyclicSP { private DirectedEdge[] edgeTo; private double[] distTo; public AcyclicSP(EdgeWeightedDigraph G,int s) { //初始化 edgeTo = new DirectedEdge[G.V()]; distTo = new double[G.V()]; for(int v = 0;v<G.V();v++) distTo[v] = Double.POSITIVE_INFINITY; distTo[s] = 0.0; //拓撲排序對象 Tolpological top = new Tolpological(G); //按照拓撲排序放鬆頂點 for(int v: top.order()) relax(G,v); } //relax()、distTo()、hasPathTo()、pathTo()同Dijkstra算法 }
改實現中不須要marked[]數組,由於按照拓撲排序處理不可能再次遇到已經被放鬆過的頂點。orm
Dijkstra算法沒法判斷含負權邊的圖的最短路徑,若是遇到負權,在沒有負權迴路(迴路的權值和爲負)存在時,能夠採用Bellman - Ford算法正確求出最短路徑。對象
當且僅當加權有向圖中至少存在一條從s到v的有向路徑且全部從s到v的有向路徑上的任意頂點都不存在與任何負權重環中,s到v的最短路徑纔是存在的。
在任意含有V個頂點的加權有向圖中給定起點s,從s沒法達到任何負權重環,一下算法可以解決其中的單源最短路徑問題:將distTo[s]初始化爲0,其餘distTo[]初始化爲無窮大。以任意順序放鬆全部邊,重複V輪。
這個算法很是簡潔通用,在進行過負權重檢測(見最後)以後,下面代碼就能夠實現Bellman-Ford算法:
for(int num = 0; num<G.V(); num++) for(v = 0;v<G.V();v++) for(DirectedEdge e: G.adj(v)) relax(e);
但根據經驗可知在任意一輪中許多邊的放鬆是不會成功的:只有上一輪distTo[]值發生變化的頂點指出的邊纔可以改變其餘distTo[]的值。爲了記錄這樣的頂點,引入一條FIFO隊列。
Bellman-Ford算法所需時間和EV成正比,空間和V成正比。
首先,將起始頂點s加入隊列中,而後進入一個循環,其中每次都從隊列中取出一個頂點將其放鬆。而後將被成功放鬆的邊所指向的頂點加入隊列中,這樣能保證:
private void relax(EdgeWeightedDigraph G,int v) { for(DirectedEdge e: G.adj(v)) { int w = e.to(); if(distTo[w]>distTo[v]+e.weight()) { distTo[w] = distTo[v]+e.weight(); edgeTo[w] = e; if(!onQ[w]) { queue.enqueue(w); onQ[w] = true; } } if(cost++%G.V()==0) findNegativeCycle(); } }
public class BellmanFordSP { private double [] distTo; //從起點到某個頂點的路徑長度 private DirectedEdge[] edgeTo; //從起點到某個頂點的最後一條邊 private boolean[] onQ; //該頂點是否存在於隊列中 private Queue<Integer> queue; //正在被放鬆的頂點 private int cost; //relax()的調用次數 private Iterable<DirectedEdge> cycle; //edgeTo[]中是否含有負權重環 public BellmanFordSP(EdgeWeightedDigraph G,int s) { distTo = new double[G.V()]; edgeTo = new DirectedEdge[G.V()]; onQ = new boolean[G.V()]; queue = new Queue<Integer>(); for(int v=0;v<G.V();v++) distTo[v] = Double.POSITIVE_INFINITY; distTo[0] = 0.0; queue.enqueue(s); onQ[s] = true; while(!queue.isEmpty()&&!hasNegativeCycle()) { int v = queue.dequeue(); onQ[v] = false; relax(G,v); } } private void relax(EdgeWeightedDigraph G,int v) {}//見上文算法 public boolean hasPathTo(int v) {}//與Dijkstra算法中方法相同 public Iterable<DirectedEdge> pathTo(int v){}//與Dijkstra算法中方法相同 /********************************************************************************************/ //負權重檢測 private void findNegativeCycle() { int V = edgeTo.length; EdgeWeightedDigraph spt; spt = new EdgeWeightedDigraph(V); for(int v = 0;v<V; v++) if(edgeTo[v]!=null) spt.addEdge(edgeTo[v]); EdgeWeightedCycleFinder cf; cf = EdgeWeightedCycleFinder(spt); cycle = cf.cycle(); } public boolean hasNegativeCycle() { return cycle != null; } public Iterable<DirectedEdge> negativeCycle(){ return cycle; } }