項目綜述
提供一副地鐵線路圖,計算指定兩站之間最短(最少經過站數)乘車路線;輸出指定地鐵線路的所有站點。以北京地鐵為例,地鐵線路信息保存在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.算是第一次寫這么完整的技術博客,也是一次不可多得的實戰經驗呢^^