BFS的若干问题

BFS,其英文全称是 Breadth First Search,意为广度优先搜索,是所有的搜索手段之一。它是从某个状态开始,将所有节点加入一个先进先出的队列,然后一层一层进行状态转移,并且展开节点。

BFS 是广度优先搜索,是将某节点所有的“枝蔓”加入搜索队列,然后去除队列的首部的节点,重复进行该动作,这样就能由开始状态“一圈一圈的查找”。 BFS 拥有一个性质,那么就是先找到的节点,所经过的步骤一定最短。它一般用于寻找在近的状态

作为搜索算法的一种,BFS 相较于 DFS 而言,BFS 是一层一层展开的,那么对于有多个终态时,最先找到的一定是最短的。

  1. 确定该题目的状态(包括边界)

  2. 找到状态转移方式

  3. 找到问题的出口,计数或者某个状态

  4. 设计搜索

会发现我们前期要找到的参数基本一致,所以在一般情况下 BFS 和 DFS 可以相互转换。

int check(参数)
{
    if(满足条件)
        return 1;
    return 0;
}
bool pd(参数){
    相应操作
}
void bfs()
{
    1. 把根节点放入队列尾端
    2. 每次从队列中取出一个节点
    3. Check 判断是不是答案,如果是结束算法 return;
    4. 把当前取出的节点扩展,如果扩展后的节点经Pd()后符合要求,就放入队列,不符合就不放。
    5. 转到步骤2,循环执行
}

如果所有节点被扩展完了,没有找到答案就无解。

BFS在网格图(棋盘)问题的最短路径搜索问题

点个赞吧谢谢ヽ( ̄ω ̄( ̄ω ̄〃)ゝ

算法

  1. 将起点入队
  2. 队首结点可扩展的点入队,并标记此点已经访问,如果没有可扩展的点,队首结点出队

重复步骤2,使到达目标位置(搜到了)或队列为空(没搜到)

工具

  1. 地图数组 
  2. 访问数组:标记在地图上已经访问过的坐标 
  3. 队列:模拟搜索(扩展)的过程
  4. 结构体(x,y,步数) 
  5. 方向数组:用于判断周围未访问的空地 
  6. 队首点:搜索过程中的每一个队首点的承接
  7. 方位值:每一个队首旗下的四个方位的可扩展性判断
  8. 扩展点(未来的新队首):方位值满足条件(空地未访问)则实例化一个点加入队列 
  9. 判断布尔(这个可以不要):用于直至队列为空的整个搜索过程都未触发"搜索到了"情况的标记,在搜索完毕后判断此旗帜输出"没搜到" 
  10. 特别地,搜索过程中对于扩展的判断可写为是否为空地的判断,这样不管是障碍体,还是数组内(提前声明的最大测试样例棋盘大小)棋盘外(并不一定每一组测试样例的棋盘都是数组那么大最大数组可能用不完)的越界情况也可以规避 

测试样例

5 4 
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1
1 1 4 3

答案为7 


模拟过程

0空地1障碍物 
s010
0000
0010
01e0
0001 
起点入队:110-第一行第一列,走的步数为0
队首可扩展点入队:121,211,-第一行二列和第二行一列,走的步数为1
此时队首可扩展点入队完毕,队首110出队
此时队首为121,可扩展点只有下面的,右边为障碍,入队222
队首121出队
211扩展,只可往下扩展,因为右边的已经被扩展(搜索)了(已经被标记以访问),312入队

如此循环往复

附注解的代码

#include<bits/stdc++.h>
using namespace std; 

int chessboard[100][100];//地图 
int vis[100][100];//与地图等长的访问数组
//方向数组 上(x-1,y)下(x+1,y)左(x,y-1)右(x,y+1)
int dx[4]={1,-1,0,0};
int dy[4]={0,0,-1,1}; 
//点需要入队,入队的过程牵涉点的坐标和步数 
struct point{
	int x,y;
	int step;
	point(int inx=1,int iny=1,int ins=0):x(inx),y(iny),step(ins){}//默认为一个在一行一列步数为0的点 
};
//队列
queue<point>que;//队列维护搜索的过程,过程的每一步都是一个点的行为(坐标,步数) 
int main(){
	ios::sync_with_stdio(0);cin.tie(nullptr);cout.tie(nullptr);
	int n,m;cin>>n>>m;//输入地图大小-行列
	int sx,sy,ex,ey;//起终点xy坐标 
	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)cin>>chessboard[i][j];//摆放棋盘 
	cin>>sx>>sy>>ex>>ey;
	//广度优先搜索
	point start(sx,sy);
	//起点入队 
	que.push(start);
	//起点已经访问 
	vis[sx][sy]=1;
	int flag=0; 
	while(!que.empty()){
		int x=que.front().x,y=que.front().y;//获取队首点的坐标 
		if(x==ex&&y==ey){//查看队首的点是否是终点 
			flag=1; 
			cout<<"搜索到该点花费步数: "<<que.front().step;
			break;
		} 
		//如果不是终点则 四向搜索 
		for(int d=0;d<=3;++d){
			int arx=x+dx[d],ary=y+dy[d];//生成该向扩展点的坐标(此时并不是真的扩展点,还需要判断) 
			//若该扩展点满足条件:未访问0空地0,则加入队列,可以扩展此点 
			if(chessboard[arx][ary]==0&&vis[arx][ary]==0){
				point arp(arx,ary,que.front().step+1);//扩展点的步数为本轮队首点的步数加一 
				que.push(arp); 
				vis[arx][ary]=1; 
			}
		} 
		//队首扩展操作执行完毕,该轮队首出队
		que.pop(); 
	}//下一轮队首点开始扩展 
	if(flag==0)cout<<"没找到答案"; 
	return 0;
}

