迷宮問題的求解(回溯法、深度優(yōu)先遍歷、廣度優(yōu)先遍歷)

轉(zhuǎn)載https://www.cnblogs.com/wanghang-learning/p/9430672.html

一、問題介紹

有一個(gè)迷宮地圖,有一些可達(dá)的位置,也有一些不可達(dá)的位置(障礙、墻壁、邊界)。從一個(gè)位置到下一個(gè)位置只能通過向上(或者向右、或者向下、或者向左)走一步來實(shí)現(xiàn),從起點(diǎn)出發(fā),如何找到一條到達(dá)終點(diǎn)的通路。本文將用兩種不同的解決思路,四種具體實(shí)現(xiàn)來求解迷宮問題。

用二維矩陣來模擬迷宮地圖,1代表該位置不可達(dá),0代表該位置可達(dá)。每走過一個(gè)位置就將地圖的對應(yīng)位置標(biāo)記,以免重復(fù)。找到通路后打印每一步的坐標(biāo),最終到達(dá)終點(diǎn)位置。

封裝了點(diǎn)Dot,以及深度優(yōu)先遍歷用到的Block,廣度優(yōu)先遍歷用到的WideBlock。

private int[][] map = {                           //迷宮地圖,1代表墻壁,0代表通路
        {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}
    }; private int mapX = map.length - 1;                //地圖xy邊界

    private int mapY = map[0].length - 1; private int startX = 1;                           //起點(diǎn)

    private int startY = 1; private int endX = mapX - 1;                      //終點(diǎn)

    private int endY = mapY - 1;

  //內(nèi)部類,封裝一個(gè)點(diǎn)
    public class Dot{
        private int x;            //行標(biāo)
        private int y;            //列標(biāo)

        public Dot(int x , int y) {
            this.x = x;
            this.y = y;
        }

        public int getX(){
            return x;
        }

        public int getY(){
            return y;
        }
    }

    //內(nèi)部類,封裝走過的每一個(gè)點(diǎn),自帶方向
    public class Block extends Dot{

        private int dir;          //方向,1向上,2向右,3向下,4向左

        public Block(int x , int y) {
            super(x , y);
            dir = 1;
        }

        public int getDir(){
            return dir;
        }

        public void changeDir(){
            dir++;
        }
    }

    /*
        廣度優(yōu)先遍歷用到的數(shù)據(jù)結(jié)構(gòu),它需要一個(gè)指向父節(jié)點(diǎn)的索引
    */
    public class WideBlock extends Dot{
        private WideBlock parent;

        public WideBlock(int x , int y , WideBlock p){
            super(x , y);
            parent = p;
        }

        public WideBlock getParent(){
            return parent;
        }
    }</pre>

二、回溯法

思路:從每一個(gè)位置出發(fā),下一步都有四種選擇(上右下左),先選擇一個(gè)方向,如果該方向能夠走下去,那么就往這個(gè)方向走,當(dāng)前位置切換為下一個(gè)位置。如果不能走,那么換個(gè)方向走,如果所有方向都走不了,那么退出當(dāng)前位置,到上一步的位置去,當(dāng)前位置切換為上一步的位置。一致這樣執(zhí)行下去,如果當(dāng)前位置是終點(diǎn),那么結(jié)束。如果走過了所有的路徑都沒能到達(dá)終點(diǎn),那么無解。下面看代碼

private Stack<Block> stack = new Stack<Block>();  //回溯法存儲路徑的棧

