Github https://github.com/mmmmm962/BeiJingSubway1
一、任务:
实现一个帮助进行地铁出行路线规划的命令行程序。
需求1:
程序启动时需要通过读取 -map 参数来获得对应的自定义地铁文件(命名为 subway.txt),从而得到地铁线路图的信息。
java subway -map subway.txt
需求2:
查询地铁线路,在应用程序需要支持一个新的命令行参数 -a
,它指定了用户希望查询的地铁线路。程序就需要能够从线路的起始站点开始,依次输出该地铁线经过的所有站点,直到终点站。输出的文件使用 -o
命令行参数来指定。
java subway -a 1号线 -map subway.txt
需求3:
获取两个站点之间的最短路径,在命令行中以 -b 参数加两个地铁站点名称分别作为出发地与目的地,比如用户希望知道 洪湖里 到复兴路 之间的最短路线是怎样的,可以使用如下命令
java subway -b 洪湖里 复兴路 -map subway.txt -o routine.txt
程序将计算从出发到目的站点之间的最短(经过的站点数最少)路线,并输出经过的站点的个数和路径(包括出发与目的站点)。注意,如果需要换乘,请在换乘站的下一行输出换乘的线路。上面样例的输出就会存入 routine.txt 文件中,文件内容如下:
3 洪湖里 西站 6号线 复兴路
二、文件的存储:
将地铁线路信息存储为subway.txt文件,放置于项目的bin目录下(为了方便起见,将纯中文名的地铁线路也标记为特定的一个数字)
*1-苹果园,古城,八角游乐园,八宝山,玉泉路,五棵松,万寿路,公主坟,军事博物馆,木樨地,南礼士路,复兴门,西单,天安门西,天安门东,王府井,东单,建国门,永安里,国贸,大望路,四惠,四惠东 *2-西直门,积水潭,鼓楼大街,安定门,雍和宫,东直门,东四十条,朝阳门,建国门,北京站,崇文门,前门,和平门,宣武门,长椿街,复兴门,阜成门,车公庄 *4-安河桥北,北宫门,西苑,圆明园,北京大学东门,中关村,海淀黄庄,人民大学,魏公村,国家图书馆,动物园,西直门,新街口,平安里,西四,灵境胡同,西单,宣武门,菜市口,陶然亭,北京南站,马家堡,角门西,公益西桥 *5-宋家庄,刘家窑,蒲黄榆,天坛东门,磁器口,崇文门,东单,灯市口,东四,张自忠路,北新桥,雍和宫,和平里北街,和平西桥,惠新西街南口,惠新西街北口,大屯路东,北苑路北,立水桥南,立水桥,天通苑南,天通苑,天通苑北
三、放置的路径:
北京地铁线路规划项目,我一共编写了Dijkstra、Line、Path、Station、subway五个JAVA文件
包含地铁线路信息的subway.txt文件放置于bin目录下,查看地铁线路的站点信息会输出到station.txt文件中,最短路径的信息会输出到routine.txt文件中,这两个文件都会生成于bin目录下
四、存储的结构:
站点信息
Station private String stationName; // 站点名称 private String lineName; // 线路名称 private List<Station> linkStation = new ArrayList<>(); // 相邻站点
相应的get、set方法
public String getStationName() { return stationName; } public void setStationName(String stationName) { this.stationName = stationName; } public String getLineName() { return lineName; } public void setLineName(String lineName) { this.lineName = lineName; } public List<Station> getLinkStation() { return linkStation; } public void setLinkStation(List<Station> linkStation) { this.linkStation = linkStation; } public Station(String stationName, String lineName) { this.stationName = stationName; this.lineName = lineName; } public Station(String stationName) { this.stationName = stationName; }
public boolean equals(Object obj) { // 判断传入对象的属性 if (this == obj) { return true; } else if (obj instanceof Station) { Station station = (Station) obj; if (station.getStationName().equals(this.getStationName())) { return true; } else { return false; } } else { return false; } }
路径信息(get、set方法略)
path private Station start; // 开始站点 private Station end; // 结束站点 private Double distance = 0.0; // 站点间的距离 private List<Station> passStation = new ArrayList<>(); //经过的站点
线路信息
line public static HashMap<String, List<Station>> lineData; // 地铁线路数据 public static LinkedHashSet<List<Station>> lineSet = new LinkedHashSet<>();// 地铁线路集合
五、基本的代码及方法:
命令行读取相关文件以及参数的设置
switch (args[0]) { // 从命令行读取参数 case "-map": if (args.length == 2) { Line.readFile = System.getProperty("user.dir") + File.separator + "\\" + args[1]; // 读取地铁信息 Line.readFile(); System.out.println("成功读取地铁信息"); } else { System.out.println("输入不正确!"); break; } break; case "-a": if (args.length == 6) { Line.readFile = System.getProperty("user.dir") + File.separator + "\\" + args[3]; Line.writeFile = System.getProperty("user.dir") + File.separator + "\\" + args[5]; Line.readFile(); Line.writeLine(args[1]); System.out.println("该线路的信息为:"); Line line = new Line(); line.readLine(); } else { System.out.println("输入不正确!"); break; } break; case "-b": if (args.length == 7) { Line.readFile = System.getProperty("user.dir") + File.separator + "\\" + args[4]; Line.writeFile = System.getProperty("user.dir") + File.separator + "\\" + args[6]; Line.readFile(); Path path = Dijkstra.calculate(new Station(args[1]), new Station(args[2])); Line.writePassStation(path); System.out.println("最短路径信息为:"); Line line = new Line(); path.readShort(); } else { System.out.println("输入不正确!"); break; } break; default: System.out.println("输入不正确!"); }
读取subway.txt文件的方法
public static void readFile() { //读取地铁线路图信息 File file = new File(readFile); BufferedReader reader = null; try { InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(file), "UTF-8"); reader = new BufferedReader(inputStreamReader); String line = null; String lineName = "1"; while ((line = reader.readLine()) != null) { if (line.trim().startsWith("*")) { String[] lineInfo = line.substring(1).split("-"); lineSet.add(getLine(lineInfo[1].trim(), lineInfo[0].trim())); } } } catch (IOException e) { e.printStackTrace(); } finally { } }
线路、站点相关的方法
public static void createline() { //存储地铁线路 lineData = new HashMap<>(); for (List<Station> stations : lineSet) { lineData.put(stations.get(1).getLineName(), stations); } } public static String getLineNameByStation(Station station) { //通过站点获取线路名称 createline(); String startname = station.getStationName(); for (Map.Entry<String, List<Station>> entry : lineData.entrySet()) { List<Station> stations = entry.getValue(); for (Station station1 : stations) { if (station1.getStationName().equals(startname)) { return entry.getKey(); } } } return ""; } public static ArrayList<Station> getLine(String lineName1, String lineName2) { // 读取线路信息 ArrayList<Station> line = new ArrayList<Station>(); String[] lineArr = lineName1.split(","); for (String s : lineArr) { line.add(new Station(s, lineName2)); } return line; } public static String writeLine(String lineName) throws UnsupportedEncodingException, FileNotFoundException { // 提取相应线路的信息 createline(); lineName = lineName.substring(0, 1); List<Station> lineInfo = lineData.get(lineName); String line = lineInfo.stream().map(x -> x.getStationName()).collect(Collectors.joining(",")); try { Files.write(Paths.get(writeFile), line.getBytes()); } catch (IOException e) { e.printStackTrace(); } return line; } public static void writePassStation(Path path) { //输出最短路径 FileWriter f = null; BufferedWriter b = null; try { f = new FileWriter(new File(writeFile), true); b = new BufferedWriter(f); b.write((path.getPassStation().size() + 1) + "\t\n"); // 写入站台数 b.write(path.getStart().getStationName() + "\t\n"); // 写入站台名称 String startLineName = getLineNameByStation(path.getStart());// 获取地铁线路 String line = startLineName; // 默认转乘地铁线路与当前一致 for (Station station : path.getPassStation()) { if (!line.equals(station.getLineName())) { b.write(station.getLineName() + "号线" + "\t\n"); // 写入转乘线路 b.write(station.getStationName() + "\t\n"); line = station.getLineName(); } else { b.write(station.getStationName() + "\t\n"); } } b.close(); f.close(); } catch (IOException e) { e.printStackTrace(); } }
六、核心Dijkstra算法:
使用dijkstra算法求解两个地铁站之间的最短路径问题,默认将相邻站点之间的距离设置为1;
存储的结构
private static HashMap<Station, Path> resultMap = new HashMap<>(); //结果集 private static List<Station> visitedStation = new ArrayList<>(); //遍历过的站点
获取相邻结点
public static List<Station> getLinkStation(Station station) { // 获取所有相邻点 List<Station> linkedStaion = new ArrayList<Station>(); for (List<Station> line : Line.lineSet) { for (int i = 0; i < line.size(); i++) { if (station.equals(line.get(i))) { if (i == 0) { linkedStaion.add(line.get(i + 1)); //此站点为起始站点时 } else if (i == (line.size() - 1)) { linkedStaion.add(line.get(i - 1)); //此站点是最后一个站点时 } else { linkedStaion.add(line.get(i + 1)); //位于其余位置 linkedStaion.add(line.get(i - 1)); } } } } return linkedStaion; }
计算最短距离
private static Station getNextStation() { //获得下一个需要分析的点 Double min = 999999.0; Station s = null; Set<Station> stations = resultMap.keySet(); for (Station station : stations) { if (visitedStation.contains(station)) { continue; } Path result = resultMap.get(station); if (result.getDistance() < min) { //比较获得最短距离 min = result.getDistance(); s = result.getEnd(); } } return s; }
循环遍历得出结果
public static Path calculate(Station start, Station end) { //循环遍历获得结果 if (!visitedStation.contains(start)) { // visitedStation.add(start); } // 如果开始站点等于终止站点,则设置result,设置距离和station。将开始结点标记已遍历 if (resultMap.isEmpty()) { List<Station> linkStation = getLinkStation(start); for (Station station : linkStation) { // 将相邻站点加入结果集 Path path = new Path(); path.setStart(start); path.setEnd(station); path.setDistance(1.0); // 默认站点间距离都为1 path.getPassStation().add(station); resultMap.put(station, path); } } Station parent = getNextStation(); if (parent == null) { // 所有站点都已经遍历完成 Path path = new Path(); path.setDistance(0.0); path.setStart(start); path.setEnd(end); return resultMap.put(end, path); } // 如果得到的最佳邻点与目标点相同,则直接返回最佳邻点对应的result对象。 if (parent.equals(end)) { return resultMap.get(parent); } List<Station> childLinkStation = getLinkStation(parent); for (Station child : childLinkStation) { if (visitedStation.contains(child)) { continue; } Double distance = 0.0; if (parent.getStationName().equals(child.getStationName())) { distance = 0.0; } Double parentDistance = resultMap.get(parent).getDistance(); distance = parentDistance + 1.0; List<Station> parentPassStation = resultMap.get(parent).getPassStation(); Path childResult = resultMap.get(child); if (childResult != null) { // 含有最佳相邻点 if (childResult.getDistance() > distance) { childResult.setDistance(distance); childResult.getPassStation().clear(); childResult.getPassStation().addAll(parentPassStation); childResult.getPassStation().add(child); } } else { childResult = new Path(); // 没有最佳相邻点 childResult.setDistance(distance); childResult.setStart(start); childResult.setEnd(child); childResult.getPassStation().addAll(parentPassStation); childResult.getPassStation().add(child); } resultMap.put(child, childResult); } visitedStation.add(parent); return calculate(start, end); }
七、测试:
正常情况测试:
需求1:读取地铁线路的信息
需求2:查询相应线路的站点信息
并且每次会在station.txt文件中有相应的输出
需求三:查询两个站点之间的最短路径
苹果园-四惠之间最短路径相差21站;
在routine.txt文件中会有相应的输出
异常情况测试:
需求1
参数不正确
输入的文件有误(会提示异常)
需求二
参数不正确
线路不存在时没有输出
需求三
参数不正确
站点输入不合理会提示异常
八、总结:
北京地铁线路规划这个项目很贴近生活、具有实际意义。这个项目让我学习了Dijkstra算法的使用,练习了java语言,结合北京的地铁线路来查看特定线路的站点,查询两个站点之间的最短路径,这一点与导航的app有异曲同工之处。将所学的知识与生活中的实际问题结合起来,会觉得更有价值。