地铁线路最短路径 - 实现

地铁线路最短路径 - 实现

主要内容

  1. 提供一幅地铁线路图 (在txt文件中以指定的格式作为输入)
  2. 在网页相应位置输入起始站和终点站
  3. 计算两站之间最短 (最少经过站数) 乘车路线
  4. 在网页中输出所计算的最短路径路线

此题中以北京地铁线路为例.

实现语言

Java, Jsp

实现算法

类职责划分 (相关类的功能描述)

  • Package: model

    1. 用于保存每个站点 (图的结点) 的信息

      • 站点名称

      • 每个站点的id.

      • 每个站点所在的地铁线路名称 (可以查出每个站点可换乘的路线, 目前在实现中没有用到, 以备下次需要时使用)

      public class BeanVertex
      
    2. 用于保存每个站点之间的地铁线路 (每条边) 的信息

      • 边权重信息 (纠结了很久最后还是使用了最简单的int二维数组来保存边的信息, 与此例中地铁线路名称等分离, 作为参数传到实现dijkstra的函数中, 存取计算最快, 并且可重用性较高).

        按照题目要求, 权重只是简化为1.

      • 数组的大小. 主要用于构造器中来初始化二维数组.

      public class BeanEdge
      
    3. 用于保存最后计算所得的最短路径的信息

      • 站点的列表
      • 线路名称的列表

      实现:

      • 重写toString函数, 使符合此次输出要求. 若有需要可更改.
      public class BeanShortestPath
      
  • Package: util

    1. 用于读取文件

      实现:

      • 创建一个graph实体, 将txt中所有信息从字符串读取, 处理存储在各类中.

        返回一个graph.

      public class Reader
      
    2. 用于构建图并实现相关计算

      • 边信息
      • 结点名称到结点class的映射. (改过很多, 由于要多次使用字符串查询结点, 最后选取map作为数据结构)

      实现:

      • Dijkstra算法. 此次案例的核心算法, 参数为起始站的id.
      • 得到最短路径. 运行Dijkstra算法后, 根据终点站和Path数组回溯寻找到整个最短路径, 结果处理后保存在 BeanShortestPath 中.
      public class Graph
      
  • Package: ui

    1. 用于实现网页的相应和传值

      实现:

      • 读取网页中用户输入的起始站和终点站名称
      • 返回寻找到的路径.
      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);
    
    	}
    
    }
    
    

测试用例 (输入输出结果截图)

  1. 主要界面

  2. 输入起始站和终点站

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

    另一个案例

  3. 起始站不存在

  4. 终点站不存在

总结

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