1. 程式人生 > 實用技巧 >地鐵線路最短線路(程式碼實現)

地鐵線路最短線路(程式碼實現)

#### 專案介紹 ##### 主要功能 提供一副地鐵線路圖,計算指定兩站之間最短(最少經過站數)乘車路線;輸出指定地鐵線路的所有站點。以北京地鐵為例,地鐵線路資訊儲存在data.txt中,格式如下:

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

實現語言

Java

實現思路

首先將地鐵線路資訊載入處理,當要尋找兩個站點最短路徑時,先查詢儲存最短路徑的檔案中是否存在該兩站點的最短路徑記錄,如沒有再找到連通兩站點的所有的線路路徑(線路不得重複,線路數亦不可大於地鐵線路總數),再根據所提供的線路路徑去尋找兩條線路間的換乘站點,比較所經站點的數量,選擇站點數最少的路徑;如存在站點數相同,則選擇經過線路最少的路徑;如所經線路相同,則都輸出。

實現演算法(FLoyd)
  • Floyd演算法又稱為插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的演算法。
  • 狀態轉移方程:map[i,j]:=min{map[i,k]+map[k,j],map[i,j]};
    map[i,j]表示i到j的最短距離,K是窮舉i,j的斷點,map[n,n]初值應該為0,或者按照題目意思來做。
  • 演算法過程
    1.從任意一條單邊路徑開始。所有兩點之間的距離是邊的權,如果兩點之間沒有邊相連,則權為無窮大。
    2.對於每一對頂點 u 和 v,看看是否存在一個頂點 w 使得從 u 到 w 再到 v 比已知的路徑更短。如果是更新它。
類職責劃分(相關類的功能描述)

BeanStation 站點的相關資訊
BeanLine 線路的相關資訊
BeanPath 路徑的相關資訊
LineManager 關於BeanLine的一些函式
StationManager 關於BeanStation的一些函式
MyUtil 呼叫LineManager和StationManager
Stater 主類(讀寫檔案和執行程式)

核心程式碼

(1) BeanStation

    private int id; //站點id
    private String StationName; //站點名
    private boolean isTransferStation; //是否是換乘站點
    private List<BeanLine> onLine = new ArrayList<>(); //所線上路

(2) BeanLine

    private int id; //線路id
    private String LineName; //線路名
    private boolean isLoop; //是否是迴環線路
    private List<BeanStation> stationList = new ArrayList<>(); //所經站點
    private Set<BeanLine> connectedLine = new HashSet<>(); //相連線路
    private int[][] Graph; //線路內站到站的最短距離

(3) BeanPath

    private String beginStation; //起始站
    private String endStation; //終點站
    private List<BeanStation> aStations = new ArrayList<>(); //所經站點
    private int sumSations;
    private List<BeanLine> aList = new ArrayList<>(); //所經線路

(4) 根據路線和起始站、終點站確定所經站點

	public BeanPath thronghStations(BeanPath path,Map<String, BeanStation> nameToStation,Map<Integer, BeanLine> idToLine){
		if(path.getBeginStation().equals(path.getEndStation())) {
			path.getaStations().add(nameToStation.get(path.getEndStation()));
			path.setSumSations(0);
			return path;
		}
		else if (path.getaList().size()==1) {
			int l=distanceBetwwenTwoStations(path.getaList().get(0), nameToStation.get(path.getBeginStation()), nameToStation.get(path.getEndStation()));
			path.getaStations().add(nameToStation.get(path.getEndStation()));
			path.getaStations().add(nameToStation.get(path.getBeginStation()));
			path.setSumSations(l);
			return path;
		}
		else {
			BeanStation nowStation= nameToStation.get(path.getBeginStation());
			List<BeanLine> list = path.getaList().subList(0, path.getaList().size()-1);
			for(BeanStation station: path.getaList().get(path.getaList().size()-1).getStationList()) {
				if(!station.getStationName().equals(path.getBeginStation())&&station.isTransferStation()) {
					if(station.getOnLine().contains(list.get(list.size()-1))) {
						BeanPath newPath = new BeanPath(station.getStationName(), path.getEndStation());
						newPath.setaList(list);
						BeanPath newPath2  = thronghStations(newPath, nameToStation, idToLine);
						int l=distanceBetwwenTwoStations(path.getaList().get(path.getaList().size()-1), nameToStation.get(path.getBeginStation()), station);
						if(path.getaStations().isEmpty()) {
							path.setaStations(newPath2.getaStations());
							path.getaStations().add(nowStation);
							path.setSumSations(newPath2.getSumSations()+l);
						}else if(l+newPath2.getSumSations()<path.getSumSations()) {
							path.setaStations(newPath2.getaStations());
							path.getaStations().add(nowStation);
							path.setSumSations(newPath2.getSumSations()+l);
						}
					}
				}
			}
			return path;
		}
	}

