地鐵線路最短路徑(JAVA實現)
專案綜述
提供一副地鐵線路圖,計算指定兩站之間最短(最少經過站數)乘車路線;輸出指定地鐵線路的所有站點。以北京地鐵為例,地鐵線路資訊儲存在data.txt中,格式如下:
地鐵線路總數
線路名1站名1站名2站名3...
線路名2站名1站名2站名3...
線路名3站名1站名2站名3......
一、需求分析
1.該程式能夠準確地讀出.txt檔案中的資料,檔案格式簡潔易懂、可靈活擴充套件
2.在某號線路上,能夠查詢各個站點的資訊,輸出該號線路上所有站點資訊
3.在出發站與目的站之間輸出一個最短路徑
二、實現語言
Java
三、實現演算法
Floyd演算法
四、類職責劃分
1.SubwayAssistantStarter類
程式入口
2.GetData類
從data.txt中讀取資料並存儲各條線路和各個站點
3.Line類
private String name; private List<String> stations = new ArrayList<String>();//該線路上的所有站點
//已省略Getter & Setter
4.Map類
private int[][] subwayline;//儲存線路 private static List<String> stations;//儲存所有站點 private int[][] path;//儲存路徑
//已省略Getter & Setter
5.Result類
private static final int max = 999999;//最大距離 private int[][] ShortestPath;//最短路徑 private int[][] ShortestDis;//最短距離
//已省略Getter & Setter
6.FrmMain類
ui主介面,包括功能選擇和顯示北京地鐵線路背景圖
7.FrmLine類 & FrmShowStations類
ui分頁面
FrmLine類:使用者需輸入格式正確的線路名
FrmShowStation類:使用者輸入格式正確的線路名後彈窗顯示具體線路資訊
8.FrmShortestPath類 & FrmShowShortestPath類
ui分頁面
FrmShortestPath類:使用者輸入格式正確的站點名
FrmShowShortestPath類:使用者輸入正確後彈窗顯示兩站間最短路徑具體資訊
五、核心程式碼
1.讀取data.txt中的資料
public class GetData {//讀取.txt檔案中的資訊 public static int linenum = 0; @SuppressWarnings("null") public GetData(String pathname, List<Line> lines) throws IOException{ //讀檔案準備 File file = new File(pathname); InputStreamReader rdr = new InputStreamReader(new FileInputStream(file)); BufferedReader br = new BufferedReader(rdr); try { String content=null; while((content=br.readLine())!=null) { linenum++; Line newline = new Line(); List<String> line = new ArrayList<String>(); String[] station_single = content.split(" "); String linename = station_single[0];//第一個元素為線路名 for(int i=1;i<station_single.length;i++) {//儲存各個站點 if(i==station_single.length-1 && station_single[i].equals(station_single[1]))//處理環線 continue; line.add(station_single[i]); } newline.setName(linename); newline.setStations(line); lines.add(newline); } br.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (br == null) try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } }
2.Floyd演算法求最短路徑
public class Result { private static final int max = 999999; private int[][] ShortestPath; private int[][] ShortestDis; public Result(int[][] G) { this.ShortestPath = new int[G.length][G.length]; this.ShortestDis = new int[G.length][G.length]; for(int i=0;i<G.length;i++) { for(int j=0;j<G.length;j++) { this.ShortestPath[i][j]=j; this.ShortestDis[i][j]=G[i][j]; this.ShortestDis[j][i]=G[j][i]; } } //Floyd核心演算法 for(int k=0;k<G.length;k++) for(int j=0;j<G.length;j++) for(int i=0;i<G.length;i++) { if(this.ShortestDis[i][j]>this.ShortestDis[i][k]+this.ShortestDis[k][j]) { this.ShortestDis[i][j]=this.ShortestDis[i][k]+this.ShortestDis[k][j]; this.ShortestPath[i][j]=this.ShortestPath[i][k]; } } } }
3.初始化整個地圖
public Map(List<String> stations) {//初始化整個地圖 this.stations = stations; this.subwayline = new int[stations.size()][stations.size()]; this.path = new int[stations.size()][stations.size()]; for(int i=0;i<stations.size();i++) { for(int j=0;j<stations.size();j++) { if(i==j) subwayline[i][j] = 0; else { subwayline[i][j] = 999999; subwayline[j][i] = 999999; } } }
4.主頁面顯示功能與北京地鐵線路背景圖
public class FrmMain extends JFrame implements ActionListener{ private static final long serialVersionUID = 1L; private JMenuBar menubar=new JMenuBar(); private JPanel statusBar = new JPanel(); private JMenu menu_Manager=new JMenu("功能"); JFrame jf=new JFrame(); private JMenuItem menuItem_Line=new JMenuItem("查詢線路"); private JMenuItem menuItem_ShortestPath=new JMenuItem("查詢最短路徑"); private static List<Line> lines = new ArrayList<>();//儲存.txt中的所有資料 private List<String> stations = new ArrayList<String>();//儲存所有站點 public FrmMain() throws IOException { //讀檔案 String filepath = "E:\\Software Engineering\\data.txt"; GetData data = new GetData(filepath,lines); //北京地鐵圖片 JLabel jl3=new JLabel(new ImageIcon("E:\\Software Engineering\\subway.jpg")); jf.add(jl3); jl3.setBounds(0, 100, 80, 60); jf.pack(); jf.setVisible(true); this.setTitle("北京地鐵小助手"); menu_Manager.add(menuItem_Line); menuItem_Line.addActionListener(this); menu_Manager.add(menuItem_ShortestPath); menuItem_ShortestPath.addActionListener(this); menubar.add(menu_Manager); statusBar.setLayout(new FlowLayout(FlowLayout.LEFT)); JLabel label=new JLabel("歡迎使用北京地鐵小助手^^"); statusBar.add(label); this.getContentPane().add(statusBar,BorderLayout.SOUTH); this.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0); } }); this.setVisible(true); this.setJMenuBar(menubar); } public void actionPerformed(ActionEvent e) { if(e.getSource()==this.menuItem_Line){ FrmLine dlg=new FrmLine(this,"查詢線路",true,lines); dlg.setVisible(true); } else if(e.getSource()==this.menuItem_ShortestPath){ FrmShortestPath dlg=new FrmShortestPath(this,"查詢最短路徑",true,lines,stations); dlg.setVisible(true); } }
5.在查詢頁面處理最短路徑
public class FrmShortestPath extends JDialog implements ActionListener{ private List<Line> lines2 = new ArrayList<>();//儲存.txt中的所有資料 private Result result; private Map map; private List<String> list1 = new ArrayList<>(); //儲存經過單個站點的地鐵線的名字,以列表儲存 private List<List<String>> lists = new ArrayList<>(); //儲存經過所有站點的地鐵線的名字,將list1依次新增進lists中 private List<Integer> passStation = new ArrayList<>(); //儲存經過站點在陣列中的下標 private JPanel toolBar = new JPanel(); private JPanel workPane = new JPanel(); private Button btnOk = new Button("查詢"); private JTextField edtStart = new JTextField(20); private JTextField edtEnd = new JTextField(20); private JLabel labelStart = new JLabel("起點:"); private JLabel labelEnd = new JLabel("終點:"); private JLabel labelTip = new JLabel("請輸入您要查詢的起點和終點^^"); public FrmShortestPath(FrmMain frmMain, String s, boolean b, List<Line> lines, List<String> stations) { super(frmMain,s,b); lines2.addAll(lines); toolBar.setLayout(new FlowLayout(FlowLayout.RIGHT)); toolBar.add(btnOk); this.getContentPane().add(toolBar, BorderLayout.SOUTH); workPane.add(labelStart); workPane.add(edtStart); workPane.add(labelEnd); workPane.add(edtEnd); workPane.add(labelTip); this.getContentPane().add(workPane, BorderLayout.CENTER); this.setSize(280, 150); double width = Toolkit.getDefaultToolkit().getScreenSize().getWidth(); double height = Toolkit.getDefaultToolkit().getScreenSize().getHeight(); this.setLocation((int) (width - this.getWidth()) / 2, (int) (height - this.getHeight()) / 2); this.validate(); this.btnOk.addActionListener(this); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { //System.exit(0); } }); //把所有站點加入stations中 for(int i=0;i<GetData.linenum;i++) { stations.addAll(lines.get(i).getStations()); } map = new Map(stations);//此map非彼map //初始化各個站點間的距離為1 for(int i=0;i<lines.size();i++) { for(int j=0;j<lines.get(i).getStations().size()-1;j++) { map.initDis(lines.get(i).getStations().get(j), lines.get(i).getStations().get(j+1)); } } //求最短路徑 result = new Result(map.getSubwayline()); } @Override public void actionPerformed(ActionEvent e) { if(e.getSource()==this.btnOk) { int flag1=-1;int flag2=-1; String start = this.edtStart.getText(); String end = this.edtEnd.getText(); flag1=FrmMain.isStation(start); flag2=FrmMain.isStation(end); if(start==null || start.equals("") || end==null || end.equals("")) try { throw new Exception(); }catch(Exception e1) { JOptionPane.showMessageDialog(null, "起點/終點不能為空!","錯誤",JOptionPane.ERROR_MESSAGE); return; } else if(flag1<0 || flag2<0){ try { throw new Exception(); }catch(Exception e1) { JOptionPane.showMessageDialog(null, "起點/終點不存在!","錯誤",JOptionPane.ERROR_MESSAGE); return; } } else if(start.equals(end)) { try { throw new Exception(); }catch(Exception e1) { JOptionPane.showMessageDialog(null, "起點和終點不能相同!","錯誤",JOptionPane.ERROR_MESSAGE); return; } } else{//查詢shortest path int i = map.getIndex(start); int j = map.getIndex(end); int shortest = result.getMinDis(i,j);//需修改 if(shortest == 999999) { try{ throw new Exception(); } catch (Exception e1) { JOptionPane.showMessageDialog(null, "兩站點不可達!","錯誤",JOptionPane.ERROR_MESSAGE); return; } } shortest++; String path = start+"到"+end+"需經過"+shortest+"個站\n"; passStation = result.indexToList(i,j);//儲存最短路徑 for(int k=0;k<passStation.size();k++) { List<String> list = new ArrayList<>(); path+=(map.getName(passStation.get(k))+"("); // System.out.println(path); for(Line l:lines2) { int flag=0; for(String name:l.getStations()) { System.out.println(map.getName(passStation.get(i))); if(map.getName(passStation.get(i)).equals(name)){ path+=(l.getName()+" "); list.add(l.getName()); if(!list1.contains(name)) { list1.add(name); flag=1; } } } if(flag==1) lists.add(list); } } path+=")"; path+="\n"; //儲存換乘車站 List<String> transfer = new ArrayList<>(); for(int p=2;p<lists.size();p++) { int flag=0; for(int q=0;q<lists.get(p).size();q++) { for(int w=0;w<lists.get(p-2).size();w++) if(lists.get(p-2).get(w).equals(lists.get(p).get(q))) { flag=1;break; } } if(flag==0) { if(!transfer.contains(list1.get(p-1))); transfer.add(list1.get(p-1)); } } path+="\n"; path+="需要換乘"+transfer.size()+"次:"; for(int a=0;a<transfer.size();a++) { path+=(transfer.get(a)+" "); } path+="\n"; FrmShowShortestPath dlg=new FrmShowShortestPath(this,"最短路徑詳情",true,path); dlg.setVisible(true); } } } }
全部程式碼詳情請移步github:https://github.com/SmellyCat44/Beijing_SubwayAssistant.git
六、測試用例
1.主介面
包括功能與北京地鐵線路背景圖
2.查詢線路
3.查詢線路·輸入為空
4.查詢線路·輸入不存在的線路
5.查詢最短路徑·同線路
6.查詢最短路徑·遠站需換乘
7.查詢最短路徑·起點/終點為空
8.查詢最短路徑·輸入站點不存在
9.查詢最短路徑·起點終點相同
七、總結
1.在上一次的需求分析中原本打算用Dijstra演算法,但是在寫程式碼的過程中發現了Floyd演算法更易實現,於是就果斷跑路Floyd演算法了,究其原因應該是需求分析不夠深入,沒有進一步結合程式碼實現來思考。
2.寫程式碼的過程中,意識到多個UI介面彈窗之間傳引數的麻煩之處,經過這一次的鍛鍊,對JAVA類與類之間傳引數有了更好的理解。
3.認識到自己對JAVA還有許多不瞭解的“簡便方法”,在本次寫部落格的過程中在CSDN中獲益匪淺,希望以後也是多多鍛鍊,繼續在CSND和Baidu中學習更多的知識。
4.至於前端UI,我還是以較為“偷懶”的方式用了在暑假短學期學到的JAVA Swing,算是對之前學習內容的一個複習與鞏固叭。
5.算是第一次寫這麼完整的技術部落格,也是一次不可多得的實戰經驗呢^^