广度优先搜索(bfs)模板.

415 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

简介:

之所以称之为宽度优先算法,是因为算法自始至终一直通过已找到和未找到顶点之间的边界向外扩展,就是说,算法首先搜索原点附近符合条件的所有顶点,然后再去搜索该顶点附近所有符合条件的其他顶点. 简单的来说此算法就是优先搜索已知部分附近的节点,并将其标记为已知部分然后再行计算.形象的说可以理解为病毒呈辐射状传播,由已知点呈网状传播至结束

算法模板:

#include <bits/stdc++.h>
using namespace std;
#define N 10000000//迷宫的规模
type start,aim;//type为某种数据类型
//start初始位置,aim目标位置
struct node{
int  x;
int step;
};
//记录两种状态
//1.记录该步的状态
//2.步数
bool vis[N];
//标记该种状态是否被走过
queue<node> q;
//广搜队列
void bfs()
{
 
    while(!q.empty())
    {
        if(q.front().x==aim)
        {
            return;
        }
        node tmp;
        //tmp用于暂时储存状态
        int X=q.front().x;
        //一维只有X
        //多维要从多重维度记录
        int Step=q.front().step;
        q.pop();
        //进行减一的操作
        //广度试探
        if(X>=1&&!vis[X-1])
            //要保证减1后有意义,所以要X >= 1
            //其中之一的试探条件
        {
        tmp.x=X-1;//进行操作
        //到达新的状态
        tmp.step=Step+1;
        vis[X-1]=1;
        //记录已经到达过的位置
        //记录该状态已经试探过了
        q.push(tmp);
        }
        //已经到达的位置不能重复到达
        //进行加一的操作
        if(X<aim&&!vis[X+1])
            //总的来说试探条件有三个
            //1.在边界之内
            //2.还未走过
            //3.该处没有”墙“
        {
        tmp.x=X+1;
        tmp.step=Step+1;
        vis[X+1]=1;
        q.push(tmp);
        }
        //进行乘两倍的操作
        if(X<aim&&!vis[2*X])
        {
            tmp.x=2*X;
            tmp.step=Step+1;
            vis[2*X]=1;
            q.push(tmp);
        }
   }
}
int main()
{
    while(cin>>start>>aim)//初始化迷宫
    {
        memset(vis,0,sizeof(vis));
        //初始化记录状态的数组
        while(!q.empty())q.pop();
        //确保在进行操作之前队列为空
        node ST;
        ST.x=start;
        ST.step=0;
        q.push(ST);
        //将根节点进队
        //同时开始广度优先搜索
        bfs();
        //可能在此处会有一个较为复杂的输出函数
        cout<<q.front().step<<endl;
    }
    return 0;
}

 注意事项:

1.从广度优先搜索的定义可以看出活节点的扩展是按照先来先处理的原则进行的,所以在算法中要用"队"来储存每个扩展出的活节点.

2.在广度优先搜索节点时,一个节点可能被作为多次扩展对象,这是需要避免的,一般可以开辟数组来记录图中的节点被搜索的情况.

例题演示:流星洗礼.

image.png

大致题意:

某处将有一场流星雨,假设小明所在初始坐标默认为原点,流星落下将造成伤害,伤害范围为落下点的坐标以及上下左右四格,流星落下的时间不等,第一行输入n,接下来n行是流星降落的x,y坐标以及时间.已知小明想要躲避流星逃到安全地方,每次移动都需要1时间单位,求小明逃到安全区域(安全区域:流星永远不会破坏到的地方.)的最短时间.(小明只能在坐标轴的第一象限以及x轴和y轴的正方向移动.)若小明必死无疑,即无法在流星下降之前逃离至安全区域就输出-1.

题目分析:

我们可以建立一个坐标系来进行标记,可以利用数组来建立,数组中存储的信息可以是该地区是否为安全地区以及该地区最后安全的时间.然后从原点开始进行广度优先搜索,若下一个目标点是危险的则可当成障碍物换一个方向搜索,直到第一次搜索到安全区域然后结束函数返还搜索时间.需要注意的是虽然小明的活动范围很大,但是流星的降落范围为300,因此坐标系的建立并不需要很大.并且小明只能在坐标轴的第一象限以及x轴和y轴的正方向移动.

代码实现:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=310;
int maze[maxn][maxn];
bool vis[maxn][maxn];
struct Node {
	int x,y,time;
};
int dx[4]= {0,0,-1,1};
int dy[4]= {1,-1,0,0};
void initMaze(int m) {
	memset(maze,-1,sizeof(maze));
	int u,v,w;
	for(int i=1; i<=m; i++) {
		scanf("%d %d %d",&u,&v,&w);
		if(maze[u][v]==-1||maze[u][v]>w) {
			maze[u][v]=w;
		}
		for(int j=0; j<4; j++) {
			int du=u+dx[j];
			int dv=v+dy[j];
			if(du<0||du>305||dv<0||dv>305)
				continue;
			if(maze[du][dv]==-1||maze[du][dv]>w)
				maze[du][dv]=w;
		}
	}
}
int bfs() {
	memset(vis,false,sizeof(vis));
	Node start;
	start.x=start.y=start.time=0;	
	queue<Node>que;
	while(!que.empty())
		que.pop();
	que.push(start);
	vis[0][0]=true;
	while(!que.empty()) {
		Node cur=que.front();
		que.pop();
		if(maze[cur.x][cur.y]==-1)
			return cur.time;
		for(int i=0; i<4; i++) {
			Node cnt;
			cnt.x=cur.x+dx[i];
			cnt.y=cur.y+dy[i];
			cnt.time=cur.time+1;
			if(cnt.x<0||cnt.x>305||cnt.y<0||cnt.y>305)//出界 
				continue;
			if(vis[cnt.x][cnt.y])//该节点是否走过. 
				continue;
			if(maze[cnt.x][cnt.y]!=-1&&maze[cnt.x][cnt.y]<=cnt.time)
				continue;
			que.push(cnt);
			vis[cnt.x][cnt.y]=true;
		}
	}
	return -1;
 
}
 
int main() {
	int m;
	while(scanf("%d",&m)!=EOF) {
		initMaze(m);
 
		int ans=bfs();
 
		printf("%d\n",ans);
 
		
		
	}
 
	return 0;
}

题目小结:

该题在理解题意的基础上比较简单,需要注意的是对附近节点的判定除了是否越界之外还要计算附近节点在进入时是否处于危险状态.并且要考虑到如果原点就是安全位置的情况.

小结:

BFS在求解最短路径或者最短步数上有很多的应用,应用最多的是在走迷宫上,若想熟练掌握bfs需要在了解算法思想和代码模板的基础上多练习不同题型.