北京地铁线路推荐项目

北京地铁线路推荐项目

一、项目介绍

Github:github.com/LinXS597/BeijingSubway

二、需求分析

  1. 设计一个存储线路信息的文件
  2. 实现地铁线路信息的获取
  3. 实现基础查询功能,查询指定地铁线路经过的站点
  4. 实现出发地到目的地最短路径的查询

三、主要功能模块介绍

序号 模块名称 主要功能 对应Java文件
1 主模块 流程控制、参数解析 src/cn/edu/zucc/Main.java
2 核心算法模块 实现Dijkstra算法 src/cn/edu/zucc/core/DijkstraUtil.java
3 输入输出模块 读出和写入txt文件数据 src/cn/edu/zucc/data/FileManager.java
4 存储模块 存储读入的站点与线路信息 src/cn/edu/zucc/model/Station.java
存储输出的推荐线路信息 src/cn/edu/zucc/model/Routine.java

1、主模块:流程控制、参数解析

  • 支持采用参数 -map 作为标志,来获取对应的自定义地铁文件,例如:

    java subway -map subway.txt

  • 支持采用参数 -a 来指定地铁线路,采用参数 -o 来输出到指定文件station.txt,例如:

    java subway -a 1号线 -map subway.txt -o station.txt

  • 支持采用参数 -b 来指定出发地与目的地,例如:

    java subway -b 天安门西 北京大学东门 -map subway.txt -o routine.txt

2、核心算法模块:实现Dijkstra算法

  • 利用 HashMap 存储全部的站点信息,并以站点名称为key

    public static HashMap<String,Station> allStation = new HashMap<>();

  • 执行算法前先对数据进行预处理操作,主要目的有三个:

    1. 遍历各条线路信息,将station去除重复后加入allStation之中,便于后续算法执行
    2. 根据传入参数,确定并设置routine的起点与终点
    3. 对一些特殊情况进行处理,如起点终点重复、起点不存在、终点不存在等

具体代码如下

	 public static Routine getRoutine ( String begin,String end)  {
        Routine routine = new Routine();
        List<Station> lineStationlist = null;


        Station station = null;
        Station repeatstation = null;

        for(String key:FileManager.subwayLineinfo.keySet()){    //遍历各条线路信息,将station去除重后加入allStation之中

            lineStationlist = FileManager.subwayLineinfo.get(key);

            for(int i = 0;i<lineStationlist.size();i++){
                station = lineStationlist.get(i);
                if(allStation.keySet().contains(station.getStationName())){ //判断是否重复
                    repeatstation = allStation.get(station.getStationName());

                    if (i==0){          //完善各个Sation中相邻站点LinkStation信息
                        repeatstation.getLinkStations().add(lineStationlist.get(i+1));
                    }else if(i==lineStationlist.size()-1){
                        repeatstation.getLinkStations().add(lineStationlist.get(i-1));
                    }else{
                        repeatstation.getLinkStations().add(lineStationlist.get(i+1));
                        repeatstation.getLinkStations().add(lineStationlist.get(i-1));
                    }
                    continue;
                }
                else{

                    if (i==0){
                        station.getLinkStations().add(lineStationlist.get(i+1));
                    }else if(i==lineStationlist.size()-1){
                        station.getLinkStations().add(lineStationlist.get(i-1));
                    }else{
                        station.getLinkStations().add(lineStationlist.get(i+1));
                        station.getLinkStations().add(lineStationlist.get(i-1));
                    }

                    allStation.put(station.getStationName(),station);

                    if(station.getStationName().equals(begin)){     //根据传入参数,确定起点,并加入到routine之中
                        routine.setBeginStation(station);
                    }

                    if(station.getStationName().equals(end)){       //根据传入参数,确定终点,并加入到routine之中
                        routine.setEndStation(station);
                    }
                }
            }
        }

        if (routine.getBeginStation().equals(routine.getEndStation())){ //一些异常情况的处理
            System.out.println("起点与终点相同,请重新输入");
            return null;
        }
        else if (routine.getBeginStation() == null){
            System.out.println("起点不存在");
            return null;
        }
        else if (routine.getEndStation() == null){
            System.out.println("终点不存在");
            return null;
        }
        else{
            routine = new DijkstraUtil().Dijkstra_algorithm(routine);
        }

        return routine;
    }
  • 执行Dijkstra算法,主要有以下几步:
    1. 定义起点到其他顶点最短距离distance,定义某个指定站点的前一个站点path,并进行初始化
      HashMap<HashMap<Station,Station>,Integer> distance = new HashMap<>();
      HashMap<Station,Station> path = new HashMap<>();
    2. 选择V,求的一个从起点到V的最短路径
    3. 修改从起点到任一顶点W可达的最短路径长度,以及W的父结点
    4. 重复2、3,求得最短距离

