地鐵線路最短路徑(實現版)


主要功能

根據所提供的地鐵線路圖(以北京地鐵為例)

計算指定兩站之間最短的乘車路線

地鐵線路信息保存在 data.txt 中,格式如下:

地鐵線路總數
線路名1 站名1 站名2 站名3 ...
線路名2 站名1 站名2 站名3 ...
線路名3 站名1 站名2 站名3 ......

最終項目給定兩個站點,輸出指定最短地鐵線路的所有站點。

實現語言

Java(利用eclipse)

實現算法

由於本題涉及的圖為無向無權圖,故使用

廣度優先搜索(BFS)算法

首先訪問一個頂點,然后是所有與其相鄰接的頂點,最后是所有與這些頂點鄰接的頂點。每個頂點會確保僅訪問一次

本題在遍歷節點時會同時記錄相關的途徑站點數量和換乘數量。

類職責划分

1.Model

用於保存地鐵線路信息的類

public class Route {
      private String rname;//線路名稱
      private ArrayList<String> route = new ArrayList<>();//線路所含站點
}

用於保存地鐵站點信息的類

public class Station {
      //讀取地鐵信息時修改
      private String sname;//站名
      private ArrayList<String> bTR = new ArrayList<>();//線路名
      private ArrayList<String> bs = new ArrayList<>();//鄰站(距離為1的站)
      //執行算法后更改
      private Station ps;//前一個站點
      private int dist;//距離(距起始站)
      private int transNum;//換乘數
      private int visited;//保存是否訪問
}

2.Control

讀取文件的類

public class ReadData {
      //保存所讀取的站點和線路信息,主函數中會引用
      private ArrayList<Station> station= new ArrayList<>();
      private ArrayList<Route> route= new ArrayList<>();
      //讀取文件的函數
      public ReadData(String fileName);
}

執行BFS算法的函數

