迷宫问题的求解(回溯法、深度优先遍历、广度优先遍历)

转载https://www.cnblogs.com/wanghang-learning/p/9430672.html

一、问题介绍

有一个迷宫地图,有一些可达的位置,也有一些不可达的位置(障碍、墙壁、边界)。从一个位置到下一个位置只能通过向上(或者向右、或者向下、或者向左)走一步来实现,从起点出发,如何找到一条到达终点的通路。本文将用两种不同的解决思路,四种具体实现来求解迷宫问题。

用二维矩阵来模拟迷宫地图,1代表该位置不可达,0代表该位置可达。每走过一个位置就将地图的对应位置标记,以免重复。找到通路后打印每一步的坐标,最终到达终点位置。

封装了点Dot,以及深度优先遍历用到的Block,广度优先遍历用到的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;                           //起点

    private int startY = 1; private int endX = mapX - 1;                      //终点

    private int endY = mapY - 1;

  //内部类,封装一个点
    public class Dot{
        private int x;            //行标
        private int y;            //列标

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

        public int getX(){
            return x;
        }

        public int getY(){
            return y;
        }
    }

    //内部类,封装走过的每一个点,自带方向
    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++;
        }
    }

    /*
        广度优先遍历用到的数据结构,它需要一个指向父节点的索引
    */
    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>

二、回溯法

思路:从每一个位置出发,下一步都有四种选择(上右下左),先选择一个方向,如果该方向能够走下去,那么就往这个方向走,当前位置切换为下一个位置。如果不能走,那么换个方向走,如果所有方向都走不了,那么退出当前位置,到上一步的位置去,当前位置切换为上一步的位置。一致这样执行下去,如果当前位置是终点,那么结束。如果走过了所有的路径都没能到达终点,那么无解。下面看代码

private Stack<Block> stack = new Stack<Block>();  //回溯法存储路径的栈

public void findPath1(){
        Block b = new Block(startX,startY);
        stack.push(b); //起点进栈 
        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;                            //把地图上对应的位置标记为1表示是当前路径上的位置,防止往回走

            if(t.getX() == endX && t.getY() == endY) {//已达终点
                return ;
            } switch(dir){ case 1:                                     //向上走一步 
                    if(x - 1 >= 0 && map[x - 1][y] == 0){   //判断向上走一步是否出界&&判断向上走一步的那个位置是否可达
                        stack.push(new Block(x - 1 , y));   //记录该位置
 }
                    t.changeDir(); //改变方向,当前方向已走过
                    continue;                               //进入下一轮循环
                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 当前Block节点已经没有方向可走,出栈
            map[t.getX()][t.getY()] = 0;                        //出栈元素对应的位置已经不再当前路径上,表示可达
 }
    } //打印栈
    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++;
        }
    }

递归实现:

 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 ; //在此处返回会使后续递归再次寻找路线会经过这里,如果不返回,整个函数执行完毕,所有路径都会被访问到
 }
        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();
        }
    }

三、深度优先遍历

思路:和上面的回溯法思想基本一样,能向某个方向走下去就一直向那个方向走,不能走就切换方向,所有方向都不能走了就回到上一层位置。

  private Stack<Dot> stackDeep = new Stack<Dot>();  //深度遍历时用的存储栈

    private Stack<Dot> sDeep = new Stack<Dot>();      //深度遍历时用到的辅助栈

   public void findPath3() { // 判断起点的合法性
        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; // 标记已访问 if (x == endX && y == endY) { while (!sDeep.empty()) {
                stackDeep.push(sDeep.pop());
            } //return ; //此处的return和上一个递归的return一样,如果返回
        }                          

        for (int i = 1; i <= 4; i++) { // 在每个方向上进行尝试
            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(); // 四个方向都已尝试过,并且没成功,退栈
 }
    } //打印深度优先遍历的栈
    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("");
            }
        }
    }

栈实现、递归和深度优先遍历,这三者的执行思路是一样的,都可以看做二叉树先根遍历的变体,起点看做根节点,每个选择方向看做一个分叉,四个方向对应四个子节点,如果某个节点四个方向都走不了,那么它就是叶子节点。每走到一个叶子节点就是一条完整的执行路径,只不过不一定到达了终点。按照先根遍历的方式,最终会走完每一条路径,如果在路径中找到了终点,那么记录下这条路径线索。二叉树先根遍历的递归实现对应了上面的递归实现,只不过这里是四叉树,栈实现可以看成先根遍历的非递归算法,而深度优先遍历和先跟遍历本就有异曲同工之妙。

四、广度优先遍历

思路:这种方法和前面三种是不同的思路。先从根节点出发,将当前节点的所有可达子节点依次访问,在依次访问子节点的子节点,一直下去直到所有节点被遍历。

 private WideBlock wb;                             //广度遍历的终点节点

    /* 广度优先遍历 */
    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){                 //判断当前节点是否已达终点
                wb = b; return;
            } if (x - 1 >= 0 && map[x - 1][y] == 0) {     //判断当前节点的能否向上走一步,如果能,则将下一步节点加入队列
                q.offer(new WideBlock(x - 1 , y , b));  //子节点入队
                map[x - 1][y] = 1;                      //标记已访问
 } if (y + 1 <= mapY && map[x][y + 1] == 0) {  //判断当前节点能否向右走一步
                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("");
            }
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352