(5) 查詢如何從一條線路到另一條線路

	public List<List<BeanLine>> LineToLine(BeanLine start, BeanLine end, int n){
		List<List<BeanLine>> array = new ArrayList<>();
		if(n==0) return array;
		if(start.getId()==end.getId()) {
			List<BeanLine> aLines = new ArrayList<>();
			aLines.add(start);
			array.add(aLines);
			return array;
		}
		for(BeanLine line: start.getConnectedLine()) {
			if(line!=start) {
				List<List<BeanLine>> l2l = LineToLine(line, end,n-1);
				if(l2l.isEmpty()) continue;
				for(List<BeanLine> aLines: l2l) {
					if(aLines.contains(start)) l2l.remove(aLines);
					else{
						aLines.add(start);
						if(!array.contains(aLines)) array.add(aLines);
					}
				}
			}
		}
		return array;
	}

(6) 根據線路資訊建立圖(BeanLine,Floyd)

  	public void createGraph(){
          int length = stationList.size();
          Graph = new int[length][length];
          for(int i=0;i<length;i++){                                      //初始化圖
              for(int j=0;j<=i;j++){
                  Graph[i][j]=Graph[j][i]=i-j;
              }
          }
          if(isLoop==true){                                        //當線路為環路時要修改站與站的距離
              Graph[0][length-1]=Graph[length-1][0]=1;
              for(int i=0;i<length;i++){
                  for(int k=0;k<length;k++){
                      for(int j=0;j<length;j++){
                          if(i!=j&&i!=k&&j!=k){
                              if(Graph[i][j]>Graph[i][k]+Graph[j][k])
                                  Graph[i][j]=Graph[j][i]=Graph[i][k]+Graph[j][k];
                          }
                      }
                  }
              }
          }
      }

(7)Starter

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import control.LineManager;
import model.BeanLine;
import model.BeanPath;
import model.BeanStation;
import util.MyUtil;

public class Starter {
	List<BeanStation> stations = new ArrayList<>(); //存放所有站
	List<BeanStation> transferStations = new ArrayList<>();  //存放換乘站點
	List<BeanLine> lines = new ArrayList<>(); //存放所有線路
	Map<String, BeanStation> nameToStationMap =  new HashMap<>(); //由站名對映站
	Map<Integer, BeanLine> idToLineMap = new HashMap<>(); //由線路id對映線路
	Map<String, BeanLine> nameToLineMap = new HashMap<>(); //由線路名對映線路
	
	//載入地鐵線路資訊
	public void loadInformation() {
		String pathName = "地鐵線路資訊.txt";
		try {
			BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(pathName),"UTF-8"));
			String string;
			while((string=br.readLine())!=null) {
				String[] a = string.split(" ");
				int lineId = lines.size();
				String lineName = a[0];
				BeanLine line = new BeanLine(lineId, lineName);
				idToLineMap.put(lineId, line);
				nameToLineMap.put(lineName, line);
				for(int i=1;i<a.length;i++) {
					if(nameToStationMap.containsKey(a[i])) {	//檢視站點是否曾出現過
						BeanStation s = nameToStationMap.get(a[i]);
						if(!s.isTransferStation()) s.setTransferStation(true);			//如果不是換乘站點就設定成換乘站點
						if(!transferStations.contains(s)) transferStations.add(s);
						s.getOnLine().add(line);
						line.getStationList().add(s);
					}
					else {
						int stationId=stations.size();
						BeanStation station = new BeanStation(stationId, a[i]);
						nameToStationMap.put(a[i], station);
						stations.add(station);
						line.getStationList().add(station);
						station.getOnLine().add(line);
					}
				}
				if(a[1].equals(a[a.length-1])) line.setLoop(true);
				line.createGraph();
				lines.add(line);
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	//查詢已確定的最短路線中是否已存在需求路線
	public boolean shortestPath(String startStation, String endStation) {
		String pathName = "已確定的最短路徑.txt";
		try {
			BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(pathName),"UTF-8"));
			String string;
			while((string=br.readLine())!=null) {
				String[] a = string.split(" ");
				if(a[0].equals(startStation)&&a[1].equals(endStation)) {
					System.out.println(string);
					return true;
				}
			}
			br.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return false;
	}
	
