1. 程式人生 > 實用技巧 >地鐵線路最短路徑問題最終實現

地鐵線路最短路徑問題最終實現

*github地址:https://github.com/zucc31801061/31801061subway

1.主要功能

給出一副地鐵線路圖,例如:

將其轉化為格式如下的.txt檔案:

  線路1 站名1 站名2 ... 站名n
  線路2 站名1 站名2 ... 站名n
  線路n 站名1 站名2 ... 站名n

例如:

則可實現以下功能:

1、輸入起點站與終點站可以查詢之間最佳搭乘方法(最短路徑)

2、可以檢視所有線路,並可以通過選擇線路檢視該線路內的所有站點

2.實現語言

Java

3.實現演算法

Dijkstra演算法

  Dijkstra演算法是從起點開始,查詢所有連通點中距離最近的中轉點,然後再以中轉站為起點來尋找下一個最近的連通點,直到遍歷到終點結束。

  由於地鐵線路站與站之間的連通預設為無權圖,故預設每個站點之間的距離為1

4.類職責劃分

1.Station類

用於儲存站點資訊,各變數都擁有set和get方法

變數宣告

private int staId;//站點編號(唯一)
private String line;//該站點所屬線路名
private String staName;//站點名

方法宣告(具體不做解釋,程式碼見github,下同)

public Station(int staId, String staName, String line);//定義Station
public String getLine();//返回線路名
public void setLine(String line);//修改線路名
public int getStaId();//返回站點編號
public void setStaId(int staId);//修改站點編號
public String getStaName();//返回站點名
public void setStaName(String staName);//修改站點名

2.ReadTxt類

用於讀取.txt檔案並存儲到對應的儲存結構中

變數宣告

static Set<String> lines = null;//地鐵線路名
static List<Station> allStations = null;//所有站點資訊(編號,站點名,所屬線路),其中換乘站應有多條資訊
static Map<String, List<Station>> map = new LinkedHashMap<>();//線路資訊,線路名,線路所含站點資訊
static int[][] graph = null;//站點間的連通訊息

方法宣告

public ReadTxt(String fileName);//執行下面兩個方法並建立Main視窗
private void readFileContent(String fileName);//讀取檔案並存入相應儲存結構
private void initialize();//初始化,給graph賦值為當前線路圖的連通圖

3.Dijkstra類

計算所有站點之間的最短路徑並將其路徑儲存

變數宣告

private Queue<Integer> visited = null;//已訪問的站點
int[] length = null;//路徑長度
private Map<String, List<Station>> map = null;
static String nowLine = null;//當前地鐵線
static String[] nextLine = null;//當前節點的下一換乘地鐵線
static HashMap<Integer, String> route = null;//路徑
static List<Station> list = null;

方法宣告

public Dijkstra(int len, Map<String, List<Station>> map);//初始化賦值
private int getIndex(Queue<Integer> q, int[] length);//獲取未訪問點中距離源點最近的點
public String shortestroute(int v, int dest);//根據起始站點id初始化輸出
public String dijkstra(int[][] weight, List<Station> list, int v, int dest);//計算最短路徑

4.FrmMain類

程式的UI介面

5.SubwayStarter類
程式入口

5.核心程式碼

1.讀取檔案的方法

private void readFileContent(String fileName) {
	File file = new File(fileName);
	InputStreamReader read = null;
	BufferedReader br = null;
	lines = new LinkedHashSet<>();
	allStations = new LinkedList<>();
	List<Station> line_stations = null;
	try {
		if(file.isFile() && file.exists()){//判斷檔案是否存在
			read = new InputStreamReader(new FileInputStream(file), "UTF-8");
			br = new BufferedReader(read);
			String tempStr = null;
			int staId = 0;//站點編號
			while ((tempStr = br.readLine())!= null) {//使用readLine方法,一次讀一行,判斷是否讀到檔案末尾
				line_stations = new LinkedList<>();
				String[] message = tempStr.split("\\s+");//分割線路站點資訊
				lines.add(message[0]);//讀取地鐵線路名
				for(int i=1; i<message.length; i++) {
					Station station = new Station(staId++, message[i], message[0]);//建立站點物件
					allStations.add(station);
					line_stations.add(station);//將站點加入當前線路
				}
				map.put(message[0], line_stations);//將路線加入map
			}
			br.close();
		}
	        else{
            	    System.out.println("找不到指定的檔案");
                }
	} catch (IOException e) {
		System.out.println("讀取檔案內容出錯");
		e.printStackTrace();
	}
}    

2.初始化graph的方法

