北京地铁出行线路规划——个人项目

image

项目地址

详见:https://github.com/magic-g-j/Subway

实现过程:Dijkstra算法和站点、路径的结构找了一些参考。根据个人需求,做了许多修改工作,并按照所需个人完成了文件的读写,程序的输出方式,以及抛出的异常,并做好测试。

一、基本功能

  • 获取地铁线路图。

以参数 -map 作为标志,来获得对应的自定义地铁线路图信息文件(命名为 Subway.txt)。输入格式如:

java Subway -map Subway.txt
  • 获取指定地铁线路。

以参数 -a 作为标志,并输入指定地铁线路,输出的文件以参数 -o 来指定。从线路的起始站点开始,依次输出该地铁线经过的所有站点,直到终点站。输入格式如:

java Subway -a 1号线 -map Subway.txt -o Station.txt
  • 输入起始站点与目的站点,输出相应最短路线。

以 -b 参数作为标志,并输入起始站点与目的站点,输出经过的站点的个数和路径(包括起始站点与目的站点),如果需要换乘,则在换乘站的下一行输出换乘的地铁线路。将结果写入 routine.txt 中。输入格式如:

java Subway -b 苹果园 雍和宫 -map Subway.txt -o Routine.txt

二、文件存储

地铁线路图信息等都以txt的文件格式存储,以方便程序读取,简洁易懂,并可以灵活拓展。

线路名称与站点名称都以“ ”空格分隔。

  • 地铁线路图信息——Subway.txt
1号线 苹果园 古城 八角游乐园 八宝山 玉泉路 五棵松 万寿路 公主坟 军事博物馆 木樨路 南礼士路 复兴门 西单 天安门西 天安门东 王府井 东单 建国门 永安里 国贸 大望路 四惠 四惠东
2号线 西直门 积水潭 鼓楼大街 安定门 雍和宫 东直门 东四十条 朝阳门 建国门 北京站 崇文门 前门 和平门 宣武门 长椿街 复兴门 阜成门 车公庄 
……
  • 地铁线所经站点——Station.txt
1号线 苹果园 古城 八角游乐园 八宝山 玉泉路 五棵松 万寿路 公主坟 军事博物馆 木樨路 南礼士路 复兴门 西单 天安门西 天安门东 王府井 东单 建国门 永安里 国贸 大望路 四惠 四惠东

给出需乘坐的站点数量以及路线,并提示换乘地铁线。

  • 起始站点与目的地间的最短路线——Routine.txt
需乘坐17个站点。
1号线:
苹果园
古城
八角游乐园
八宝山
玉泉路
五棵松
万寿路
公主坟
军事博物馆
(同站换乘)9号线:
白堆子
白石桥南
国家图书馆
(同站换乘)4号线大兴线:
动物园
西直门
(同站换乘)2号线:
积水潭
鼓楼大街
安定门
雍和宫

三、代码分析

  • Subway.java

main函数。

构造了三个FileOperate类的构造器,以实现不同参数运行分别所实现的功能。

System.exit(0);

是用于在抓到异常时,给出相应的提示,而不输出多余的异常情况,而结束程序的运行。不符合三种输入格式,则会提示“ERROR”。

public static void main(String [] args) throws Exception{
//    	D:\eclipse-workspace\Subway\src
//    	chcp 936 
        new Subway();
//      java Subway -map Subway.txt
        if(args.length==2) {
            try {
            	new FileOperate(args[0], args[1]);
			} catch (Exception e) {
				System.exit(0);
			}
        }
//      java Subway -a 1号线 -map Subway.txt -o Station.txt
        else if(args.length==6){
            try {
            	new FileOperate(args[0],args[1],args[2],args[3],args[4],args[5]);
    		} catch (Exception e) {
    			System.exit(0);
    		}
        }
//      java Subway -b 苹果园 雍和宫 -map Subway.txt -o Routine.txt
        else if(args.length==7){
        	try {
        		new FileOperate(args[0],args[1],args[2],args[3],args[4],args[5],args[6]);
    		} catch (Exception e) {
    			System.exit(0);
    		}
        }
        else{
//            System.out.println("ERROR!");
        	throw new Exception("ERROR");
        }
    }
  • Station.java

站点的结构。

以stationName和lineName构造站点,allLinkStations是一个存储该站点所有相邻站点的列表,LinkedHashSet是Set集合的一个实现,因此allLines用于存储所有地铁线路,以站点名称在allLines中遍历,返回对应站点。并且需要重写equals函数,以判断两个站点是否相同。

    private String stationName;
    private String lineName;
    private List<Station> allLinkStations=new ArrayList<>();//所有相邻站点
    public static LinkedHashSet<List<Station>> allLines=new LinkedHashSet<>();//所有地铁线路
  • Path.java

