带权图的最短路径算法(Dijkstra)实现
一,介绍
本文实现带权图的最短路径算法。给定图中一个顶点,求解该顶点到图中所有其他顶点的最短路径 以及 最短路径的长度。在决定写这篇文章之前,在网上找了很多关于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属性)取 ❶ ❷ 中较小的那个。
伪代码如下:
- DIJKSTRA(G,w,s)
- 初始化
- 构造堆(Q=V(G))
- while(!isEmpty(Q))
- v=EXTRACT-MIN(Q)
- foreach vertex v_adj belogns to Adj[v]
- 更新v的邻接点 v_adj
三,具体代码实现
在讲解具体实现前,先介绍下如何构造图。假设图中的数据存储在文件中,文件的格式如下:
(图的顶点及边信息---暂且用无向图举例)
第一列代表顶点的编号(不用管) ;第二列表示 边的 起始顶点的标识(vertexLabel)
第三列表示 边的 终点的标识;第四列表示边的权值。比如,对于权值为1的那条边而言,它对应的 起始顶点编号为0,对应的结点顶点的编号为1
关于图的解释,可参考:
这里由于是带权图,故边类(Edge.java)需要有一个权值(边的权值)。
- private class Edge{
- private int weight;//边的权值(带权图)
- private Vertex endVertex;
- public Edge(int weight, Vertex endVertex) {
- this.weight = weight;
- this.endVertex = endVertex;
- }
图采用的是邻接表实现,因此每个顶点都会有一个邻接点列表。
- private class Vertex implements Comparable<Vertex>
- {
- private String vertexLabel;//顶点标识
- private List<Edge> adjEdges;//顶点的所有邻接边(点)
- private int dist;//顶点到源点的最短距离
- private Vertex preNode;//追溯最短路径
- public Vertex(String vertexLabel){
- this.vertexLabel = vertexLabel;
- adjEdges = new LinkedList<Edge>();
- dist = Integer.MAX_VALUE;
- preNode = null;
- }
- @Override
- public int compareTo(Vertex v) {
- if(this.dist > v.dist)
- return 1;
- else if(this.dist < v.dist)
- return -1;
- return 0;
- }
- }
①第4行 adjEdges 是顶点的邻接点列表,表明图采用的是邻接表存储。第5行 dist 表示的是该顶点到源点的最短距离(从而不需要一个单独的距离数组)。第6行preNode 表示该顶点的前驱顶点, 用来记录源点到该顶点路径中经历了哪些顶点。
②Vertex类实现了Comparable接口,因为需要将顶点存储到最小堆中,而最小堆存储的元素需要实现Comparable接口(可以进行顶点的比较)。
最关键的是实现Dijkstra算法中用到的最小堆。关于最小堆的实现,可参考:数据结构--堆的实现之深入分析 本程序就是用的它。
然后是 dijkstra 的具体实现代码:
- public void dijkstra(){
- BinaryHeap<Vertex> heap = new BinaryHeap<WeightedGraph.Vertex>();
- init(heap);//inital heap
- while(!heap.isEmpty())
- {
- Vertex v = heap.deleteMin();
- List<Edge> adjEdges = v.adjEdges;//获取v的所有邻接点
- for (Edge e : adjEdges) {
- Vertex adjNode = e.endVertex;
- //update
- if(adjNode.dist > e.weight + v.dist){
- adjNode.dist = e.weight + v.dist;
- adjNode.preNode = v;
- }
- }//end for
- //更新之后破坏了堆序性质,需要进行堆调整,这里直接重新构造堆(相当于decreaseKey)
- heap.buildHeap();
- }
- }
①第7行,从堆中出一个距离源点路径最短的顶点。刚好符合堆的基本操作(删除堆顶元素),这里也体现了Dijkstra是个贪心算法。
②第8-10行,获取顶点的邻接点
③第12行--15行的if语句,执行更新操作。关于更新操作的具体解释,可参考上面的介绍。
④由于 ③中的更新操作,破坏了堆序的性质,故需要进行堆调整。但是如何调整呢?由于堆不支持将堆中某个结点的权值降低,故在第19行,直接再次建堆。以保证堆序性质 。但是这里的时间复杂度就大了,故推荐使用更好的数据结构来实现,如Fib堆,因为Fib堆的将某个结点的权值降低是很方便的。
时间复杂度简要分析如下:buildHeap()的时间复杂度为O(N),对于图中每个顶点v,出堆时都需要重新构造堆,故最坏情况下时间复杂度为O(V^2)
整个完整代码实现如下:
- import java.util.LinkedHashMap;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- public class WeightedGraph{
- private class Vertex implements Comparable<Vertex>
- {
- private String vertexLabel;//顶点标识
- private List<Edge> adjEdges;//顶点的所有邻接边(点)
- private int dist;//顶点到源点的最短距离
- private Vertex preNode;//前驱顶点
- public Vertex(String vertexLabel){
- this.vertexLabel = vertexLabel;
- adjEdges = new LinkedList<Edge>();
- dist = Integer.MAX_VALUE;
- preNode = null;
- }
- @Override
- public int compareTo(Vertex v) {
- if(this.dist > v.dist)
- return 1;
- else if(this.dist < v.dist)
- return -1;
- return 0;
- }
- }
- private class Edge{
- private int weight;//边的权值(带权图)
- private Vertex endVertex;
- public Edge(int weight, Vertex endVertex) {
- this.weight = weight;
- this.endVertex = endVertex;
- }
- }
- private Map<String, Vertex> weightedGraph;//存储图(各个顶点)
- private Vertex startVertex;//单源最短路径的起始顶点
- //图的信息保存在文件中,从文件中读取成字符串graphContent
- public WeightedGraph(String graphContent) {
- weightedGraph = new LinkedHashMap<String, WeightedGraph.Vertex>();
- buildGraph(graphContent);//解析字符串构造图
- }
- private void buildGraph(String graphContent){
- String[] lines = graphContent.split("\n");
- String startNodeLabel, endNodeLabel;
- Vertex startNode, endNode;
- int weight;
- for(int i = 0; i < lines.length; i++){
- String[] nodesInfo = lines[i].split(",");
- startNodeLabel = nodesInfo[1];
- endNodeLabel = nodesInfo[2];
- weight = Integer.valueOf(nodesInfo[3]);
- endNode = weightedGraph.get(endNodeLabel);
- if(endNode == null){
- endNode = new Vertex(endNodeLabel);
- weightedGraph.put(endNodeLabel, endNode);
- }
- startNode = weightedGraph.get(startNodeLabel);
- if(startNode == null){
- startNode = new Vertex(startNodeLabel);
- weightedGraph.put(startNodeLabel, startNode);
- }
- Edge e = new Edge(weight, endNode);
- //对于无向图而言,起点和终点都要添加边
- // endNode.adjEdges.add(e);
- startNode.adjEdges.add(e);
- }
- startVertex = weightedGraph.get(lines[0].split(",")[1]);//总是以文件中第一行第二列的那个标识顶点作为源点
- }
- public void dijkstra(){
- BinaryHeap<Vertex> heap = new BinaryHeap<WeightedGraph.Vertex>();
- init(heap);//inital heap
- while(!heap.isEmpty())
- {
- Vertex v = heap.deleteMin();
- List<Edge> adjEdges = v.adjEdges;//获取v的所有邻接点
- for (Edge e : adjEdges) {
- Vertex adjNode = e.endVertex;
- //update
- if(adjNode.dist > e.weight + v.dist){
- adjNode.dist = e.weight + v.dist;
- adjNode.preNode = v;
- }
- }//end for
- //更新之后破坏了堆序性质,需要进行堆调整,这里直接重新构造堆(相当于decreaseKey)
- heap.buildHeap();
- }
- }
- private void init(BinaryHeap<Vertex> heap){
- startVertex.dist = 0;//源点到其自身的距离为0
- for (Vertex v : weightedGraph.values()) {
- heap.insert(v);
- }
- }
- public void showDistance(){
- for (Vertex v : weightedGraph.values()) {
- printPath(v);
- System.out.println();
- System.out.println("顶点 " + v.vertexLabel + "到源点" + startVertex.vertexLabel + " 的距离: " + v.dist);
- }
- }
- //打印源点到 end 顶点的 最短路径
- private void printPath(Vertex end)
- {
- if(end.preNode != null)
- printPath(end.preNode);
- System.out.print(end.vertexLabel + "--> ");
- }
- }
buildGraph()方法中:如果是有向图,只需要起点添加边;如果是无向图,则起点和终点都需要添加边。但不管是有向图还是无向图Dijkstra算法都一样。
- Edge e = new Edge(weight, endNode);
- //对于无向图而言,起点和终点都要添加边
- // endNode.adjEdges.add(e);
- startNode.adjEdges.add(e);
关于如何测试WeightedGraph.java,需要构造一个图。构造图:可参考有向图的拓扑排序算法JAVA实现 中的“完整代码实现”中的FileUtil.java 和 TestXXX.java
- public class TestDijkstra {
- public static void main(String[] args) {
- String graphFilePath;
- if(args.length == 0)
- graphFilePath = "F:\\graph2.txt";
- else
- graphFilePath = args[0];
- String graphContent = FileUtil.read(graphFilePath, null);
- WeightedGraph graph = new WeightedGraph(graphContent);
- graph.dijkstra();
- graph.showDistance();
- }
- }
带权图的最短路径算法(Dijkstra)实现的更多相关文章
- 有向有权图的最短路径算法--Dijkstra算法
Dijkstra算法 1.定义概览 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.Di ...
- 无向带权图的最小生成树算法——Prim及Kruskal算法思路
边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权. 最小生成树(MST):权值最小的生成树. 生成树和最小生成树的应用:要连通n个城市需要n-1条边线路.可以 ...
- 图的最短路径算法Dijkstra算法模板
Dijkstra算法:伪代码 //G为图,一般设为全局变量,数组d[u]为原点到达个点的额最短路径, s为起点 Dijkstra(G, d[u], s){ 初始化: for (循环n次){ u = 是 ...
- Java数据结构——带权图
带权图的最小生成树--Prim算法和Kruskal算法 带权图的最短路径算法--Dijkstra算法 package graph; // path.java // demonstrates short ...
- 最短路径算法-Dijkstra算法的应用之单词转换(词梯问题)(转)
一,问题描述 在英文单词表中,有一些单词非常相似,它们可以通过只变换一个字符而得到另一个单词.比如:hive-->five:wine-->line:line-->nine:nine- ...
- java数据结构----带权图
1.带权图:要引入带权图,首先要引入最小生成树,当所有的边拥有相同的权值时.问题变得简单了,算法可以选择任意一条边加入最小生成树.但是当边有不同的权值时,需要用一些算法决策来选择正确的边. 2.带权图 ...
- 图中最短路径算法(Dijkstra算法)(转)
1.Dijkstra 1) 适用条件&范围: a) 单源最短路径(从源点s到其它所有顶点v); b) 有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E ...
- 最短路径算法Dijkstra和A*
在设计基于地图的游戏,特别是isometric斜45度视角游戏时,几乎必须要用到最短路径算法.Dijkstra算法是寻找当前最优路径(距离原点最近),如果遇到更短的路径,则修改路径(边松弛). Ast ...
- C语言——无向带权图邻接矩阵的建立
#include <stdio.h> #include "Graph.h" #define MAX_INT 32767 /* #define vnum 20 #defi ...
随机推荐
- 20款时尚的 WordPress 简洁主题【免费下载】
在这篇文章中,我们收集了20款时尚的 WordPress 简洁模板.WordPress 是最流行的博客系统,插件众多,易于扩充功能.安装和使用都非常方便,而且有许多第三方开发的免费模板,安装方式简单易 ...
- Eclipse spket插件 内置js文件
这一篇将怎么在spket内置js文件,而不用用户自己去添加. 1. 在开发的Eclipse的 运行配置将下面几个插件勾选上. 2. 在org.eclipse.ui.startup拓展里执 ...
- 为Sharepoint 2010 批量创建SharePoint测试用户
无意搜到下面一篇文章,http://www.cnblogs.com/lambertqin/archive/2012/04/19/2457372.html,原作者写的太"高大上",可 ...
- Android开发学习——搭建开发环境
在学校开课学习了android的一些简单的UI组件,布局,四大组件学习了2个,数据存储及网络通信,都是一些简单的概念,入门而已.许多东西需要自己去学习. 学习一下 Android开发环境的搭建,两种方 ...
- Android ANR产生的原理和如何避免
在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框.用户可以选择 ...
- 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 ...
- 【代码笔记】iOS-点击cell时候的动画翻转
一,效果图. 二,工程图. 三,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController ...
- 网络热恋之json解析
现在的app开发很少有用到XML解析的了,主流的则是JSON. // // ViewController.m // CX-JSON解析(三方JSONKit-master) #import " ...
- C#复习④
C#复习④ 2016年6月16日 12:37 Main Classes and Structs 类和结构体 1.Contents of Classes 字段,常量,方法,构造函数,析构函数: 特性,事 ...
- HtmlHelper使用大全
许多时候我们会遇到如下场景在写一个编辑数据的页面时,我们通常会写如下代码1:<inputtype ="text" value='<%=ViewData["ti ...