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

目录

1.问题引入

2.知识讲解

3.例题解析 

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

题目描述

输入格式

输出格式

样例

输入数据#1

输出数据#1

输入数据#2

输出数据#2

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

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


1.问题引入

c++深搜1-迷宫类问题_第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)点,发现没法可走了,如下图所示。

c++深搜1-迷宫类问题_第2张图片

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

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

 

c++深搜1-迷宫类问题_第3张图片

因为要求了行走顺序是左、上、右、下,所以走到(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 
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”。

c++深搜1-迷宫类问题_第4张图片

【问题分析】

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

#include 
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数组,记录从起点到任何一个点的最短距离,当所有的路径搜索完后,就可以得到起点到终点的最短距离了。 

c++深搜1-迷宫类问题_第5张图片

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

#include
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)点的最短距离为k
    if (x == r && y == c) return ;  //搜到终点,提前结束本轮dfs
    for (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)的最短步数。实际上,到任何一点(如果能走到)的最短步数都算出来了。 

 

你可能感兴趣的:(C++,知识点,作业,c++,算法,Mark1277,深搜-迷宫类问题)