路径的结构。

startStation和endStation记录路径的起始站点和目的站点,allPassStations是一个存储所经站点的列表,distance来记录路径的长度,相邻站点距离默认都为1。

    private Station startStation;
    private Station endStation;
    private List<Station> allPassStations=new ArrayList<>();//所有已经过站点
    private int distance=0;
  • FileOperate.java

对文件做读写操作,并判断不同情况。以System.out.println形式输出异常提示并不合适,有时候会出现一大串,所以加了throw new Exception的形式,这样异常情况时,输出会简短许多。

其中,setAllLines是读取文件,并存储好个线路、站点、相邻站点的信息。getPath是在相应文件中写入最短路径。详见源代码。

public FileOperate(String str1, String str2) throws Exception {
        if(str1.equals("-map")){
            for(int i=0;i<getFile(str2).size();i++){
                getFilePrint(str2,i);
                System.out.println("");
            }
        }else{
            System.out.println("查询地铁线路图,请按照格式输入如:java Subway -map Subway.txt");
        	throw new Exception();
        }
    }
	
	public FileOperate(String str, String str1, String str2, String str3, String str4, String str5) throws Exception {
		if(str.equals("-a") && str2.equals("-map") && str4.equals("-o")){
			List<String> lines = getFile(str3);
			int flag = 0;
			for(int i=0;i<lines.size();i++) {
				String[] lineAndStations = lines.get(i).split(" ");
				if(lineAndStations[0].equals(str1)){
					getFilePrint(str3,i);
//            	    System.out.println(line.get(i));
					writeFile(lines.get(i),str5);
					flag = 1;
				}
            }
			if(flag==0) {
				System.out.println("不存在此线路!");
				throw new Exception();
			}
        }
		else{
			System.out.println("查询地铁线路,请按照格式输入如:java Subway -a 1号线 -map Subway.txt -o Station.txt");
			throw new Exception();
        }
    }
	 
	public FileOperate(String str, String str1, String str2, String str3, String str4, String str5, String str6) throws Exception {
		if(str.equals("-b") &&  str3.equals("-map") && str5.equals("-o")) {
			if(str1.equals(str2)) {
				System.out.println("起始站点与目的站点相同,请重新输入!");
				throw new Exception();
			}
			 
			List<String> lines = getFile(str4);
			int flag1 = 0;
			int flag2 = 0;
			for(int i=0;i<lines.size();i++) {
				String[] lineAndStations = lines.get(i).split(" ");
				for(int j=1;j<lineAndStations.length;j++) {
					if(lineAndStations[j].equals(str1)){
						flag1 = 1;
                	}	
					if(lineAndStations[j].equals(str2)){
						flag2 = 1;
                	}
            	}
            }
			if(flag1==0) {
				System.out.println("起始站点不存在!");
				throw new Exception();
			}
				
			if(flag2==0) {
				System.out.println("目的站点不存在!");
				throw new Exception();
			}
             
//          获取最短路径
			setAllLines(str4);
			getPath(str6,str1,str2);
			for(int i=0;i<getFile(str6).size();i++){
				getFilePrint(str6,i);
            }
		}
		else {
			System.out.println("查询地铁线路,请按照格式输入如:java Subway -b 苹果园 雍和宫 -map Subway.txt -o Routine.txt");
			throw new Exception();
		}
	}
  • Dijkstra.java

Dijkstra算法,参考链接:https://juejin.im/entry/589049ea0ce4630056dc74d6

//	  计算最短路径(相邻站点距离默认都为1)
     public static Path getShortestPath(Station startStation, Station endStation) { 
    	 //添加起始站点
    	 if (!outList.contains(startStation)) {
    		 outList.add(startStation);
         }
    	  
        //第一次用起始站点的相邻站点初始化
         if (path.isEmpty()) {
        	 List<Station> allLinkStations = Station.getAllLinkStations(startStation);
        	 for (Station station : allLinkStations) {
        		 Path p = new Path();
        		 p.setStartStation(startStation);
        		 p.setEndStation(station);
        		 int distance = 1;//起始站点距离初始为1
                  
        		 p.setDistance(distance);
        		 p.getAllPassStations().add(station);//起始站点已经分析过
        		 path.put(station, p);
            }
        }
          
        //获取下一站点
        int d = Integer.MAX_VALUE;
      	Station nextStation = null;
      	Set<Station> stations = path.keySet();
      	for (Station s:stations) {
      		if (outList.contains(s)) {
      			continue;
              }
      		Path p = path.get(s);
      		if (p.getDistance()<d) {
      			d = p.getDistance();
      			nextStation = p.getEndStation();
              }
          }

        if (nextStation.equals(endStation)) {
        	return path.get(nextStation);
        }
          
        List<Station> nextLinkStations = Station.getAllLinkStations(nextStation);
        for (Station station:nextLinkStations) {	  
        	// 已经分析过的station,不再做任何经分析
        	if (outList.contains(station)) {
        		continue;
            }
            //每增加一个站点,距离+1
        	int distance = path.get(nextStation).getDistance()+1;

        	List<Station> allPassStations = path.get(nextStation).getAllPassStations();
        	Path p = path.get(station);
        	if (p != null) {
        		//已经计算过到此station的距离比较出最小的距离
        		if (p.getDistance()>distance) {
        			p.setDistance(distance);
        			//重置start到各站的最短路径
        			p.getAllPassStations().clear();
        			p.getAllPassStations().addAll(allPassStations);
        			p.getAllPassStations().add(station);
                }
            } 
            
        	else {//还没有计算过到此station的距离
        		p = new Path();
        		p.setDistance(distance);
        		p.setStartStation(startStation);
        		p.setEndStation(station);

        		p.getAllPassStations().addAll(allPassStations);
        		p.getAllPassStations().add(station);
            }
        	path.put(station, p);
        }
        outList.add(nextStation);
        getShortestPath(startStation, endStation);//重复计算,往外面站点扩展
        
        return path.get(endStation);
     }