纯享版

#include<bits/stdc++.h>
using namespace std; 
int chessboard[100][100],vis[100][100];
int dx[4]={1,-1,0,0},dy[4]={0,0,-1,1};
struct point{
	int x,y,step;
	point(int inx,int iny,int ins=0):x(inx),y(iny),step(ins){}
};
queue<point>que;

int main(){
	int n,m;cin>>n>>m;//输入地图大小-行列
	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)cin>>chessboard[i][j];//摆放棋盘,0是空地 
	int sx,sy,ex,ey;cin>>sx>>sy>>ex>>ey;//起终点坐标 
	//-----广度优先搜索部分: 
	point start(sx,sy);que.push(start);vis[sx][sy]=1;
	while(!que.empty()){
		int x=que.front().x,y=que.front().y;
		if(x==ex&&y==ey){
			cout<<que.front().step;//最短路径长度 
			break;
		} 
		for(int d=0;d<=3;++d){
			int arx=x+dx[d],ary=y+dy[d];
			if(!chessboard[arx][ary]&&!vis[arx][ary]){ 
				point arp(arx,ary,que.front().step+1);
				que.push(arp); 
				vis[arx][ary]=1; 
			}
		}
		que.pop(); 
	}
	return 0;
}

例题

长草

题目入口

题目描述:

小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。

小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。

这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,

这四小块空地都将变为有草的小块。请告诉小明,k 个月后空地上哪些地方有草。

输入描述:

输入的第一行包含两个整数n,m。

接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。

接下来包含一个整数 k。 其中,2≤n,m≤1000,1≤k≤1000。

输出描述:

输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。

输入输出样例:

示例:

输入:

解释

4 5 .g... ..... ..g.. ..... 2

输出:

解释

gggg. gggg. ggggg .ggg.

运行限制:

    最大运行时间:1s
    最大运行内存: 256M

解题思路:

这个题目简直就是为了广度优先搜索设置模板题,由于这个题目时输出广度优先搜索 K 次扩展后的终态,那我们就不用设置 Check 函数。

这里用一个N×M 的矩阵来表示草地。

  1. 算法开始:

    将字母为 g 的草地的位置加入队列,然后向下执行

  2. 判断边界:

    判断是否已经长了草,判断是否超出边界范围

  3. 搜索过程:

    不断从队列取出一个节点,进行上下左右的扩展,执行 2 判断边界,符合就放入队列,不符合就跳过。

    执行 K 次扩展,输出草地状态。

  4. check(参数):

    这里不需要进行 Check


#include <bits/stdc++.h>
using namespace std;
const int M = 1005;
struct PII
{
    int first;
    int second;
};
// C++ 有个数据类型叫 pair 上面的就可以定义为 pair<int,int> 用起来比较方便。
PII tempPair;//临时结点
char Map[M][M];
//---------图的路径搜索常用方向移动表示-------
int dx[4]= {0,1,-1,0};
int dy[4]= {1,0,0,-1};
// 两两组合形成上下左右四个方向
//      1------------------> x
//      |
//      |
//      |
//      |
//      |
//      |
//      |
//      ↓
//      y

// dx[0]=0 dy[0]=1 那么代表向下的方向

// dx[1]=1 dy[1]=0 那么代表向右的方向

