迷宫问题—DFS与BFS区别及联系

  以2019蓝桥杯C/C++B组的题目为例,来进行说明DFS和BFS的区别

题目描述

下图给出了一个迷宫的平面图,其中标记为 1 的为障碍,标记为 0 的为可以通行的地方。

010000
000100
001001
110000

迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这个它的上、下、左、右四个方向之一。对于上面的迷宫,从入口开始,可以按DRRURRDDDR 的顺序通过迷宫,一共 10 步。其中 D、U、L、R 分别表示向下、向上、向左、向右走。对于下面这个更复杂的迷宫(30 行 50 列),请找出一种通过迷宫的方式,其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。请注意在字典序中D<L<R<U。

01010101001011001001010110010110100100001000101010
00001000100000101010010000100000001001100110100101
01111011010010001000001101001011100011000000010000
01000000001010100011010000101000001010101011001011
00011111000000101000010010100010100000101100000000
11001000110101000010101100011010011010101011110111
00011011010101001001001010000001000101001110000000
10100000101000100110101010111110011000010000111010
00111000001010100001100010000001000101001100001001
11000110100001110010001001010101010101010001101000
00010000100100000101001010101110100010101010000101
11100100101001001000010000010101010100100100010100
00000010000000101011001111010001100000101010100011
10101010011100001000011000010110011110110100001000
10101010100001101010100101000010100000111011101001
10000000101100010000101100101101001011100000000100
10101001000000010100100001000100000100011110101001
00101001010101101001010100011010101101110000110101
11001010000100001100000010100101000001000111000010
00001000110000110101101000000100101001001000011101
10100101000101000000001110110010110101101010100001
00101000010000110101010000100010001001000100010101
10100001000110010001000010101001010101011111010010
00000100101000000110010100101001000001000000000010
11010000001001110111001001000011101001011011101000
00000110100010001000100000001000011101000000110011
10101000101000100010001111100010101001010000001000
10000010100101001010110000000100101010001011101000
00111100001000010000000110111000000001000000001011
10000001100111010111010001000110111010101101111000

  这里先给出结论,这道题目适合用BFS来做,DFS做会超时,而且最短的判断条件也会很复杂。所以DFS适合用来搜索一条路径,BFS适合用来搜索最短路径。
  具体分析在代码注释中,运行程序需要把将以上地图保存到当前程序所在目录中,并命名maze.txt

一、C++(DFS)
#include<iostream>
#include<stack>
#include<algorithm>
#include<fstream>

using namespace std;

const int M = 30;
const int N = 50;


int maze[M][N];

struct node
{
	int x;
	int y;
	int dir;
};

/*由于此题需要按字母序输出最后结果,所以我们只需要将方向遍历的次序
更改为字母序即可,即下左右上*/
int dir[][2] = {{1,0},{0,-1},{0,1},{-1,0}};

int main()
{
	stack<node* > sk;
	node* nd = new node();
	ifstream fin("maze.txt");
	for(int i=0; i<M; i++)
	{
		string str;
		getline(fin,str);
		for(int j=0; j<N; j++)
			maze[i][j] = str[j] - '0';
	}

		
	nd->x = 0;
	nd->y = 0;
	nd->dir = -1;
	maze[0][0] = 1;
	sk.push(nd);
	while(!(sk.top()->x == M-1 && sk.top()->y == N-1))
	{
		nd = sk.top();
		if(nd->dir == 3)
		{
			maze[nd->x][nd->y] = 0;
			sk.pop();
			delete nd;
			continue;
		}
		for(int i= ++nd->dir; i<4;i++)  //根据栈顶结点的方向选择
		{
			int x = nd->x + dir[i][0];
			int y = nd->y + dir[i][1];
			if(x>=0 && x<M && y>=0 && y<N && maze[x][y] == 0) //没有越界才入栈
			{
				nd->dir = i;
				node* nd2 = new node();
				nd2->x = x;
				nd2->y = y;
				nd2->dir = -1;
				sk.push(nd2);
				maze[x][y] = 1;
				break;
			}
		}
	}
	for(int i=0; i<M; i++)
	{
		for(int j=0; j<N; j++)
		    cout<<maze[i][j];
		cout<<endl;
	}
	
	string result = "";
	char dir_c[] = {'U','R', 'D', 'L'};
	sk.pop(); //去除最后一个点,因为其还没有下一步
	while(!sk.empty())
	{
		nd = sk.top();
		sk.pop();
		result += dir_c[nd->dir];
	}
	
	reverse(result.begin(),result.end());
	cout<<result;
	
}

运行结果:(没有做最短路径比较,那估计要跑到地老天荒。。。)

UUUUDDLDDDDDDUDDDDUUURUURURUURURUURUURUUDUUUUURRURUURRUUDDDDDLDDDLLLDDUDUUDDLDLDDUUDDDDDLDDDDDDLRLRRRRRRLRLLDDDLDDLLDDDUURUUUDDUDDUUUDDDDLLDLLRLDLLLLRLRRLLRLDLDDDDLLRRRLLLLRRLLLRLLRRURRLLLDDDDDDUUUDDLLLDLDDDUUDDDDDUUDDUURRRUUDDUURURUUDDUURRUURRRURUUURUUUUUUUDDDDDDDLLRRRRRLDLLDDDDDDUUUUUUDD

