BFS广搜(超详细图解)

目录

BFS广度优先搜索

理解

引入

图解

相关知识

有趣的解释 (引用)

模板

e.g. 1:

输入格式

输出格式

(分析+图解+思维)

参考代码

e.g. 2:

输入格式

输出格式

图解

参考代码

e.g. 3:

输入格式

输出格式

图解

参考代码

推荐题目

原手稿


BFS广度优先搜索

理解

BFS与DFS虽然同为暴力算法,但bfs与dfs还是有一定的差距,dfs是一条路走到黑,沿着一条路径一直向下搜索,直到没有下一步,再回溯到上一个节点继续向下搜索;而bfs是把每一个点的分节点搜索完再继续搜索下一层(后面会详细讲),到找到时会通过一条最优路回溯回去。就像走迷宫,dfs是一个人走,而bfs是一群人走。

引入

可以先看一下这两个问题,浅浅的思考一下:

可以发现,bfs主要以解决最小最少最短一类的问题,这就是bfs于dfs的一大特征。(关于第二个问题中的线性筛素数,本萌新之后会写一篇文章来表达一下我的理解)

图解

我们以红色为起点,绿色为终点,黄色为已访问过的点。

搜索第一层

搜索第二层

搜索第三层

 搜索第四层并找到终点

相关知识

在bfs中,有一个必要的工具——队列。队列分为两种:手工STL(不熟悉的话可以看一下我之前写的介绍队列的文章)

bfs中的队列有着先进先出的特性,图解:

1进队列

1出队列,2,3进队列

2,3出队列,4,5,6进队列

4,5,6出队列

有趣的解释 (引用)

模板

跟dfs一样,bfs也有模板:

void bfs()

{

        初始化,初始状态存数组

        int front=0,rear=1,q[max_size]//构建队列

        标记初始点位

        while(front<rear)

        {

                front++;//指向带扩展结点

                for(int i=1;i<=maxi;i++)

                {

                        if(满足条件||不重复)

                        {

                                rear++;

                                将新结点压入队列

                        }

                }

        }

}

e.g. 1:

爱与愁大神买完东西后,打算坐车离开中山路。现在爱与愁大神在 x1​,y1​ 处,车站在 x2​,y2​ 处。现在给出一个 n×n(n≤1000) 的地图,0 表示马路,1 表示店铺(不能从店铺穿过),爱与愁大神只能垂直或水平着在马路上行进。爱与愁大神为了节省时间,他要求最短到达目的地距离(每两个相邻坐标间距离为 1)。你能帮他解决吗?

输入格式

第 1 行包含一个数 n。

第 2 行到第 n+1 行:整个地图描述(0 表示马路,1 表示店铺,注意两个数之间没有空格)。

第 n+2 行:四个数 x1​,y1​,x2​,y2​。

输出格式

只有 1 行,即最短到达目的地距离。

3

0 0 1

1 0 1

1 0 0

1 1 3 3

4

5

0 1 0 0 0

0 1 0 1 0

0 0 0 0 0

0 1 1 1 0

0 0 0 1 0

1 1 5 5

8

(分析+图解+思维)

这道题是求最短的问题,自然想到bfs(因为bfs是一格一格的向外拓展,当遇到终点时一定会有一个最优方案)

我们可以先定义两个二维数组,一个用来存maps,一个用来存最短距离d。

我们可以先将地图中的数字全看为-1:

注:(红色为最短距离,其他颜色为分支拓展,接下来将以数据2展开讲解)

我们从(0,0)开始搜索,具体如以下图示

最后我们就得到了最短距离——8。

参考代码
#include<iostream>
#include<queue>
using namespace std;
struct node//结构体
{
	int x,y;
};
queue <node> q;
int d[1100][1100];
int n,x,y,tx,ty;
int sa,sb,ta,tb;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,1,-1};
char maps[1100][1100];
bool vis[1100][1100];
int bfs(int sx,int sy)
{
	q.push((node){sx,sy});
	vis[sx][sy]=true;
	while(q.size())
	{
		x=q.front().x;
		y=q.front().y;
		q.pop();
		if(x==ta&&y==tb)
            return d[x][y];
		for(int i=0;i<4;i++)
		{
			tx=x+dx[i];
			ty=y+dy[i];
			if(tx<=0||tx>n||ty<=0||ty>n)//边界
                continue;
			if(maps[tx][ty]=='1'||vis[tx][ty]==true)
                continue;
			d[tx][ty]=d[x][y]+1;//实现上面图解操作
			vis[tx][ty]=true;
			q.push((node){tx,ty});
		}
	}
	return -1;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	    for(int j=1;j<=n;j++)
	        cin>>maps[i][j];
	cin>>sa>>sb>>ta>>tb;
	cout<<bfs(sa,sb);
	return 0;

}

e.g. 2:

巫妖王的天灾军团终于卷土重来,血色十字军组织了一支先锋军前往诺森德大陆对抗天灾军团,以及一切沾有亡灵气息的生物。孤立于联盟和部落的血色先锋军很快就遭到了天灾军团的重重包围,现在他们将主力只好聚集了起来,以抵抗天灾军团的围剿。可怕的是,他们之中有人感染上了亡灵瘟疫,如果不设法阻止瘟疫的扩散,很快就会遭到灭顶之灾。大领主阿比迪斯已经开始调查瘟疫的源头。原来是血色先锋军的内部出现了叛徒,这个叛徒已经投靠了天灾军团,想要将整个血色先锋军全部转化为天灾军团!无需惊讶,你就是那个叛徒。在你的行踪败露之前,要尽快完成巫妖王交给你的任务。军团是一个 n 行 m 列的矩阵,每个单元是一个血色先锋军的成员。感染瘟疫的人,每过一个小时,就会向四周扩散瘟疫,直到所有人全部感染上瘟疫。你已经掌握了感染源的位置,任务是算出血色先锋军的领主们感染瘟疫的时间,并且将它报告给巫妖王,以便对血色先锋军进行一轮有针对性的围剿。

