本文参考自:Dijkstra算法求单源最短路径Java实现。
之前使用Matlab实现过一个Bellman-Ford单源最短路径算法,由于项目需要,现在需要用java实现一下。
如果所采用的实现方式合适,Dijkstra算法的运行时间要低于前面所说的Bellman_Ford算法,但是Dijkstra算法要求图中没有负边。Dijkstra算法在运行过程中维持的关键信息是一组结点集合S,即已经找到的最短路径的节点的集合。算法重复从未找到最短路径的结点集合Q中选择最短路径估计最小的结点u,将u加入到集合S,然后对所有从u发出的边进行松弛)。下面是《算法导论》书中所给的伪代码:
下面,我们从下面的例子中了解这个算法具体的做法。
图有两种较为简单的表示方式:
注意,上述的两种方法不仅可以解决无向图的单源最短路径问题,还可以解决有向图的单源最短路径问题。
如下所示,顶点需要的字段包括节点的名字、到该节点的最短路径长度的估计值、当前节点的前一个节点。
package com.traffic.dijs;
public class Vertex {
private final static int infinite_dis = Integer.MAX_VALUE;
private String name; //节点名字
private int adjuDist; //最短路径的估计值
private Vertex parent; //当前从初始节点到此节点的最短路径下,的父节点。
//...省略get和set方法,以及构造函数
}
如下所示,边需要的信息包括起始节点、终止节点,以及这条边的权重。
package com.traffic.dijs;
public class Edge {
private Vertex startVertex; // 此有向边的起始点
private Vertex endVertex; // 此有向边的终点
private int weight; // 此有向边的权值
//...省略get和set方法,构造函数等。
}
首先,我们可以看到在创建MapBuilder对象时,需要传入两个参数:
接下来介绍一下一个交通线路图的实例,这里是一个6行6列的道路。其中包括36个路口,以及60条道路,路径的长度和路口的信息以文档的形式给出(华为题目)。首先,看一下路口信息。
#(id,roadId,roadId,roadId,roadId)
(1, 5000, 5005, -1, -1)
(2, 5001, 5006, 5000, -1)
(3, 5002, 5007, 5001, -1)
(4, 5003, 5008, 5002, -1)
(5, 5004, 5009, 5003, -1)
(6, -1, 5010, 5004, -1)
(7, 5011, 5016, -1, 5005)
(8, 5012, 5017, 5011, 5006)
(9, 5013, 5018, 5012, 5007)
(10, 5014, 5019, 5013, 5008)
(11, 5015, 5020, 5014, 5009)
(12, -1, 5021, 5015, 5010)
(13, 5022, 5027, -1, 5016)
(14, 5023, 5028, 5022, 5017)
(15, 5024, 5029, 5023, 5018)
(16, 5025, 5030, 5024, 5019)
(17, 5026, 5031, 5025, 5020)
(18, -1, 5032, 5026, 5021)
(19, 5033, 5038, -1, 5027)
(20, 5034, 5039, 5033, 5028)
(21, 5035, 5040, 5034, 5029)
(22, 5036, 5041, 5035, 5030)
(23, 5037, 5042, 5036, 5031)
(24, -1, 5043, 5037, 5032)
(25, 5044, 5049, -1, 5038)
(26, 5045, 5050, 5044, 5039)
(27, 5046, 5051, 5045, 5040)
(28, 5047, 5052, 5046, 5041)
(29, 5048, 5053, 5047, 5042)
(30, -1, 5054, 5048, 5043)
(31, 5055, -1, -1, 5049)
(32, 5056, -1, 5055, 5050)
(33, 5057, -1, 5056, 5051)
(34, 5058, -1, 5057, 5052)
(35, 5059, -1, 5058, 5053)
(36, -1, -1, 5059, 5054)
接着,看一下道路的信息。
#(id,length,speed,channel,from,to,isDuplex)
(5000, 10, 5, 1, 1, 2, 1)
(5001, 10, 5, 1, 2, 3, 1)
(5002, 10, 5, 1, 3, 4, 1)
(5003, 10, 5, 1, 4, 5, 1)
(5004, 10, 5, 1, 5, 6, 1)
(5005, 10, 5, 1, 1, 7, 1)
(5006, 10, 5, 1, 2, 8, 1)
(5007, 10, 5, 1, 3, 9, 1)
(5008, 10, 5, 1, 4, 10, 1)
(5009, 10, 5, 1, 5, 11, 1)
(5010, 10, 5, 1, 6, 12, 1)
(5011, 10, 5, 1, 7, 8, 1)
(5012, 10, 5, 1, 8, 9, 1)
(5013, 10, 5, 1, 9, 10, 1)
(5014, 10, 5, 1, 10, 11, 1)
(5015, 10, 5, 1, 11, 12, 1)
(5016, 10, 5, 1, 7, 13, 1)
(5017, 10, 5, 1, 8, 14, 1)
(5018, 10, 5, 1, 9, 15, 1)
(5019, 10, 5, 1, 10, 16, 1)
(5020, 10, 5, 1, 11, 17, 1)
(5021, 10, 5, 1, 12, 18, 1)
(5022, 10, 5, 1, 13, 14, 1)
(5023, 10, 5, 1, 14, 15, 1)
(5024, 10, 5, 1, 15, 16, 1)
(5025, 10, 5, 1, 16, 17, 1)
(5026, 10, 5, 1, 17, 18, 1)
(5027, 10, 5, 1, 13, 19, 1)
(5028, 10, 5, 1, 14, 20, 1)
(5029, 10, 5, 1, 15, 21, 1)
(5030, 10, 5, 1, 16, 22, 1)
(5031, 10, 5, 1, 17, 23, 1)
(5032, 10, 5, 1, 18, 24, 1)
(5033, 10, 5, 1, 19, 20, 1)
(5034, 10, 5, 1, 20, 21, 1)
(5035, 10, 5, 1, 21, 22, 1)
(5036, 10, 5, 1, 22, 23, 1)
(5037, 10, 5, 1, 23, 24, 1)
(5038, 10, 5, 1, 19, 25, 1)
(5039, 10, 5, 1, 20, 26, 1)
(5040, 10, 5, 1, 21, 27, 1)
(5041, 10, 5, 1, 22, 28, 1)
(5042, 10, 5, 1, 23, 29, 1)
(5043, 10, 5, 1, 24, 30, 1)
(5044, 10, 5, 1, 25, 26, 1)
(5045, 10, 5, 1, 26, 27, 1)
(5046, 10, 5, 1, 27, 28, 1)
(5047, 10, 5, 1, 28, 29, 1)
(5048, 10, 5, 1, 29, 30, 1)
(5049, 10, 5, 1, 25, 31, 1)
(5050, 10, 5, 1, 26, 32, 1)
(5051, 10, 5, 1, 27, 33, 1)
(5052, 10, 5, 1, 28, 34, 1)
(5053, 10, 5, 1, 29, 35, 1)
(5054, 10, 5, 1, 30, 36, 1)
(5055, 10, 5, 1, 31, 32, 1)
(5056, 10, 5, 1, 32, 33, 1)
(5057, 10, 5, 1, 33, 34, 1)
(5058, 10, 5, 1, 34, 35, 1)
(5059, 10, 5, 1, 35, 36, 1)
下面,我们就先把上述的道路构造出来。
package com.traffic.models;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.traffic.dijs.Edge;
import com.traffic.dijs.MapBuilder;
import com.traffic.dijs.Vertex;
public class BuildRoadMap {
private HashMap _roadHashMap = new HashMap();// 路的字典
private HashMap _vertexHashMap = new HashMap();// 路口(顶点)的字典
public MapBuilder _map = null;
public List _vertexList = new LinkedList();// 路口顶点
public HashMap _roadStatus = new HashMap();// 路况(二维数组)
/**
* 初始化路况
*
* @param roads
*/
public void initRoadStatus(List roads) {
for (int i = 0; i < roads.size(); i++) {
Road road = roads.get(i);
int rId = road.getId();
int d = road.getDuplex();
int channel = road.getChanel();
int len = road.getLen();
int[][] status = null;
// 是否双向
if (d == 0) {
status = new int[channel][len];// 单向
} else {
status = new int[channel * 2][len];// 双向,用奇数代表一个方向,偶数代表另一个方向
}
_roadStatus.put(rId, status);// 每条路的路况的HashMap
_roadHashMap.put(rId, road);// 路根据ID的HashMap
}
}
/**
* 初始化路线图
*
* @param crosses
* 路口
*/
public void initRoadMap(List crosses) {
for (int i = 0; i < crosses.size(); i++) {
Cross cross = crosses.get(i);
int cId = cross.getId();
Vertex vertex = new Vertex(cId + "");
_vertexList.add(vertex);
_vertexHashMap.put(cId, vertex);
}
Map> vertex_edgeList_map = new HashMap>();// 顶点和边的映射
for (int i = 0; i < crosses.size(); i++) {
List edges = new LinkedList();// 每个路口最多可以通往4个其他的路口,即最多有四条边
// 当前路口
Cross cross = crosses.get(i);
int uRoadId = cross.getUid();
int rRoadId = cross.getRid();
int dRoadId = cross.getDid();
int lRoadId = cross.getLid();
// 路口北方道路
if (uRoadId != -1) {
Road uRoad = _roadHashMap.get(uRoadId);// 获取道路对象
int sR = uRoad.getSid();// 当前路口北方的道路的开始路口编号
int eR = uRoad.getEid();// 当前路口北方的道路的结束路口编号
if (uRoad.getDuplex() == 0) {
// 单向(起点为当前路口)
if (sR == cross.getId()) {
edges.add(new Edge(_vertexHashMap.get(sR), _vertexHashMap.get(eR), uRoad.getLen()));
}
} else {
// 双向
if (sR == cross.getId()) {
edges.add(new Edge(_vertexHashMap.get(sR), _vertexHashMap.get(eR), uRoad.getLen()));
} else {
edges.add(new Edge(_vertexHashMap.get(eR), _vertexHashMap.get(sR), uRoad.getLen()));
}
}
}
// 路口东方道路
if (rRoadId != -1) {
Road rRoad = _roadHashMap.get(rRoadId);
int sR = rRoad.getSid();// 当前路口北方的道路的开始路口编号
int eR = rRoad.getEid();// 当前路口北方的道路的结束路口编号
if (rRoad.getDuplex() == 0) {
// 单向(起点为当前路口)
if (sR == cross.getId()) {
edges.add(new Edge(_vertexHashMap.get(sR), _vertexHashMap.get(eR), rRoad.getLen()));
}
} else {
// 双向
if (sR == cross.getId()) {
edges.add(new Edge(_vertexHashMap.get(sR), _vertexHashMap.get(eR), rRoad.getLen()));
} else {
edges.add(new Edge(_vertexHashMap.get(eR), _vertexHashMap.get(sR), rRoad.getLen()));
}
}
}
// 路口南方道路
if (dRoadId != -1) {
Road dRoad = _roadHashMap.get(dRoadId);
int sR = dRoad.getSid();// 当前路口北方的道路的开始路口编号
int eR = dRoad.getEid();// 当前路口北方的道路的结束路口编号
if (dRoad.getDuplex() == 0) {
// 单向(起点为当前路口)
if (sR == cross.getId()) {
edges.add(new Edge(_vertexHashMap.get(sR), _vertexHashMap.get(eR), dRoad.getLen()));
}
} else {
// 双向
if (sR == cross.getId()) {
edges.add(new Edge(_vertexHashMap.get(sR), _vertexHashMap.get(eR), dRoad.getLen()));
} else {
edges.add(new Edge(_vertexHashMap.get(eR), _vertexHashMap.get(sR), dRoad.getLen()));
}
}
}
// 路口西方道路
if (lRoadId != -1) {
Road lRoad = _roadHashMap.get(lRoadId);
int sR = lRoad.getSid();// 当前路口北方的道路的开始路口编号
int eR = lRoad.getEid();// 当前路口北方的道路的结束路口编号
if (lRoad.getDuplex() == 0) {
// 单向(起点为当前路口)
if (sR == cross.getId()) {
edges.add(new Edge(_vertexHashMap.get(sR), _vertexHashMap.get(eR), lRoad.getLen()));
}
} else {
// 双向
if (sR == cross.getId()) {
edges.add(new Edge(_vertexHashMap.get(sR), _vertexHashMap.get(eR), lRoad.getLen()));
} else {
edges.add(new Edge(_vertexHashMap.get(eR), _vertexHashMap.get(sR), lRoad.getLen()));
}
}
}
vertex_edgeList_map.put(_vertexList.get(i), edges);
}
_map = new MapBuilder(_vertexList, vertex_edgeList_map);
}
public BuildRoadMap(List roads, List crosses) {
initRoadStatus(roads);
initRoadMap(crosses);
}
}
package com.traffic.dijs;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
public class MapBuilder {
private List vertexList; // 图的顶点集
private Map> ver_edgeList_map; // 图的每个顶点对应的有向边
private LinkedList S; // 已经求到最短路径的顶点集合
private LinkedList Q; // 尚未求到最短路径的顶点集合
public MapBuilder(List vertexList, Map> ver_edgeList_map) {
super();
this.vertexList = vertexList;
this.ver_edgeList_map = ver_edgeList_map;
}
public List getVertexList() {
return vertexList;
}
public void setVertexList(List vertexList) {
this.vertexList = vertexList;
}
public Map> getVer_edgeList_map() {
return ver_edgeList_map;
}
public void setVer_edgeList_map(Map> ver_edgeList_map) {
this.ver_edgeList_map = ver_edgeList_map;
}
/**
* 初始化源点
*
* @param v
*/
public void INITIALIZE_SINGLE_SOURCE(Vertex v) {
v.setParent(null);
v.setAdjuDist(0);
}
/**
*
* @param startIndex
* dijkstra遍历的起点节点下标
* @param destIndex
* dijkstra遍历的终点节点下标
*/
public void dijkstraTravasal(int startIndex, int destIndex) {
Vertex start = vertexList.get(startIndex);
// 初始化源点
INITIALIZE_SINGLE_SOURCE(start);
// 初始化集合S和Q
S = new LinkedList<>();
Q = new LinkedList<>();
for (int i = 0; i < vertexList.size(); i++) {
Q.add(vertexList.get(i));
}
// 松弛操作
while (!Q.isEmpty()) {
Vertex u = EXTRACT_MIN(Q);// 尚未求到最短路径的顶点集合
S.add(u);
List list = ver_edgeList_map.get(u);
for (Edge edge : list) {
RELAX(edge);
}
}
// 输出结果
ShowResult(startIndex, destIndex);
}
/**
* 求集合Q所有顶点到其他顶点的最短路径
*
* @param q
* @return
*/
private Vertex EXTRACT_MIN(LinkedList q) {
// 判断Q是否为空
if (q.isEmpty())
return null;
int min = 0;
for (int i = 0; i < q.size(); i++) {
if (q.get(min).getAdjuDist() > q.get(i).getAdjuDist()) {
min = i;
}
}
Vertex min_Vertex = q.remove(min);
return min_Vertex;
}
/**
* 松弛
*
* @param edge
*/
public void RELAX(Edge edge) {
Vertex v1 = edge.getStartVertex();
Vertex v2 = edge.getEndVertex();
int w = edge.getWeight();
if (v2.getAdjuDist() > v1.getAdjuDist() + w) {
v2.setAdjuDist(v1.getAdjuDist() + w);
v2.setParent(v1);
}
}
/**
* 输出结果
*/
private void ShowResult(int startIndex, int destIndex) {
Stack routes = new Stack<>();
// 倒序入栈
Vertex v = vertexList.get(destIndex);
while (v != null) {
routes.push(v);
v = v.getParent();
}
// 正序出栈
System.out.print("(" + vertexList.get(destIndex).getAdjuDist() + ") : ");
while (!routes.isEmpty()) {
System.out.print(routes.pop().getName());
if (!routes.isEmpty()) {
System.out.print("-->");
}
}
}
}
主方法调用路径构造方法,求最短路径。
package com.traffic.init;
import java.util.List;
import com.traffic.models.BuildRoadMap;
import com.traffic.models.Car;
import com.traffic.models.Cross;
import com.traffic.models.Road;
import com.traffic.utils.BeanUtils;
public class Init {
private static BeanUtils beanUtils = new BeanUtils();
private static String base_path = "data/config/";
public Init() {
// 1.加载道路、路口和车辆信息
List carList = beanUtils.readFileGetCarBean(base_path + "car.txt");
List roadList = beanUtils.readFileGetRoadBean(base_path + "road.txt");
List crossList = beanUtils.readFileGetCrossBean(base_path + "cross.txt");
BuildRoadMap mapBuilder = new BuildRoadMap(roadList, crossList);
mapBuilder._map.dijkstraTravasal(0, 32);
}
public static void main(String[] args) {
Init init = new Init();
}
}
结果如下所示。
(70) : 1-->2-->3-->9-->15-->21-->27-->33