咱们实现两个类,权重边类和有向图类。有向图类中组合权重边类来实现加权有向图。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; } }