c++深搜1-迷宫类问题

news/2024/11/24 20:42:30/

目录

1.问题引入

2.知识讲解

3.例题解析 

【例题1】迷宫的第一条出路。

题目描述

输入格式

输出格式

样例

输入数据#1

输出数据#1

输入数据#2

输出数据#2

【例题2】迷宫的所有路径。 

【例题3】走出迷宫的最少步数。 


1.问题引入

拓拓小时候玩迷宫游戏时,看到迷宫图都晕头转向,但是聪明的大佬却能每次飞快地找到一条迷宫路径,从而走出迷宫。

拓拓问大佬道:“大佬,你是怎么这么快找到迷宫的路径的?”

大佬道:“写个搜索代码,一下就找到了呀!”

拓拓立马急不可待,道:“大佬快教教我,我也要学!”

同学们,你能帮助拓拓找到右边迷宫的正确路径吗? 

2.知识讲解

下面总结一下基本的代码框架。全局状态变量

void dfs(当前状态) {if (当前状态是目标状态)          // 结束条件进行相应处理;for (所有可行的新状态) {        // 扩展新的状态if (新状态没有访问过 && 需要访问) {dfs(新状态);}}
}
int main() {...dfs(初始状态);...
}

3.例题解析 

【例题1】迷宫的第一条出路。

题目描述

已知一N×N的迷宫,允许往上、下、左、右四个方向行走,现请你按照左、上、右、下顺序进行搜索,也就是 (0,−1),(−1,0),(0,1),(1,0)(0,−1),(−1,0),(0,1),(1,0)。找出第一条从左上角到右下角的路径。

输入格式

输入数据有若干行,第一行有一个自然数N(N≤20),表示迷宫的大小,其后有N行数据,每行有N个0或1(数字之间没有空格,0表示可以通过,1表示不能通过),用以描述迷宫地图。入口在左上角(1,1)处,出口在右下角(N,N)处。所有迷宫保证存在从入口到出口的可行路径。

输出格式

输出数据仅一行,为按照要求的搜索顺序找到的从入口到出口的第一条路径(搜索顺序:左、上、右、下)。

样例

输入数据#1

4
0001
0100
0010
0110

Copy

输出数据#1

(1,1)->(1,2)->(1,3)->(2,3)->(2,4)->(3,4)->(4,4)

Copy

输入数据#2

4
0000
0111
0100
0000

Copy

输出数据#2

(1,1)->(2,1)->(3,1)->(4,1)->(4,2)->(4,3)->(3,3)->(3,4)->(4,4)

【问题分析】

首先需要一个g数组存放迷宫,还需要另一个rec数组存放走过的坐标(其他的数据存储方式也可以,只要能记录点的坐标即可)。那么我们每走到一个点,就可以记录到rec数组中去。因题目要求顺序是左、上、右、下,所以在(1,1)点只能优先向右,到达(1,2)点还是向右,到达(1,3)继续向右,到达(1,4)点,发现没法可走了,如下图所示。

当在(1,4)点发现无路可走时,自动返回到上一层的(1,3)点;同样地,在(1,3)点也无路可走,返回上一层的(1,2)点;在(1,2)点也无路可走,返回上一层的(1,1)点。注意,随着点的返回,也应当让rec数组返回到上一层,若有新的方向可走,就重新记录并覆盖掉之前的错误坐标。这样就可以保证rec数组中存放的是第一条可行的路径。

现在我们回到(1,1)点,还有往下走的方向可行,继续往下搜索。和上面一样,没走到一个新的点,就记录坐标到rec数组中,直到走到终点,就得到了第一条路径。

 

因为要求了行走顺序是左、上、右、下,所以走到(4,3)点时是向左,不能走(左边走过了),向上可以走;到达(3,3)点时,左和上都不能走,所以往右走;到达(3,4)点,左、上、右都不能走,只能往下走,最终到达了终点(4,4)。

