Dijkstra 单源最短路径算法

前提:图中不能有负权边

图中的蓝色顶点表示已加入最短路径树。红色的边就是最短路径。

图的右方左部分是索引堆,右部分是distTo[v] (表示从源点到v的已知最短路径长度)。

Dijkstra算法思路 :

image.png

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

image.png

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

image.png

从2开始,将访问1、3、4。

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

  1. public class DijkstraSP<E extends Number & Comparable>{
  2. private WeightedGraph G;
  3. // 图的引用
  4. private int s;
  5. // 起始点
  6. private Number[] distTo;
  7. // distTo[i]存储从起始点s到顶点i的最短路径长度
  8. private boolean[] marked;
  9. // 标记数组, 在算法运行过程中标记节点i是否被访问
  10. private Edge<E>[] from;
  11. // from[i]记录最短路径中, 到达i点的边是哪一条
  12. // 可以用来恢复整个最短路径
  13. public DijkstraSP(WeightedGraph graph,int s){
  14. this.G=graph;
  15. this.s=s;
  16. distTo=new Number[G.V()];
  17. marked=new boolean[G.V()];
  18. from=new Edge[G.V()];
  19. //使用索引堆记录当前找到的到达每个顶点的最短距离 ---> Number类型
  20. IndexMinHeap<E> indexMinHeap=new IndexMinHeap<>(G.V());
  21. //初始化起始点
  22. distTo[s] = 0.0;
  23. from[s]=new Edge<E>(s,s,(E)((Number)0.0));
  24. marked[s]=true;
  25. indexMinHeap.insert(s,(E)distTo[s]);
  26. while (!indexMinHeap.isEmpty()){
  27. int v=indexMinHeap.extractMinIndex();
  28. // distTo[v]就是s到v的最短距离
  29. marked[v] = true;
  30. for(Object item:G.adj(v)){
  31. Edge<E> edge=(Edge<E>)item;
  32. int w=edge.other(v);
  33. //如果从s点到w点的最短路径还没有找到
  34. if(!marked[w]){
  35. // 如果w点以前没有访问过,
  36. // 或者访问过, 但是通过当前的v点到w点距离更短, 则进行更新
  37. if(from[w]==null ||
  38. (distTo[v].doubleValue()+edge.wt().doubleValue() < distTo[w].doubleValue())){
  39. distTo[w]=distTo[v].doubleValue()+edge.wt().doubleValue();
  40. from[w]=edge;
  41. if(indexMinHeap.contain(w)){
  42. indexMinHeap.change(w,(E)distTo[w]);
  43. }else{
  44. indexMinHeap.insert(w,(E)distTo[w]);
  45. }
  46. }
  47. }
  48. }
  49. }
  50. }
  51. //获取从s点到w点的最短路径长度
  52. public Number shortestPathTo(int w){
  53. assert hasPathTo(w);
  54. return distTo[w];
  55. }
  56. // 判断从s点到w点是否联通
  57. public boolean hasPathTo(int w){
  58. assert w>=0 && w<G.V();
  59. return marked[w];
  60. }
  61. //寻找从s到w的最短路径, 将整个路径经过的边存放在res中
  62. public Vector<Edge<E>> shortestPath(int w){
  63. assert hasPathTo(w);
  64. //通过from数组逆向查找到从s到w的路径, 存放到栈中
  65. Stack<Edge<E>> stack=new Stack<>();
  66. Edge<E> e=from[w];
  67. while (e.v()!=s){
  68. stack.push(e);
  69. e=from[e.v()];
  70. }
  71. //最后e.v()就是s,那么e这条边入栈
  72. stack.push(e);
  73. //从栈中依次取出元素, 获得顺序的从s到w的路径
  74. Vector<Edge<E>> res=new Vector<>();
  75. while (!stack.isEmpty()){
  76. Edge<E> edge=stack.pop();
  77. res.add(edge);
  78. }
  79. return res;
  80. }
  81. // 打印出从s点到w点的路径
  82. public void showPath(int w){
  83. assert hasPathTo(w);
  84. Vector<Edge<E>> path = shortestPath(w);
  85. for( int i = 0 ; i < path.size() ; i ++ ){
  86. System.out.print( path.elementAt(i).v() + " -> ");
  87. if( i == path.size()-1 ){
  88. System.out.println(path.elementAt(i).w());
  89. }
  90. }
  91. }
  92. }

Bellman-Ford 单源最短路径算法

1. 负权边问题

顶点0、1、2构成了一个负权环。

显然,有负权环的图,没有最短路径。

image.png

如果一个图没有负权环,从一点到另外一点的最短路径,
最多经过所有的V个顶点,有(V-1)条边。
否则,存在顶点经过了两次,也就是说存在负权环。

2. 思路

  • 对一个顶点的一次松弛操作,就是找到经过这个点的另外一条路径,多一条边,权值更小。
  • 如果一个图没有负权环,从一个顶点到另外一个顶点的最短路径,最多经过所有的V个顶点,有(V-1)条边。
  • 对所有的点进行(V-1)次松弛操作,理论上可以找到到其他所有点的最短路径
  • 如果还可以继续松弛,说明原图中有负权环。

3. 实践

  1. public BellmanFordSP(WeightedGraph graph,int s){
  2. this.G=graph;
  3. this.s=s;
  4. distTo=new Number[G.V()];
  5. marked=new boolean[G.V()];
  6. from=new Edge[G.V()];
  7. // // 设置distTo[s] = 0, 并且让from[s]不为NULL, 表示初始s节点可达且距离为0
  8. distTo[s] = 0.0;
  9. from[s] = new Edge<E>(s, s, (E)(Number)(0.0));
  10. // Bellman-Ford的过程
  11. // 进行V-1次循环, 每一次循环求出从起点到其余所有点, 最多使用pass步可到达的最短距离
  12. for(int pass=1;pass<G.V();pass++){
  13. // 每次循环中对所有的边进行一遍松弛操作
  14. // 遍历所有边的方式是先遍历所有的顶点, 然后遍历和所有顶点相邻的所有边
  15. for(int i=0;i<G.V();i++){
  16. for(Object item:G.adj(i)){
  17. Edge<E> e=(Edge<E>)item;
  18. // 对于每一个边首先判断e->v()可达
  19. // 之后看如果e->w()以前没有到达过, 显然我们可以更新distTo[e->w()]
  20. // 或者e->w()以前虽然到达过, 但是通过这个e我们可以获得一个更短的距离,
  21. // 即可以进行一次松弛操作, 我们也可以更新distTo[e->w()]
  22. if(from[e.v()]!=null &&
  23. (from[e.w()]==null || distTo[e.v()].doubleValue()+e.wt().doubleValue()<distTo[e.w()].doubleValue())){
  24. distTo[e.w()]=distTo[e.v()].doubleValue()+e.wt().doubleValue();
  25. from[e.w()]=e;
  26. }
  27. }
  28. }
  29. }
  30. }

4. 查看图中是否有负权环