地铁线路项目学习记录
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<Jarray.size();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 }
总结
总体来说,顺利完成要求,但方法很繁琐,主要还是开始时考虑得不到位,写到中途又不愿意放弃前期花进去的时间。就当是一次教训,以后还是以此为戒。