項目介紹:
提供一副地鐵線路圖,計算指定兩站之間最短(最少經過站數)乘車路線;輸出指定地鐵線路的所有站點。以北京地鐵為例,地鐵線路信息保存在data.txt中,格式如下:
地鐵線路總數
線路名1 站名1 站名2 站名3 ...
線路名2 站名1 站名2 站名3 ...
線路名3 站名1 站名2 站名3 ......
需求分析:
1、主要功能
(1)能實現地鐵信息的導入
(2)能夠保存各個站點的信息
(3)輸入起始站點和目標站點后計算出指定兩站之間最短乘車路線
(4)能輸出乘車線路經過的所有站點的信息
(5)能夠給出站點的換乘信息
(6)若有多個最短路線時優先選擇換乘少的線路
(7)若有多個最優解則全部輸出
2、實現語言
java
3、實現算法
Dijkstra算法
①基本思想:設置一個集合S存放已經找到最短路徑的頂點,S的初始狀態只包含源點v,對vi∈V-S,假設從源點v到vi的有向邊為最短路徑。以后每求得一條最短路徑v, …, vk,就將vk加入集合S中,並將路徑v, …, vk , vi與原來的假設相比較,取路徑長度較小者為最短路徑。重復上述過程,直到集合V中全部頂點加入到集合S中。
4、類職責划分(相關類的功能進行描述)
- Line類
用於保存線路信息
List<Station> line | 保存線路信息 |
- Station類
用於保存站點信息,線路名稱,測試站點到某一個目標站點所經過的所有站集合
String name | 站點名稱 |
String Linename | 線路名稱 |
Station prevsation | 在當前線路時,所在站點的前一個站 |
Station nextsation | 在當前線路時,所在站點的后一個站 |
Map<Station,LinkedHashSet<Station>> ordermap | 測試站點到某一個目標站點(key)所經過的所有站集合 |
- ReadFile類
基本功能時讀取文件“地鐵線路信息.txt”內容,儲存到Line,Station中,保存所有線路及該線路的站點信息,站點總數
Set<List<Station>> lineSet | 保存所有線路信息 |
totalStation | 保存站點總數 |
readText() | 函數用於讀取文件 |
- dijkstra類
用於計算最短路徑得出結果輸出
List<Station> getAllNearStations(Station station) | 函數用於得到與傳入參數所有相鄰的站點 |
Station getShortPath(Station station) | 函數用於得到參數到各個站的最短距離站 |
void result(Station s1,Station s2) | 函數用於得出最終結果並輸出 |
void main(String[] args) | 主函數用於輸入起始站點 |
5、核心代碼
Line類
public class Line { //集合保存線路信息 public static List<Station> line = new ArrayList<Station>(); public static List<Station> getLine() { return line; } public static void setLine(List<Station> line) { Line.line = line; } }
Station類
public class Station { //地鐵站名稱 private String name; //地鐵線路名 private String Linename; //在當前線路時,所在站點的前一個站 public Station prevsation; //在當前線路時,所在站點的后一個站 public Station nextsation; //測試站點到某一個目標站點(key)所經過的所有站集合 private Map<Station,LinkedHashSet<Station>> ordermap = new HashMap<Station,LinkedHashSet<Station>>(); public String getLinename() { return Linename; } public void setLinename(String linename) { Linename = linename; } public Station (String name){ this.name = name; } public Station() { // TODO Auto-generated constructor stub } public String getName() { return name; } public void setName(String name) { this.name = name; } //記錄當前測試站點到目標站點所經過的所有站點,並儲存 public LinkedHashSet<Station> getAllPassedStations(Station station) { if(ordermap.get(station) == null){ LinkedHashSet<Station> set = new LinkedHashSet<Station>(); set.add(this);//this為當前類的實例 ordermap.put(station, set); } return ordermap.get(station); } public Map<Station, LinkedHashSet<Station>> getOrderMap() { return ordermap; } @Override public boolean equals(Object obj) { if(this == obj){ return true; } else if(obj instanceof Station){ Station s = (Station) obj; if(s.getName().equals(this.getName())){ return true; } else { return false; } } else { return false; } } @Override public int hashCode() { return this.getName().hashCode(); } }
ReadFile類
public class ReadFile { //集合保存所有線路及該線路的站點信息 public static Set<List<Station>> lineSet = new HashSet<List<Station>>(); //所有站點數 public static int totalStation = 0; //文件讀取函數 public static void readText(){ Station Prevstation = new Station(); try { File file=new File("地鐵線路信息.txt"); //判斷文件是否存在 if(file.isFile() && file.exists()){ InputStreamReader reader = new InputStreamReader(new FileInputStream(file)); BufferedReader bufferedReader = new BufferedReader(reader); String stationline = null; int flag = 0; while((stationline = bufferedReader.readLine()) != null){ String[] tmp = stationline.split(" ");//分割符號,將空格置空 List<Station> Linestations = new ArrayList<Station>();//設置一個鏈表保存線路信息 for(int j=1;j<tmp.length;j++) { Station station = new Station(); station.setLinename(tmp[0]);//將每一行的第一個字符串存入線路名 station.setName(tmp[j]);//保存站點名稱 Linestations.add(station);//保存線路信息 totalStation++;//計算總站點數 if(flag != 0) {//當flag不為零時,存在前后站點 station.prevsation = Prevstation; Prevstation.nextsation = station; } Prevstation = station;//記錄從零開始記錄的站點 flag++; } lineSet.add(Linestations);//添加為線路集 } // 關閉文件 reader.close(); bufferedReader.close(); } else System.out.println("找不到指定的文件!"); } catch (Exception e) { System.out.println("文件內容出錯!"); e.printStackTrace(); } } }
dijkstra類
public class dijkstra { private List<Station> List1 = new ArrayList<Station>();//保存已經遍歷過的站點 //得到與傳入參數所有相鄰的站點 private List<Station> getAllNearStations(Station station){ List<Station> linkedStaions = new ArrayList<Station>(); for(List<Station> line : ReadFile.lineSet){//遍歷所有線路 if(line.contains(station)){//當前線路中包含參數站點 Station s = line.get(line.indexOf(station));//取出當前線路上的該站點的值 if(s.prevsation != null){ linkedStaions.add(s.prevsation);//得到當前線路的該站點的前一個站點 } if(s.nextsation != null){ linkedStaions.add(s.nextsation);//得到當前線路的該站點的后一個站點 } } } return linkedStaions; } //得到參數到各個站的最短距離每一站距離為1 private Station getShortPath(Station station){ int min = 10000;//定義一個較大的數用做比較 Station rets = null; for(Station s :station.getOrderMap().keySet()){//遍歷當前測試站點到所有站點 if(List1.contains(s)){ continue; } LinkedHashSet<Station> set = station.getAllPassedStations(s);//當前測試站點到未遍歷站點的路徑 if(set.size() < min){ min = set.size();//取路徑最短的哪一個 rets = s; } } return rets;//返回最近的這一個站點 } //計算最短經過路徑 public void result(Station s1,Station s2){ int flag = 0; String tempname = ""; String linename = ""; int flag1=0; //遍歷所有站點,判斷輸入的站名名稱是否存在 for(List<Station> line : ReadFile.lineSet){ if(line.contains(s1)){ flag1=1; } } if(flag1==0) { System.out.println("輸入站點不存在,請重新輸入!"); return ; } int flag2=0; for(List<Station> line : ReadFile.lineSet){ if(line.contains(s2)){ flag2=1; } } if(flag2==0) { System.out.println("輸入站點不存在,請重新輸入!"); return ; } //當所有站點都遍歷完 if(List1.size() == ReadFile.totalStation){ System.out.println("起點:"+s1.getName()+" 終點站:"+s2.getName()+",共經過"+(s1.getAllPassedStations(s2).size()-1)+"站"); int p=0; for(Station station : s1.getAllPassedStations(s2)){//輸出遍歷s1到s2經過的路徑 p++; if(flag == 0) { tempname = station.getName(); } else if(flag == 1) { linename=station.getLinename(); System.out.print(station.getLinename()+tempname+"->"); } else { if(!station.getLinename().equals(linename)) {//換乘線路換行輸出 linename=station.getLinename(); System.out.println(); } if(p<s1.getAllPassedStations(s2).size()-1) { System.out.print(station.getLinename()+" "+station.getName()+"->"); } else { System.out.print(station.getLinename()+" "+station.getName()); } } flag++; } return; } if(!List1.contains(s1)){//將當前測試站點儲存在已遍歷鏈表 List1.add(s1); } //如果起點的測試站點的OrderMap為空,則第一次用該站的相鄰站點初始化 if(s1.getOrderMap().isEmpty()){ List<Station> Linkedstations = getAllNearStations(s1); for(Station s : Linkedstations){ s1.getAllPassedStations(s).add(s); } } Station psation = getShortPath(s1);//獲取距離起點站s1最近的一個站 if(psation == s2){ System.out.println("找到目標站點:"+s2+",共經過"+(s1.getAllPassedStations(s2).size()-1)+"站"); for(Station station : s1.getAllPassedStations(s2)){ System.out.print(station.getName()+"->"); } return; } for(Station station1 : getAllNearStations(psation)){ if(List1.contains(station1)){ continue; } int shortestPath = (s1.getAllPassedStations(psation).size()-1) + 1;//計算所需距離 if(s1.getAllPassedStations(station1).contains(station1)){ //如果s1已經計算過到此station1的經過距離,那么比較出最小的距離 if((s1.getAllPassedStations(station1).size()-1) > shortestPath){ //重置S1到周圍各站的最小路徑 s1.getAllPassedStations(station1).clear(); s1.getAllPassedStations(station1).addAll(s1.getAllPassedStations(psation)); s1.getAllPassedStations(station1).add(station1); } } else { //如果s1還沒有計算過到此station1的經過距離 s1.getAllPassedStations(station1).addAll(s1.getAllPassedStations(psation)); s1.getAllPassedStations(station1).add(station1); } } List1.add(psation); result(s1,s2);//重復計算,往外面站點擴展 } public static void main(String[] args) { dijkstra subway = new dijkstra(); ReadFile.readText(); Scanner input =new Scanner(System.in); System.out.print("請輸入起點:"); String str1 = input.next(); System.out.print("請輸入終點:"); String str2 = input.next(); subway.result(new Station(str1), new Station(str2)); } }
6、測試用例
測試一:站點輸入有誤
測試二:不用換乘
測試三:不用換乘
測試四:換乘
測試五:換乘
7、總結
1.沒有完成需求分析中的選擇換乘少的路線和輸出全部最優解,要進一步完善思考。
2.寫代碼的過程中,意識到自己對於最短路徑算法的理解不透徹,完成過程中查找了很多資料。
3.認識到自己的不足要多多鍛煉努力。
完整代碼已上傳https://github.com/tongyao063/SubWay2.git