DFS练习-迷宫(最短路径)问题详解 一波三折 图片+文字
dfs迷宫问题的详细解析,图文并茂,包含洛谷acwing上的两题。
如果你没有dfs基础,可以先看看我的前2篇文章
C语言递归+DFS(深度优先搜索算法)详解 图文并茂,手把手教你画树状图
一.题目描述
我们先来解决迷宫问题的初级版。(洛谷B3625)
二.图文解析
我们先用一个样例来分析一下迷宫怎么走(灰色阴影代表墙不能走)
。
我们首先需要明白dfs的特点:不撞南墙不回头。对于一条路径,它会从起点一直走到此路不通的位置或者终点。但对于任意一个位置,有上下左右四个方向,程序又该怎么走呢?所以我们需要规定一个顺序->右下左上
准备工作做好了,我们就可以自己模拟程序的思路来走走迷宫了。
(走到一个位置就打勾,标记该位置已经走过,对应代码里该位置的状态设置为1)
像图片这样,我们成功获得了其中一条路径。我们再继续模拟一遍回溯。
(蓝色线代表回溯的过程,回溯到一个位置,该位置就打个半勾—>标记为未走过)
三.代码实现
①我们立马想到dfs的cp——状态数组
在迷宫问题里,状态数组就来存储一个位置到底有没有走过。
int state[101][101] = { 0 };//状态数组,令0代表未走,1代表走了
②怎么实现程序前进的效果呢
每一个位置都对应一个坐标,那么当位置改变,对应的其实就是坐标的改变。
每前进一步,我们只需把下一步的坐标传给dfs函数就行啦
③完整代码
int m,n;
char maze[101][101];//存放输入的迷宫
int state[101][101] = { 0 };//状态数组,令0代表未走,1代表走了
void dfs(int x, int y, int count)
{
if (x == n && y == m)
{
printf("Yes\n");
exit(0);
}
//右
if (maze[x + 1][y] == '.' && state[x + 1][y] == 0)
{
state[x + 1][y] = 1;
dfs(x + 1, y, count + 1);
state[x + 1][y] = 0;
}
//下
if (maze[x][y - 1] == '.' && state[x][y - 1] == 0)
{
state[x][y - 1] = 1;
dfs(x, y - 1, count + 1);
state[x][y - 1] = 0;
}
//左
if (maze[x - 1][y] == '.' && state[x - 1][y] == 0)
{
state[x - 1][y] = 1;
dfs(x - 1, y, count + 1);
state[x - 1][y] = 0;
}
//上
if (maze[x][y + 1] == '.' && state[x][y + 1] == 0)
{
state[x][y + 1] = 1;
dfs(x, y + 1, count + 1);
state[x][y + 1] = 0;
}
return;
}
int main()
{
scanf("%d %d", &n, &m);
getchar();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
scanf("%c", &maze[i][j]);
}
getchar();//getchar是为了吃掉换行符
}
dfs(1, 1, 0);
printf("No\n");
return 0;
}
四.代码优化—方向数组
观察上方代码,右下左上的代码雷同,不够简洁美观。如何达到简洁的效果呢?我们想到dfs的一个老朋友——for循环。在这里,我们还需根据上方画的方位图,搭配以下的方向数组。
int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };//顺时针试探
for (int i = 0; i <=3; i++)
{
int tx, ty;
tx = x + dx[i];
ty = y + dy[i];
if (maze[tx][ty] == '.' && state[tx][ty] == 0)
{
state[tx][ty] = 1;
dfs(tx, ty);
state[tx][ty] = 0;
}
}
附上优化后的完整代码
int m,n;
char maze[101][101];//存放输入的迷宫
int state[101][101] = { 0 };//状态数组,令0代表未走,1代表走了
//方向数组版
int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
void dfs(int x, int y)
{
if (x == n && y == m)
{
printf("Yes\n");
exit(0);
}
//顺时针试探
for (int i = 0; i <=3; i++)
{
int tx, ty;
tx = x + dx[i];
ty = y + dy[i];
if (maze[tx][ty] == '.' && state[tx][ty] == 0)
{
state[tx][ty] = 1;
dfs(tx, ty);
state[tx][ty] = 0;
}
}
return;
}
int main()
{
scanf("%d %d", &n, &m);
getchar();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
scanf("%c", &maze[i][j]);
}
getchar();
}
dfs(1, 1);
printf("No\n");
return 0;
}
五.震惊!洛谷竟然不给通过
当你把以上代码信心满满地提交给洛谷,你会发现有6个测试点都超时了!!!这怎么肥事!!!
实际上,你只需把恢复现场的那行代码删掉即可AC。
state[tx][ty] = 0;
同时强调,回溯≠恢复现场
为什么呢?这需要好好讲讲。其实结合迷宫和dfs的特点我们可以想到,当dfs已经把一条路探寻完了开始回溯,其实已经意味着这条路找不到出口。如果我们还在回溯的过程中又恢复现场,那么以后程序可能还会再走一遍走这条路!一旦迷宫大起来,将是多出极大量的无用功。我们结合下面这张图感受一下。
六.将此题小小升级,找出最短路径的步数,终点改为任意
dfs一次只能探一条路,要找出最短路径,意味着要把所有通路都找出来,然后比较路径,得出最终结果。那么我们只需从如下两个方面改动以上的代码:
①修改程序退出条件,要求程序结束时把所有路都走了一遍。
去除exit(0),改为将当前步数与上一条路径的步数比较。
②添加计数功能,并实现得出最小值的要求
我们直接多传一个计数功能的参数给dfs函数。添加一个最小值变量,每次走到终点都与上次步数相比。
不是很难,我们来看看最终代码》》》
int m,n;
int min = 999999;
char maze[101][101];//存放输入的迷宫
int state[101][101] = { 0 };//状态数组,令0代表未走,1代表走了
int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };
void dfs(int x, int y, int count)
{
if (x == n && y == m)
{
if (count < min)
{
min = count;
}
return;
}
for (int i = 0; i <=3; i++)
{
int tx, ty;
tx = x + dx[i];
ty = y + dy[i];
if (maze[tx][ty] == '.' && state[tx][ty] == 0)
{
state[tx][ty] = 1;
dfs(tx, ty,count+1);
}
}
return;
}
int main()
{
scanf("%d %d", &n, &m);
getchar();
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
scanf("%c", &maze[i][j]);
}
getchar();
}
dfs(1, 1, 0);
printf("%d\n",min);
return 0;
}
acwing上没提交过,不知道能不能过,但是既然已经涉及到了最短路径,咱们最好还是用bfs写吧,下篇文章给大家带来bfs的写法~
好了,这篇文章就到这里啦,有任何问题请留言。给个点赞关注,就是对我的最大鼓励啦,谢谢~
更多推荐
所有评论(0)