1. 程式人生 > 實用技巧 >北京地鐵兩站點間最短路徑實現

北京地鐵兩站點間最短路徑實現

主要功能

提供一副如下所示的地鐵線路圖

地鐵線路文字儲存如下圖所示

計算指定兩站之間最短(最少經過站數)乘車路線;輸出指定地鐵線路的所有站點。以北京地鐵為例,地鐵線路資訊儲存在data.txt中,格式如下:

地鐵線路總數

線路名1 站名1 站名2 站名3 ...

線路名2 站名1 站名2 站名3 ...

線路名3 站名1 站名2 站名3 ......

需求分析

讀入給定的地鐵線路文字,獲取地鐵線路資訊。

處理輸入的命令,若兩站正確存線上路資訊中,程式需要根據命令給出兩站間的最短路徑,並輸出所需乘坐的地鐵線路以及地鐵經過的站點;若不存在這兩個站點,則給出錯誤提示。

實現語言

JAVA

實現演算法

Dijkstra

類職責劃分

為了清晰的表明各類用途,建立了兩個package,分別為model和util,在model包中存放Station類和Result類兩個模型,在util包中存放SubwayData類和Util類兩個主要的程式實現功能類。此外還有一個Main類,作為程式的入口。

1、Station類

儲存各個地鐵站的資訊,主要資訊有站名、站點線路、與站點相連的地鐵站。

2、Result類

儲存程式執行後的結果,主要資訊有起點站、終點站、經過站數、本站的上一站、本站線路、上一站到本站是否有換乘。

3、SubwayData類

在這個類中存放了地鐵的圖結構。本類實現了讀入地鐵線路資料並儲存、處理各個車站換乘等資訊的功能。

4、Util類

在這個類實現程式核心功能————利用dijkstra演算法尋找最短路徑。本類實現的其他功能還有生成乘車路徑、獲取地鐵線路所有站點。

5、Main類

這個類是程式的入口,實現了程式的簡單操作、提示功能,將以上四個類功能融合實現程式執行。

核心程式碼

完整程式碼已上傳到GitHub上:

https://github.com/Bazingaali/SE_Subway/

1、Station類


public class Station{
	private String name; //站名
	private ArrayList<String> subway = new ArrayList<String>();  //站點線路
	private ArrayList<Station> connect = new ArrayList<Station>();  //與站點相連的地鐵站
	
	public Station() {
		super();
		// TODO Auto-generated constructor stub
	}
	public Station(String name, String subway) {
        this.name = name;
        this.subway.add(subway);
    }

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public ArrayList<String> getSubway() {
		return subway;
	}
	public void setSubway(ArrayList<String> subway) {
		this.subway = subway;
	}
	public ArrayList<Station> getConnect() {
		return connect;
	}
	public void setConnect(ArrayList<Station> connect) {
		this.connect = connect;
	}
}

2、Result類

public class Result {
	private Station begin;  //起始站
    private Station end;   //終點站
    private int step;  //經過站數
    private Station stationbefore;  //本站的上一站
    private String linenumber;   //本站線路
    private int changeline;  //上一站到本站是否有換乘,0為無換乘,1為需換乘
	public Station getBegin() {
		return begin;
	}
	public void setBegin(Station begin) {
		this.begin = begin;
	}
	public Station getEnd() {
		return end;
	}
	public void setEnd(Station end) {
		this.end = end;
	}
	public int getStep() {
		return step;
	}
	public void setStep(int step) {
		this.step = step;
	}
	public Station getStationbefore() {
		return stationbefore;
	}
	public void setStationbefore(Station stationbefore) {
		this.stationbefore = stationbefore;
	}
	public String getLinenumber() {
		return linenumber;
	}
	public void setLinenumber(String linenumber) {
		this.linenumber = linenumber;
	}
	public int getChangeline() {
		return changeline;
	}
	public void setChangeline(int changeline) {
		this.changeline = changeline;
	} 
}

3、SubwayData類

public class SubwayData {
	public static LinkedHashSet<List<Station>> lineset = new LinkedHashSet<List<Station>>(); //儲存所有線路
	