总结一下思路:创建rec数组,存放路径。可以继续走的时候,存放下一个点放到rec数组中。当四个方向都不能走的时候,递归回到上一层,rec数组的记录的位置上移一层。这样,就可以覆盖掉错误的路径。当我们走到终点时,就结束。

我们可以用递归函数中的参数k记录到达rec的哪一层。dfs(x,y,k)表示向路径数组rec中第k行的位置,记录一个坐标(x,y)。在递归下一层时,和之前的深搜一样,要传参新的点和新的位置dfs(nx,ny,k+1),存放下一个点。 

参考代码如下:

#include <bits/stdc++.h>
using namespace std;
int n;
char a[25][25];  //存放迷宫
int rec[410][2];  //路径数组,存放迷宫的第一条正确路径
//方向数组:左、上、右、下
int dx[4] = {0, -1, 0, 1};
int dy[4] = {-1, 0, 1, 0};
bool flag = false;  //用于标记搜索结束
void display(int k) {  //自定义函数,用于输出第一条路径for (int i = 1; i <= k; i++) {cout << "(" << rec[i][0] << "," << rec[i][1] << ")";if (i != k) cout << "->";}
}
void dfs(int x, int y, int k) {rec[k][0] = x, rec[k][1] = y; //向rec的第k行记录当前点的坐标(x,y)a[x][y] = '1'; //修改当前点为'1',防止死循环和提高效率if (x == n && y == n) {display(k); //打印路径flag = true; //标记结束搜索}if(flag) return; //第一条路径找到了,就结束 if(flag) exit(0); for (int i = 0; i < 4; i++) { //由点(x,y)扩展 新的点int nx = x + dx[i], ny = y + dy[i];if (a[nx][ny] == '0')  //新的点可以访问dfs(nx, ny, k + 1);}
}
int main() {cin >> n;//n行n列for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)cin >> a[i][j];dfs(1, 1, 1);//调用深搜函数return 0;
}

说明:

(1) rec数组至少要开辟400行,原因是因为最多会有20×20个坐标。

(2) 题目没有判断超过边界,是因为存放迷宫的数组是char类型的全局数组,全部初始化为'\0'(就是ASCII码值为0的字符)。同样地,第0行、第0列、第n+1行、第n+1列也会自动初始化为字符'\0',自然也就不等于字符'0'(ASCII码值为48)。

(3) 因为(2)的原因,建议不要使用0下标开始存放迷宫,而且数组要稍微开大一些。

【例题2】迷宫的所有路径。 

拓拓在森林里游玩,不小心走进了一个迷宫里面,这个迷宫是一个n行m列(n,m≤10)的矩阵,迷宫中有些格子是可以走的,有些格子是不能走的,能走的格子用“o”(小写字母o)表示,不能走的格子用“#”表示。拓拓选择走出迷宫的策略是:先向右,如果右边走不通则选择向下,如果下边走不通则选择向左,如果左边走不通则选择向上;如果四个方向都走不通,则后退选择其他能走的路径。拓拓从类似样例所示的迷宫的左上角(1,1)点进入迷宫(请注意:入口1,1和出口的n,m点都不是#),请问拓拓有哪些方法可以走出迷宫,走到(n,m)点;请编程求出所有可能的路径,输出这些路径,如果不存在任何的路径可以走出迷宫,请输出“no”。

【问题分析】

和上一题一样,我们创建a数组用来存放迷宫,创建rec数组存放路径。不同的是,这一题需要输出所有的路径,所以不能简单的把走过的路标记一下,还要考虑到后面新的路径。所以我们在调用dfs之前,把起点标记一下,然后在dfs的过程中,每次标记走过的点,当回到本层递归时,再取消标记。这样既能保证递归过程中不会往回走,还能保证所有的路径走完后,回到当前点还可以走新的路径。 