public void findPath1(){
        Block b = new Block(startX,startY);
        stack.push(b); //起點(diǎn)進(jìn)棧 
        while(!stack.empty()){                        //??沾硭新窂揭炎咄?,沒有找到通路
            Block t = stack.peek(); int x = t.getX();                         //獲取棧頂元素的x
            int y = t.getY();                         //獲取棧頂元素的y
            int dir = t.getDir();                     //獲取棧頂元素的下一步方向
 map[x][y] = 1;                            //把地圖上對應(yīng)的位置標(biāo)記為1表示是當(dāng)前路徑上的位置,防止往回走

            if(t.getX() == endX && t.getY() == endY) {//已達(dá)終點(diǎn)
                return ;
            } switch(dir){ case 1:                                     //向上走一步 
                    if(x - 1 >= 0 && map[x - 1][y] == 0){   //判斷向上走一步是否出界&&判斷向上走一步的那個(gè)位置是否可達(dá)
                        stack.push(new Block(x - 1 , y));   //記錄該位置
 }
                    t.changeDir(); //改變方向,當(dāng)前方向已走過
                    continue;                               //進(jìn)入下一輪循環(huán)
                case 2: if(y + 1 <= mapY && map[x][y+1] == 0){
                        stack.push(new Block(x , y + 1));
                    }
                    t.changeDir(); continue; case 3: if(x + 1 <= mapX && map[x+1][y] == 0){
                        stack.push(new Block(x + 1 , y));
                    }
                    t.changeDir(); continue; case 4: if(y - 1 >= 0 && map[x][y - 1] == 0){
                        stack.push(new Block(x , y - 1));
                    }
                    t.changeDir(); continue;
            }
            t = stack.pop();                                    //dir > 4 當(dāng)前Block節(jié)點(diǎn)已經(jīng)沒有方向可走,出棧
            map[t.getX()][t.getY()] = 0;                        //出棧元素對應(yīng)的位置已經(jīng)不再當(dāng)前路徑上,表示可達(dá)
 }
    } //打印棧
    public void printStack(){ int count = 1;
        while(!stack.empty()){
            Block b = stack.pop();
            System.out.print("(" + b.getX() + "," + b.getY() + ") ");
            if(count % 10 == 0)
                System.out.println("");
            count++;
        }
    }

遞歸實(shí)現(xiàn):

 private Stack<Block> s = new Stack<Block>();      //回溯法全局輔助棧
   private Stack<Block> stack = new Stack<Block>();  //回溯法存儲路徑的棧 
    public void findPath2(){ if(startX >= 0 && startX <= mapX && startY >= 0 && startY <= mapY && map[startX][startY] == 0){ 
            find(startX , startY);
        }
    } private void find(int x , int y){
     map[x][y] = 1; if(x == endX && y == endY) {
            s.push(new Block(x , y)); while(!s.empty()){
                stack.push(s.pop());
            } //return ; //在此處返回會使后續(xù)遞歸再次尋找路線會經(jīng)過這里,如果不返回,整個(gè)函數(shù)執(zhí)行完畢,所有路徑都會被訪問到
 }
        s.push(new Block(x , y));if( x - 1 >= 0 && map[x - 1][y] == 0 ){  //可以往上走,那么往上走
            find(x - 1 , y);
        } if(x + 1 <= mapX && map[x + 1][y] == 0){ //可以往下走,那么往下走
            find(x + 1 , y);
        } if(y - 1 >= 0 && map[x][y - 1] ==0){     //往左
            find(x , y - 1);
        } if(y + 1 <= mapY && map[x][y + 1] == 0){
            find(x , y + 1);
        } if(!s.empty()){
            s.pop();
        }
    }

三、深度優(yōu)先遍歷

思路:和上面的回溯法思想基本一樣,能向某個(gè)方向走下去就一直向那個(gè)方向走,不能走就切換方向,所有方向都不能走了就回到上一層位置。

  private Stack<Dot> stackDeep = new Stack<Dot>();  //深度遍歷時(shí)用的存儲棧

    private Stack<Dot> sDeep = new Stack<Dot>();      //深度遍歷時(shí)用到的輔助棧

   public void findPath3() { // 判斷起點(diǎn)的合法性
        if (startX >= 0 && startX <= mapX && startY >= 0 && startY <= mapY && map[startX][startY] == 0) {
            deepFirst(startX, startY);
        }
    } public void deepFirst(int x, int y) {
        Dot b = new Dot(x, y);
        sDeep.push(b);
     map[x][y] = 1; // 標(biāo)記已訪問 if (x == endX && y == endY) { while (!sDeep.empty()) {
                stackDeep.push(sDeep.pop());
            } //return ; //此處的return和上一個(gè)遞歸的return一樣,如果返回
        }                          

        for (int i = 1; i <= 4; i++) { // 在每個(gè)方向上進(jìn)行嘗試
            if (i == 1) { // 上
                if (x - 1 >= 0 && map[x - 1][y] == 0) {
                    deepFirst(x - 1, y);
                }
            } else if (i == 2) { // 右
                if (y + 1 <= mapY && map[x][y + 1] == 0) {
                    deepFirst(x, y + 1);
                }
            } else if (i == 3) { // 下
                if (x + 1 <= mapX && map[x + 1][y] == 0) {
                    deepFirst(x + 1, y);
                }
            } else { // 左
                if (y - 1 >= 0 && map[x][y - 1] == 0) {
                    deepFirst(x, y - 1);
                }
            }
        } if(!sDeep.empty()) {
            sDeep.pop(); // 四個(gè)方向都已嘗試過,并且沒成功,退棧
 }
    } //打印深度優(yōu)先遍歷的棧
    public void printDeepStack(){ int count = 1; while(!stackDeep.empty()){
            Dot b = stackDeep.pop();
            System.out.print("(" + b.getX() + "," + b.getY() + ") "); if(count++ % 10 == 0){
                System.out.println("");
            }
        }
    }

