导航

北京地铁出行线路规划

Posted on 2019-10-14 04:58  31701062马钰萍  阅读(349)  评论(0编辑  收藏  举报

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有异曲同工之处。将所学的知识与生活中的实际问题结合起来,会觉得更有价值。