#include <bits/stdc++.h>
using namespace std;
char a[15][15];  //存图
int n, m, qx, qy, zx, zy;
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
int rec[110][2];  //存放路径
int cnt;  //路径个数
void dfs(int x, int y, int k) {rec[k][0] = x, rec[k][1] = y;  //向rec数组中第k层存放点(x,y)if (x == n && y == m) {  //判断是否到达终点cout << ++cnt << ":";for (int i = 1; i < k; i++)cout << rec[i][0] << "," << rec[i][1] << "->";cout << rec[k][0] << "," << rec[k][1] << endl;return ;}for (int i = 0; i < 4; i++) {  //扩展新的点int nx = x + dx[i], ny = y + dy[i];if (a[nx][ny] == 'o') {  a[nx][ny] = '#';    //已经走过,标记为不可通行dfs(nx, ny, k + 1);a[nx][ny] = 'o';    //全部走完,取消标记}}
}
int main() {cin >> n >> m;for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++)cin >> a[i][j];a[1][1] = '#';//注意:起始点需要在dfs之前标记为已走过=dfs(1, 1, 1);if (cnt == 0) cout << "no";return 0;
}

说明:(1) 题目中,可以不需要判断是否超过边界,原因和上一题的说明一样。

(2) main函数调用dfs函数之前,需要提前标记起点,否则在递归的过程中,可能再次回到起点,把起点存储两次。

(3) 因为是输出所有的路径,需要dfs函数把所有可能的情况都递归搜索一遍,所以不需要提前结束dfs函数。

【例题3】走出迷宫的最少步数。 

一个迷宫由R行C列格子组成,有的格子里有障碍物,不能走;有的格子是空地,可以走。给定一个迷宫,求从左上角走到右下角最少需要走多少步(数据保证一定能走到)。只能在水平方向或垂直方向走,不能斜着走。空地格子用'.'表示,有障碍物的格子用'#'表示。计算步数要包括起点和终点。

输入数据:

4 
....
....
.###
....

输出数据:

17

【问题分析】

首先需要一个char数组存放迷宫,考虑到每次递归搜索都要记录起点到当前点的步数,我们可以再开辟一个step数组,记录从起点到任何一个点的最短距离,当所有的路径搜索完后,就可以得到起点到终点的最短距离了。 

如果走到某个点(x,y)需要的步数,比step数组中记录的最少步数还少,则按照新的方案走该点。注意到step数组的每个元素的初始值可以设置为INT_MAX。最终step数组记录了从起点到每个点至少需要多少步,step[n][m]就是最终结果。

#include<bits/stdc++.h>
using namespace std;
int r, c;
char a[50][50]; //地图
int step[50][50]; //存步数
int dx[] = {0, 0, 1, -1}; //没规定方向,保证是四个正确的方向即可
int dy[] = {1, -1, 0, 0};
void dfs(int x, int y, int k) {step[x][y] = k;  //记录到达(x,y)点的最短距离为kif (x == r && y == c) return ;  //搜到终点,提前结束本轮dfsfor (int i = 0; i < 4; i++) {int nx = x + dx[i], ny = y + dy[i];// 确保新的路径走到点(nx,ny)的步数比之前step数组中存储的步数要小if (a[nx][ny] == '.' && k + 1 < step[nx][ny]) dfs(nx, ny, k + 1);}
}
int main() {cin >> r >> c;for (int i = 1; i <= r; i++)for (int j = 1; j <= c; j++) {cin >> a[i][j];step[i][j] = INT_MAX;}dfs(1, 1, 1);cout << step[r][c];return 0;
}

说明:(1) int dx[],创建一维数组时不写大小,程序会自动分析赋值的内容开辟空间。

(2) k + 1 < step[nx][ny],此次的走法到达点(nx,ny)是k+1步,如果比之前某种最短的走法step[nx][ny]的步数更短,才选择这种走法。否则,不需要选择新的走法,选择之前最短的走法step[nx][ny]更好。

(3) 在调用搜索函数dfs之前,记得给step数组的所有元素赋值为INT_MAX。

