详解Java利用深度优先遍历解决迷宫问题

目录
  • 什么是深度优先
  • 一个简单的例子
  • 程序实现

什么是深度优先

什么是深度,即向下,深度优先,即向下优先,一口气走到底,走到底发现没路再往回走。

在算法实现上来讲,深度优先可以考虑是递归的代名词,深度优先搜索必然需要使用到递归的思路。

有的人可能会说了,我可以用栈来实现,以迭代的方式,那么问题来了,栈这种数据结构,同学们认为是否也囊括了递归呢?Java语言的方法区本身也是实现在一个栈空间上的。

一个简单的例子

我们以一个简单的迷宫为例,以1代表墙,0代表路径,我们构造一个具有出入口的迷宫。

1 1 0 1 1 1 1 1 1

1 0 0 0 0 0 0 1 1

1 0 1 1 1 1 0 1 1

1 0 0 0 0 1 0 0 1

1 1 1 1 1 1 1 0 1

以上面这个0为入口,下面这个0为出口,那么深度优先的算法遍历顺序,方向的遍历顺序为左下右上,以dp[0][2]为入口,我把这个过程列在下面了:

第一步:

dp[0][2] -> dp[1][2]

第二步:

dp[1][2] -> dp[1][1]

第三步:

dp[1][1] -> dp[2][1]

第四步:

dp[2][1] -> dp[3][1]

第五步:

dp[3][1] -> dp[3][2]

第六步:

dp[3][2] -> dp[3][3]

第七步:

dp[3][3] -> dp[3][4]

第八步:

dp[3][4] -> dp[3][5] 由于 dp[3][5]是墙,所以深度优先算法需要开始回退,最终会回退到dp[1][2]这个位置,然后向右走

第八步:

dp[1][2] -> dp[1][3]

第九步:

dp[1][3] -> dp[1][4]

第十步:

dp[1][4] -> dp[1][5]

第十一步:

dp[1][5] -> dp[1][6]

第十二步:

dp[1][6] -> dp[2][6]

第十三步:

dp[2][6] -> dp[3][6]

第十四步:

dp[3][6] -> dp[3][7]

第十五步:

dp[3][7] -> dp[4][7] 终点,程序退出

可以发现,深度优先算法有点像我们的人生,需要不断试错,错了就退,直到找到一条通往出口的路。

现在让我们动手用代码实现一下上面的步骤吧。

程序实现

以深度优先的方式解决这个问题,主要考虑两点,首先是如何扩展节点,我们的顺序是左,下,右,上,那么,应该以什么样的方式实现这个呢?第二点,就是如何实现深度优先,虽然原理上肯定是递归,但是应该如何递归呢?要解决这两个问题,请看示例代码,以Java为例:

package com.chaojilaji.book;

import com.chaojilaji.book.util.InputUtils;

import java.util.HashSet;
import java.util.Set;

import static com.chaojilaji.book.util.CheckUtils.canAdd;

public class Dfs {

    public static Integer dfs(String[][] a, int currentX, int currentY, int chux, int chuy, Set<Integer> cache) {
        System.out.println(currentY + " " + currentX);
        if (currentX == chux && currentY == chuy) {
            return 1;
        }
        // TODO: 2022/1/11 枚举子节点,左 下 右 上
        int[] x = new int[]{-1, 0, 1, 0};
        int[] y = new int[]{0, 1, 0, -1};
        for (int i = 0; i < 4; i++) {
            if (canAdd(a, currentX + x[i], currentY + y[i], cache)) {
                Integer tmp = dfs(a, currentX + x[i], currentY + y[i], chux, chuy, cache);
                if (tmp != 0) {
                    System.out.println(currentY + " " + currentX + " 结果路径");
                    return tmp + 1;
                }
            }
        }
        System.out.println(currentY + " " + currentX + " 回滚");
        return 0;
    }

    public static Integer getAns(String[][] a) {
        int m = a[0].length;
        int n = a.length;
        int rux = -1, ruy = 0;
        int chux = -1, chuy = n - 1;
        for (int i = 0; i < m; i++) {
            if (a[0][i].equals("0")) {
                // TODO: 2022/1/11 找到入口
                rux = i;
            }
            if (a[n - 1][i].equals("0")) {
                chux = i;
            }
        }
        Set<Integer> cache = new HashSet<>();
        cache.add(rux * 100000 + ruy);
        System.out.println("打印行走过程");
        return dfs(a, rux, ruy, chux, chuy, cache)-1;
    }

