地铁线路最短路径 - 实现
地铁线路最短路径 - 实现
主要内容

- 提供一幅地铁线路图 (在txt文件中以指定的格式作为输入)
- 在网页相应位置输入起始站和终点站
- 计算两站之间最短 (最少经过站数) 乘车路线
- 在网页中输出所计算的最短路径路线
此题中以北京地铁线路为例.
实现语言
Java, Jsp
实现算法
-
Dijkstra
主要参考中国大学MOOC-浙江大学数据结构中的内容, 根据

来编写代码.
类职责划分 (相关类的功能描述)
-
Package: model
-
用于保存每个站点 (图的结点) 的信息
-
站点名称
-
每个站点的id.
-
每个站点所在的地铁线路名称 (可以查出每个站点可换乘的路线, 目前在实现中没有用到, 以备下次需要时使用)
public class BeanVertex -
-
用于保存每个站点之间的地铁线路 (每条边) 的信息
-
边权重信息 (纠结了很久最后还是使用了最简单的int二维数组来保存边的信息, 与此例中地铁线路名称等分离, 作为参数传到实现dijkstra的函数中, 存取计算最快, 并且可重用性较高).
按照题目要求, 权重只是简化为1.
-
数组的大小. 主要用于构造器中来初始化二维数组.
public class BeanEdge -
-
用于保存最后计算所得的最短路径的信息
- 站点的列表
- 线路名称的列表
实现:
- 重写toString函数, 使符合此次输出要求. 若有需要可更改.
public class BeanShortestPath
-
-
Package: util
-
用于读取文件
实现:
-
创建一个graph实体, 将txt中所有信息从字符串读取, 处理存储在各类中.
返回一个graph.
public class Reader -
-
用于构建图并实现相关计算
- 边信息
- 结点名称到结点class的映射. (改过很多, 由于要多次使用字符串查询结点, 最后选取map作为数据结构)
实现:
- Dijkstra算法. 此次案例的核心算法, 参数为起始站的id.
- 得到最短路径. 运行Dijkstra算法后, 根据终点站和Path数组回溯寻找到整个最短路径, 结果处理后保存在 BeanShortestPath 中.
public class Graph
-
-
Package: ui
-
用于实现网页的相应和传值
实现:
- 读取网页中用户输入的起始站和终点站名称
- 返回寻找到的路径.
public class InputStations extends HttpServlet
-
核心代码 (所有类的代码标注)
此处没有给出jsp等相关代码, 详细的可前往GitHub查看.
-
Package: model
package model; import java.util.ArrayList; public class BeanVertex { int id; String stationName; ArrayList<String> line = new ArrayList<String>(); public BeanVertex(int id, String stationName) { super(); this.id = id; this.stationName = stationName; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getStationName() { return stationName; } public void setStationName(String stationName) { this.stationName = stationName; } public ArrayList<String> getLine() { return line; } public void setLine(ArrayList<String> line) { this.line = line; } public void addLine(String lineNum) { this.line.add(lineNum); } }package model; public class BeanEdge { int[][] edgeValues; String[][] edgeNames; int length; /** * @param length */ public BeanEdge(int length) { super(); this.length = length; edgeValues = new int[length][length]; edgeNames = new String[length][length]; } public int[][] getEdgeValues() { return edgeValues; } public void setEdgeValues(int[][] edgeValues) { this.edgeValues = edgeValues; } public String[][] getEdgeNames() { return edgeNames; } public void setEdgeNames(String[][] edgeNames) { this.edgeNames = edgeNames; } }package model; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class BeanShortestPath { List<BeanVertex> stations; List<String> lineNames; public BeanShortestPath() { super(); stations = new ArrayList<>(); lineNames = new ArrayList<>(); } public List<BeanVertex> getStations() { return stations; } public void setStations(List<BeanVertex> stations) { this.stations = stations; } public List<String> getLineNames() { return lineNames; } public void setLineNames(List<String> lineNames) { this.lineNames = lineNames; } @Override public String toString() { String ret = new String(); Set<String> lines = new LinkedHashSet<>(lineNames); ret += stations.get(0).getStationName() + " --> "; boolean isFirst = true; for (String line: lines) { if (isFirst) { ret += line; isFirst = false; } else { ret += " --> "; ret += stations.get(lineNames.indexOf(line)).getStationName(); ret += " --> "; ret += line; } } ret += " --> " + stations.get(stations.size()-1).getStationName(); return ret; } } -
Package: util
package util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import model.BeanEdge; import model.BeanShortestPath; import model.BeanVertex; public class Reader { public static Graph readSubwayInfo(String txtPath) { Graph graph = null; BufferedReader reader; try { // reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("src\\地铁线路信息.txt")), "UTF-8")); // reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("E:\\地铁线路信息.txt")), "UTF-8")); reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(txtPath)), "UTF-8")); // 先将文件中的数据读入 (因为要重复用到) ArrayList<String> txtLines = new ArrayList<String>(); { // 使变量名txtLine只在此区域生效 便于下面for each遍历时取同样的变量名 String txtLine; while ((txtLine = reader.readLine()) != null) { txtLines.add(txtLine); } } // 保存所有站点 Map<String, BeanVertex> stations = new HashMap<>(); for (String txtLine: txtLines) { String[] stationNames = txtLine.split(" "); String lineName = stationNames[0]; for (int i=1; i<stationNames.length; i++) { // 0是lineName if (!stations.containsKey(stationNames[i])) { // map中没有 需要创建后加入 BeanVertex v = new BeanVertex(stations.size(), stationNames[i]); v.getLine().add(lineName); stations.put(stationNames[i], v); } else { // map中有 需要加一个lineName stations.get(stationNames[i]).getLine().add(lineName); } } } // 保存边信息 即站点之间的联系 以及边名称(线路名称) BeanEdge edge = new BeanEdge(stations.size()); // int[][] edge = new int[stations.size()][stations.size()]; // 得先得到所有站点才知道站点的size // String[][] edgeName = new String[stations.size()][stations.size()]; for (String txtLine: txtLines) { String[] stationNames = txtLine.split(" "); String lineName = stationNames[0]; for (int i=1; i<stationNames.length-1; i++) { BeanVertex station1 = stations.get(stationNames[i]); BeanVertex station2 = stations.get(stationNames[i+1]); edge.getEdgeValues()[station1.getId()][station2.getId()] = 1; edge.getEdgeValues()[station2.getId()][station1.getId()] = 1; edge.getEdgeNames()[station1.getId()][station2.getId()] = lineName; edge.getEdgeNames()[station2.getId()][station1.getId()] = lineName; } } // 构建图 graph = new Graph(edge, stations); } catch (UnsupportedEncodingException | FileNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return graph; } }package util; import java.util.Collections; import java.util.HashMap; import java.util.Map; import model.BeanEdge; import model.BeanShortestPath; import model.BeanVertex; public class Graph { private BeanEdge edges; private Map<String, BeanVertex> vertexes; /** * @param edges * @param vertexes */ public Graph(BeanEdge edges, Map<String, BeanVertex> vertexes) { super(); this.edges = edges; this.vertexes = vertexes; } public int[] dijkstra(int startVertexIndex) { boolean[] collected = new boolean[edges.getEdgeValues().length]; int[] dist = new int[edges.getEdgeValues().length]; int[] path = new int[edges.getEdgeValues().length]; // 初始化 for (int i=0; i<edges.getEdgeValues().length; i++) { if (i==startVertexIndex) { // 等于起始点 collected[i] = true; dist[i] = 0; path[i] = i; } else { // 不是起始点 collected[i] = false; if (edges.getEdgeValues()[startVertexIndex][i] > 0) { // 起始点能到 dist[i] = edges.getEdgeValues()[startVertexIndex][i]; path[i] = startVertexIndex; } else { dist[i] = Integer.MAX_VALUE; path[i] = -1; } } } while (true) { int minDistIndex = getMinVertex(collected, dist); // 未收录顶点中 collected=false 的dist最小者 if (minDistIndex == -1) { break; } collected[minDistIndex] = true; for (int i=0; i<edges.getEdgeValues().length; i++) { // minVertex的每个邻接点 if (edges.getEdgeValues()[minDistIndex][i]>0 && collected[i] == false) { dist[i] = dist[minDistIndex] + edges.getEdgeValues()[minDistIndex][i]; path[i] = minDistIndex; } } } return path; } private int getMinVertex(boolean[] collected, int[] dist) { int minDist = Integer.MAX_VALUE; int minDistIndex = -1; for (int i=0; i<edges.getEdgeValues().length; i++) { if (collected[i] == false && dist[i]<minDist) { minDist = dist[i]; minDistIndex = i; } } return minDistIndex; } public BeanShortestPath getShortestPath(String startStationName, String endStationName) { int[] path = dijkstra(vertexes.get(startStationName).getId()); Map<Integer, BeanVertex> id2StationName = new HashMap<>(); // 再搞一个id到结点的映射 id2StationName = getId2StationName(); BeanShortestPath shortestPath = new BeanShortestPath(); int stationId = vertexes.get(endStationName).getId(); while (stationId != vertexes.get(startStationName).getId()) { shortestPath.getStations().add(id2StationName.get(stationId)); // 根据id找名字找站点 shortestPath.getLineNames().add(edges.getEdgeNames()[stationId][path[stationId]]); // 当前站到 上一站 的名字 stationId = path[stationId]; } shortestPath.getStations().add(id2StationName.get(stationId)); // 加入起点 Collections.reverse(shortestPath.getStations()); Collections.reverse(shortestPath.getLineNames()); return shortestPath; } private Map<Integer, BeanVertex> getId2StationName() { Map<Integer, BeanVertex> id2StationName = new HashMap<>(); for (BeanVertex v: vertexes.values()) { id2StationName.put(v.getId(), v); } return id2StationName; } public Map<String, BeanVertex> getVertexes() { return vertexes; } public BeanEdge getEdges() { return edges; } public void setEdges(BeanEdge edges) { this.edges = edges; } public void setVertexes(Map<String, BeanVertex> vertexes) { this.vertexes = vertexes; } } -
Package: ui
package ui; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import model.BeanShortestPath; import util.Graph; import util.Reader; /** * Servlet implementation class InputStations */ @WebServlet("/InputStations") public class InputStations extends HttpServlet { private static final long serialVersionUID = 1L; /** * Default constructor. */ public InputStations() { // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); //设置请求的字符集 response.setContentType("text/html;charset=utf-8"); //设置文本类型 Graph graph = Reader.readSubwayInfo("E:\\EclipsForJava\\workspace\\2020-2021-1\\SoftwareEngineering\\Lab1\\SubwayShortestPath-Web\\地铁线路信息.txt"); String startStationName = request.getParameter("startStationName"); String endStationName = request.getParameter("endStationName"); BeanShortestPath shortestPath = graph.getShortestPath(startStationName, endStationName); request.setAttribute("result", shortestPath.toString()); request.getRequestDispatcher("OutputPath.jsp").forward(request,response); } }
测试用例 (输入输出结果截图)
-
主要界面

-
输入起始站和终点站

显示线路. 可返回继续查看.

另一个案例


-
起始站不存在


-
终点站不存在


总结
- 第一次使用博客的形式来督促自己学习总结, 反思才是更快进步的方法.
- 此次案例的实现在有限的时间内做了一个小型的JavaWeb程序, 巩固了相关知识.
- 不足之处还是有很多. 整个实现的过程中依旧有很多前期没有想清楚的结构和接口, 导致最后实现时仍然出现各种反复删改. 还有很多不合理的地方.
- 按照题目要求只选择站点最少的路线, 但实际还应考虑换乘次数等实际需求, 待完善.
- 此次主要目的是熟悉一个开发过程, 重点虽不是UI, 但希望自己有时间可以回来再完善.

浙公网安备 33010602011771号