// dx[2]=-1 dy[0]=0 那么代表向左的方向

// dx[3]=0 dy[1]=-1 那么代表向上的方向

int n;// n 行
int m;// m 列
int k;// k 次

queue<PII > q; //广度优先搜索所用的队列

int len;//记录节点数量方便后续k的计算
bool pd(int x, int y)
{
    if(x<1)
        return 0;
    // /x 轴坐标 左侧越界
    else if(x>n)
        return 0;
    //x 轴坐标 右侧越界
    else  if(y<1)
        return 0;
    //y 轴坐标 上侧越界
    else if(y>m)
        return 0;
    //y 轴坐标 下侧越界
    else if(Map[x][y]=='g')
        return 0;
    //已经长草了
    else return 1;
    // 在范围内,且没长草
}

void BFS()
{
    //BFS
    while(!q.empty()&&k>0)
    {
        tempPair = q.front();
        q.pop();
        //这两步是取出队首的节点

        int x = tempPair.first;//横坐标
        int y = tempPair.second;//纵坐标

        for(int i=0; i<4; i++)
        {
            int nowx = x+dx[i]; //扩展后的横坐标
            int nowy = y+dy[i]; //扩展后的纵坐标

            if(pd(nowx,nowy))
            {
                q.push({nowx,nowy});
                Map[nowx][nowy]='g';
            }
            //符合要求执行扩展,不符合要求,忽略即可。
        }

        len--; //没取出一个节点len  -1
        if(len==0)
        {
            //当len =0 时,代表当前层扩展完了,那么就代表第一个月扩展完了
            k--; // 所以k--
            len = q.size(); // 当前层扩展完了,那就该扩展下一层了,所以len又被赋值为下一层的节点数目的值
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            cin>>Map[i][j];
            if(Map[i][j]=='g')
            {
                tempPair.first=i;
                tempPair.second=j;
               // cout<<i<<""<<j<<endl;
                q.push(tempPair);//将初始有树的结点加入队列
            }
        }
    }

    len = q.size();//记录第一层的节点数量方便后续k的计算
    cin>>k;
    BFS();
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            cout<<Map[i][j];
        }

        cout<<endl;
    }
    return 0;
}

 走迷宫

题目入口

题目描述:

给定一个N×M 的网格迷宫 G。G 的每个格子要么是道路,要么是障碍物(道路用 1表示,障碍物用 0 表示)。

已知迷宫的入口位置为 (x1​,y1​),出口位置为(x2​,y2​)。问从入口走到出口,最少要走多少个格子。

输入:

5 5
1 0 1 1 0
1 1 0 1 1
0 1 0 1 1
1 1 1 1 1
1 0 0 0 1
1 1 5 5

输入第 1 行包含两个正整数 N,M,分别表示迷宫的大小。

接下来输入一个N×M 的矩阵。若Gi,j​=1 表示其为道路,否则表示其为障碍物。

最后一行输入四个整数 x1​,y1​,x2​,y2​,表示入口的位置和出口的位置。

1≤N,M≤102,0≤Gi,j​≤1,1≤x1​,x2​≤N,1≤y1​,y2​≤M。

输出:

输出仅一行,包含一个整数表示答案。

若无法从入口到出口,则输出 −1−1。

输入输出样例:

输入

5 5

1 0 1 1 0

1 1 0 1 1

0 1 0 1 1

1 1 1 1 1

1 0 0 0 1

1 1 5 5

输出

8

运行限制:

最大运行时间:1s
最大运行内存: 128M

解题思路:

  1. 算法开始:

    我们以起点开始做 BFS ,将入口压入栈开始执行 BFS 算法

  2. 判断边界:

    在当前行,当前列的位置上判断是否满足条件,若不满足,跳到第 5 步,即不符合边界条件。 判断条件如下:

    1. vis[x][y] >= 1 标记数组已经被标记,已被走过,不能再走,超出边界

    2. x<1 从左侧走出方格

    3. x>n 从右侧走出方格

    4. y<1 从上侧走出方格

    5. y>n 从下侧走出方格

    6. map[x][y] != 1 没有路不能走

  3. 搜索过程:

    调用 Check 函数。

    如果边界条件满足,就继续调用搜索,找到下一步的位置

    每次找到下一个位置的时候,令其Vis[nextx][nexty] = 当前 Vis+1

    这样既能用 vis 数组标记又能使用 vis 数组存步数,从 1 开始,即开始节点是 1 ,所以最后要减去 1 。

  4. check(参数):

    如果当搜索到 x=终点x, y=终点y 时,就找到了终点,此时他的Vis 数组就存储了他的步数,但是是从 1 开始的。