在这里插入图片描述

  这是找到的一条路径,由于采用的遍历方式是下左右上,导致路径一致往左下角跑,路径变得更长了。甚至运行了100多秒才出现这个结果,看到太久没结果,我都以为是程序写错了。然后放着没管它,过了快两分钟突然有结果了。。。太难了。
  得出结论,方向的遍历对地图的深度优先搜索性能也有一定影响。故采用深度优先搜索适合于搜索一条路径,代码比较简单。

二、C++(BFS)
#include<iostream>
#include<queue>
#include<stack>
#include<algorithm>
#include<fstream>

using namespace std;

const int M = 30;
const int N = 50;


int maze[M][N];
int maze2[M][N]; //这个只是用来输出图形,查看路径用的

struct node
{
	int x;
	int y;
	int fx;  //这个用于保存前驱结点
	int fy;
	int dir;
};

/*由于此题需要按字母序输出最后结果,所以我们只需要将方向遍历的次序
更改为字母序即可,即下左右上*/
int dir[][2] = {{1,0},{0,-1},{0,1},{-1,0}};

int main()
{
	queue<node* > sk; //这个队列用于广度优先搜索
	stack<node* > re; //这个栈用于最后反索引,得出结果
	node* nd = new node();
	ifstream fin("maze.txt");
	for(int i=0; i<M; i++)
	{
		string str;
		getline(fin,str);
		for(int j=0; j<N; j++)
		{
			maze[i][j] = str[j] - '0';
			maze2[i][j] = str[j] - '0';
		}
	}


	nd->x = 0;
	nd->y = 0;
	nd->fx = -1;
	nd->fy = -1;  //第一个结点的前驱用(-1,-1)来表示,便于最后的输出
	nd->dir = -1;
	maze[0][0] = 1;
	sk.push(nd);
	while(!(sk.front()->x == M-1 && sk.front()->y == N-1))  //后面需要把终点带上,这里的终点还没有入栈
	{
		nd = sk.front();
		sk.pop(); //从广搜队列里移除
		re.push(nd); //把该结点放到结果栈中
		for(int i= 0; i<4;i++)  //这里每个结点遍历四周就行了
		{
			int x = nd->x + dir[i][0];
			int y = nd->y + dir[i][1];
			if(x>=0 && x<M && y>=0 && y<N && maze[x][y] == 0) //没有越界才入队
			{
				node* nd2 = new node();
				nd2->x = x;
				nd2->y = y;
				nd2->dir = i;  //现在将方向保存在,后继结点中,因为是一个一对多的关系
				nd2->fx = nd->x;
				nd2->fy = nd->y; //保存上一个结点的值
				sk.push(nd2);
				maze[x][y] = 1; //设置该结点已经搜索过,且已入队
			}
		}
	}


	string result = "";
	char dir_c[] = {'D','L', 'R', 'U'};
    nd = sk.front();  //因为出口还保留在广度搜索的队头
	
	while(!re.empty())
	{
		node *nd2 = re.top();
		//如果该结点的前驱是栈顶结点,则记录这个值
		if(nd->fx == nd2->x && nd->fy == nd2->y)
		{
			maze2[nd->x][nd->y] = 9;  //只是为了输出有路线的地图
			result += dir_c[nd->dir];  //这里的结果还是反的,最后的翻转顺序
			nd = nd2;  //继续下一结点
		}
		re.pop();
	}
	maze2[nd->x][nd->y] = 9;  //因为最后一个点并没有进入循环上面的循环中
	
	for(int i=0; i<M; i++)
	{
		for(int j=0; j<N; j++)
		    cout<<maze2[i][j];
		cout<<endl;
	}
	reverse(result.begin(),result.end());
	cout<<result;

}

运行结果:(2秒,当然这是带了很多输入输出信息的)

DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRURRUURRDDDDRDRRRRRRDRRURRDDDRRRRUURUUUUUUULULLUUUURRRRUULLLUUUULLUUULUURRURRURURRRDDRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR

在这里插入图片描述

  这个结果,就比深搜好多了,这也是题目最终的结果,最短路径,且按字典序最小。

三、总结

写完这两个代码之后,发现,广度优先搜索的框架只需要在深搜的基础上做一点点的变动即可:

  1. 将DFS的栈改成队列(BFS中加入的那个队列只是为了便于后面的反序索引结果)
  2. DFS遍历方向的时候,是从栈顶结点已经走过的方向开始的,为了避免重复走。但是BFS每一个队头结点都是进行四个方向的遍历,将这个结点周围未走过的点全部变成自己的下一步结点
  3. 最后得出结果的时候,DFS只需要遍历当前栈即可。但是BFS要根据最后一个结点的上一步索引,一步一步的回溯到起点,可能这个地方麻烦一点,但其实只是多了个判断而已

  深搜与广搜的用处真的很大,对于后续的算法学习十分重要,尤其是在树和图之后。其基本思想并不难,但是真正写代码就可能出现很多问题,各种边界问题都得考虑到。

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值