棧實(shí)現(xiàn)、遞歸和深度優(yōu)先遍歷,這三者的執(zhí)行思路是一樣的,都可以看做二叉樹先根遍歷的變體,起點(diǎn)看做根節(jié)點(diǎn),每個(gè)選擇方向看做一個(gè)分叉,四個(gè)方向?qū)?yīng)四個(gè)子節(jié)點(diǎn),如果某個(gè)節(jié)點(diǎn)四個(gè)方向都走不了,那么它就是葉子節(jié)點(diǎn)。每走到一個(gè)葉子節(jié)點(diǎn)就是一條完整的執(zhí)行路徑,只不過不一定到達(dá)了終點(diǎn)。按照先根遍歷的方式,最終會走完每一條路徑,如果在路徑中找到了終點(diǎn),那么記錄下這條路徑線索。二叉樹先根遍歷的遞歸實(shí)現(xiàn)對應(yīng)了上面的遞歸實(shí)現(xiàn),只不過這里是四叉樹,棧實(shí)現(xiàn)可以看成先根遍歷的非遞歸算法,而深度優(yōu)先遍歷和先跟遍歷本就有異曲同工之妙。

四、廣度優(yōu)先遍歷

思路:這種方法和前面三種是不同的思路。先從根節(jié)點(diǎn)出發(fā),將當(dāng)前節(jié)點(diǎn)的所有可達(dá)子節(jié)點(diǎn)依次訪問,在依次訪問子節(jié)點(diǎn)的子節(jié)點(diǎn),一直下去直到所有節(jié)點(diǎn)被遍歷。

 private WideBlock wb;                             //廣度遍歷的終點(diǎn)節(jié)點(diǎn)

    /* 廣度優(yōu)先遍歷 */
    public void findPath4(){
        wideFirst();
    } public void wideFirst(){
        WideBlock start = new WideBlock(startX , startY , null);
        Queue<WideBlock> q = new LinkedList<WideBlock>();  
        map[startX][startY] = 1;                       
        q.offer(start); while(q.peek() != null){
            WideBlock b = q.poll(); int x = b.getX(); int y = b.getY(); if(x == endX && y == endY){                 //判斷當(dāng)前節(jié)點(diǎn)是否已達(dá)終點(diǎn)
                wb = b; return;
            } if (x - 1 >= 0 && map[x - 1][y] == 0) {     //判斷當(dāng)前節(jié)點(diǎn)的能否向上走一步,如果能,則將下一步節(jié)點(diǎn)加入隊(duì)列
                q.offer(new WideBlock(x - 1 , y , b));  //子節(jié)點(diǎn)入隊(duì)
                map[x - 1][y] = 1;                      //標(biāo)記已訪問
 } if (y + 1 <= mapY && map[x][y + 1] == 0) {  //判斷當(dāng)前節(jié)點(diǎn)能否向右走一步
                q.offer(new WideBlock(x , y + 1 , b));
                map[x][y + 1] = 1;
            } if (x + 1 <= mapX && map[x + 1][y] == 0) {
                q.offer(new WideBlock(x + 1 , y , b));
                map[x + 1][y] = 1;
            } if (y - 1 >= 0 && map[x][y - 1] == 0) {
                q.offer(new WideBlock(x , y - 1 , b));
                map[x][y -1] = 1;
            }
        }
    } //打印廣度遍歷的路徑
    public void printWidePath(){
        WideBlock b = wb; int count = 1; while(b != null){
            System.out.print("(" + b.getX() + "," + b.getY() + ") ");
            b = b.getParent(); if(count++ % 10 == 0){
                System.out.println("");
            }
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容