一,介绍

本文实现带权图的最短路径算法。给定图中一个顶点,求解该顶点到图中所有其他顶点的最短路径 以及 最短路径的长度。在决定写这篇文章之前,在网上找了很多关于Dijkstra算法实现,但大部分是不带权的。不带权的Dijkstra算法要简单得多(可参考我的另一篇:无向图的最短路径算法JAVA实现);而对于带权的Dijkstra算法,最关键的是如何“更新邻接点的权值”。本文采用最小堆作为辅助,以重新构造堆的方式实现更新邻接点权值。

对于图而言,存在有向图和无向图。本算法只需要修改一行代码,即可同时实现带权有向图的Dijkstra和带权无向图的Dijkstra。因为,不管图是否是有向的还是无向的,只是构造图的方式不一样而已,而 Dijkstra算法都是一样的。

Dijkstra算法的实现需要一个辅助堆,用来选取当前到源点的距离 最小的那个顶点,这里采用了最小堆来实现。用最小堆保存图中所有顶点到源点的距离,因为Dijkstra算法运行过程中,需要每次选取当前到源点 距离最短 的那个顶点,这步操作用“出堆”很容易实现,但是,当选出该顶点之后, 需要不断地更新该顶点的邻接点到源点的距离。而最小堆不能很好地支持这种更新操作(关于最小堆可参考:),这也是为什么《算法导论》中推荐使用菲波拉契堆或者配对堆实现Dijkstra算法的原因。

二,Dijkstra实现思路

①初始化,源点的距离初始化为0(源点到它自己的距离当然是0了),源点的前驱顶点为null(因为是从源点开始的嘛,求源点到图中所有其他顶点的minDistance...)。所有其他顶点的前驱顶点也初始化为null,且顶点的“距离”(dist)属性初始化为无穷大(Integer.MAX_VALUE),即其他顶点到源点的距离 为无穷大。

②构造堆。将所有的顶点按照“距离”属性(dist) 构造最小堆。显然,由于源点的“距离”属性为0,其他顶点的“距离”属性为Integer.MAX_VALUE,故最开始构造的堆的 堆顶元素为源点。

③只要堆中还存在元素(while循环),执行deleteMin从堆中删除堆顶元素,记该元素为v,寻找v的所有邻接点,更新v的所有邻接点的距离。怎么更新的呢?就是比较:❶v的邻接点到源点的距离(dist属性)   ;  ❷v到源点的距离(dist属性) 加上  v 到v的邻接点的这条 边的权值 

v的邻接点的距离(dist属性)取 ❶ ❷ 中较小的那个。

伪代码如下:

  1. DIJKSTRA(G,w,s)
  2. 初始化
  3. 构造堆(Q=V(G))
  4. while(!isEmpty(Q))
  5. v=EXTRACT-MIN(Q)
  6. foreach vertex v_adj belogns to Adj[v]
  7. 更新v的邻接点 v_adj

三,具体代码实现

在讲解具体实现前,先介绍下如何构造图。假设图中的数据存储在文件中,文件的格式如下:

(图的顶点及边信息---暂且用无向图举例)

第一列代表顶点的编号(不用管) ;第二列表示 边的 起始顶点的标识(vertexLabel)

第三列表示 边的 终点的标识;第四列表示边的权值。比如,对于权值为1的那条边而言,它对应的 起始顶点编号为0,对应的结点顶点的编号为1

关于图的解释,可参考