    public static void demo() {
        String x = "1  1  0  1  1  1  1  1  1\n" +
                "1  0  0  0  0  0  0  1  1\n" +
                "1  0  1  1  1  1  0  1  1\n" +
                "1  0  0  0  0  1  0  0  1\n" +
                "1  1  1  1  1  1  1  0  1";
        String[][] a = InputUtils.getInput(x);
        Integer ans = getAns(a);
        System.out.println(ans == -1 ? "不可达" : "可达,需要行走" + ans + "步");

    }

    public static void main(String[] args) {
        demo();
    }

}

这里的canAdd方法是临界判断函数,如下:

/**
     * 临界判断
     * @param a
     * @param x
     * @param y
     * @param cache
     * @return
     */
public static Boolean canAdd(String[][] a, Integer x, Integer y, Set<Integer> cache) {
    int m = a[0].length;
    int n = a.length;
    if (x < 0 || x >= m) {
        return false;
    }
    if (y < 0 || y >= n) {
        return false;
    }
    if (a[y][x].equals("0") && !cache.contains(x * 100000 + y)) {
        cache.add(x * 100000 + y);
        return true;
    }
    return false;
}

可以瞧见,这里面最核心的代码在于dfs这个函数,让我们来深入分析一波

public static Integer dfs(String[][] a, int currentX, int currentY, int chux, int chuy, Set<Integer> cache) {
    System.out.println(currentY + " " + currentX);
    if (currentX == chux && currentY == chuy) {
        return 1;
    }
    // TODO: 2022/1/11 枚举子节点,左 下 右 上
    int[] x = new int[]{-1, 0, 1, 0};
    int[] y = new int[]{0, 1, 0, -1};
    for (int i = 0; i < 4; i++) {
        if (canAdd(a, currentX + x[i], currentY + y[i], cache)) {
            Integer tmp = dfs(a, currentX + x[i], currentY + y[i], chux, chuy, cache);
            if (tmp != 0) {
                System.out.println(currentY + " " + currentX + " 结果路径");
                return tmp + 1;
            }
        }
    }
    System.out.println(currentY + " " + currentX + " 回滚");
    return 0;
}

首先,dfs深度优先,首先应该写的是判断终止条件,这里的终止条件就是到达终点,即目前的横纵坐标等于出口的横纵坐标。

然后,我们利用两个方向数组作为移动方案,也就是

// TODO: 2022/1/11 枚举子节点,左 下 右 上
    int[] x = new int[]{-1, 0, 1, 0};
    int[] y = new int[]{0, 1, 0, -1};
    for (int i = 0; i < 4; i++) {
        if (canAdd(a, currentX + x[i], currentY + y[i], cache)) {
        }
    }

这种方法,是数组类型的移动方式的兼容写法,不管你的移动方向有多少,都可以配在x和y两个数组中。定义了四个方向,现在我们需要思考递归的过程。

既然我完成的时候是返回1,那么其实如果在这条路上的所有都应该加1,所以,就有了下面的判断

if (canAdd(a, currentX + x[i], currentY + y[i], cache)) {
    Integer tmp = dfs(a, currentX + x[i], currentY + y[i], chux, chuy, cache);
    if (tmp != 0) {
        System.out.println(currentY + " " + currentX + " 结果路径");
        return tmp + 1;
    }
}

当子dfs出来的结果不为0,说明该子dfs是可以到达出口的,那么直接把结果加1返回给上层即可。如果子dfs出来的结果为0,说明该子dfs是不能到达出口的,就直接返回0即可。

