栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【算法笔记】广度优先搜索BFS

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【算法笔记】广度优先搜索BFS

【算法笔记】广度优先搜索BFS
    • 例题1
      • 思路
      • 代码
    • 例题2
      • 思路
      • 代码
    • 提示

广度优先搜索一般由队列实现,且总是按层次的顺序进行遍历,基本写法如下:

void BFS(int s){
	queue q;
	q.push(s); 		//定义一个队列q,并将起点s入队
	//队列q非空
	while(!q.empty()){
		取出队首元素top;
		访问队首元素top;
		将队首元素出队;
		将top的下一层结点中未曾入队的结点全部入队,并设置为已入队; 	//标记层号为now的层号+1,同时设置这些入队的结点已入队
	}
}
例题1

给出m×n的矩阵,矩阵中的元素为0或1,称位置(x,y)与其上下左右四个位置是相邻的。如果矩阵中有若干个1是相邻的(不必两两相邻),那么称这些1构成了一个块,求给定矩阵中块的个数。

输入:
6 7
0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0

块的个数为4

思路

枚举每一个位置的元素,如果为0,则跳过;如果为1,则使用BFS查询与该位置相邻的4个位置(前提是不出界),判断他们是否为1,如果某个相邻的位置为1,则同样去查询与该位置相邻的4个位置,直到整个1块访问完毕。为了防止走回头路,可以设置一个bool型数组inq来记录每个位置是否在BFS中已入过队。
对当前位置(x,y)来说,与其相邻的四个位,置分别为(x,y+1),(x,y-1),(x+1,y),(x-1,y),那么不妨设置下面两个增量数组,来表示四个方向:

int X[]={0,0,1,-1};
int Y[]={1,-1,0,0};

这样就可以用for循环来枚举四个方向,以确定与当前坐标(nowX,nowY)相邻的4个位置:

for(int i=0;i<4;i++){
	newX=nowX+X[i];
	newY=nowY+Y[i];
}
代码
#include 
#include 
using namespace std;
const int maxn=100;
struct node{
	int x,y;
}Node;

int m,n;	//矩阵大小为 n×m 
int matrix[maxn][maxn];		//01矩阵 
bool inq[maxn][maxn]={false}; //记录位置x,y是否已入过队

//增量数组 
int X[]={0,0,1,-1};
int Y[]={1,-1,0,0}; 

//判断坐标x,y是否需要访问
bool judge(int x,int y){
	//越界返回false
	if(x>=n||x<0||y>=m||y<0) return false;
	//当前位置为0,或x,y已入过队,返回false
	if(matrix[x][y]==0||inq[x][y]==true) return false;
	//以上都不满足,返回true 
	return true; 
} 

//bfs函数访问位置x,y所在的块,将该块中所有1的inq都设置为true 
void BFS(int x,int y){
	queue q;
	Node.x=x,Node.y=y;	//当前结点坐标 
	q.push(Node);
	inq[x][y]=true;		//设置x,y已入过队 
	while(!q.empty()){
		node top=q.front();	//取出队首元素
		q.pop();			//队首元素出队
		//得到相邻位置 
		for(int i=0;i<4;i++){
			int newX=top.x+X[i];
			int newY=top.y+Y[i];
			//如果新的位置需要访问=true 
			if(judge(newX,newY)){
				//设置Node坐标为new
				Node.x=newX,Node.y=newY;
				q.push(Node);
				inq[newX][newY]=true;	//标记新的位置已入过队 
			}
		} 
	}
}

int main(){
	scanf("%d%d",&n,&m);
	for(int x=0;x
		for(int y=0;y
			scanf("%d",&matrix[x][y]);	//读入01矩阵 
		}
	}
	int ans=0;	//存放块数
	//枚举每一个位置 
	for(int x=0;x
		for(int y=0;y
			//元素为1,且未入过队 
			if(matrix[x][y]==1&&inq[x][y]==false){
				ans++;	//块数加一
				BFS(x,y);	//访问整个块,将该块所有的1的inq都标记为true 
			}
		}
	} 
	printf("%dn",ans); 
	return 0;
}
例题2

