title: SubwayReport
date: 2019-10-08 15:49:41
tags: cnbolgsWork
---
地铁项目完成总结
相关链接
GitHUb Page: 内容与本篇相同
GitHub Repo: SubwayRouter
功能模块分离
IOManager
IOManager的Package用来处理与系统文件交互的class类,现在其中有txtOutputer和xmlReader两个类。
xmlReader用来从已经编写好的地铁xml文件中获得地图数据。
txtOutputer用来将程序要求输出的地铁线路信息或者地铁寻路信息输出到文件。
Model
Model的Package内放置了本程序的模型类包括Map(地铁图)、Line(地铁线路)、Station(地铁站)、StationContainer(地铁站集合)、LineContainer(地铁线路集合)、RouterContainer(地铁路线)
其中Map、Line和Station之间是包含关系,两个相邻模型间都通过一个List存储对方。但是Map例外,因为不存在两个不同的地铁区块包含同一条地铁线路。本句意在说明Line和Station是多对多的关系,Map和Line是一对多的关系。而Station中除了存储在那几条地铁线上,还有一个connect的List用来存储相邻的地铁站。
因为本程序要求不显示的声明换乘地铁站,而是判断多个地铁线的同名地铁站为换乘站,所以LineContainer和StationContainer的model都因为这个需求而诞生。当程序在不同的地铁线中读取到了同名的地铁站时,按照现实逻辑该地铁站只是一个地铁换乘站而不是两个地铁站分立在两条地铁线,所以不同Line中对应的Station对象应该是同一个,所以需求一个Station的集合来获取恰当的Station对象。我没有使用Java提供的Set集合,而是自己实现了一个StationContainer并写了对应的getStationByName方法来通过地铁站的命名来获取一个Station对象。在StationContainer中我也提供了contains方法来判断一个Station是否被放入了集合中。而LineContainer的作用也是类似的。除了处理地铁地图的作用外,这两个集合类也被用来获取从用户命令行参数输入的数据对应的对象。
RouterContainer的作用和前两个类不同。本程序中,我选择使用bfs(广度优先搜索)来搜索两个地铁站中的线路,所以需要一个对象放入队列中进行操作,而Java提供的List显然不够好。所以我实现了RouterContainer来存放一条地铁线路。RouterContainer中有一个List来存放以往的线路,并且有一个Station引用当前位置的地铁站。为了保证地铁线路不回头搜索,又提供了contains方法来判断有没有经过一个地铁站。
Router
Route的Package中类被用来提供具体的算法来实现题目要求的两种功能。
LinePrinter类用来提供-a功能,即输出一条地铁线路的所有地铁站。LinePrinter主要是通过调用txtOutputer类来对本地目录进行读写。
RouteSearch类用来提供-b功能,即接收两个地铁站的名字后返回这两个地铁的最短路径。
xml文件的读取
dom的相关操作
在xml文件读取这块,我选择dom4j来获取一个xml文件的dom。
具体操作如下
Document document; xmlReader reader = new xmlReader(); document = reader.getDocumentByFilename("sample.xml");
然后我通过getAllSubwayLine方法来获取所有地铁线路的List
代码如下
private List<Element> getAllSubwayLine(Document doc) { List<Element> ls = new ArrayList<Element>(); Element root = doc.getRootElement(); for (Iterator i = root.elementIterator(); i.hasNext(); ) { Element el = (Element) i.next(); ls.add(el); } return ls; }
因为事先规定所有对象的名字通过name属性放在标签内,所以通过如下的方法获取
document.getRootElement().attributeValue("name")
地铁线路的建模
用上面的dom操作我们遍历每一个地铁线来获得每一个地铁站。在这一步,我们每次获得一个地铁站的名称,这时我们检查StationContainer集合中是否已经有这个地铁站的对象,若有则直接从集合中获取,反之则建立这个对象并放入集合中。通过这种操作,我们得以处理换乘站的情况,保证了不会重复建立同一个地铁站的对象。如果不这样进行处理,除了会导致浪费内存空间外,最主要的后果是不能将不同地铁线路间的地铁站加入换乘站的connect的List中,这样将会导致最后无法正确进行寻路。
通过建立一个lastStation的引用,在每次获得Station对象时短暂存储,来讲相邻的地铁站互相加入自己的connect中。
Line newline = new Line(l.attributeValue("name")); //this station list is for line List<Station> StationList = new ArrayList<Station>(); //iterator all the station in line list Station lastStation = null; for (Iterator i = l.elementIterator(); i.hasNext(); ) { Element el = (Element) i.next(); Station newstation = null; if (sc.contains(el.attributeValue("name"))) { newstation = sc.getStationByName(el.attributeValue("name")); } else { newstation = new Station(el.attributeValue("name")); sc.add(newstation); } if (lastStation != null) { lastStation.addConnect(newstation); newstation.addConnect(lastStation); } newstation.addSite(newline); lastStation = newstation; StationList.add(newstation); }
对应功能的算法实现
从功能实现开始,我们正式开始使用之前已经处理好的IOManager中的各种功能。
输出一条地铁线上的所有地铁站
首先先建立一个新的文件用来存放输出
private static File fileMake(String name) { File file = new File(name); if (file.exists()) { try { if (!file.createNewFile()) { System.out.println("文件已经存在,新的输出已经成功覆盖。"); } } catch (IOException e) { e.printStackTrace(); } } return file; }
随后我们简单的遍历获得的Line中的list通过BufferedWriter输出到文件即可。值得注意的是windows下的文件换行是\r\n
public static void fillStationTxt(Line line, String filename) { File file = txtOutputer.fileMake(filename); try { BufferedWriter writer = new BufferedWriter(new FileWriter(file)); for (Station station : line.getStations()) { writer.write(station.getName() + "\r\n"); writer.flush(); } writer.close(); } catch (IOException e) { e.printStackTrace(); } }
寻找两个地铁站之间的最短路
同上一个功能先建立文件进行输出,这里不再赘述。在这一步我们先通过StationContainer来获取对应的地铁站对象,然后调用RouteSearch中的search方法来进行最短路搜索。
在进行搜索的开始,我们可以对这一步的功能进行一步分类,分成相同地铁线路的情况和不同地铁线路的情况。如果我们要寻找线路的两个地铁站是同一条线路的话,显然不需要进行bfs搜索,只要获取他们公共的地铁线然后按顺序来输出即可。
对于不同线路的地铁站我们利用bfs的方法来搜索线路。首先建立一个队列用来存放RouterContainer。第一步是放入一个只有起点的RouterContainer,随后从这个队列开始bfs。每次从队列中取出一个RouterContainer,然后获得当前地铁站的对象,对于每一个连接该地铁站的地铁站,copy一个RouterContainer加入该节点再放回队列。每次放回的时候记得判断这个节点是否是回头,若是则不放回。
代码如下:
RouteContainer rc = new RouteContainer(); rc.setNowPlace(start); rc.addStation(start); queue.add(rc); RouteContainer endRoute = null; while (!queue.isEmpty()) { RouteContainer nowPlace = (RouteContainer) queue.poll(); if (nowPlace.getNowPlace().getName().equals(end.getName())) { endRoute = nowPlace.copy(); break; } for (Station station : nowPlace.getNowPlace().getConnect()) { if (!nowPlace.contains(station)) { RouteContainer newRoute = nowPlace.copy(); newRoute.addStation(station); newRoute.setNowPlace(station); queue.add(newRoute); } } }