1. 程式人生 > >資料結構_棧的應用_迷宮求解問題java實現

資料結構_棧的應用_迷宮求解問題java實現

這篇文章講述的是資料結構部分的迷宮求解問題的java實現,如有錯誤或者不當之處,還望各位大神批評指正。

問題描述

假設有一個迷宮使用二維陣列,牆使用1表示,路徑使用0表示,可達路徑使用*表示,試寫一演算法計算出從起點到終點的一條可行路徑。

演算法分析

  1. 使用二維陣列存放初始化的迷宮
  2. 藉助棧來遍歷迷宮的路徑,棧中儲存的是正確的路徑
  3. 業務邏輯如下
while(棧不為空){
         if(找到終點){
             元素全部出棧,且將其標誌位設為*
         }else{
             若沒不是終點,則判斷上下左右位置若合法則壓進棧,若無路可走則出棧
         }
}

程式碼實現

  • 迷宮的元素型別
/*迷宮單元的資料結構*/
    private class Cellimpl implements Cell{
        private int x ;                         //單元所在行
        private int y ;                         //單元所在列
        private boolean visited = false ;       //單元是否已被訪問
        private char mark ;                     //單元格的型別,1表示牆,0表示路,*表示可行路徑
/*省略get和set方法*/
  • 迷宮的資料結構類及相關方法
/*迷宮的資料結構*/
class Maze {
    /*迷宮大小*/
    final int LENGTH = 10 ;

    /*迷宮資料域*/
    private Cell CELLS [][] ;


    /*迷宮單元的資料結構*/
    private class Cellimpl implements Cell{
        private int x ;                         //單元所在行
        private int y ;                         //單元所在列
private boolean visited = false ; //單元是否已被訪問 private char mark ; //單元格的型別,1表示牆,0表示路,*表示可行路徑 public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public boolean isVisited() { return visited; } public void setVisited(boolean visited) { this.visited = visited; } public char getMark() { return mark; } public void setMark(char mark) { this.mark = mark; } } /** * @explain createMaze方法: 建立迷宮 * @param maze 傳入表示迷宮的二維陣列 * @throws * @author 葉清逸 * @date 2018年7月31日 下午8:49:23 */ public void create(char maze[][]){ /*為迷宮的資料域分配空間*/ CELLS = new Cell[LENGTH][LENGTH] ; /*將二維陣列型別轉換為迷宮*/ for(int i=0 ; i<maze.length ; i++){ for(int j=0 ; j<maze.length ; j++){ /*獲取陣列元素*/ Cell cell = new Cellimpl() ; /*初始化該迷宮元素*/ char c = maze[i][j] ; /*將二維陣列元素轉換為迷宮元素*/ if(c == '1'){ cell.setMark('1') ; //設定標識位1 }else if(c == '0'){ cell.setMark('0') ; //設定標識為0 } cell.setX(i) ; //設定橫座標 cell.setY(j) ; //設定縱座標 cell.setVisited(false); //設定訪問標識 /*將該元素放入迷宮對應位置*/ CELLS[i][j] = cell ; } } } /** * @explain print方法: 列印迷宮 * @throws * @author 葉清逸 * @date 2018年7月31日 下午9:20:35 */ public void print(){ /*遍歷整個迷宮的資料域*/ for(int i=0 ; i<LENGTH ; i++){ for(int j=0 ; j<LENGTH ; j++){ /*列印該迷宮單元的標識*/ System.out.print(CELLS[i][j].getMark()+" "); } System.out.println(); } } public Cell[][] getCELLS() { return CELLS; } }
  • 尋路演算法(核心程式碼)
/**
     * @explain findPath方法: 計算迷宮起點到終點的路徑
     * @param maze 要尋路迷宮
     * @param sx 起點的橫座標
     * @param sy 起點的縱座標
     * @param ex 終點的橫座標
     * @param ey 終點的縱座標
     * @return boolean 起點到終點間是否存在路徑,若存在返回true,不存在返回false
     * @throws 
     * @author 葉清逸
     * @date 2018年7月31日 下午9:29:07
     */
    private static boolean findPath(Maze maze , int sx , int sy , int ex , int ey){
        boolean flag = false ;
        /*獲取迷宮的資料域*/
        Cell[][]cells = maze.getCELLS() ;
        /*初始化輔助棧*/
        Stack stack = new LinkStack() ;
        stack.init();
        /*獲取起點與終點*/
        Cell startCell = cells[sx][sy] ;
        Cell endCell = cells[ex][ey] ;
        /*將起點加入棧中*/
        stack.push(startCell);
        startCell.setVisited(true);                     //設定起點已被訪問
        /*窮舉出所有情況*/
        while(!stack.isEmpty()){
            /*取出棧頂元素,若其是終點,則順序將所訪問的節點標識製為* */
            Cell curCell = (Cell)stack.getTop() ;
            if(curCell == endCell){
                while(!stack.isEmpty()){
                    Cell cell = (Cell)stack.pop() ;
                    cell.setMark('*') ;
                }
                flag = true ;
            }else{
                /*獲取該點的橫座標和縱座標*/
                int x = curCell.getX() ;
                int y = curCell.getY() ;
                /*若棧頂元素不為終點,判斷上下左右位置是否合法,若合法壓入棧*/
                if(cells[x+1][y].getMark() == '0' && cells[x+1][y].isVisited() == false){           //右邊
                    stack.push(cells[x+1][y]);
                    cells[x+1][y].setVisited(true);
                }else if(cells[x][y+1].getMark() == '0' && cells[x][y+1].isVisited() == false){     //下邊
                    stack.push(cells[x][y+1]);
                    cells[x][y+1].setVisited(true);
                }else if(cells[x-1][y].getMark() == '0' && cells[x-1][y].isVisited() == false){     //左邊
                    stack.push(cells[x-1][y]);
                    cells[x-1][y].setVisited(true);
                }else if(cells[x][y-1].getMark() == '0' && cells[x][y-1].isVisited() == false){
                    stack.push(cells[x][y-1]);                                                      //上邊
                    cells[x][y-1].setVisited(true);
                }else{
                    stack.pop() ;                                                                   //若為死路則退棧
                }
            }
        }
        return flag ;
    }

程式完整程式碼

package stack_question;

import stack.LinkStack;
import stack.Stack;

/**
 * @author 葉清逸
 * @date 2018年7月31日下午8:00:38
 * @version 1.0
 * @project stack_question
 */
public class Q2_MazePath {
    /**
     * 問題描述:假設有一個迷宮使用二維陣列,牆使用1表示,路徑使用0表示,可達路徑使用*表示,試寫一演算法計算
     *          出從起點到終點的一條可行路徑。
     * 
     * 演算法分析:1. 使用二維陣列存放初始化的迷宮
     *          2. 藉助棧來遍歷迷宮的路徑,棧中儲存的是正確的路徑
     *          3. 業務邏輯如下
     *                      while(棧不為空){
     *                          if(找到終點){
     *                                 元素全部出棧,且將其標誌位設為*
     *                          }else{
     *                              若沒不是終點,則判斷上下左右位置若合法則壓進棧,若無路可走則出棧
     *                          }
     *                      }
     */
    public static void main(String[] args) {
        /*初始化迷宮的二維陣列*/
        char [][] mazearr = {{'1','1','1','1','1','1','1','1','1','1'} ,
                             {'1','0','0','1','0','0','0','1','0','1'} ,
                             {'1','0','0','1','0','0','0','1','0','1'} ,
                             {'1','0','0','0','0','1','1','0','0','1'} ,
                             {'1','0','1','1','1','0','0','0','0','1'} ,
                             {'1','0','0','0','1','0','0','0','0','1'} ,
                             {'1','0','1','0','0','0','1','0','0','1'} ,
                             {'1','0','1','1','1','0','1','1','0','1'} ,
                             {'1','1','0','0','0','0','0','0','0','1'} ,
                             {'1','1','1','1','1','1','1','1','1','1'} } ; 

        /*初始化迷宮*/
        Maze maze = new Maze() ;
        maze.create(mazearr) ;
        System.out.println("迷宮初始狀態:");
        maze.print();

        /*計算從起點到終點的路徑*/
        boolean flag = findPath(maze, 1, 1, 8, 8) ;
        if(flag){
            System.out.println("路徑已找到,如下:");
            maze.print();
        }else{
            System.out.println("起點與終點之間沒有可達路徑");
        }

    }

    /**
     * @explain findPath方法: 計算迷宮起點到終點的路徑
     * @param maze 要尋路迷宮
     * @param sx 起點的橫座標
     * @param sy 起點的縱座標
     * @param ex 終點的橫座標
     * @param ey 終點的縱座標
     * @return boolean 起點到終點間是否存在路徑,若存在返回true,不存在返回false
     * @throws 
     * @author 葉清逸
     * @date 2018年7月31日 下午9:29:07
     */
    private static boolean findPath(Maze maze , int sx , int sy , int ex , int ey){
        boolean flag = false ;
        /*獲取迷宮的資料域*/
        Cell[][]cells = maze.getCELLS() ;
        /*初始化輔助棧*/
        Stack stack = new LinkStack() ;
        stack.init();
        /*獲取起點與終點*/
        Cell startCell = cells[sx][sy] ;
        Cell endCell = cells[ex][ey] ;
        /*將起點加入棧中*/
        stack.push(startCell);
        startCell.setVisited(true);                     //設定起點已被訪問
        /*窮舉出所有情況*/
        while(!stack.isEmpty()){
            /*取出棧頂元素,若其是終點,則順序將所訪問的節點標識製為* */
            Cell curCell = (Cell)stack.getTop() ;
            if(curCell == endCell){
                while(!stack.isEmpty()){
                    Cell cell = (Cell)stack.pop() ;
                    cell.setMark('*') ;
                }
                flag = true ;
            }else{
                /*獲取該點的橫座標和縱座標*/
                int x = curCell.getX() ;
                int y = curCell.getY() ;
                /*若棧頂元素不為終點,判斷上下左右位置是否合法,若合法壓入棧*/
                if(cells[x+1][y].getMark() == '0' && cells[x+1][y].isVisited() == false){           //右邊
                    stack.push(cells[x+1][y]);
                    cells[x+1][y].setVisited(true);
                }else if(cells[x][y+1].getMark() == '0' && cells[x][y+1].isVisited() == false){     //下邊
                    stack.push(cells[x][y+1]);
                    cells[x][y+1].setVisited(true);
                }else if(cells[x-1][y].getMark() == '0' && cells[x-1][y].isVisited() == false){     //左邊
                    stack.push(cells[x-1][y]);
                    cells[x-1][y].setVisited(true);
                }else if(cells[x][y-1].getMark() == '0' && cells[x][y-1].isVisited() == false){
                    stack.push(cells[x][y-1]);                                                      //上邊
                    cells[x][y-1].setVisited(true);
                }else{
                    stack.pop() ;                                                                   //若為死路則退棧
                }
            }
        }
        return flag ;
    }
}

/*迷宮元素的介面*/
interface Cell{
    //獲取元素的橫座標
    public int getX() ;
    //設定元素的橫座標
    public void setX(int x) ;
    //獲取元素的縱座標
    public int getY() ;
    //設定元素的縱座標
    public void setY(int y) ;
    //是否被訪問過
    public boolean isVisited() ;
    //訪問元素節點
    public void setVisited(boolean visited) ;
    //獲取元素標識
    public char getMark() ;
    //設定元素標識
    public void setMark(char mark) ;
}

/*迷宮的資料結構*/
class Maze {
    /*迷宮大小*/
    final int LENGTH = 10 ;

    /*迷宮資料域*/
    private Cell CELLS [][] ;


    /*迷宮單元的資料結構*/
    private class Cellimpl implements Cell{
        private int x ;                         //單元所在行
        private int y ;                         //單元所在列
        private boolean visited = false ;       //單元是否已被訪問
        private char mark ;                     //單元格的型別,1表示牆,0表示路,*表示可行路徑

        public int getX() {
            return x;
        }
        public void setX(int x) {
            this.x = x;
        }
        public int getY() {
            return y;
        }
        public void setY(int y) {
            this.y = y;
        }
        public boolean isVisited() {
            return visited;
        }
        public void setVisited(boolean visited) {
            this.visited = visited;
        }
        public char getMark() {
            return mark;
        }
        public void setMark(char mark) {
            this.mark = mark;
        }
    }
    /**
     * @explain createMaze方法: 建立迷宮
     * @param maze 傳入表示迷宮的二維陣列
     * @throws 
     * @author 葉清逸
     * @date 2018年7月31日 下午8:49:23
     */
    public void create(char maze[][]){
        /*為迷宮的資料域分配空間*/
        CELLS = new Cell[LENGTH][LENGTH] ;

        /*將二維陣列型別轉換為迷宮*/
        for(int i=0 ; i<maze.length ; i++){
            for(int j=0 ; j<maze.length ; j++){
                /*獲取陣列元素*/
                Cell cell = new Cellimpl() ;
                /*初始化該迷宮元素*/
                char c = maze[i][j] ;
                /*將二維陣列元素轉換為迷宮元素*/
                if(c == '1'){
                    cell.setMark('1') ;                         //設定標識位1
                }else if(c == '0'){
                    cell.setMark('0') ;                         //設定標識為0
                }
                cell.setX(i) ;                                  //設定橫座標
                cell.setY(j) ;                                  //設定縱座標
                cell.setVisited(false);                         //設定訪問標識

                /*將該元素放入迷宮對應位置*/
                CELLS[i][j] = cell ;
            }
        }
    }
    /**
     * @explain print方法: 列印迷宮
     * @throws 
     * @author 葉清逸
     * @date 2018年7月31日 下午9:20:35
     */
    public void print(){
        /*遍歷整個迷宮的資料域*/
        for(int i=0 ; i<LENGTH ; i++){
            for(int j=0 ; j<LENGTH ; j++){
                /*列印該迷宮單元的標識*/
                System.out.print(CELLS[i][j].getMark()+" ");
            }
            System.out.println();
        }
    }
    public Cell[][] getCELLS() {
        return CELLS;
    }
}

樣例輸出

迷宮初始狀態:
1 1 1 1 1 1 1 1 1 1 
1 0 0 1 0 0 0 1 0 1 
1 0 0 1 0 0 0 1 0 1 
1 0 0 0 0 1 1 0 0 1 
1 0 1 1 1 0 0 0 0 1 
1 0 0 0 1 0 0 0 0 1 
1 0 1 0 0 0 1 0 0 1 
1 0 1 1 1 0 1 1 0 1 
1 1 0 0 0 0 0 0 0 1 
1 1 1 1 1 1 1 1 1 1 
路徑已找到,如下:
1 1 1 1 1 1 1 1 1 1 
1 * 0 1 0 0 0 1 0 1 
1 * 0 1 0 0 0 1 0 1 
1 * 0 0 0 1 1 0 0 1 
1 * 1 1 1 0 0 0 0 1 
1 * * * 1 0 0 0 0 1 
1 0 1 * * * 1 0 0 1 
1 0 1 1 1 * 1 1 0 1 
1 1 0 0 0 * * * * 1 
1 1 1 1 1 1 1 1 1 1 

程式的問題

由於探索路徑的方法不同造成的結果不同,如程式中遍歷當前節點的上下左右節點時採取的是右下左上的方案,該方案適用於起點在左上,終點在右下的路徑探尋,若探尋起點在右下終點在左上的路徑時則會造成走過多的路,如若將上例的終點改為(5,5)則會造成如下情況:

路徑已找到,如下:
1 1 1 1 1 1 1 1 1 1 
1 * 0 1 0 0 0 1 0 1 
1 * 0 1 0 0 0 1 0 1 
1 * 0 0 0 1 1 * * 1 
1 * 1 1 1 * * * * 1 
1 * * * 1 * * * * 1 
1 0 1 * * * 1 0 * 1 
1 0 1 1 1 * 1 1 * 1 
1 1 0 0 0 * * * * 1 
1 1 1 1 1 1 1 1 1 1 

這樣雖然可以正確找到路徑,但也會多走很多路