四、测试分析

测试输出结果以及抛出异常

  • 1、获取地铁线路图。
    每条线路之间空行。
java Subway -map Subway.txt

1号线 苹果园 古城 八角游乐园 八宝山 玉泉路 五棵松 万寿路 公主坟 军事博物馆 木樨路 南礼士路 复兴门 西单 天安门西 天安门东 王府井 东单 建国门 永安里 国贸 大望路 四惠 四惠东

2号线 西直门 积水潭 鼓楼大街 安定门 雍和宫 东直门 东四十条 朝阳门 建国门 北京站 崇文门 前门 和平门 宣武门 长椿街 复兴门 阜成门 车公庄 
……

当格式错误:

java Subway -ma Subwa.txt

查询地铁线路图,请按照格式输入如:java Subway -map Subway.txt

在以下两种输入中,都做了对此情况的相应的输出。

当文件不存在时:

java Subway -map Subwa.txt

Subwa.txt文件不存在!

在以下两种输入中,都做了对此情况的相应的输出。

  • 2、获取指定地铁线路。
java Subway -a 1号线 -map Subway.txt -o Station.txt

1号线 苹果园 古城 八角游乐园 八宝山 玉泉路 五棵松 万寿路 公主坟 军事博物馆 木樨路 南礼士路 复兴门 西单 天安门西 天安门东 王府井 东单 建国门 永安里 国贸 大望路 四惠 四惠东

当指定线路不存在时:

java Subway -a 20号线 -map Subway.txt -o Station.txt

不存在此线路!
  • 3、获取起始站点到目的站点最短线路。

无需换乘:

java Subway -b 苹果园 五棵松 -map Subway.txt -o Routine.txt

需乘坐5个站点。
1号线:
苹果园
古城
八角游乐园
八宝山
玉泉路
五棵松

需换乘:

java Subway -b 苹果园 雍和宫 -map Subway.txt -o Routine.txt

需乘坐17个站点。
1号线:
苹果园
古城
八角游乐园
八宝山
玉泉路
五棵松
万寿路
公主坟
军事博物馆
(同站换乘)9号线:
白堆子
白石桥南
国家图书馆
(同站换乘)4号线大兴线:
动物园
西直门
(同站换乘)2号线:
积水潭
鼓楼大街
安定门
雍和宫

当起始站点与目的站点一样时:

java Subway -b 苹果园 苹果园 -map Subway.txt -o Routine.txt

起始站点与目的站点相同,请重新输入!

当起始站点不存在:

java Subway -b 西湖 苹果园 -map Subway.txt -o Routine.txt

起始站点不存在!

当目的站点不存在:

java Subway -b 苹果园 西湖 -map Subway.txt -o Routine.txt

目的站点不存在!

五、个人小结

在完成个人项目的过程中,尽管与计划有所偏差,但还是很有收获的。从本次实验中,我觉得理清思路很重要,整个工程需要一个清晰的结构。不论是每一个java文件之间的结构,还是每一个函数之间的结构,还是数据存储的结构都十分重要,方便调用,也方便测试和修改。

另外,在命令行中出现过显示乱码的问题,可以用这一句解决:chcp 936。

地铁出行线路规划比较贴近我们的生活,我们只是用一个较为简单的方式,将线路查询功能模拟了出来。但我所做的这个项目还有不够完善的地方,主要是我没有合理分配好时间,只完成了基本功能,之后希望能做好结构和功能界面等方面的完善。

posted @ 2019-10-15 04:40  31701031蒋好  阅读(261)  评论(0编辑  收藏  举报