给定一个n*m大小的迷宫,其中*代表不可通过的墙壁,.代表平地,S表示起点,T代表终点。当前位置是x,y,每次只能前往上下左右四个位置的平地,求从起点S到终点T的最少步数。
输入:
. . . . .
. * . * .
. * S * .
. * * * .
. . . T *
S坐标为(2,2),T坐标为(4,3)

思路

本题求最少步数,BFS是通过层次顺序来遍历,因此可以从起点S开始计数遍历的层数,在到达终点T时的层数就是需要求解的起点S到达终点T的最少步数。

代码
#include 
#include 
#include 
using namespace std;
const int maxn=100;
struct node{
	int x,y;
	int step;	//step为从起点S到达该位置的最少步数(层数) 
}S,T,Node;		//	S起点,T终点,Node临时结点

int n,m;	//n行,m列
char maze[maxn][maxn];	//迷宫
bool inq[maxn][maxn]={false}; 	//记录xy是否已入队 
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};

//检测位置x,y是否有效
bool test(int x,int y){
	if(x<0||x>=n||y<0||y>=m) return false; //越界 
	if(maze[x][y]=='*') return false;	//墙壁,不可通过
	if(inq[x][y]==true) return false;	//已入过队
	return true;	//有效位置 
} 

int BFS(){
	queue q;
	q.push(S);	//起点S入队
	while(!q.empty()){
		node top=q.front();
		q.pop();
		//找到终点T,返回最少步数 
		if(top.x==T.x&&top.y==T.y){
			return top.step;
		}
		//得到相邻位置 
		for(int i=0;i<4;i++){
			int newX=top.x+X[i];
			int newY=top.y+Y[i];
			//位置有效 
			if(test(newX,newY)){
				Node.x=newX,Node.y=newY;
				Node.step=top.step+1;
				q.push(Node);	//	结点入队
				inq[newX][newY]==true;	//	设置结点已入队 
			}
		}
	} 
	return -1;	//无法到达终点T时返回-1 
} 

int main(){
	scanf("%d%d",&n,&m);
	//输入迷宫 
	for(int i=0;i
		getchar();	//过滤掉每行后面的换行符 
		for(int j=0;j
			maze[i][j]=getchar();
		}
		maze[i][m+1]='';
	}
	scanf("%d%d%d%d",&S.x,&S.y,&T.x,&T.y);		// 起点和终点的坐标
	S.step=0;
	printf("%dn",BFS()); 
	return 0;
}
提示

当使用STL的queue时,元素入队的push操作只是制造了该元素的一个副本入队,因此在入队后对原来元素的修改不会影响队列中的副本,而对队列中副本的修改也不会改变原来元素。
如:

#include 
#include 
using namespace std;
struct node{
	int data;
}a[10];
int main(){
	queue q;
	for(int i=1;i<=3;i++){
		a[i].data=i;
		q.push(a[i]);
	}
	//尝试直接把队首元素的数据域改为100
	q.front().data=100;
	printf("%d %d %dn",a[1].data,a[2].data,a[3].data);
	//尝试直接修改a[1]的数据域为200
	a[1].data=200;
	printf("%dn",q.front().data); 
	return 0;
}

所以说,当需要对队列中元素进行修改而不仅仅是访问时,队列中存放的元素最好不要是元素本身,而是他们的编号(如果是数组的话则是下标)
如:

#include 
#include 
using namespace std;
struct node{
	int data;
}a[10];
int main(){
	queue q; 	//q存放结构体数组中元素的下标 
	for(int i=1;i<=3;i++){
		a[i].data=i;	//结构体数组赋值 
		q.push(i);		//将数组下标i入队 
	}
	//把队首元素的数据域改为100
	a[q.front()].data=100;
	printf("%dn",a[1].data);
	return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/832648.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号