目录
一、情景导入
今天我们来学习一下DFS-深度优先探索的经典问题---DFS解决走迷宫的问题,希望我们能在解决问题的过程中去理解DFS算法。
那么什么是DFS?
通俗点说从一个顶点开始,沿着一条路径一直搜索到该路径的尽头(即无法再搜索下去),然后回溯到上一个顶点,继续搜索下一条路径,直到所有路径都被遍历完,其实也就是一种递归加回溯的思想。
那么这就是是DFS的伪代码
那么我们可以清楚的看到DFS的设计步骤包括
-
确定该题目的状态(包括边界)
-
找到状态转移方式
-
找到问题的出口,计数或者某个状态
-
设计搜索
二、问题描述
下面我们通过一个例题来学习一下DFS在走迷宫问题中的经典
处于起点(1,1)的小明想去寻找二维迷宫中处于终点(p,q)的小红,二维迷宫中有许多的障碍物,请你设计一种算法,使得小明可以绕过这些障碍物最终找到小红,并且最后输出小明走最少的步数;
现在输入要求如下:
第一行输入n*m大小的二维迷宫大小;
第二行输入小红的终点坐标;
第三行输入二维迷宫的地图;(用0表示可以通行,1表示不能通行)
三、解决思路
这类题很明显可以用上我们的DFS去解决,我们去遍历小明走过路径的所有选择,将错误的路径回溯,直到找到正确的路径为止。
我们先来准备工作:
tips:使用全局变量在许多问题中会很方便,因为它的生存周期长,不用在主函数传入所写的DFS()中
解释一下:
1.#include<bits/stdc++.h>---c++的万能头文件,相当于一个很大的集合,不过用的时候得注意,其中包含的标识符可能会与你定义的变量名冲突。
2.using namespace std----它告诉编译器你想要在当前的作用域(通常是源文件或函数)中使用std
(标准)命名空间中的名称,而不需要每次都加上std::
前缀。
3.方向数组是为了让小明在面临每一次选择时能上下左右去考虑
主函数:
解释一下:
1.在C++中,我们经常使用标准库中的cout与cin
对象来输出\输入信息到控制台,其实可以理解为printf()与scanf(),不过可以使你的输入输出更加简洁
DFS()算法:
过程分析:
1.DFS()函数接收三个形参,前两个是小明的当前坐标x,y,后一个是小明当前所走的步数,因为小明的起始坐标固定在(1,1),所以我们将book这个用来记录小明走过的路径的二维数组的起点也设置为1,防止小明在选择时走回原路。
2.if()的dp判断是每一次小明递归的首先检查条件,检查小明的坐标当前是否满足终点坐标(p,q),这是每一次递归的最优先,不能将它移位
3.临时变量tx,ty的生存周期就在DFS中,其主要作用是通过方向数组,赋予小明下一步是向左,还是向右,向上还是向下,因为方向数组用for()循环推动,所以在当前step中不会重复
4.check()条件就是if()中的边界判断,判断tx、ty是否越界,如果越界就不执行下面的语句,从而重新执行方向数组的for()语句,从而选择另一个不满足check(),也就是我们期望的下一步去探寻终点坐标
5.book数组理解:book
数组用于标记在深度优先搜索(DFS)过程中哪些格子已经被访问过。这种机制对于防止DFS重复访问相同的格子以及进行“回溯”至关重要它包括:
-
访问格子:
- 当DFS函数
dfs(int x, int y, int step)
访问一个格子(x, y)
时,它首先会检查book[x][y]
是否为0。如果是,则标记该格子为已访问(book[x][y] = 1
),然后递归地探索从该格子出发的所有可能路径。
- 当DFS函数
-
递归探索:
- 在递归探索过程中,DFS会尝试所有四个方向(上、下、左、右)。对于每个方向,它都会计算新的坐标
(tx, ty)
,并检查这些坐标是否有效(即没有越界且该格子未被访问过,并且该格子在迷宫中是可通行的,即ans[tx][ty] == 0
)。 - 如果满足这些条件,DFS会再次调用自身,继续探索从
(tx, ty)
出发的路径。
- 在递归探索过程中,DFS会尝试所有四个方向(上、下、左、右)。对于每个方向,它都会计算新的坐标
-
回溯:
- 当从
(tx, ty)
出发的所有可能路径都已经被探索过后(即所有四个方向都已经尝试过),DFS会返回到(x, y)
。此时,为了避免在下一次从(x, y)
出发时再次探索(tx, ty)
,需要将(tx, ty)
标记为未访问(book[tx][ty] = 0
)。这就是所谓的“回溯”。 - 通过将已访问的格子标记为未访问,DFS可以确保在下一次从
(x, y)
出发时能够探索其他未访问的格子
- 当从
最后的答案如下:
湖北师范大学计算机信息与工程学院-马列
湖师计信学科小组