	//顯示路徑並寫入檔案
	public void writeFile(BeanPath path) {
		try {
			File file = new File("已確定的最短路徑.txt");
			if (!file.exists()) 
			    file.createNewFile();
			BufferedWriter out = new BufferedWriter(new FileWriter(file,true));
	        System.out.println(path.toString());
	        out.write(path.toString()+"\r\n");
	        out.flush(); // 把快取區內容壓入檔案
	        out.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		Starter starter = new Starter();
		starter.loadInformation();
		for(BeanStation s: starter.transferStations) {
			for(BeanLine l: s.getOnLine()) {
				MyUtil.lineManager.addConnectedLines(s.getOnLine(), l);
			}
		}
		Scanner input = new Scanner(System.in);
		System.out.print("請輸入起始站:");
		String begin = input.nextLine().trim();
		System.out.print("請輸入終點站:");
		String end = input.nextLine().trim();
		if(!(starter.nameToStationMap.containsKey(begin)&&starter.nameToStationMap.containsKey(end))) {
			System.out.println("站名不存在");
			System.exit(0);
		}
		if(!starter.shortestPath(begin, end)) {
			BeanStation s1=starter.nameToStationMap.get(begin);
			BeanStation s2=starter.nameToStationMap.get(end);
			List<List<BeanLine>> allLines = new ArrayList<>();
			for(BeanLine l1: s1.getOnLine()) {
				for(BeanLine l2: s2.getOnLine()) {
					allLines.addAll(MyUtil.lineManager.LineToLine(l1, l2,23));
				}
			}
			List<BeanPath> allPaths = new ArrayList<>();
			for(List<BeanLine> line: allLines) {
				BeanPath path = new BeanPath(begin, end);
				path.setaList(line);
				path = MyUtil.stationManager.thronghStations(path, starter.nameToStationMap, starter.idToLineMap);
				allPaths.add(path);
			}
			Comparator<BeanPath> com = new MyComparator();
			Collections.sort(allPaths, com);
			if(allPaths.isEmpty()) {
				System.out.println("無可選路線");
			}else {
				starter.writeFile(allPaths.get(0));
				for(int i=1;i<allPaths.size();i++) {
					if(allPaths.get(i).getSumSations()==allPaths.get(0).getSumSations()&&allPaths.get(i).getaList().size()==allPaths.get(0).getaList().size()) {
						starter.writeFile(allPaths.get(i));
					}
				}
			}
		}
			
	}
}
//自定義函式,按所經站點從少到多排序,如相同,則按所經線路從少到多排序
class MyComparator implements Comparator<BeanPath> {

	public int compare(BeanPath p1,BeanPath p2) {
		if(p1.getSumSations()!=p2.getSumSations()) return p2.getSumSations()-p1.getSumSations();
		else {
			return p2.getaList().size()-p1.getaList().size();
		}
	}
}

測試用例

(1)
(2)
(3)
(4)

總結

這次作業讓我對java有了更深的瞭解,明白了一些類在呼叫時需要注意的地方,並通過網上學習知道了如何去避免或是解決問題
在程式設計實踐中總能找到本以為自己會卻做不到的地方
程式碼依舊存在一些問題未解決,且有機會的話我會把ui補上
github地址:https://github.com/onshero/SubwayShortestPath