回溯算法之迷宫问题(Maze)

前言

    迷宫问题是回溯算法的经典问题,其中的思路方法很重要。

算法思路

一、回溯算法

    回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在搜素尝试过程中寻找问题的解,当发现已满足求解条件时,就“回溯”返回,尝试别的路径。
    回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标,但当搜索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种不通就回退再走的技术称为回溯法,而满足回溯条件的某个状态的点称为“回溯”点,许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
    从一条路往前走,能进则进,不能进则退回来,换一条路再试。

二、经典问题之迷宫问题(Maze)

    单迷宫是只有一种走法的迷宫。对于单迷宫而言,有一种万能的破解方法,即沿着某一面墙壁走。在走的时候,左(右)手一直摸着左(右)边的墙壁,这种方法可能费时最长,也可能会使你走遍迷宫的每一个角落和每一条死路,但玩者绝不会永远困在里面。
在这里插入图片描述

(一)问题阐述

    迷宫问题:当从一个入口进入时,会遇到很多墙,在走的过程中如果遇到墙就要返回上一个位置,看上一个位置是否有其他方向的路可以走,依次循环进行,直到找到出口位置。迷宫有很多墙,阻挡住迷宫的路无法进行下去,所以需要在可行走的路中,找到最优达到出口的路。
    可以先遍历所有可能出去的路,如果遇到墙,标记下来,以免下次重复遍历。
    首先每次进入只能走一步;
    将走过的路进行标记,表示已经走过;
    若遇到墙的话,需要退到上一步,判断是否还有其他方向的路可以走。
下图为迷宫图(仅供参考):
在这里插入图片描述

(二)解题思路

    首先可以将迷宫用二维数组来表示:1代表该位置不可达(就是墙),0代表该位置可达(就是路),如下图所示,把出口和入口表示出来。(即入口坐标为maze[1][0],出口坐标为maze[7][8])
在这里插入图片描述
    然后从入口开始先标记,把每次走的路的坐标先标记下来,防止会重复走同一条路;再去判断每走一步是否可以通,从上右下左四个方向来判断,这里可以将四个方向表示为二维数组,向上走的话就是给y轴-1,向右走就是给x轴+1,向下走就给y轴+1,向左走就是给x轴-1,即int[][] direction = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};给所在坐标x和y值加二维数组得值就可以改变方向。
在这里插入图片描述
    如果所在坐标有其他方向的路可以走,然后判断其他方向的坐标是否在整个迷宫的矩阵范围内,是否是可以走的路,是否已经走过,如果都满足就可以继续向下走;如果遇到走不通的路,就需要消掉。
    本文中所说的迷宫是一种简单的迷宫,只是提供一种解迷宫问题的思想,要是遇到比较难或者复杂的迷宫问题,可以基于此思想上进行加工。

(三)算法代码(java)

package part05.分治回溯;

import part02.动态链表.LinkedList;

//迷宫
public class Maze {
    //定义始点
    private static int enterX = 1;
    private static int enterY = 0;
    //定义终点
    private static int exitX = 7;
    private static int exitY = 8;
    //创建一个存放坐标的栈
    private static LinkedList<String> stack = new LinkedList<>();
    //定义一个表示坐标已走过的数组
    private static boolean[][] vistied = new boolean[9][9];
    //定义一个方向数组 上右下左
    private static int[][] direction = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
    private static int[][] maze = {
            {1, 1, 1, 1, 1, 1, 1, 1, 1},
            {0, 0, 1, 0, 0, 0, 1, 1, 1},
            {1, 0, 1, 1, 1, 0, 1, 1, 1},
            {1, 0, 0, 1, 0, 0, 1, 1, 1},
            {1, 1, 0, 1, 1, 0, 0, 0, 1},
            {1, 0, 0, 0, 0, 0, 1, 0, 1},
            {1, 0, 1, 1, 1, 0, 0, 0, 1},
            {1, 1, 0, 0, 0, 0, 1, 0, 0},
            {1, 1, 1, 1, 1, 1, 1, 1, 1}
    };

    public static void main(String[] args) {
        if (go(enterX,enterY)) {
            //求解迷宫的解,如果可以走通返回true
            System.out.println("能走通!");
            //打印能走通的路线
            while (!stack.isEmpty()) {//前提是不为空
                System.out.print(stack.poll());//把所有的栈里面的元素出队
            }
        } else {
            System.out.println("不能走通!");
        }
    }

    //递归求解迷宫的解,以当前的x,y向下寻找终点
    private static boolean go(int x, int y) {
        stack.push("(" + x + "," + y + ")");//先将坐标进栈
        vistied[x][y] = true;//标记进栈的点已走过
        //如果走到终点的话就返回,向上级汇报,可以走通(回溯)
        if (x == exitX && y == exitY) {
            return true;
        }
        //向四个方向遍历
        for (int i = 0; i < direction.length; i++) {//这里是循环四个方向,如果一个方向不通,遍历找四个方向,若一个方向通就可以继续走,若都不通就是且已经回溯完就是都不通
            /*
             * direction = {
             * {-1, 0},
             * {0, 1},
             * {1, 0},
             * {0, -1}}
             * */
            //direction数组是二维数组,里面每个是一维数组,0,0指的是二维数组的第一行,一维数组里面的第一列
            int newX = x + direction[i][0];
            int newY = y + direction[i][1];
            //
            if (isInArea(newX, newY) && isRoad(newX, newY) && !vistied[newX][newY]) {//如果坐标满足在给的9*9矩阵区域里,且在路上,并且没有走过,则继续向下走
                if (go(newX, newY)) {//这里是如果可以走下去,一直调用,可以走下去就返回true(递归)
                    return true;//(回溯)
                }
            }
        }
        stack.pop();//如果不满足,走不下去,则出栈
        return false;
    }
    //坐标在路上
    private static boolean isRoad(int x, int y) {
        return maze[x][y] == 0;//0位置表示是路
    }
    //坐标在区域内
    private static boolean isInArea(int x, int y) {
        return x >= 0 && x < 9 && y >= 0 && y < 9;
    }
}


    注意:此代码中用到的LinkedList类是自己所写的结构,相应的代码中用到就是其中所写的方法,但是本意基本不变,只要有队列和栈的基本的入栈,出栈,入队,出队等一些基本方法即可,用jdk中自带的方法就可以。

结果图

在这里插入图片描述
在这里插入图片描述

  • 14
    点赞
  • 138
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值