#include <bits/stdc++.h>
using namespace std;

int vis[150][150]; //用于存储是否访问过,并且存储长度

char G[150][150]; //用于存储题目给出的地图

int n,m,ans=0;

int dx[4] = {0,0,-1,1};

int dy[4] = {1,-1,0,0};

//上下左右移动,不会的看前面的代码

struct node
{
    int x;
    int y;
};

node Start,End;
bool pd(int x,int y)
{


    if(x<1)
        return 0;
    //从左侧走出方格

    else if(x>n)
        return 0;
    //从右侧走出方格

    else if(y<1)
        return 0;
    //从上侧走出方格

    else if(y>m)
        return 0;
    //从下侧走出方格

    else if( vis[x][y]!=0)
        //已经访问了
        return 0;
    else if(G[x][y]!='1') return 0;
    //不是路不能走
    else return 1;
}

bool  check(int x, int y)
{

    if(x == End.x&& y == End.y)   //找到终点,把距离给他
    {
        ans  =  vis[x][ y];
        return 1;
    }

    else    return 0;

}
void bfs()
{
    queue<node>q;

    node now,next;

    q.push(Start);     //将起点压人队列中

    vis[Start.x][Start.y] = 1;

    while(!q.empty())
    {
        now = q.front();

        if(check(now.x,now.y))
            return ;

        q.pop();     //将队列最前面的弹出。

        for(int i=0; i<4; i++)  //四个方向
        {

            int nextx = now.x + dx[i];
            int nexty = now.y + dy[i];

            if(pd(nextx,nexty))  //判断是否符合条件
            {

                next.x=nextx;
                next.y=nexty;

                q.push(next);

                vis[nextx][nexty] = vis[now.x][now.y]+1; //步数+1
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    //memset(vis,0,sizeof(vis));
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            cin>>G[i][j];
        }
    }

    cin>>Start.x>>Start.y>>End.x>>End.y;

    ans = 0;

    bfs();
    cout<<ans-1<<endl;

    return 0;
}

迷宫

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

解释

010000

000100

001001

110000

迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这个它的上、下、左、右四个方向之一。

对于上面的迷宫,从入口开始,可以按 DRRURRDDDR 的顺序通过迷宫, 一共 1010 步。其中 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 打印路径。

#include<bits/stdc++.h>
using namespace std;
#define maxn 2000

string maze[maxn]= {
                  "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"};
bool vis[maxn][maxn];//标记
int dir[4][2]={{1,0},{0,-1},{0,1},{-1,0}};//D L R U

bool in(int x,int y)
{
    return x<30&&x>=0&&y>=0&&y<50;
}

struct node
{
    int x,y,d;
    char pos;//存储D L R U
};

node father[maxn][maxn];//当前节点的父节点
node now,nex;//指向当前和下一个位置

void dfs(int x,int y)//递归打印
{
    if(x==0&&y==0)//找到起点开始正向打印路径
        return;
    else
        dfs(father[x][y].x,father[x][y].y);

    cout<<father[x][y].pos;
}

void bfs(int x,int y)
{
    queue<node> q;

    now.x=x;
    now.y=y;
    now.d=0;
    q.push(now);

    vis[x][y]=true;
    while(!q.empty())
    {
        now=q.front();
        q.pop();
        for(int i=0;i<4;i++)//走下左右上按字典序的四个方向
        {
            int tx=now.x+dir[i][0];
            int ty=now.y+dir[i][1];
            if(in(tx,ty)&&!vis[tx][ty]&&maze[tx][ty]!='1')//判断是否超出范围,是否用过,是否为1
            {
                vis[tx][ty]=true;//标记为用过

                nex.x=tx;
                nex.y=ty;
                nex.d=now.d+1;
                q.push(nex);//压入队列

                father[tx][ty].x=now.x;//存储父节点坐标
                father[tx][ty].y=now.y;
                if(i==0)//存储路径
                    father[tx][ty].pos='D';
                else if(i==1)
                    father[tx][ty].pos='L';
                else if(i==2)
                    father[tx][ty].pos='R';
                else if(i==3)
                    father[tx][ty].pos='U';


            }
        }
    }
}

int main()
{

    bfs(0,0);
    dfs(29,49);//打印路径

    return 0;
}

点个赞吧谢谢ヽ( ̄ω ̄( ̄ω ̄〃)ゝ

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值