public class BFS {
      //根據讀入的始發站進行BFS遍歷,得出最小路徑圖
      public ArrayList<Station> FindMin(String firstStation,ArrayList<Station> station);
      //根據讀入的終點站輸出從始發站到終點站的路徑
      public void shortPath(String endStation,ArrayList<Station> station);

3.Util

執行程序的主函數位置 public class Start

核心代碼

以下代碼為簡化代碼,完整代碼已上傳至Github

public ReadData(String fileName) 讀取 data.txt 中地鐵線路信息並保存

//線路部分
reader = new BufferedReader(new FileReader(file));
String tempString = null;
while((tempString = reader.readLine()) != null) {//讀取每行
      Route r = new Route();
      String[] t = tempString.split(" ");//每行分為多個字符串(包括線路名和站點名)
      r.setRname(t[0]);//首字符串為線路名
      for(int i=1;i<t.length;i++) {
            r.addRoute(t[i]);//剩下字符均為線路內站點名稱
      }
      route.add(r);//將結果添加至結果集
}
//站點部分
String rname = route.get(i).getRname();
ArrayList<String> l = route.get(i).getRoute();//臨時保存當前線路內部站點
//判斷環線(若線路首尾相同則為環線)
if(l.get(0).equals(l.get(l.size()-1))) {
      int flag=0;
      //優先處理首尾站點
      for(int k=0;k<station.size();k++) {
            //若該站點曾經錄入過,則添加相關信息
            //有環線的站點首尾相同且需添加兩個相鄰站
            if(station.get(k).getSname().equals(l.get(0))) {
                  station.get(k).setBTR(rname);//更新所屬線路
                  //更新鄰站
                  station.get(k).setBs(l.get(1));
                  station.get(k).setBs(l.get(l.size()-2));
                  flag=1;
                  break;
            }
      }
      //若該站點從未錄入,則新建
      if(flag==0) {
            Station s = new Station();
            s.setSname(l.get(0));//添加站點名稱
            s.setBTR(rname);//添加所屬線路
            //添加鄰站
            s.setBs(l.get(1));
            s.setBs(l.get(l.size()-2));
            station.add(s);
      }
}
//對於非環路,首尾兩站僅添加一個相鄰站點
//對於剩余站點,添加前后兩個站點即可
//(該部分代碼類似,在此省略)

public ArrayList<Station> FindMin(String firstStation,ArrayList<Station> station) 執行BFS遍歷,結果返回修改原站點信息

ArrayList<Station> result = new ArrayList<>();//結果集
//找到始發站
int fsIndex=-1;
for(int i=0;i<station.size();i++) {
      Station tmp = station.get(i);
      if(tmp.getSname().equals(firstStation)) {//名稱相同即找到
      fsIndex=i;
      break;
      }
}
//若未找到則報錯結束
if(fsIndex==-1) {
      System.out.println("未找到該起始站點");
      System.exit(0);//未找到則退出
      return station;
}
		
//執行算法
Queue<Station> queue = new LinkedList<>();//鏈表模擬隊列
station.get(fsIndex).setVisited(1);//標記訪問
queue.offer(station.get(fsIndex));//初始站點入隊列
		
int dist=0;//保存步數
while(!queue.isEmpty()) {
      Station tmpS = queue.remove();//移出隊列頭部
			
      if(dist==0) {//判斷是不是隊頭
            tmpS.setDist(dist);//存入步數
            dist++;
      }else {
            //判斷是否換乘
            dist=tmpS.getPs().getDist();
            tmpS.setDist(dist+1);
            dist++;
      }
      result.add(tmpS);//結果集增加
			
      ArrayList<Station> tmpBs = tmpS.getBs(station);
      for(int i=0;i<tmpBs.size();i++) {
            if(tmpBs.get(i).getVisited()==0) {//判斷是否訪問過
                  tmpBs.get(i).setPs(tmpS);//保存前置站點為當前站點
                  tmpBs.get(i).setVisited(1);//標記訪問
                  queue.offer(tmpBs.get(i));//若未訪問過則直接存入隊列
            }
      }
}
		
return result;//返回結果集

public void shortPath(String endStation,ArrayList<Station> station) 根據終點站查找線路規划並輸出

//找到終點站
int endIndex=-1;
for(int i=0;i<station.size();i++) {
      Station tmp = station.get(i);
      if(tmp.getSname().equals(endStation)) {
            endIndex=i;
            break;
      }
}
//若未找到則報錯結束
if(endIndex==-1) {
      System.out.println("未找到該終點站");
      System.exit(0);
      return ;
}

站點數據中保存了該站點的前置站點,用此特性配合堆棧可以逆序輸出(回到正常順序)

Stack<Station> stack = new Stack<>();//建立棧以實現逆序輸出
Station tmp = station.get(endIndex);//棧底為終點站
if(tmp.getDist()==0) {
      System.out.println("該站為始發站");
      return ;
}
int dist = tmp.getDist();//用於保存途經站點數
int transNum = 0;//用於保存換乘數
//逐步入棧
while(tmp.getPs()!=null) {
      stack.push(tmp);
      tmp=tmp.getPs();//更新為前置站點入棧
}
//判斷換乘
ArrayList<String> r1 =tmp.getBTR();
ArrayList<String> r2 = stack.peek().getBTR();
String now="";//用於保存當前線路
int flag=0;
//尋找當前線路
for(int i=0;i<r1.size();i++) {
      for(int j=0;j<r2.size();j++) {
            if(r1.get(i).equals(r2.get(j))) {
                  now=r1.get(i);
                  flag=1;
                  break;
            }
      }
      if(flag==1) {
            break;
      }
}
System.out.println("當前為:"+now);
System.out.print(tmp.getSname());
//逐步出棧
while(!stack.isEmpty()) {
      //判斷是否換乘
      r1 = tmp.getBTR();
      r2 = stack.peek().getBTR();
      flag=0;
      for(int i=0;i<r1.size();i++) {
            for(int j=0;j<r2.size();j++) {
                  //若兩個站點所共有的線路與當前線路不同,則為換乘線路
                  if(r1.get(i).equals(r2.get(j))&&(!now.equals(r1.get(i)))) {
                        now=r1.get(i);//更改當前線路
                        flag=1;
                        break;
                  }
            }
            if(flag==1) {
                  break;
            }
      }
      if(flag==1) {
            tmp=stack.peek();
            System.out.println();
            System.out.println("轉至:"+now);
            System.out.print(stack.pop().getSname());
            transNum++;
      }else {
            tmp=stack.peek();
            System.out.print("-->"+stack.pop().getSname());
      }
}
System.out.println();
dist--;
System.out.println("途徑站數"+dist);
System.out.println("換乘數"+transNum);

主函數

public static void main(String[] args) throws Exception{
      Scanner input = new Scanner(System.in);
		
      //讀取文件
      ReadData file = new ReadData(FILEPATH);
      //提取所保存的站點和線路信息
      ArrayList<Station> station= file.getStation();
      ArrayList<Route> route= file.getRoute();
		
      //輸出所有線路名稱
      for(int i=0;i<route.size();i++) {
            System.out.print(route.get(i).getRname()+" ");
      }
      System.out.println();
      //輸出菜單
      System.out.println("請選擇所需任務: 1.查詢線路 2.規划路線");
      //輸入
      int choose = input.nextInt();
      if(choose==1) {
            System.out.print("請輸入所需查詢的線路名: ");
            String name = input.next();
            int index=-1;
            for(int i=0;i<route.size();i++) {
                  if(route.get(i).getRname().equals(name)) {
                        index=i;
                        break;
                  }
            }
            if(index==-1) {
                 System.out.println("該線路名錯誤");
            }else {
                  System.out.println(route.get(index).getRname()+":"+route.get(index).allRoute());
            }
      }else if(choose==2) {
            BFS bfs = new BFS();
            System.out.print("請輸入始發站: ");
            String start = input.next();
            station=bfs.FindMin(start,station);
            System.out.print("請輸入終點站: ");
            String end = input.next();
            bfs.shortPath(end, station);
      }
		
}

測試用例

1.查找線路

(1)正常查詢

(2)線路不存在

2.規划路線

(1)始發站不存在

(2)終點站不存在

(3)始發站與終點站相同

(4)同線路(無換乘)查詢

(5)多線路(換乘)查詢

總結

·對模塊化的編程有了更多的體會,編碼能力還需提升,新的數據結構還需學習。
·第一次嘗試博客園上進行編寫,了解了基本的網絡平台編寫流程,有了新的體驗。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM