IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> BFS应用之蒜头君回家—C说算法系列 -> 正文阅读

[数据结构与算法]BFS应用之蒜头君回家—C说算法系列

对此题有疑问的小伙伴们,相信你们之前肯定对BFS广度优先搜索算法有所了解,也做过一些较为基础的例题,最经典的问题之一应该是迷宫问题从起点S出发,求到达终点E的最短路径,迷宫问题的解决思路是利用BFS从起点S出发,一层一层往外进行地毯式的搜索,直至到达终点E,即可求出最短路径。可以看出,在迷宫问题中,我们只需要应用一次BFS算法,即可求出最短路径。因为起点S和终点E的坐标都只有一个,即有唯一的起点坐标和终点坐标。

解题思路:

? ? ? ? 首先来分析一下问题, 这道题最关键的一个地方就是钥匙被放置在多处,蒜头君有多种选择,那怎么知道哪一条路径(起点->钥匙位置->家)是最短的呢?

? ? ? ? 在这里,我就以一个简单地图的例子来说明,为大家讲清楚这个问题? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 如下图,是一个4行6列的地图,S为起点,T为终点,P为钥匙,#为障碍物,不可通行

?分析:从以上地图分析出,要从起点S开始走,然后拿到钥匙P,最后到达终点T,有两条最短路径可以走,这两条路径分别是:?

路径一:总共需要9步

路径二:总共需要7步

?算出以上两条路径之后,我们还需要取其中最短的一条路径,即5+2=7。所以蒜头君拿钥匙? 回家的最短路径是7。

再次分析:以上地图中有两把钥匙,所以我们需要分别求出经过这两把钥匙再回到家的最短路径即求出每把钥匙到家到起点的这两个值,然后将其相加,求出某条最短路径然后将n条回家的路径进行比较,算出蒜头君回家的最短路径。也就是说当算一把钥匙到家和到起点的最短距离时,我们需要应用两次BFS,如果说地图中有非常多把钥匙,那么要进行的BFS次数就非常多了,此时程序会超时。那有没有别的方法可以解决程序超时的问题呢?或者说这道题要怎么优化?

优化的思路:我们可以在BFS的时候将标记数组多开一维度,表示是否已经取得了钥匙的状态。如果到达终点并且取得钥匙的状态被标记,bfs结束,即找到了最短路径。可能你还是无法理解,我们不妨用刚刚那个例子来进行一个讲解,首先,我们有一个4行6列的地图,S为起点,T为终点,.为道路(钥匙和家所在的地方也可以视为道路),#为障碍物,不可通行,如下图所示:

我们来模拟一下BFS的执行过程,大家都知道BFS的执行需要队列和标记数组。为了表示每一个坐标的状态(走到当前坐标时手上是否有钥匙、当前坐标的位置、走到当前坐标时距离起点S经过了多少步),我们需要构建一个结构体struct,结构体的成员包括如下几个:

?注意:此处为也可以不为结构体添加构造函数,添加构造函数的目的时方便我们构造结构体变量,不要也行,只不过,构造结构体变量时,就显得复杂一点而已。

模拟步骤:

? ? ? ? 第一步:起点S在(4,2)处,将其入队,此时队列里有一个元素,并且设置为起点访问过,此时蒜头君身上没有钥匙,此坐标点所表示的队列元素的值为:

? ? ? ? ?第二步:将S点周围的点全部入队,我们不妨约定,按照右 下 左 上的方向进行BFS,要注意看图示内容的文字说明,后面的步骤大同小异

? ? ? ? 第三步:S点扩展完毕,出队,请注意看图示文字说明?

? ? ? ? 第四步:扩展(4,3)点,如下图所示:

?? ? ? ? 第五步:(4,3)出队,此时队首元素是(3,2),从(3,2)开始扩展

以上第9行第3列写错了,2代表的含义应该是距离S共2步

?? ? ? ? 第六步:扩展(3,2)点,如下图所示:

??? ? ? ? 第七步:(3,2)扩展完毕,出队,如下图所示:

??? ? ? ? 第八步:扩展(3,3)点,如下图所示:

??? ? ? ? 第九步:(3,3)扩展完毕,出队,如下图所示:

???? ? ? ? 第十步:(3,1)是队首元素,开始扩展,如下图所示:

????? ? ? ? 第11步:(3,1)出队,如下图所示

????? ? ? ? 第12步:(2,2)是队首元素,开始扩展,如下图所示:

?????? ? ? ? 第13步:(2,2)出队,如下图所示

?????? ? ? ? 第14步:(3,4)是队首元素,开始扩展,如下图所示:

?????? ? ? ? 第15步:(3,4)出队,如下图所示

??????? ? ? ? 第16步:(2,1)是队首元素,开始扩展,如下图所示:

??????? ? ? ? 第17步:(2,1)出队,如下图所示

????????第18步:(1,2)是队首元素,开始扩展,如下图所示:

? ? ? ? 第19步:(1,2)出队,如下图所示

????????第20步:(3,5)是队首元素,开始扩展,如下图所示:

?? ? ? ? 第21步:(3,5)出队,如下图所示

??? ? ? ? 第21步:(2,4)为队首元素,开始扩展,发现它不能扩展任何点,出队

???? ? ? ? 第22步:(1,1)为队首元素,开始扩展,注意:从(1,1)这个点开始,蒜头君已经获得了钥匙,所以此时v[x][y][1]这个元素派上用场了,观察图示,你会发现之前用的都是v[x][y][0]

?? ? ? 第23步:(1,1)出队,如下图所示

??? ? ? 第23步:(1,3)为队首元素,开始扩展,没有点可扩展,(1,3)出队,如下图所示

???? ? ? 第24步:(3,6)为队首元素,开始扩展

??? ? ? 第25步:(3,6)出队,如下图所示

???? ? ? 第26步:(2,5)为队首元素,开始扩展,没有元素可扩展,(2,5)出队,如下图所示

????? ? ? 第27步:(1,2)为队首元素,开始扩展

? ? ?? ? 第28步:(1,2)出队,如图所示:

?????? ? ? 第29步:(2,1)为队首元素,开始扩展

?? ? ?? ? 第30步:(2,1)出队,如图所示:

??? ? ?? ? 第31步:(3,5)为队首元素,开始扩展发现,当扩展到该点左边的点的时候,到达终点,可以直接得出最短路径为7?

分析总结我们发现,整个过程只用了1次BFS,便求出了蒜头君拿到钥匙然后回家的最短路径,因为我们引入了一个关键的数据结构—为标记数组多开了一个维度,v[x][y][0]代表没有钥匙时是否访问了v[x][y][0]=0代表(x,y)点在没有钥匙时,也没有被访问,v[x][y][0]=1代表在没有钥匙时,该点被访问,v[x][y][1]=0代表(x,y)点在有钥匙时,没有被访问,v[x][y][1]=1,代表有钥匙,该点被访问。这一段话要仔细去斟酌。

代码实现1:

#include<bits/stdc++.h>
using namespace std;
/*
算出拿钥匙的路+回家的路的最短距离 
思路1:求出钥匙到家到起点的两个值,将二者相加即可,求出最小的即为 最短距离 

思路2:较难理解,可以利用excel表格画图
提示:同学们要注意,老师上课讲得一些题目都非常经典,那么都是取自于历年的一些真题,上课以题目为主
通过题目的方式去讲解相关的知识,所以同学们一定要学会相关的一些写法以及相关的一些思路 

样例1:
8 10
P.####.#P#
..#..#...#
..#T##.#.#
..........
..##.#####
..........
#####...##
###....S##

样例1答案: 
21

*/
const int N = 2005;
int n,m; //地图大小
int sx,sy,ex,ey; //起点和终点 
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; //右 下 左 上
char a[N][N]; //地图是一个二维数组,行列不超过2000 
bool v[N][N][2]; //一般搜索都会涉及到一个访问数组,不管是DFS还是BFS,都需要将访问过的点设置为已经访问 
// 要特别注意v这个数组的含义,第三维用来标记蒜头君走过某个点的时候是否已经带了钥匙 
struct node
{
	int x,y; //坐标
	int step;
	bool flag; //钥匙的状态,1代表有钥匙,0代表没有钥匙	
	//为了更好进行入队,添加构造函数
	node(int xx,int yy,int stepp,bool flagg)
	{
		x=xx;
		y=yy;
		step=stepp;
		flag=flagg;	
	} 
}; 
queue<node> q;

int bfs(int x,int y)
{
	//一开始在起点时步数为0,并且手里没有钥匙,可以用false代表,也可以用0代表 
	q.push(node(x,y,0,false));
	v[x][y][0]=true; //第三维为0,因为时没有钥匙的状态,true代表访问,也可以用1代表 
	while(!q.empty())
	{
		//获取队首元素
		node now = q.front();
		q.pop();	//获取队首元素之后,就可以实现出队了,因为队首元素已经保存在now了,不会丢失
		//队首可扩展的点进行入队
		for(int i=0;i<4;i++)
		{
			int tx=now.x+dir[i][0];
			int ty=now.y+dir[i][1];
			if(tx>=0&&tx<n&&ty>=0&&ty<m&&a[tx][ty]!='#'&&!v[tx][ty][now.flag])
			{
				v[tx][ty][now.flag]=1;
				if(a[tx][ty]=='P')
				{
					q.push(node(tx,ty,now.step+1,true));
				}
				else if(a[tx][ty]=='T'&&now.flag==1)
				{
					return now.step+1;
				}
				else
				{
						q.push(node(tx,ty,now.step+1,now.flag));  //此点是否带了钥匙受队首元素的影响 
				}
			}
		}
	}
}