	public SubwayData(String filename) throws IOException {
		File file = new File(filename);
		InputStreamReader reader = new InputStreamReader( new FileInputStream(file),"UTF-8"); //輸入字元格式為UTF-8,防止亂碼
        BufferedReader br = new BufferedReader(reader);
        
        String readtxt = "";
        readtxt = br.readLine();//在文件第一行表示共有幾條地鐵線路,獲取地鐵線路數量,為後面的迴圈做準備
        int linenumber = Integer.parseInt(readtxt);//linenumber中存放地鐵線路數
        
        for(int i = 0 ; i < linenumber ; i++) {  //往lineSet集合中新增各條地鐵線路資訊
        	List<Station> line = new ArrayList<Station>();//儲存各條地鐵線資訊
        	readtxt = br.readLine();
        	String[] lineinformation = readtxt.split(" "); 
        	String name = lineinformation[0];
        	for(int j = 1 ; j < lineinformation.length ; j++) {  //往line中新增各個站的資訊
        		int flag = 0;//識別符號,flag=1時處理結束,跳出迴圈
        		for(List<Station> l:lineset) {  //處理換乘站資訊
        			for(int k = 0  ; k < l.size() ; k++) {
        				if(l.get(k).getName().equals(lineinformation[j])) {  
        					ArrayList<String> line1 = l.get(k).getSubway();
        					line1.add(name);
        					l.get(k).setSubway(line1);
        					line.add(l.get(k));
        					flag=1;
        					break;
        				}
        			}
        			if(flag==1)
        				break;
        		}
        		if(j==lineinformation.length-1&&lineinformation[j].equals(lineinformation[1])) {  //處理環線資訊
        			line.get(0).getConnect().add(line.get(line.size()-1));//環線的第1站與最後一站相同,將首尾連線起來
        			line.get(line.size()-1).getConnect().add(line.get(0));
        			flag=1;
        		}
        		if(flag==0)
        			line.add(new Station(lineinformation[j],name));
        	}
        	for(int j = 0;j < line.size() ;j++) {  //處理每一個車站的相鄰車站
        		ArrayList<Station> connectStations=line.get(j).getConnect();
        		if(j==0) {//處理首站的相鄰車站
        			connectStations.add(line.get(j+1));
        			line.get(j).setConnect(connectStations);
        		}
        		else if(j==line.size()-1) {//處理終點站的相鄰車站
        			connectStations.add(line.get(j-1));
        			line.get(j).setConnect(connectStations);
        		}
        		else {//處理其他車站的相鄰車站
        			connectStations.add(line.get(j+1));
        			connectStations.add(line.get(j-1));
        			line.get(j).setConnect(connectStations);
        		}
        	}
        	lineset.add(line); 
        }
        br.close();
	}
}

4、Util類

public class Util {
	private static ArrayList<Station> analysised = new ArrayList<>();  //已經分析過的站點
	private static HashMap<Station, Result> resultmap = new HashMap<>();  //結果集
	
	private static List<String> getsameline(List<String> list1,List<String> list2) {//得到list1和list2中相同的線路
		List<String> sameline=new ArrayList<String>();
		for(String l1:list1) {
			for(String l2:list2) {
				if(l1.equals(l2))
					sameline.add(l1);
			}
		}
		return sameline;
	}
	
	private static Station getnextstation() {//得到下一個分析站點
        int min=66666;//儲存以本站出發各個節點到到達站的最小距離。首先將min設定為一個較大值,後續比較逐漸縮小
        Station nextstation = null;//下一個節點先置空節點
        Set<Station> stations = resultmap.keySet();
        for (Station station : stations) {
            if (analysised.contains(station)) {//首先判斷當前節點是否已經被分析過,若分析過,則跳過
                continue;
            }
            Result result1 = resultmap.get(station);//儲存結果
            if (result1.getStep() < min) {
                min = result1.getStep();
                nextstation = result1.getEnd();
            }
        }
        return nextstation;
    }
	
	public static Result dijkstrashortest(Station begin, Station end) {  //dijkstra演算法計算兩地鐵站中最短路徑
		for(List<Station> list:SubwayData.lineset) {  
			for(int k = 0 ; k < list.size() ; k++) {
                Result result = new Result();//初始化結果集
                result.setBegin(begin);
                result.setEnd(list.get(k));
                result.setStep(666666);
                result.setChangeline(0);
                resultmap.put(list.get(k), result);
			}
		}
		
		for(Station s:begin.getConnect()) {  //對結果集逐個賦值
			resultmap.get(s).setStep(1);
			resultmap.get(s).setStationbefore(begin);
			List<String> samelines = getsameline(begin.getSubway(),s.getSubway());
			resultmap.get(s).setLinenumber(samelines.get(0));
		}
		
		resultmap.get(begin).setStep(0);
		analysised.add(begin);
        Station nextstation = getnextstation(); //對下一個站點進行分析
        while(nextstation!=null) { //計算最短路徑
        	for(Station s:nextstation.getConnect()) {
        		if(resultmap.get(nextstation).getStep()+1<resultmap.get(s).getStep()) {  //更新最短路徑
        			resultmap.get(s).setStep(resultmap.get(nextstation).getStep()+1);
        			resultmap.get(s).setStationbefore(nextstation);
        			List<String> samelines = getsameline(nextstation.getSubway(),s.getSubway());
        			if(!samelines.contains(resultmap.get(nextstation).getLinenumber())) {  //判斷是否換乘
        				resultmap.get(s).setLinenumber(samelines.get(0));
        				resultmap.get(s).setChangeline(1);
        			}
        			else {
        				resultmap.get(s).setLinenumber(resultmap.get(nextstation).getLinenumber());
        			}
        		}
        	}
        	analysised.add(nextstation); //在已分析節點陣列中加上此站
        	nextstation = getnextstation();//得到下一站,繼續迴圈分析
        }
        return resultmap.get(end);
    }
	