这里由于是带权图,故边类(Edge.java)需要有一个权值(边的权值)。

  1. private class Edge{
  2. private int weight;//边的权值(带权图)
  3. private Vertex endVertex;
  4. public Edge(int weight, Vertex endVertex) {
  5. this.weight = weight;
  6. this.endVertex = endVertex;
  7. }

图采用的是邻接表实现,因此每个顶点都会有一个邻接点列表。

  1. private class Vertex implements Comparable<Vertex>
  2. {
  3. private String vertexLabel;//顶点标识
  4. private List<Edge> adjEdges;//顶点的所有邻接边(点)
  5. private int dist;//顶点到源点的最短距离
  6. private Vertex preNode;//追溯最短路径
  7.  
  8. public Vertex(String vertexLabel){
  9. this.vertexLabel = vertexLabel;
  10. adjEdges = new LinkedList<Edge>();
  11. dist = Integer.MAX_VALUE;
  12. preNode = null;
  13. }
  14.  
  15. @Override
  16. public int compareTo(Vertex v) {
  17. if(this.dist > v.dist)
  18. return 1;
  19. else if(this.dist < v.dist)
  20. return -1;
  21. return 0;
  22. }
  23. }

①第4行 adjEdges 是顶点的邻接点列表,表明图采用的是邻接表存储。第5行 dist 表示的是该顶点到源点的最短距离(从而不需要一个单独的距离数组)。第6行preNode 表示该顶点的前驱顶点, 用来记录源点到该顶点路径中经历了哪些顶点。

②Vertex类实现了Comparable接口,因为需要将顶点存储到最小堆中,而最小堆存储的元素需要实现Comparable接口(可以进行顶点的比较)。

最关键的是实现Dijkstra算法中用到的最小堆。关于最小堆的实现,可参考:数据结构--堆的实现之深入分析 本程序就是用的它。

然后是 dijkstra 的具体实现代码:

  1. public void dijkstra(){
  2. BinaryHeap<Vertex> heap = new BinaryHeap<WeightedGraph.Vertex>();
  3. init(heap);//inital heap
  4.  
  5. while(!heap.isEmpty())
  6. {
  7. Vertex v = heap.deleteMin();
  8. List<Edge> adjEdges = v.adjEdges;//获取v的所有邻接点
  9. for (Edge e : adjEdges) {
  10. Vertex adjNode = e.endVertex;
  11. //update
  12. if(adjNode.dist > e.weight + v.dist){
  13. adjNode.dist = e.weight + v.dist;
  14. adjNode.preNode = v;
  15. }
  16. }//end for
  17.  
  18. //更新之后破坏了堆序性质,需要进行堆调整,这里直接重新构造堆(相当于decreaseKey)
  19. heap.buildHeap();
  20. }
  21.  
  22. }

①第7行,从堆中出一个距离源点路径最短的顶点。刚好符合堆的基本操作(删除堆顶元素),这里也体现了Dijkstra是个贪心算法。

②第8-10行,获取顶点的邻接点

③第12行--15行的if语句,执行更新操作。关于更新操作的具体解释,可参考上面的介绍。

④由于 ③中的更新操作,破坏了堆序的性质,故需要进行堆调整。但是如何调整呢?由于堆不支持将堆中某个结点的权值降低,故在第19行,直接再次建堆。以保证堆序性质 。但是这里的时间复杂度就大了,故推荐使用更好的数据结构来实现,如Fib堆,因为Fib堆的将某个结点的权值降低是很方便的。

时间复杂度简要分析如下:buildHeap()的时间复杂度为O(N),对于图中每个顶点v,出堆时都需要重新构造堆,故最坏情况下时间复杂度为O(V^2)

整个完整代码实现如下:

  1. import java.util.LinkedHashMap;
  2. import java.util.LinkedList;
  3. import java.util.List;
  4. import java.util.Map;
  5.  
  6. public class WeightedGraph{
  7. private class Vertex implements Comparable<Vertex>
  8. {
  9. private String vertexLabel;//顶点标识
  10. private List<Edge> adjEdges;//顶点的所有邻接边(点)
  11. private int dist;//顶点到源点的最短距离
  12. private Vertex preNode;//前驱顶点
  13.  
  14. public Vertex(String vertexLabel){
  15. this.vertexLabel = vertexLabel;
  16. adjEdges = new LinkedList<Edge>();
  17. dist = Integer.MAX_VALUE;
  18. preNode = null;
  19. }
  20.  
  21. @Override
  22. public int compareTo(Vertex v) {
  23. if(this.dist > v.dist)
  24. return 1;
  25. else if(this.dist < v.dist)
  26. return -1;
  27. return 0;
  28. }
  29. }
  30.  
  31. private class Edge{
  32. private int weight;//边的权值(带权图)
  33. private Vertex endVertex;
  34. public Edge(int weight, Vertex endVertex) {
  35. this.weight = weight;
  36. this.endVertex = endVertex;
  37. }
  38. }
  39.  
  40. private Map<String, Vertex> weightedGraph;//存储图(各个顶点)
  41. private Vertex startVertex;//单源最短路径的起始顶点
  42.  
  43. //图的信息保存在文件中,从文件中读取成字符串graphContent
  44. public WeightedGraph(String graphContent) {
  45. weightedGraph = new LinkedHashMap<String, WeightedGraph.Vertex>();
  46. buildGraph(graphContent);//解析字符串构造图
  47. }
  48. private void buildGraph(String graphContent){
  49. String[] lines = graphContent.split("\n");
  50.  
  51. String startNodeLabel, endNodeLabel;
  52. Vertex startNode, endNode;
  53. int weight;
  54. for(int i = 0; i < lines.length; i++){
  55. String[] nodesInfo = lines[i].split(",");
  56. startNodeLabel = nodesInfo[1];
  57. endNodeLabel = nodesInfo[2];
  58. weight = Integer.valueOf(nodesInfo[3]);
  59.  
  60. endNode = weightedGraph.get(endNodeLabel);
  61. if(endNode == null){
  62. endNode = new Vertex(endNodeLabel);
  63. weightedGraph.put(endNodeLabel, endNode);
  64. }
  65.  
  66. startNode = weightedGraph.get(startNodeLabel);
  67. if(startNode == null){
  68. startNode = new Vertex(startNodeLabel);
  69. weightedGraph.put(startNodeLabel, startNode);
  70. }
  71. Edge e = new Edge(weight, endNode);
  72. //对于无向图而言,起点和终点都要添加边
  73. // endNode.adjEdges.add(e);
  74. startNode.adjEdges.add(e);
  75. }
  76. startVertex = weightedGraph.get(lines[0].split(",")[1]);//总是以文件中第一行第二列的那个标识顶点作为源点
  77. }
  78.  
  79. public void dijkstra(){
  80. BinaryHeap<Vertex> heap = new BinaryHeap<WeightedGraph.Vertex>();
  81. init(heap);//inital heap
  82.  
  83. while(!heap.isEmpty())
  84. {
  85. Vertex v = heap.deleteMin();
  86. List<Edge> adjEdges = v.adjEdges;//获取v的所有邻接点
  87. for (Edge e : adjEdges) {
  88. Vertex adjNode = e.endVertex;
  89. //update
  90. if(adjNode.dist > e.weight + v.dist){
  91. adjNode.dist = e.weight + v.dist;
  92. adjNode.preNode = v;
  93. }
  94. }//end for
  95.  
  96. //更新之后破坏了堆序性质,需要进行堆调整,这里直接重新构造堆(相当于decreaseKey)
  97. heap.buildHeap();
  98. }
  99.  
  100. }
  101. private void init(BinaryHeap<Vertex> heap){
  102. startVertex.dist = 0;//源点到其自身的距离为0
  103. for (Vertex v : weightedGraph.values()) {
  104. heap.insert(v);
  105. }
  106. }
  107.  
  108. public void showDistance(){
  109. for (Vertex v : weightedGraph.values()) {
  110. printPath(v);
  111. System.out.println();
  112. System.out.println("顶点 " + v.vertexLabel + "到源点" + startVertex.vertexLabel + " 的距离: " + v.dist);
  113. }
  114. }
  115.  
  116. //打印源点到 end 顶点的 最短路径
  117. private void printPath(Vertex end)
  118. {
  119. if(end.preNode != null)
  120. printPath(end.preNode);
  121. System.out.print(end.vertexLabel + "--> ");
  122. }
  123. }

buildGraph()方法中:如果是有向图,只需要起点添加边;如果是无向图,则起点和终点都需要添加边。但不管是有向图还是无向图Dijkstra算法都一样。

  1. Edge e = new Edge(weight, endNode);
  2. //对于无向图而言,起点和终点都要添加边
  3. // endNode.adjEdges.add(e);
  4. startNode.adjEdges.add(e);

关于如何测试WeightedGraph.java,需要构造一个图。构造图:可参考有向图的拓扑排序算法JAVA实现 中的“完整代码实现”中的FileUtil.java 和 TestXXX.java

  1. public class TestDijkstra {
  2. public static void main(String[] args) {
  3. String graphFilePath;
  4. if(args.length == 0)
  5. graphFilePath = "F:\\graph2.txt";
  6. else
  7. graphFilePath = args[0];
  8.  
  9. String graphContent = FileUtil.read(graphFilePath, null);
  10. WeightedGraph graph = new WeightedGraph(graphContent);
  11. graph.dijkstra();
  12.  
  13. graph.showDistance();
  14. }
  15. }

带权图的最短路径算法(Dijkstra)实现的更多相关文章

  1. 有向有权图的最短路径算法--Dijkstra算法

    Dijkstra算法 1.定义概览 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.Di ...

  2. 无向带权图的最小生成树算法——Prim及Kruskal算法思路

    边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权. 最小生成树(MST):权值最小的生成树. 生成树和最小生成树的应用:要连通n个城市需要n-1条边线路.可以 ...

  3. 图的最短路径算法Dijkstra算法模板

    Dijkstra算法:伪代码 //G为图,一般设为全局变量,数组d[u]为原点到达个点的额最短路径, s为起点 Dijkstra(G, d[u], s){ 初始化: for (循环n次){ u = 是 ...

  4. Java数据结构——带权图

    带权图的最小生成树--Prim算法和Kruskal算法 带权图的最短路径算法--Dijkstra算法 package graph; // path.java // demonstrates short ...

  5. 最短路径算法-Dijkstra算法的应用之单词转换(词梯问题)(转)

    一,问题描述 在英文单词表中,有一些单词非常相似,它们可以通过只变换一个字符而得到另一个单词.比如:hive-->five:wine-->line:line-->nine:nine- ...

  6. java数据结构----带权图

    1.带权图:要引入带权图,首先要引入最小生成树,当所有的边拥有相同的权值时.问题变得简单了,算法可以选择任意一条边加入最小生成树.但是当边有不同的权值时,需要用一些算法决策来选择正确的边. 2.带权图 ...

  7. 图中最短路径算法(Dijkstra算法)(转)

    1.Dijkstra 1)      适用条件&范围: a)   单源最短路径(从源点s到其它所有顶点v); b)   有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E ...

  8. 最短路径算法Dijkstra和A*

    在设计基于地图的游戏,特别是isometric斜45度视角游戏时,几乎必须要用到最短路径算法.Dijkstra算法是寻找当前最优路径(距离原点最近),如果遇到更短的路径,则修改路径(边松弛). Ast ...

  9. C语言——无向带权图邻接矩阵的建立

    #include <stdio.h> #include "Graph.h" #define MAX_INT 32767 /* #define vnum 20 #defi ...

