Dijkstra 单源最短路径算法
前提:图中不能有负权边
图中的蓝色顶点表示已加入最短路径树。红色的边就是最短路径。
图的右方左部分是索引堆,右部分是distTo[v] (表示从源点到v的已知最短路径长度)。
Dijkstra算法思路 :

将0顶点开始,将访问顶点1、2、3

到没有访问过的顶点的最短路径是2,也就是顶点2

从2开始,将访问1、3、4。
先访问顶点 1 ,则0->2->1的路径就为3,此时不需要考虑0->1权为5的边了。

访问 顶点 4 ,则 0->2->4的路径长为7。

访问 顶点 3,则 0->2->3的路径长为5,此时就不需要考虑0->3权为6的边了。

此时所能到达的最短路径是顶点1。

考虑顶点1的邻边,1->4的路径更小。

此时所能到达的最短路径是顶点4。

此时所能到达的最短路径是顶点3.

public class DijkstraSP<E extends Number & Comparable>{private WeightedGraph G;// 图的引用private int s;// 起始点private Number[] distTo;// distTo[i]存储从起始点s到顶点i的最短路径长度private boolean[] marked;// 标记数组, 在算法运行过程中标记节点i是否被访问private Edge<E>[] from;// from[i]记录最短路径中, 到达i点的边是哪一条// 可以用来恢复整个最短路径public DijkstraSP(WeightedGraph graph,int s){this.G=graph;this.s=s;distTo=new Number[G.V()];marked=new boolean[G.V()];from=new Edge[G.V()];//使用索引堆记录当前找到的到达每个顶点的最短距离 ---> Number类型IndexMinHeap<E> indexMinHeap=new IndexMinHeap<>(G.V());//初始化起始点distTo[s] = 0.0;from[s]=new Edge<E>(s,s,(E)((Number)0.0));marked[s]=true;indexMinHeap.insert(s,(E)distTo[s]);while (!indexMinHeap.isEmpty()){int v=indexMinHeap.extractMinIndex();// distTo[v]就是s到v的最短距离marked[v] = true;for(Object item:G.adj(v)){Edge<E> edge=(Edge<E>)item;int w=edge.other(v);//如果从s点到w点的最短路径还没有找到if(!marked[w]){// 如果w点以前没有访问过,// 或者访问过, 但是通过当前的v点到w点距离更短, 则进行更新if(from[w]==null ||(distTo[v].doubleValue()+edge.wt().doubleValue() < distTo[w].doubleValue())){distTo[w]=distTo[v].doubleValue()+edge.wt().doubleValue();from[w]=edge;if(indexMinHeap.contain(w)){indexMinHeap.change(w,(E)distTo[w]);}else{indexMinHeap.insert(w,(E)distTo[w]);}}}}}}//获取从s点到w点的最短路径长度public Number shortestPathTo(int w){assert hasPathTo(w);return distTo[w];}// 判断从s点到w点是否联通public boolean hasPathTo(int w){assert w>=0 && w<G.V();return marked[w];}//寻找从s到w的最短路径, 将整个路径经过的边存放在res中public Vector<Edge<E>> shortestPath(int w){assert hasPathTo(w);//通过from数组逆向查找到从s到w的路径, 存放到栈中Stack<Edge<E>> stack=new Stack<>();Edge<E> e=from[w];while (e.v()!=s){stack.push(e);e=from[e.v()];}//最后e.v()就是s,那么e这条边入栈stack.push(e);//从栈中依次取出元素, 获得顺序的从s到w的路径Vector<Edge<E>> res=new Vector<>();while (!stack.isEmpty()){Edge<E> edge=stack.pop();res.add(edge);}return res;}// 打印出从s点到w点的路径public void showPath(int w){assert hasPathTo(w);Vector<Edge<E>> path = shortestPath(w);for( int i = 0 ; i < path.size() ; i ++ ){System.out.print( path.elementAt(i).v() + " -> ");if( i == path.size()-1 ){System.out.println(path.elementAt(i).w());}}}}
Bellman-Ford 单源最短路径算法
1. 负权边问题
顶点0、1、2构成了一个负权环。
显然,有负权环的图,没有最短路径。

如果一个图没有负权环,从一点到另外一点的最短路径,
最多经过所有的V个顶点,有(V-1)条边。
否则,存在顶点经过了两次,也就是说存在负权环。
2. 思路
- 对一个顶点的一次松弛操作,就是找到经过这个点的另外一条路径,多一条边,权值更小。
- 如果一个图没有负权环,从一个顶点到另外一个顶点的最短路径,最多经过所有的V个顶点,有(V-1)条边。
- 对所有的点进行(V-1)次松弛操作,理论上可以找到到其他所有点的最短路径。
- 如果还可以继续松弛,说明原图中有负权环。
3. 实践
public BellmanFordSP(WeightedGraph graph,int s){this.G=graph;this.s=s;distTo=new Number[G.V()];marked=new boolean[G.V()];from=new Edge[G.V()];// // 设置distTo[s] = 0, 并且让from[s]不为NULL, 表示初始s节点可达且距离为0distTo[s] = 0.0;from[s] = new Edge<E>(s, s, (E)(Number)(0.0));// Bellman-Ford的过程// 进行V-1次循环, 每一次循环求出从起点到其余所有点, 最多使用pass步可到达的最短距离for(int pass=1;pass<G.V();pass++){// 每次循环中对所有的边进行一遍松弛操作// 遍历所有边的方式是先遍历所有的顶点, 然后遍历和所有顶点相邻的所有边for(int i=0;i<G.V();i++){for(Object item:G.adj(i)){Edge<E> e=(Edge<E>)item;// 对于每一个边首先判断e->v()可达// 之后看如果e->w()以前没有到达过, 显然我们可以更新distTo[e->w()]// 或者e->w()以前虽然到达过, 但是通过这个e我们可以获得一个更短的距离,// 即可以进行一次松弛操作, 我们也可以更新distTo[e->w()]if(from[e.v()]!=null &&(from[e.w()]==null || distTo[e.v()].doubleValue()+e.wt().doubleValue()<distTo[e.w()].doubleValue())){distTo[e.w()]=distTo[e.v()].doubleValue()+e.wt().doubleValue();from[e.w()]=e;}}}}}