int main()
{
	cin>>n>>m;  //n行m列
	for(int i=0;i<n;i++)
	{
		cin>>a[i]; 
	}	
	//寻找起点S的坐标位置
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(a[i][j]=='S')	
			{
				sx=i;
				sy=j;
			}
		}	
	} 
	//从起点出发,进行bfs
	cout<<bfs(sx,sy); 
	return 0;
}

代码实现2:

#include<bits/stdc++.h>
using namespace std;
/*
算出拿钥匙的路+回家的路的最短距离 
思路1:求出钥匙到家到起点的两个值,将二者相加即可,求出最小的即为 最短距离 

思路2:较难理解,可以利用excel表格画图
提示:同学们要注意,老师上课讲得一些题目都非常经典,那么都是取自于历年的一些真题,上课以题目为主
通过题目的方式去讲解相关的知识,所以同学们一定要学会相关的一些写法以及相关的一些思路 

样例1:
8 10
P.####.#P#
..#..#...#
..#T##.#.#
..........
..##.#####
..........
#####...##
###....S##

样例1答案: 
21

*/
const int N = 2005;
int n,m; //地图大小
int sx,sy,ex,ey; //起点和终点 
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; //右 下 左 上
char a[N][N]; //地图是一个二维数组,行列不超过2000 
bool v[N][N][2]; //一般搜索都会涉及到一个访问数组,不管是DFS还是BFS,都需要将访问过的点设置为已经访问 
// 要特别注意v这个数组的含义,第三维用来标记蒜头君走过某个点的时候是否已经带了钥匙 
struct node
{
	int x,y; //坐标
	int step;
	bool flag; //钥匙的状态,1代表有钥匙,0代表没有钥匙	
	//为了更好进行入队,添加构造函数
	node(int xx,int yy,int stepp,bool flagg)
	{
		x=xx;
		y=yy;
		step=stepp;
		flag=flagg;	
	} 
}; 
queue<node> q;

void bfs(int x,int y)
{
	//一开始在起点时步数为0,并且手里没有钥匙,可以用false代表,也可以用0代表 
	q.push(node(x,y,0,false));
	v[x][y][0]=true; //第三维为0,因为时没有钥匙的状态,true代表访问,也可以用1代表 
	while(!q.empty())
	{
		//获取队首元素
		node now = q.front();
		q.pop();	//获取队首元素之后,就可以实现出队了,因为队首元素已经保存在now了,不会丢失
		//判定是否抵达终点
		if(a[now.x][now.y]=='T')
		{
			if(now.flag)
			{
				cout<<now.step<<endl;
				break;		
			}
			continue; //到达终点但是没有钥匙,这个点无需再扩展 
		} 
		//队首可扩展的点进行入队
		for(int i=0;i<4;i++)
		{
			int tx=now.x+dir[i][0];
			int ty=now.y+dir[i][1];
			if(tx>=0&&tx<n&&ty>=0&&ty<m&&a[tx][ty]!='#'&&!v[tx][ty][now.flag])
			{
				v[tx][ty][now.flag]=1;
				if(a[tx][ty]=='P')
				{
					q.push(node(tx,ty,now.step+1,true));
				}
				else
				{
					q.push(node(tx,ty,now.step+1,now.flag));  //此点是否带了钥匙受队首元素的影响 
				}
			}
		}
	}
}

int main()
{
	cin>>n>>m;  //n行m列
	for(int i=0;i<n;i++)
	{
		cin>>a[i]; 
	}	
	//寻找起点S的坐标位置
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			if(a[i][j]=='S')	
			{
				sx=i;
				sy=j;
			}
		}	
	} 
	//从起点出发,进行bfs
	bfs(sx,sy); 
	return 0;
}

总结: 如果对以上的讲解内容或者代码有任何疑问的话,可在评论区里回复,我会及时答复,也可以私信我,将你添加到算法群聊中,一起来学习成长。这一期的算法课堂到这里就结束啦!

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-30 18:50:35  更:2022-03-30 18:52:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 11:41:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码