主要功能
根據所提供的地鐵線路圖(以北京地鐵為例)
計算指定兩站之間最短的乘車路線。
地鐵線路信息保存在 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)多線路(換乘)查詢
總結
·對模塊化的編程有了更多的體會,編碼能力還需提升,新的數據結構還需學習。
·第一次嘗試博客園上進行編寫,了解了基本的網絡平台編寫流程,有了新的體驗。