输入格式

第 1 行:四个整数 n,m,a,b,表示军团矩阵有 n 行 m 列。有 a 个感染源,b 为血色敢死队中领主的数量。

接下来 a 行:每行有两个整数 x,y,表示感染源在第 x 行第 y 列。

接下来 b 行:每行有两个整数 x,y,表示领主的位置在第 x 行第 y 列。

输出格式

第 1 至 b 行:每行一个整数,表示这个领主感染瘟疫的时间,输出顺序与输入顺序一致。如果某个人的位置在感染源,那么他感染瘟疫的时间为 0。

图解

红色为感染源,蓝色为领主。

参考代码
#include<iostream>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int,int> P;//存坐标
int n,m,a,b;
int d[1000][1000];
P q[300000];//手工队列
int front=0,rear=-1;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
void bfs()
{
	while(front<=rear)
	{
		P t=q[front++];
		for(int i=0;i<4;i++)
		{
			int a=t.x+dx[i];
			int b=t.y+dy[i];
			if(a<1||a>n||b<1||b>m)
			continue;
			if(d[a][b]>=0)
			continue;
			d[a][b]=d[t.x][t.y]+1;
			q[++rear]={a,b};
		}
	}
}
int main()
{
	cin>>n>>m>>a>>b;
	memset(d,-1,sizeof d);
	while(a--)
	{
		int g,h;
		scanf("%d%d",&g,&h);
		q[++rear]={g,h};
		d[g][h]=0;
	}
	bfs();
	while(b--)
	{
		int g,h;
		scanf("%d%d",&g,&h);
		cout<<d[g][h]<<endl;
	}
}

e.g. 3:

贝茜听说一场特别的流星雨即将到来:这些流星会撞向地球,并摧毁它们所撞击的任何东西。她为自己的安全感到焦虑,发誓要找到一个安全的地方(一个永远不会被流星摧毁的地方)。

如果将牧场放入一个直角坐标系中,贝茜现在的位置是原点,并且,贝茜不能踏上一块被流星砸过的土地。

根据预报,一共有 M 颗流星 (1≤M≤50,000) 会坠落在农场上,其中第 i 颗流星会在时刻 Ti​(0≤Ti​≤1000)砸在坐标为 (Xi​,Yi​)(0≤Xi​≤300,0≤Yi​≤300) 的格子里。流星的力量会将它所在的格子,以及周围 4 个相邻的格子都化为焦土,当然贝茜也无法再在这些格子上行走。

贝茜在时刻 0 开始行动,她只能在第一象限中,平行于坐标轴行动,每 1 个时刻中,她能移动到相邻的(一般是 4 个)格子中的任意一个,当然目标格子要没有被烧焦才行。如果一个格子在时刻 t 被流星撞击或烧焦,那么贝茜只能在 t 之前的时刻在这个格子里出现。 贝茜一开始在 (0,0)。

请你计算一下,贝茜最少需要多少时间才能到达一个安全的格子。如果不可能到达输出 −1。

输入格式

共 M+1 行,第 1 行输入一个整数 M,接下来的 M 行每行输入三个整数分别为 Xi​,Yi​,Ti​。

输出格式

贝茜到达安全地点所需的最短时间,如果不可能,则为 −1。

图解

下面两幅图需要对照着看

贝茜从第0时刻开始逃生,结合两张图可以看出,贝茜在逃生时只能走红色区域,如果走其他区域,那么贝茜在2秒后都会死于流星(需要剪枝的情况),最后,贝茜在到达绿色安全区域时所需的最短时间为5。

参考代码
#include<iostream>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int,int> P;
int m;
int d[500][500];
int f[500][500];
P q[100000];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int bfs()
{
	q[0]={0,0};
	d[0][0]=0;
	int front=0,rear=0;
	while(front<=rear)
	{
		P t=q[front++];
		for(int i=0;i<4;i++)
		{
			int a=t.x+dx[i];
			int b=t.y+dy[i];
			if(a<0||b<0)
			continue;
			if(d[a][b])
			continue;
			if(d[t.x][t.y]+1>=f[a][b])
			continue;
			d[a][b]=d[t.x][t.y]+1;
			q[++rear]={a,b};
			if(f[a][b]>1e9)
			return d[a][b];
		}
	}return -1;
}
int main()
{
	scanf("%d",&m);
	memset(f,0x3f,sizeof f);
	while(m--)
	{
		int g,h,t;
		cin>>g>>h>>t;
		f[g][h]=min(t,f[g][h]);
		for(int i=0;i<4;i++)
		{
			int a=g+dx[i];
			int b=h+dy[i];
			if(a<0||b<0||a>300||b>300)
			continue;
			f[a][b]=min(t,f[a][b]);
		}
	}
	int ans=bfs();
	printf("%d",ans);
}

推荐题目

洛谷:1443,1162,1747,2658,2730

原手稿

 推荐大家平时做题也多写多画

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值