【java】单源最短路径算法实现

本文参考自:Dijkstra算法求单源最短路径Java实现

之前使用Matlab实现过一个Bellman-Ford单源最短路径算法,由于项目需要,现在需要用java实现一下。

1.算法原理介绍

如果所采用的实现方式合适,Dijkstra算法的运行时间要低于前面所说的Bellman_Ford算法,但是Dijkstra算法要求图中没有负边。Dijkstra算法在运行过程中维持的关键信息是一组结点集合S,即已经找到的最短路径的节点的集合。算法重复从未找到最短路径的结点集合Q中选择最短路径估计最小的结点u,将u加入到集合S,然后对所有从u发出的边进行松弛)。下面是《算法导论》书中所给的伪代码:

下面,我们从下面的例子中了解这个算法具体的做法。 

  1. 第一行,INITIALIZE_SINGLE_SOURCE() 初始化了源点的信息,即设置源点的最短路径值为0,同时,设置源点的父亲节点为null(后面会需要由后往前遍历得到最短路径,用来判断最短路径是否遍历完成)。
  2. 第二行和第三行,设置了两个列表(S:已经求得最短路径的节点的集合,即图中的黑色顶点;Q:尚未求得最短路径的集合,即图中的白色顶点)。初始时,S为空,Q为所有的节点的集合。初始时,有向图的信息如图(a)所示;
  3. 第四行到第八行是一个循环,用于求有向图的最短路径;
  4. 第四行,判断尚未求得最短路径的集合Q为空,若是,则代表所有的顶点都已经找到最短路径,不再执行循环。
  5. 第五行,灰色的顶点u是通过EXTRACT_MIN() 方法求得的。该方法具体来说,就是从尚未求得最短路径的集合Q中查找出当前距离源点距离最短的点,同时将其从Q中删除;
  6. 第六行,将灰色顶点u加入到已经求得最短路径的节点的集合S中;
  7. 第七行和第八行,找到所有以灰色顶点为起始顶点的边,并对这些边所对应的结束顶点进行松弛操作。所谓松弛操作,简单来讲就是计算每一个从源点开始,沿着最短路径经过灰色顶点u下一顶点的路径长度。若该长度小于该顶点中存储的最短路径估计,那么就执行更新,即修改该顶点中存储的最短路径估计为当前的路径长度,同时设置该顶点的父亲顶点为灰色顶点u。

2.图的表示

图有两种较为简单的表示方式:

  • 邻接矩阵:二维数组存储图的结构,之前的Matlab版本的Bellman-Ford算法就是采用这种方式;
  • 邻接表:Map<Vertext,List<Edge>>存储图的结构,今天要介绍的方式。

注意,上述的两种方法不仅可以解决无向图的单源最短路径问题,还可以解决有向图的单源最短路径问题。

2.1 图中顶点的表示

如下所示,顶点需要的字段包括节点的名字、到该节点的最短路径长度的估计值、当前节点的前一个节点。

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方法,以及构造函数
}

2.2 图中边的表示

如下所示,边需要的信息包括起始节点、终止节点,以及这条边的权重。

package com.traffic.dijs;

public class Edge {
	private Vertex startVertex; // 此有向边的起始点
	private Vertex endVertex; // 此有向边的终点
	private int weight; // 此有向边的权值
        //...省略get和set方法,构造函数等。
}

3.最短路径的求法

3.1 图的构造

首先,我们可以看到在创建MapBuilder对象时,需要传入两个参数:

  • 顶点组成的列表;
  • 以该顶点为键,以顶点所对应的边的集合为值的Map字典。

接下来介绍一下一个交通线路图的实例,这里是一个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<Integer, Road> _roadHashMap = new HashMap<Integer, Road>();// 路的字典
	private HashMap<Integer, Vertex> _vertexHashMap = new HashMap<Integer, Vertex>();// 路口(顶点)的字典

	public MapBuilder _map = null;

	public List<Vertex> _vertexList = new LinkedList<Vertex>();// 路口顶点

	public HashMap<Integer, int[][]> _roadStatus = new HashMap<Integer, int[][]>();// 路况(二维数组)

	/**
	 * 初始化路况
	 * 
	 * @param roads
	 */
	public void initRoadStatus(List<Road> 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<Cross> 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, List<Edge>> vertex_edgeList_map = new HashMap<Vertex, List<Edge>>();// 顶点和边的映射

		for (int i = 0; i < crosses.size(); i++) {
			List<Edge> edges = new LinkedList<Edge>();// 每个路口最多可以通往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<Road> roads, List<Cross> crosses) {
		initRoadStatus(roads);
		initRoadMap(crosses);
	}
}

3.2 求最短路径 

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<Vertex> vertexList; // 图的顶点集
	private Map<Vertex, List<Edge>> ver_edgeList_map; // 图的每个顶点对应的有向边

	private LinkedList<Vertex> S; // 已经求到最短路径的顶点集合
	private LinkedList<Vertex> Q; // 尚未求到最短路径的顶点集合

	public MapBuilder(List<Vertex> vertexList, Map<Vertex, List<Edge>> ver_edgeList_map) {
		super();
		this.vertexList = vertexList;
		this.ver_edgeList_map = ver_edgeList_map;
	}

	public List<Vertex> getVertexList() {
		return vertexList;
	}

	public void setVertexList(List<Vertex> vertexList) {
		this.vertexList = vertexList;
	}

	public Map<Vertex, List<Edge>> getVer_edgeList_map() {
		return ver_edgeList_map;
	}

	public void setVer_edgeList_map(Map<Vertex, List<Edge>> 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<Edge> list = ver_edgeList_map.get(u);
			for (Edge edge : list) {
				RELAX(edge);
			}
		}

		// 输出结果
		ShowResult(startIndex, destIndex);
	}

	/**
	 * 求集合Q所有顶点到其他顶点的最短路径
	 * 
	 * @param q
	 * @return
	 */
	private Vertex EXTRACT_MIN(LinkedList<Vertex> 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<Vertex> 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<Car> carList = beanUtils.readFileGetCarBean(base_path + "car.txt");
		List<Road> roadList = beanUtils.readFileGetRoadBean(base_path + "road.txt");
		List<Cross> 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
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值