地铁线路项目学习记录
github地址:https://github.com/zky320/Subway
使用的命令行语句
1. 获取对应的自定义地铁文件(命名为 subway.txt)
java -cp .:* subway -map subway.txt
2. 获取指定铁路线路的所有站点名称,并存入指定文件
java -cp .:* subway -a 1号线 -map subway.txt -o station.txt
输出文件格式:
1号线:
······
洪湖里
西站(可转6号线)
西北角
······
3. 获取起点站和终点站的最短路径,并存入指定文件
java -cp .:* subway -b 洪湖里 复兴路 -map subway.txt -o routine.txt
输出文件格式:
3
洪湖里
西站
6号线
复兴路
Tips:java文件中需要调用jar包,命令行中的格式为 java -cp .:A.jar B ,而 java -cp .:* subway 可以调用当前文件夹中的所有jar包。
测试用例(最短路径):例子
1. 在同一条线路(的头)上,不经过任何转站点:刘园,洪湖里
2. 在同一条线路(的尾)上,不经过任何转站点:市民广场,东海路
3. 在同一条线路的中间,不经过任何转站点:北竹林,新开河
4. 起点和终点都是转站点(在同一条线上):西站,下瓦房
5. 起点和终点都是转站点(不在同一条线上):西站,张兴庄
6. 起点为转站点,终点非转站点:西站,一号桥
7. 起点非转站点,终点为转站点(中途包括逆向):李楼,直沽
8. 起点和终点都非转站点:天泰路,和平路
9. 5号线和6号线特殊交叠,起点和终点都在5号线和6号线上:肿瘤医院,文化中心
10. 5号线和6号线特殊交叠,起点和终点都不在5号线和6号线上,但途经(不发生转站):西南楼,体育中心
11. 5号线和6号线特殊交叠,起点和终点都不在5号线和6号线上,但途经(发生转站):西南楼,水上公园东路
部分错误提示
1. 命令错误
2. 自定义地铁文件错误
3. 无指定地铁线路
4. 起点终点相同
5. 琐碎的输入错误或命令错误
代码实现
使用编程语言:java
1. 读取对应的自定义文件:
自定义文件格式:
6 ->地铁线路数
15 ->转站数
1 23 ->地铁线路名 该地铁线路含有多少站点
刘园 0 ->站点名 转站(不转站为0,转站则为转站的地铁线路名)
西横堤 0
果酒厂 0
本溪路 0
勤俭道 0
洪湖里 0
西站 6
······
【一开始为三元组(地铁线路名,地铁站名,转站线路号),开始写代码的时候发现这样的格式存储起来不太方便,改成了二元组(地铁站名,转站线路号)】
存储数据方式:Json对象+Json数组
{ "1号线": [ { "siteName":"刘园" , "transfer":"0" }, { "siteName":"西横堤" , "transfer":"0" }, { "siteName":"果酒厂" , "transfer":"0" }, ........ ] } { "2号线": [ { "siteName":"曹庄" , "transfer":"0" }, { "siteName":"卞兴" , "transfer":"0" }, { "siteName":"芥园西道" , "transfer":"0" }, ........ ] } { ........ }
读取文件并存储数据代码如下:
1 public static void readtxt(String txt){ 2 int mark=0; 3 int nn=0; //用于添加subwayN,用于后期读取JsonArray来创建图 4 int subwaynum=0; 5 int subwaysum=0; 6 JSONArray jArr = null; 7 String txtname = txt; 8 9 try (FileReader reader = new FileReader(txtname); 10 BufferedReader br = new BufferedReader(reader) // 建立一个对象,它把文件内容转成计算机能读懂的语言 11 ) { 12 String line; 13 while ((line = br.readLine()) != null) { 14 String[] str=line.split(" "); 15 Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$"); 16 if(str.length==1&&mark==0){ //说明是第一行,记录地铁线路有几条 17 num=Integer.valueOf(str[0]); 18 subwayN=new String[num]; //将几号线的名字都存起来 19 mark++; 20 // System.out.println(num); 21 } 22 else if(str.length==1&&mark!=0){ 23 trannum=Integer.valueOf(str[0]); 24 // System.out.println(trannum); 25 } 26 else if(pattern.matcher(str[0]).matches()&&pattern.matcher(str[1]).matches()){ //说明是描述线路的两个数字 27 subwaynum=Integer.valueOf(str[0]); 28 subwaysum=Integer.valueOf(str[1]); 29 subwayN[nn++]=str[0]+"号线"; 30 jArr = new JSONArray(); 31 // System.out.println(subwaynum+" "+subwaysum); 32 } 33 else { //正常站点名称 34 JSONObject jobj = new JSONObject(); 35 try { 36 jobj.put("siteName", str[0]); 37 jobj.put("transfer", str[1]); 38 39 } catch (JSONException e) { 40 e.printStackTrace(); 41 } 42 jArr.add(jobj); 43 flag++; 44 if(flag==subwaysum){ 45 all.put(subwaynum+"号线",jArr); 46 flag=0; 47 } 48 } 49 } 50 if(flag==num){ 51 all.put(subwaynum+"号线",jArr); 52 } 53 // System.out.println(all); 54 // System.out.println(all.getJSONArray("1号线").getJSONObject(0).getString("siteName")); 55 } catch (IOException e) { 56 e.printStackTrace(); 57 } 58 }
2. 输出地铁线路所有地铁站名称:
将Json对应对象中的数组读出,并全部输出,代码如下:
1 public static void writeallstation(String subwaynum,String txt) { 2 JSONArray Jarray = all.getJSONArray(subwaynum); 3 try { 4 File writeName = new File(txt); // 相对路径,如果没有则要建立一个新的output.txt文件 5 writeName.createNewFile(); // 创建新文件,有同名的文件的话直接覆盖 6 try (FileWriter writer = new FileWriter(writeName); 7 BufferedWriter out = new BufferedWriter(writer) 8 ) { 9 out.write(subwaynum+":\n"); 10 for(int i=0;i){ 11 // System.out.print(Jarray.getJSONObject(i).getString("siteName")); 12 out.write(Jarray.getJSONObject(i).getString("siteName")); 13 String num=Jarray.getJSONObject(i).getString("transfer"); 14 if(!num.equals("0")){ 15 if(num.length()==1){ 16 // System.out.println("(可转"+num+"号线)"); 17 out.write("(可转"+num+"号线)\n"); 18 } 19 else{ 20 // System.out.println("(可转"+num.charAt(0)+"号线和"+num.charAt(1)+"号线)"); 21 out.write("(可转"+num.charAt(0)+"号线和"+num.charAt(1)+"号线)\n"); 22 } 23 } 24 else{ 25 // System.out.println(); 26 out.write("\n"); 27 } 28 } 29 out.flush(); // 把缓存区内容压入文件 30 } 31 } catch (IOException e) { 32 e.printStackTrace(); 33 } 34 }
3. 输出最短路径:
将转站节点(一共15个)连接成图,之后将起点站和终点站加入图中,中途出现各种特殊情况,需要将加入点连入图中后删除原本已有的另一条边。在建立图的过程中,一一记录节点在图中的序号,连成完整的无向图后使用 Dijstra 算法取其最短路径。在输出起点到终点的地铁路线时,需要输出转站情况,所以再这之前,将各条地铁线路上包含的图中节点(在图中的序号)进行了记录,当判断改站为转站点时,需检查改点与下一个节点同时出现在哪一条地铁线路中,若改号线路与当前不是同一条,则输出转站情况。
建图过程中做了数据记录以参照
【 过程中本来想换成将所有站点(一共141个)连成图,之后直接使用 Dijstra 算法取其最短路径。但转念一想觉得原理相同,最终还是选择了原本的方法,写到后期有点后悔,因为各种繁琐 】
最短路径 Dijstra 算法代码如下:
1 public static String dijkstra(int start, int end) { 2 // 初始化,第一个顶点求出 3 int n=record.size(); 4 final int M = Integer.MAX_VALUE; 5 int[] shortest = new int[n]; //存放从start到其他节点的最短路径 6 boolean[] visited = new boolean[n]; //标记当前该顶点的最短路径是否已经求出,true表示已经求出 7 shortest[start] = 0; 8 visited[start] = true; 9 10 //存放从start到其他各节点的最短路径 11 String[] path = new String[n]; 12 for(int i = 0; i < n; i++){ 13 path[i] = new String(start + "->" + i); 14 } 15 for(int count = 0; count != n-1; count ++){ 16 //选出一个距离初始顶点最近的为标记顶点 17 int k = M; 18 int min = M; 19 for(int i =0; i< n ; i++){//遍历每一个顶点 20 if( !visited[i] && graph[start][i] != M){ //如果该顶点未被遍历过且与start相连 21 if(min == -1 || min > graph[start][i]){ //找到与start最近的点 22 min = graph[start][i]; 23 k = i; 24 } 25 } 26 } 27 //正确的图生成的矩阵不可能出现K== M的情况 28 if(k == M) { 29 System.out.println("the input map matrix is wrong!"); 30 return null; 31 } 32 shortest[k] = min; 33 visited[k] = true; 34 //以k为中心点,更新start到未访问点的距离 35 for (int i = 0; i < n; i++) { 36 if (!visited[i] && graph[k][i] != M) { 37 int callen = min + graph[k][i]; 38 if (graph[start][i] == M || graph[start][i] > callen) { 39 graph[start][i] = callen; 40 path[i] = path[k] + "->" + i; 41 } 42 } 43 } 44 } 45 46 // System.out.println("从"+start+"出发到"+end+"的最短路径为:"+path[end]); 47 48 return path[end]; 49 }
总结
总体来说,顺利完成要求,但方法很繁琐,主要还是开始时考虑得不到位,写到中途又不愿意放弃前期花进去的时间。就当是一次教训,以后还是以此为戒。