一、项目介绍
实现一个帮助进行地铁出行路线规划的命令行程序。
github链接:https://github.com/blakeyoungc/subway
主要需求:1、地铁线路图信息的导入
2、查询指定地铁线经过的站点
3、计算从出发到目的站点之间的最短(经过的站点数最少)路线,并输出经过的站点的个数和路径(包括出发与目的站点)。
采用https://map.bjsubway.com/上的北京地铁线路图:
二、需求实现
主要模块介绍:
序号 | 模块名 | 功能 | 对应java类 |
1 | 主模块(main函数) | 对输入参数的控制 | Main.java |
2 | 文件输入输出模块 | txt文件的读写 | Manager.java |
3 | 求最短路径(dij算法) | 实现两个站点的最短路线查找 | Subway.java |
Main函数中区分输入不同参数时执行的操作。
public class Main { public static void main(String[] args) { if(args.length==2||args.length==6||args.length==7) { //通过case分别处理不同参数输入时的操作 switch(args[0]) { case "-map": Manager.loadfile(); break; case "-a": Manager.loadfile(); Manager.searchstations(args[1]); break; case "-b": Manager.loadfile(); Route results=Subway.getRoutine(args[1],args[2]); Manager.outputroute(results); break; } } else System.out.println("格式错误!"); } }
车站及线路类以及其方法的设计
车站类:
import java.util.*; public class Station { private String stationName; //站点名 private String lineName; //所在路线名 private List<Station> linkStations = new ArrayList<>(); //存储相邻的站点 public Station(String stationName){ this.stationName = stationName; } public Station(String stationName,String lineName){ this.stationName = stationName; this.lineName = lineName; } 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> getLinkStations() { return linkStations; } public void setLinkStations(List<Station> linkStations) { this.linkStations = linkStations; } }
线路类:
public class Route { private Station startstation; //起点 private Station destination; //终点 private List<Station>passedStations=new ArrayList<>(); //存储最短路径经过的路线站点 public Station getStartStation() { return startstation; } public void setStartStation(Station startstation) { this.startstation = startstation; } public Station getDestination() { return destination; } public void setDestination(Station destination) { this.destination = destination; } public List<Station> getPassedStations() { return passedStations; } public void setPassedStations(List<Station> passedStations) { this.passedStations = passedStations; } }
需求1、地铁线路图信息的导入
程序启动时需要通过读取 -map 参数来获得对应的自定义地铁文件(命名为 subway.txt),从而得到地铁线路图的信息。
示例: java subway -map subway.txt
通过Hashmap存储subway.txt中的线路站点信息,其中key存线路名,value存站点类的表,存到subwaymap中以供后面使用。
public static HashMap<String, List<Station>> subwaymap = new HashMap<>(); public static void loadfile() { File file=new File("C://subway.txt"); try { InputStreamReader inputStreamReader=new InputStreamReader(new FileInputStream(file),"GBK"); BufferedReader reader=new BufferedReader(inputStreamReader); String data=null; while((data=reader.readLine())!=null) { String[] linedata=data.trim().split(" "); //按空格分割读取txt文件的数据 List<Station>stations=new ArrayList<>(); for(int i = 1;i<linedata.length;i++){ Station station = new Station(linedata[i],linedata[0]); stations.add(station); } subwaymap.put(linedata[0], stations); //按序存入subwaymap } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
subway.txt中的北京地铁线路信息截图
需求2、查询指定地铁线经过的站点。
程序启动时需要通过读取 -a 参数来获得线路名,并输出该线路的站点到文件“station.txt”。
示例: -a 10号线 -map subway.txt -o station.txt
把之前subwaymap中的数据根据用户输入的线路名进行筛选,有序读取该线路经过的站点并写入“station.txt”文件。
public static void searchstations(String routename) { File file=new File("C://station.txt"); try { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file),"GBK"); BufferedWriter writer = new BufferedWriter(outputStreamWriter); List<Station> list = subwaymap.get(routename); writer.write(routename+" "); for(int i=0;i<list.size();i++){ writer.write(list.get(i).getStationName()+" "); } writer.close(); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
查询10号线的所有站点,运行完成后station.txt结果截图
需求3、查询两个站点的最短路径线路及换乘信息
程序启动时需要通过读取 -b 参数来获得输入的起点和终点,输出两个站点之间的最短路径经过的站点。
示例: -b 巴沟 土桥 -map subway.txt -o routine.txt
查询从巴沟到土桥的最短路线站点及换乘线路。
通过Dijkstra算法求最短路径。
步骤如下:
1:遍历所有节点找到未访问过的节点中累积权值(其实就是从源节点到当前节点的路径值和)最小的(设为A)。
2:遍历该节点所有可达边(连通到目标节点B),如果节点A累积权值加可达边权值小于目标节点B自身的累积权值,则对目标节点B进行更新。
3:将节点A设定为已访问。
4:重复(1)直到所有节点均访问完毕。
public Route Dijkstra(Route routine) { Station s; Station S; HashMap<Station,Station> D = new HashMap<>(); HashMap<Station,Station> prev = new HashMap<>(); HashMap<Station,Integer> searched = new HashMap<>(); HashMap<HashMap<Station,Station>,Integer> distance = new HashMap<>(); for (String key :allStation.keySet()){ s = allStation.get(key); searched.put(s,new Integer(0)); if (!routine.getStartStation().equals(s)){ if (routine.getStartStation().getLinkStations().contains(s)){ D = new HashMap<>(); D.put(routine.getStartStation(),s); distance.put(D,new Integer(1)); prev.put(s,routine.getStartStation()); } else{ D = new HashMap<>(); D.put(routine.getStartStation(),s); distance.put(D,new Integer(Integer.MAX_VALUE)); } } else{ D = new HashMap<>(); D.put(routine.getStartStation(),s); distance.put(D,new Integer(0)); } } searched.put(routine.getStartStation(),1); while (true){ S = FindMinDist(routine,distance,searched); if (S.getStationName().equals("-1")) break; searched.put(S,1); for (String key:allStation.keySet()){ if (S.getLinkStations().contains(allStation.get(key))&&searched.get(allStation.get(key))==0){ if (distance.get(getFromtoFin(routine,S))+1<distance.get(getFromtoFin(routine,allStation.get(key)))){ distance.put(getFromtoFin(routine,allStation.get(key)),distance.get(getFromtoFin(routine,S))+1); prev.put(allStation.get(key),S); } } } } S = prev.get(routine.getDestination()); while(!S.equals(routine.getStartStation())){ routine.getPassedStations().add(0,S); S = prev.get(S); } routine.getPassedStations().add(0,routine.getStartStation()); routine.getPassedStations().add(routine.getDestination()); return routine; }
通过算法生成最短路径后,再进行换乘线路的判断,同时将数据输出到“routine.txt”文件中。
public static void outputroute(Route routine) { // TODO Auto-generated method stub File file=new File("C://routine.txt"); try { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(file),"GBK"); BufferedWriter writer = new BufferedWriter(outputStreamWriter); writer.write(routine.getPassedStations().size()-1+" "); String linename="0"; for (int i = 0; i < routine.getPassedStations().size();i++) { writer.write(routine.getPassedStations().get(i).getStationName()+" "); if (i<routine.getPassedStations().size()-1){ if (!routine.getPassedStations().get(i).getLineName().equals(routine.getPassedStations().get(i+1).getLineName())){ //换乘线路的判断 linename = routine.getPassedStations().get(i+1).getLineName(); if(linename!="0") writer.write("换乘"+linename+" "); } } } writer.close(); }catch(IOException e){ e.printStackTrace(); } }
routine.txt中的最短路线站点及换乘信息
异常情况处理:
起点与终点相同:-b 土桥 土桥 -map subway.txt -o routine.txt
输出:起点与终点相同
起点或终点不存在: -b 123 456 -map subway.txt -o routine.txt
输出:起点或终点不存在
查询线路不存在: -a 1000号线 -map subway.txt -o station.txt
输出:线路不存在
三、小结
本次个人作业中间出现了各种错误,其实还是对java代码的熟悉度不够,实验中多次出现Exception in thread "main" java.lang.NullPointerException的错误,主要因为数组为null了,没有初始化,浪费了很多时间找错,后续还要继续学习熟悉。