(4) 调用dfs函数时,第三个参数初始应当给1,而不是0,是因为题目要求了起点也要计算步数。

(5) 最终step[r][c]存放的就是从左上角(1,1)到(r,c)的最短步数。实际上,到任何一点(如果能走到)的最短步数都算出来了。 

 


http://www.ppmy.cn/news/332321.html

相关文章

Hazel游戏引擎(013)Layers游戏的层级

文中若有代码、术语等错误&#xff0c;欢迎指正 文章目录 前言增加Layer后的主要类图项目相关代码项目流程效果 LayerStack类的错误 前言 此节目的 为完成008事件系统设计的第四步&#xff0c;将事件从Application传递分发给Layer层。 使引擎事件系统模块完整 Layer的理解 …

Docker常用基本命令

一、docker的基础命令 1、启动docker systemctl start docker 2、关闭docker systemctl stop docker 3、重启docker systemctl restart docker 4、设置docker开机自启动 systemctl enable docker 5 &#xff0c; 查看docker运行状态&#xff08;显示绿色代表正常启动…

MACHENIKE 机械师F117 B1 8代i7独显全面屏游戏本轻薄笔记本电脑怎么样?

机械师F117-B1【猎空】全面屏 立省500元 全面屏 RGB机械键盘 i7-8750H处理器/GTX 1050Ti 4GB/256GB PCIe SSD/金属拉丝机身/RGB 炫彩机械键盘/15.6”超窄边框屏/20mm AC金属拉丝设计/发光logo及CNC钻孔科技蓝灯带/跑分27万 官方活动链接:https://s.click.taobao.com/OhS34Rw …

雷神ZERO游戏本和ROG冰刃5Plus的 区别 选哪个

雷神ZERO搭载 16.0 英寸 16:10 窄边框全面屏&#xff0c;具有 2.5K 高分辨率、165Hz 刷新率&#xff0c;同时支持 100% sRGB 色域&#xff0c;500nit 高亮度。选雷神ZERO游戏本还是ROG冰刃5Plus这些点很重要看过你就懂了http://www.adiannao.cn/dy 此外&#xff0c;该机采用风…

第一次“盲硬件知识”拆机——固态硬盘的新发现

一图胜千言&#xff0c;图1三星固态硬盘&#xff08;型号&#xff1a;Samsung MZVLW256HEHP PM961 256GB M.2 NVMe PCIe Internal SSD - OEM&#xff09;图1 三星固态硬盘 图2 佳翼 X16 PCIE 3.0 m.2 NVME满速M-Key扩展GEN3转接卡pci-e 金金燕MX16 图2 固态硬盘转接卡 最初&a…

Linux查看硬盘属性(机械硬盘/固态硬盘)

通过命令lsblk -d -o name,rota查看&#xff0c;0表示固态硬盘&#xff0c;1表示机械硬盘&#xff0c;sda为机械硬盘&#xff0c;sdb为固态硬盘。

购买固态硬盘的参数

1: 购买固态硬盘的参数参数一: 总线 什么是总线: 总线是&#xff1a; 计算机各种功能部件之间传毒数据信息的公共通信干线。。数据从固态硬盘中拿出来需要走的路。参数二: 硬盘到CPU 之间需要走的路: 总线SATA (乡间小路, 最快550m/s);总线: PCIE (告诉公路, 最快路线) …

笔记本固态硬盘温度测试软件,台式电脑ssd固态硬盘温度多少算正常?查看ssd固态硬盘温度的方法...

‍ ‍   我们都知道ssd固态硬盘即固态电子存储阵列硬盘&#xff0c;其接口的规范和定义、功能及使用方法上与普通硬盘的完全相同&#xff0c;在产品外形和尺寸上也完全与普通硬盘一致。台式电脑ssd固态硬盘温度一直是大家关心的&#xff0c;如果温度太高会断电容易导致数据丢…