private void initialize() {
	int count = 0;//站點數量
	allStations = new LinkedList<>();
	
	for (String st : map.keySet()) {//遍歷map中地鐵線路
		count += map.get(st).size();
		for (Station s : map.get(st)) {//遍歷當前線路站點
			allStations.add(s);
		}
	}
	graph = new int[count][count];
	for (int i = 0; i < count; i++) {
		for (int j = 0; j < count; j++)
			graph[i][j] = -1;
	}
	for (int i = 0; i < count; i++) {
		String name = allStations.get(i).getStaName();
		graph[i][i] = 0;
		for (Station s : allStations) {
			if (s.getStaName().equals(name)) {//可換乘站和環線起止站的連通
				int id = s.getStaId();
				if (id - 1 >= 0) {
					if (allStations.get(id - 1).getLine().equals(allStations.get(id).getLine())) {
						graph[i][id - 1] = 1;
						graph[id - 1][i] = 1;
					}
				}
				if (id + 1 < count) {
					if (allStations.get(id + 1).getLine().equals(allStations.get(id).getLine())) {
						graph[i][id + 1] = 1;
						graph[id + 1][i] = 1;
					}
				}
			}
		}
	}
}

3.Dijkstra演算法

public String dijkstra(int[][] weight, List<Station> list, int v, int dest) {
	Dijkstra.list = list;
	//路徑HashMap route;
	route = new HashMap<Integer, String>();
	for (int i = 0; i < list.size(); i++) {
		route.put(i, "");
	}
	//初始化路徑長度陣列length
	for (int i = 0; i < list.size(); i++) {
		route.put(i, route.get(i) + "" + list.get(v).getStaName());
		if (i == v) {
			length[i] = 0;
		}
		//連通站點
		else if (weight[v][i] != -1) {
			length[i] = weight[v][i];
			//獲取當前站點所屬的線路
			nowLine = list.get(v).getLine();
			StringBuffer sbf = new StringBuffer();
			for (Station s : map.get(nowLine)) {
				sbf.append(s.getStaName());
			}
			//起點站和下一站點是否屬於同一地鐵線
			if (sbf.indexOf(list.get(i).getStaName()) != -1) {
				route.put(i, route.get(i) + "\n\t-->" + list.get(i).getStaName());
				nextLine[i] = nowLine;
			}
			else {
				route.put(i, route.get(i) + "\n-->換乘" + list.get(i).getLine() + "\n\t-->" + list.get(i).getStaName());
				nextLine[i] = list.get(i).getLine();
			}
		}
		//不連通
		else
			length[i] = Integer.MAX_VALUE;
	}
	visited.add(v);
	//迭代尋找最優線路
	while (visited.size() < list.size()) {
		int k = getIndex(visited, length);// 獲取未訪問點中距離源點最近的點
		visited.add(k);
		if (k != -1) {
			for (int j = 0; j < list.size(); j++) {
				if (weight[k][j] != -1) {// 判斷k點能夠直接到達的點
					//通過遍歷各點,比較是否有比當前更短的路徑,有的話,則更新length,並更新route。
					if (length[j] > length[k] + weight[k][j]) {
						length[j] = length[k] + weight[k][j];
						nowLine = nextLine[k];
						StringBuffer sbf = new StringBuffer();
						for (Station s : map.get(nowLine)) {
							sbf.append(s.getStaName());
						}
						//判斷到下一站點是否需要換乘
						if (sbf.indexOf(list.get(j).getStaName()) != -1) {
							route.put(j, route.get(k) + "\n\t-->" + list.get(j).getStaName());
							nextLine[j] = nowLine;
						}
						else {
							StringBuffer temp = new StringBuffer();
							for (String str : map.keySet()) {
								temp = new StringBuffer();
								for (Station s : map.get(str)) {
									temp.append(s.getStaName());
								}
								if (temp.indexOf(list.get(j).getStaName()) != -1 && temp.indexOf(list.get(k).getStaName()) != -1) {
								  route.put(j,route.get(k) + "\n-->換乘" + str + "\n\t-->" + list.get(j).getStaName());
								  nextLine[j] = str;
                                      } } } } } } } } visited.clear(); return this.shortestroute(v, dest); }

6.測試用例

1.初始化介面

2.完整執行結果

3.檢視對應線路的站點

4.部分錯誤提示框

7.個人總結

一開始以為要把環線的首尾站和換乘站點區分開來,花了很多功夫,後面發現可以直接在連通圖上實現,和換乘站歸為一類

最短路徑的演算法需要固定站點,所以建立在staid的基礎上,所以當輸入站點名字有很多不同站點時需要一一對應的遍歷最短路徑,覺得這裡的演算法可以優化

*github地址在部落格首