到此这篇关于详解Java利用深度优先遍历解决迷宫问题的文章就介绍到这了,更多相关Java深度优先遍历内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深度优先与广度优先Java实现代码示例

    在编程生活中,我们总会遇见树性结构,这几天刚好需要对树形结构操作,就记录下自己的操作方式以及过程.现在假设有一颗这样树,(是不是二叉树都没关系,原理都是一样的) 1.深度优先 英文缩写为DFS即Depth First Search. 深度优先搜索是一种在开发爬虫早期使用较多的方法.它的目的是要达到被搜索结构的叶结点(即那些不包含任何超链的HTML文件) .在一个HTML文件中,当一个超链被选择后,被链接的HTML文件将执行深度优先搜索,即在搜索其余的超链结果之前必须先完整地搜索单独的一条链.深度

  • Java实现二叉树的深度优先遍历和广度优先遍历算法示例

    本文实例讲述了Java实现二叉树的深度优先遍历和广度优先遍历算法.分享给大家供大家参考,具体如下: 1. 分析 二叉树的深度优先遍历的非递归的通用做法是采用栈,广度优先遍历的非递归的通用做法是采用队列. 深度优先遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次.要特别注意的是,二叉树的深度优先遍历比较特殊,可以细分为先序遍历.中序遍历.后序遍历.具体说明如下: 先序遍历:对任一子树,先访问根,然后遍历其左子树,最后遍历其右子树. 中序遍历:对任一子树,先遍历其左子树,然

  • Java编程实现深度优先遍历与连通分量代码示例

    深度优先遍历 深度优先遍历类似于一个人走迷宫: 如图所示,从起点开始选择一条边走到下一个顶点,没到一个顶点便标记此顶点已到达. 当来到一个标记过的顶点时回退到上一个顶点,再选择一条没有到达过的顶点. 当回退到的路口已没有可走的通道时继续回退. 而连通分量,看概念:无向图G的极大连通子图称为G的连通分量( Connected Component).任何连通图的连通分量只有一个,即是其自身,非连通的无向图有多个连通分量. 下面看看具体实例: package com.dataStructure.gra

  • Java编程实现基于图的深度优先搜索和广度优先搜索完整代码

    为了解15puzzle问题,了解了一下深度优先搜索和广度优先搜索.先来讨论一下深度优先搜索(DFS),深度优先的目的就是优先搜索距离起始顶点最远的那些路径,而广度优先搜索则是先搜索距离起始顶点最近的那些路径.我想着深度优先搜索和回溯有什么区别呢?百度一下,说回溯是深搜的一种,区别在于回溯不保留搜索树.那么广度优先搜索(BFS)呢?它有哪些应用呢?答:最短路径,分酒问题,八数码问题等.言归正传,这里笔者用java简单实现了一下广搜和深搜.其中深搜是用图+栈实现的,广搜使用图+队列实现的,代码如下:

  • java图的深度优先遍历实现随机生成迷宫

    最近经常在机房看同学在玩一个走迷宫的游戏,比较有趣,自己也用java写一个实现随机生成迷宫的算法,其实就是一个图的深度优先遍历算法.基本思想就是,迷宫中的每个点都有四面墙,然后呢. 1.从任意一点开始访问(我的算法中固定是从(0,0)点开始),往四个方向中的随机一个访问(每访问到一个可访问的点,就去掉该点的那个方向的墙),被访问点继续以这种方识向下进行访问. 2.对每个被访问的点都被标识为已访问,当一个点对某个方向进行访问时我们首先会判断被访问点是否已被访问,或者触到边界.如果该点四个方向皆已访

  • Java基于深度优先遍历的随机迷宫生成算法

    这两天因为要做一个随机的地图生成系统,所以一直在研究随机迷宫生成算法,好吧,算是有一点小小的成果. 随机迷宫生成我自己的理解简而言之分为以下几步: 1.建立一张地图,我用的二维数组表示,地图上全是障碍物.然后再创建一个用来表示每个格子是否被访问过的二维数组.再创建一个用来表示路径的栈结构. 2.随机选择地图上的一点,呃为了方便我初始点直接取的是左上角即坐标表示为0,0的格子.终点的话因为不涉及到交互就暂时没有. 3.查找当前格子的邻接格(注意,这里的邻接格子都是还未被访问的,下面的代码里有写).

  • 详解Java利用深度优先遍历解决迷宫问题

    目录 什么是深度优先 一个简单的例子 程序实现 什么是深度优先 什么是深度,即向下,深度优先,即向下优先,一口气走到底,走到底发现没路再往回走. 在算法实现上来讲,深度优先可以考虑是递归的代名词,深度优先搜索必然需要使用到递归的思路. 有的人可能会说了,我可以用栈来实现,以迭代的方式,那么问题来了,栈这种数据结构,同学们认为是否也囊括了递归呢?Java语言的方法区本身也是实现在一个栈空间上的. 一个简单的例子 我们以一个简单的迷宫为例,以1代表墙,0代表路径,我们构造一个具有出入口的迷宫. 1

  • 详解Java利用同步块synchronized()保证并发安全

    本文实例为大家分享了Java利用同步块synchronized()保证并发安全的具体代码,供大家参考,具体内容如下 package day10; /** * 同步块 * 有效地缩小同步范围 * 可以在保证并发安全的同时尽可能提高并发效率 * * 实例:模拟两个人同时进店买衣服,为提高效率 * 只在试衣服阶段进行同步排队过程,其他阶段无需排队. * @author kaixu * */ public class SyncDemo2 { public static void main(String[

  • 详解Java利用ExecutorService实现同步执行大量线程

    自从java1.5以后,官网就推出了Executor这样一个类,这个类,可以维护我们的大量线程在操作临界资源时的稳定性. 先上一段代码吧: TestRunnable.java public class TestRunnable implements Runnable { private String name; public TestRunnable(String name) { this.name = name; } @Override public void run() { while (t

  • 详解Java利用实现对称加密(DES、3DES、AES)

    有两句话是这么说的: 1)算法和数据结构就是编程的一个重要部分,你若失掉了算法和数据结构,你就把一切都失掉了. 2)编程就是算法和数据结构,算法和数据结构是编程的灵魂. 注意,这可不是我说的,是无数程序员总结的,话说的很实在也很精辟,若想长久可持续发展,多研究算法还是很有必要的,今天我给大家说说加密算法中的对称加密算法,并且这里将教会大家对称加密算法的编程使用.包含DES.3DES和AES三种对称加密算法的编程使用,干货满满. 1.对称密码算法 对称密码算法是当今应用范围最广,使用频率最高的加密

  • Java利用深度搜索解决数独游戏详解

    目录 一.问题描述 二.输入和输出 三.输入和输出样例 四.分析 五.算法设计 六.代码 七.测试 一.问题描述 数独是一项非常简单的任务.如下图所示,一张 9 行 9 列的表被分成 9 个 3*3 的小方格.在一些单元格中写上十进制数字 1~9,其他单元格为空.目标是用 1 ~9 的数字填充空单元格,每个单元格一个数字,这样在每行.每列和每个被标记为 3*3 的子正方形内,所有 1~9 的数字都会出现.编写一个程序来解决给定的数独任务. 二.输入和输出 1.输入 对于每个测试用例,后面都跟 9

  • 详解Java删除Map中元素java.util.ConcurrentModificationException”异常解决

    今天在使用map并需要根据某些条件删除map元素时,自然而然想到调用Map中的remove(Object key)函数进行删除,代码如下: //遍历map,如果key<5,那么就删除此元素. Map<Integer, Integer> users = new LinkedHashMap<Integer, Integer>(); for (Map.Entry<Integer,Integer> entry : users.entrySet()){ for (int i

  • 详解java解决XSS攻击常用方法总结

    前言 在项目验收阶段,通常会对待验收项目做一些安全漏洞的测试,比如接口攻击,并发测试,XSS注入,SQL恶意注入测试,安全越权等操作,这时,就是考验项目的安全方面是否做的足够健壮的时候,本篇对XSS脚本攻击在实际WEB项目中的处理办法,提供2种可实行的方法 xss攻击 XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序.这些恶意网页程序通常是JavaScript,但实际上也可以包括Java. VBScript.Acti

  • 详解Java 二叉树的实现和遍历

    目录 什么是二叉树 二叉树建树 前序建树 中序建树 后序建树 二叉树的遍历 什么是二叉树 简单理解为对于一个节点来说,最多拥有一个上级节点,同时最多具备左右两个下级节点的数据结构. 由于很多排序算法都是基于二叉树实现的,多叉树也是二叉树延伸过去的,所以二叉树的建树和遍历就显得非常重要. 二叉树建树 一般情况是给你一个串,要求让你以前序,中序,后序的方式建树.那么此时我们就需要首先了解三个概念: 前序遍历 中序遍历 后序遍历 我们来看看一棵二叉树的结构: 0 1 2 3 4 5 6 0就是整个二叉

  • 详解如何利用Python绘制迷宫小游戏

    目录 构思 绘制迷宫 走出迷宫 完整代码 更大的挑战 关于坐标系设置 周末在家,儿子闹着要玩游戏,让玩吧,不利于健康,不让玩吧,扛不住他折腾,于是想,不如一起搞个小游戏玩玩! 之前给他编过猜数字 和 掷骰子 游戏,现在已经没有吸引力了,就对他说:“我们来玩个迷宫游戏吧.” 果不其然,有了兴趣,于是和他一起设计实现起来,现在一起看看我们是怎么做的吧,说不定也能成为一个陪娃神器~ 先一睹为快: 构思 迷宫游戏,相对比较简单,设置好地图,然后用递归算法来寻找出口,并将过程显示出来,增强趣味性. 不如想

  • 详解Java 二叉树的实现和遍历

    目录 什么是二叉树 二叉树建树 前序建树 中序建树 后序建树 二叉树的遍历 什么是二叉树 简单理解为对于一个节点来说,最多拥有一个上级节点,同时最多具备左右两个下级节点的数据结构. 由于很多排序算法都是基于二叉树实现的,多叉树也是二叉树延伸过去的,所以二叉树的建树和遍历就显得非常重要. 二叉树建树 一般情况是给你一个串,要求让你以前序,中序,后序的方式建树.那么此时我们就需要首先了解三个概念: 前序遍历 中序遍历 后序遍历 我们来看看一棵二叉树的结构: 0 1 2 3 4 5 6 0就是整个二叉

随机推荐