随机推荐

  1. 20款时尚的 WordPress 简洁主题【免费下载】

    在这篇文章中,我们收集了20款时尚的 WordPress 简洁模板.WordPress 是最流行的博客系统,插件众多,易于扩充功能.安装和使用都非常方便,而且有许多第三方开发的免费模板,安装方式简单易 ...

  2. Eclipse spket插件 内置js文件

    这一篇将怎么在spket内置js文件,而不用用户自己去添加.    1. 在开发的Eclipse的 运行配置将下面几个插件勾选上.     2. 在org.eclipse.ui.startup拓展里执 ...

  3. 为Sharepoint 2010 批量创建SharePoint测试用户

    无意搜到下面一篇文章,http://www.cnblogs.com/lambertqin/archive/2012/04/19/2457372.html,原作者写的太"高大上",可 ...

  4. Android开发学习——搭建开发环境

    在学校开课学习了android的一些简单的UI组件,布局,四大组件学习了2个,数据存储及网络通信,都是一些简单的概念,入门而已.许多东西需要自己去学习. 学习一下 Android开发环境的搭建,两种方 ...

  5. Android ANR产生的原理和如何避免

    在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框.用户可以选择 ...

  6. Xcode cannot launch because the device is locked.

    When you plug in your iPhone, it will ask you to trust the computer. If you already trust and unlock ...

  7. 【代码笔记】iOS-点击cell时候的动画翻转

    一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController ...

  8. 网络热恋之json解析

    现在的app开发很少有用到XML解析的了,主流的则是JSON. // // ViewController.m // CX-JSON解析(三方JSONKit-master) #import " ...

  9. C#复习④

    C#复习④ 2016年6月16日 12:37 Main Classes and Structs 类和结构体 1.Contents of Classes 字段,常量,方法,构造函数,析构函数: 特性,事 ...

  10. HtmlHelper使用大全

    许多时候我们会遇到如下场景在写一个编辑数据的页面时,我们通常会写如下代码1:<inputtype ="text" value='<%=ViewData["ti ...