	public static List<Station> getLineStation(String linename){  //獲取地鐵線路的所有站點
		int flag = 0;//識別符號,flag=1時處理結束,跳出迴圈
		for (List<Station> list:SubwayData.lineset) {
			flag = 0;
			for(Station station : list) {
				if(!station.getSubway().contains(linename))
					flag = 1;
			}
			if(flag == 0)
				return list;
		}
		return null;
	}
	
	public static List<String> getPath(Result r){//生成乘車路線
		List<String> path=new ArrayList<String>();//儲存乘車路線
		Stack<Station> stationtemp=new Stack<Station>();//利用棧儲存各站資訊
		Station s=r.getStationbefore();
		while(!s.equals(r.getBegin())) {
			stationtemp.push(s);
			s=resultmap.get(s).getStationbefore();
		}
		path.add("!!首先在" + r.getBegin().getName() + "乘坐" + resultmap.get(stationtemp.peek()).getLinenumber());
		path.add(r.getBegin().getName());
		while(!stationtemp.empty()) {
			if(resultmap.get(stationtemp.peek()).getChangeline()==1) {
				path.add("!!在本站換乘"+resultmap.get(stationtemp.peek()).getLinenumber());
				path.add(stationtemp.pop().getName());
			}
			else
				path.add(stationtemp.pop().getName());
		}
		if(r.getChangeline()==1) {
			path.add("!!在本站換乘"+r.getLinenumber());
			path.add(r.getEnd().getName());
		}
		else
		    path.add(r.getEnd().getName());
		return path;
	}
}

5、Main類

public class Main {
	public static void main(String[] args) throws IOException {
		Scanner scanner = new Scanner(System.in);
		new SubwayData("subwaydata.txt");
		
		System.out.print("請選擇查詢內容:1、查詢指定地鐵線路;2、查詢兩站間地鐵路徑  ");
		int nn = Integer.parseInt(scanner.next());
		if(nn==1) {
			System.out.print("請輸入需要查詢的地鐵線路  ");
			String searchline = scanner.next();
			List<Station> stations = Util.getLineStation(searchline);
			String put = "";
			if(stations==null)
				put = "該地鐵線路不存在";
			else {
				for(int i=0;i<stations.size();i++) {
					if(i==stations.size()-1&&stations.get(i).getConnect().contains(stations.get(0)))
						put = put + stations.get(i).getName() + "  !!" + searchline + "為環線" + "!!";
				    else
				    	put = put + stations.get(i).getName()+" ";
			    }
			}
			printout(put , "result.txt");
			System.out.print(put);
		}	
		else if(nn==2) {
			System.out.print("請輸入起始站與到達站,兩站中以空格分隔  ");
			String station1 = scanner.next();
			String station2 = scanner.next();
			
		   	Station begin = null;
		   	Station end = null;
		   	for(List<Station> list : SubwayData.lineset){
				for(int k=0 ;k<list.size() ;k++) {
	                if(list.get(k).getName().equals(station1)) {
	                	begin = list.get(k);
	                }
	                if(list.get(k).getName().equals(station2)) {
	                	end = list.get(k);
	                }
				}
			}
		   	String put = "";
		   	if(begin==null)
		   		put = "出發站不存在";
		   	else if (end==null)
		   		put = "到達站不存在";
		   	else {
		   		Result result = Util.dijkstrashortest(begin , end);
		   		List<String> path = Util.getPath(result);
		   		put = "從" + station1 + "到" + station2 + "最少經過"+ (result.getStep() + 1) + "站,以下為詳細乘車方案\n";
		   		for(String s:path)
		   			put = put + s + "\n";
		   	}
		   	printout(put,"result.txt");
			System.out.print(put);
		}
		return;
	}
	
	 public static void printout(String content,String path) throws IOException {
		 File file = new File(path);
	     if(!file.exists()){
	 	    file.createNewFile();
	     }
	     FileOutputStream outputStream = new FileOutputStream(file);
		    byte[]  bytes = content.getBytes("UTF-8");
		    outputStream.write(bytes);
		    outputStream.close();
	 }
}

測試用例

源程式執行時,會將輸出結果輸出至主目錄下的result.txt文件中,為方便測試結果展示,以下測試結果均在控制檯中輸出展示。

1、查詢存在的地鐵線路,輸出該條地鐵線路全線站點

若查詢地鐵線路為環線,則在輸出完站點後提示該線路為環線

2、查詢不存在的地鐵線路,則會提示錯誤

3、查詢兩站間的最短路徑(若兩站之間有多條相同步長的最短路徑,則為使用者展示換成次數最少的乘坐方式

3.1、兩站在同一條線路上(無需換乘)

3.2、兩站在不同線路上(需要換乘)

3.3、出發站點不存在

3.4、到達站點不存在

總結

經過本專案的磨鍊,我收穫了很多,也學會了很多有用的東西,以下是我對本次作業詳細的總結。
1、對於一個較複雜的軟體的開發步驟有了一定的瞭解
2、程式設計能力得到了一定的提升,對於java有了更深的理解,也發現自己還有很多不足,需要進行更深入的學習
3、第一次寫部落格,也是一種鍛鍊,在寫部落格的過程中也學到了很多