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

地鐵線路最短路徑(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.算是第一次寫這麼完整的技術部落格,也是一次不可多得的實戰經驗呢^^