具体代码如下:

	public Routine Dijkstra_algorithm ( Routine routine ) {

        HashMap<HashMap<Station,Station>,Integer> distance = new HashMap<>();   //储存各站点之间的最短距离,以Integer为单位,表示站点数
        HashMap<Station,Integer> collected = new HashMap<>();                   //判断该Station是否被访问过
        HashMap<Station,Station> path = new HashMap<>();                        //存储某个指定站点的前一个站点
        HashMap<Station,Station> disitem = new HashMap<>();
        Station item;
        Station V;

        for (String key :allStation.keySet()){      //初始化distance、collected与path
            item = allStation.get(key);

            collected.put(item,new Integer(0));

            if (!routine.getBeginStation().equals(item)){
                if (routine.getBeginStation().getLinkStations().contains(item)){    //若与起点相邻,则将距离设置为1,并将对应的path设置为起点
                    disitem = new HashMap<>();
                    disitem.put(routine.getBeginStation(),item);
                    distance.put(disitem,new Integer(1));
                    path.put(item,routine.getBeginStation());
                }
                else{
                    disitem = new HashMap<>();                              //若未与起点相邻,则将初值设置为10000
                    disitem.put(routine.getBeginStation(),item);
                    distance.put(disitem,new Integer(10000));
                }
            }
            else{
                disitem = new HashMap<>();
                disitem.put(routine.getBeginStation(),item);
                distance.put(disitem,new Integer(0));
            }
        }

        collected.put(routine.getBeginStation(),1);

        while (true){
            V = FindMinDist(routine,distance,collected);    //取未被访问顶点中distance最小者
            if (V.getStationName().equals("-1"))            //若这样的V不存在,算法结束
                break;

            collected.put(V,1);

            for (String key:allStation.keySet()){           //遍历每个站点
                if (V.getLinkStations().contains(allStation.get(key))&&collected.get(allStation.get(key))==0){
                    if (distance.get(getFromtoFin(routine,V))+1<distance.get(getFromtoFin(routine,allStation.get(key)))){   //若收录的顶点使distance变小,则进行更新
                        distance.put(getFromtoFin(routine,allStation.get(key)),distance.get(getFromtoFin(routine,V))+1);
                        path.put(allStation.get(key),V);
                    }
                }
            }
        }
        V = path.get(routine.getEndStation());

        while(!V.equals(routine.getBeginStation())){        //将最短路径各站点数据存入routine之中
            routine.getPassStations().add(0,V);
            V = path.get(V);
        }
        routine.getPassStations().add(0,routine.getBeginStation());
        routine.getPassStations().add(routine.getEndStation());


        return routine;         //返回
    }

3、输入输出模块与存储模块:读出和写入txt文件数据

  • READ_FILE 和 WRITE_FILE 分别用于存储读入文件与输出文件路径信息
	public  static String READ_FILE;
    public  static String WRITE_FILE;
  • 利用HashMap存储线路数据

    public static HashMap<String, List<Station>> subwayLineinfo = new HashMap<>();

  • 存储站点信息的Station类设计

    	private String stationName;     //站点名称

    	private String lineName;        //线路名称

    	private List<Station> linkStations = new ArrayList<>();     //相邻站点


  • 存储最短路径信息的Rountine类设计

    	private Station beginStation;   //出发站点

    	private Station endStation;     //结束站点

    	private List<Station> passStations = new ArrayList<>();     //最短路径上的站点

  • 读入地铁线路与站点数据

subway.txt 文件设计如下:

	1号线 站点1 站点2 ...
	2号线 站点1 站点2 ...
	3号线 站点1 站点2 ...
	...
  • 写入指定地铁线路的站点信息

station.txt 文件设计如下:

	1号线
	站点1
	站点2
	站点3
	...
  • 写入指定站点之间的最短路径上的站点信息

routine.txt 文件设计如下:

	11
	天安门西
	西单
	复兴门
	--->换乘地铁--<2号线>--
	阜成门
	车公庄
	西直门
	--->换乘地铁--<13号线>--
	大钟寺
	知春路
	--->换乘地铁--<10号线>--
	知春里
	海淀黄庄
	--->换乘地铁--<4号线大兴线>--
	中关村
	北京大学东门

写入时需判断相邻的两个站点是否在同一条线路上,如果不在,则需更新线路信息并写入routine.txt中

	public static String getLineNmae(Station station1,Station station2){        //判断两个站点是否在同一条线路上
        String res = null;

        List<Station> item;
        
        for (String key : subwayLineinfo.keySet()){
            item = subwayLineinfo.get(key);
            if (item.contains(station1)&&item.contains(station2)){
                return key;     //如果是,返回线路名称,否则,返回null
            }

        }

        return res;
    }

四、测试分析

1) 重要功能测试

  1. 读入文件,命令行参数为:-map subway.txt

    • 测试结果:成功
  2. 查询线路,命令行参数为:-a 1号线 -map subway.txt -o station.txt

    • 测试结果:成功

2) 核心功能测试

  • 查询最短线路,命令行参数为:-b 天安门西 北京大学东门 -map subway.txt -o routine.txt

    • 测试结果:成功

3) 异常情况处理测试:

  1. 起点与终点相同:-b 天安门西 天安门西 -map subway.txt -o routine.txt

    • 显示提示信息:起点与终点相同
  2. 起点或终点不存在: -b 天安门西 杭州东站 -map subway.txt -o routine.txt

    • 显示提示信息:起点或终点不存在
  3. 查询线路不存在: -a 20号线 -map subway.txt -o station.txt

    • 显示提示信息:线路不存在
  4. 命令行参数格式错误: -f 天安门西 天安门西 -map subway.txt -o routine.txt

    • 显示提示信息:验证参数格式
posted @ 2019-10-11 15:36  31703178林型双  阅读(412)  评论(0编辑  收藏  举报