迷宫路径打印问题详解

前言:

   之前一直不理解打印多条路径的时候在回溯时要把已访问的点设为未访问,还有路径打印的具体过程。

一、题目描述

问题:给出一个n*n的迷宫,起点为(0,0),终点为(n-1,n-1).可以向上、下、左、右四个方向走.

任务:1.判断是否有可行路径

           2.如果有可行路径,输出所有的可行路径.

二、解题要点:

1.找到符合要求的邻接点,对其相应操作

         由题意可知:符合要求的邻接点位于当前点的上下左右四个方向,并且邻接点的位置为0,并且不能出界,并且没有被访问过,即:

if(0<=nx && nx<n && 0<=ny && ny<m && maze[nx][ny]==0 && vis[nx][ny]==0)

    对邻接点进行相应操作:将访问过的点置为已访问,并且由于要打印路径,将其放入栈内。

2.找到解或者走不下去的时候进行相应操作:

          找到解的情况:某个点走到的位置是终点位置,此时要打印路径。并且由于本题是找到多条路径,所以在找到一条路径后,我们要利用return语句进行回溯操作,继续找其它路径。

    走不下去的情况:如果相邻点出界,,我们就利用return语句回溯到上一个点。(为什么需要写这个,后面的if语句不是已经限制了不能出界吗,表面根本没有去试探这个点的可能性,所以我感觉不需要回溯。如果说if语句中没有写这个,那我们可以进行回溯操作。

3.为什么回溯的时候要将已访问的点设为未访问,这样不会以后走到重复的路上吗?

 回溯设为未访问的原因:恢复现场。

    比如拿终点来说,我们走过后将其设为已访问,但是由于是打印多条路径,设为已访问的话意味着即使有其它路径可以走到终点,那再也不会走到了。

对于

以后是否会走到重复的路上的?

      会,因为有可能某个点是多个点的邻接点。

如何去解释死循环问题,我还没有想好

   不会进行死循环,因为用一个for语句控制的,如果说该点是正确路径或者不是正确路径,我们都会进行回退,此时for循环的i+1了,不会再在此点把刚才那个不对的邻接点再走一遍,而是迈向了此点的下一个邻接点,所以不会一直反复从此点去走刚才那个点(一直走同一条路)。但如果后续其它点的相邻点再去走这条路(另一条路),那是另一回事了,不会死循环。

我们平时说的死循环是什么意思?

   是指我们两个点一直在循环反复的走,也就是说一直走的是一同一条路,这样下去肯定会陷入死循环,所以最好的办法就是把访问过的点设为已访问,不让一直在同一条路上一直走。

  虽然我们路径打印中的把已访问的点设为了未访问,但是由于走的是不同的路,所以不会陷入死循环。

4.路径的打印

    打印的原则是:如果是正确路径上的点就将其放入栈中。代码中我们的写法是:我们首先将出发的第一个点放入栈中,因为第一个点肯定是在路径当中。接下来我们将它的邻接点放入栈中。此时有个疑惑:如果说这个邻接点不是正确路径上的点,但是之前已经将这个错误的点放入了栈中,应该怎么办?因为如果是错误的点,我们肯定会通过return语句进行回溯操作,此时就退出了当前这个点的dfs语句,就可以继续进行这个语句下面的语句了,也就是将之前这个点从栈中弹出来。

     栈中之前已经有值了,那我们要打印第二条路径怎么办?

     因为我们之前已经将第一条的路径又重新放入了栈中。此时我们从终点开始进行回溯,我们之前的操作是哪个点要回退,就把这个点弹出栈和置为未访问。所以终点被弹出了栈,我们回退到了终点的上一个点,如果说上一个点没有其他通往路径的点,那么我们继续回退,将其弹出。。。。一直到某个点不需要回退,它还有其他邻近点通往终点,那么我们将这个点压入栈。。。。

 

5.对于return语句进行回退的理解:

 

      假设当前程序执行到了第5个点,按照DFS的思想,我要先去看看我的相邻点是否可以走的到,利用一个for循环去找一下相邻点的位置,当i=0的时候,走到了6点,然后对6点进行dfs,发现6点出界了,就利用return语句回溯,就表明程序的dfs(6)这条语句结束了,它可以继续往下执行下面语句:

    由于刚才进行了回溯,意味着我们可以对5点的下一个相邻点进行DFS了。  

6.为什么需要2个栈?

     因为我们存储的路径是将起点存到了栈的最底下,而将终点放到了栈的最上面。但是我们输出的时候是从起点往终点输出,所以需要一个栈作为临时栈去进行打印输出。但是我感觉,完全可以用一个数组去做,只不过把这个数组进行逆序打印就可以了。

三、程序代码

#include<iostream>

#include<stack>

using namespace std;

 

int maze[10][10];//迷宫

int vis[10][10];//记录迷宫中的某个位置是否访问过

int n,m;   //行和列

 

int dir[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};//四个方向

 

struct point//位置

{

int x,y;

} p;

 

stack<point> path,temp;//记录路径,temp是一个临时变量,和path一起处理路径

 

int count;//路径条数

 

void dfs(int x,int y)//x,y:当前位置

{

if(x==n-1 && y==m-1)//成功---下面处理路径问题

{

cout << "******************路径"<< ++count <<  "******************" << endl;

while(!path.empty())//将path里面的点取出来,放在temp里面

{//path从栈顶-栈底的方向,路径是从终点-起点的顺序

point p1 = path.top();

path.pop();

temp.push(p1);

}

while(!temp.empty())

{//输出temp里面的路径,这样刚好是从起点到终点的顺序

point p1 = temp.top();

temp.pop();

path.push(p1);//将路径放回path里面,因为后面还要回溯!!!

cout << "(" << p1.x << "," << p1.y << ")" << endl;

}

return;

}

 

if(x<0 || x>=n || y<0 || y>=m)//越界

return;

 

//如果到了这一步,说明还没有成功,没有出界

for(int i=0;i<4;i++)//从4个方向探测

{

int nx = x + dir[i][0];

int ny = y + dir[i][1];//nx,ny:选择一个方向,前进一步之后,新的坐标

if(0<=nx && nx<n && 0<=ny && ny<m && maze[nx][ny]==0 && vis[nx][ny]==0)

{//条件:nx,ny没有出界,maze[nx][ny]=0这个点不是障碍可以走,vis[nx][ny]=0说明(nx,ny)没有访问过,可以访问

 

vis[nx][ny]=1;//设为访问过

p.x = nx;

p.y = ny;

path.push(p);//让当前点进栈

 

dfs(nx,ny);//进一步探测

 

vis[nx][ny]=0;//回溯

path.pop();//由于是回溯,所以当前点属于退回去的点,需要出栈

}

}

}

 

int main()

{

count = 0;

freopen("in.txt","r",stdin);//读取.cpp文件同目录下的名为in.txt的文件

 

p.x = 0;

p.y = 0;

path.push(p);//起点先入栈

 

cin >> n >> m;

for(int i=0;i<n;i++)

{

for(int j=0;j<m;j++)

{

vis[i][j] = 0;

cin >> maze[i][j];

}

}

dfs(0,0);

 

return 0;

}

 

参考链接:

  1. https://blog.csdn.net/ten_sory/article/details/66975811
  1. https://blog.csdn.net/ljwjojoyy/article/details/82935885?depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-1&utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-1

 

 


版权声明:本文为neverthe原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。