BFS学习心得——常见BFS模版、配习题讲解

1 BFS模版

按层次的顺序进行遍历

void BFS(int s){
  queue<int> q;
  q.push(s);
  while(){
    取出队首元素top;
    访问队首元素;
    将队首元素出队;
    将top的下一层结点未入队的结点全部入队,并设置为已入队;
  }
}

2 详细说明

  • 1 定义队列q,并将队首s入队
  • 2 写一个while循环,循环条件是队列q非空
  • 3 在while循环中,先取队首元素top,并访问它(访问:可以是任何事,比如将其输出),将其出队
  • 4 将top的下一层结点中所有未曾入队的元素入队,并标记它们的层号为now+1,同时设置这些入队的结点已入队
  • 5 返回2继续循环

3 题目

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

Sample Input:
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

Sample Output:
4

参考代码:

#include <cstdio>
#include <queue>

using std::queue;

const int MAXN =100;

struct node
{
    int x,y;//位置x,y
}Node;

int n, m;    //矩阵大小m,n
int matrix[MAXN][MAXN];//矩阵
bool inq[MAXN][MAXN] = {false};//记录{x,y}是否入队

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

bool judge(int x, int y){
    //越界
    if(x >= n || x < 0 || y >= m || y < 0) return false;
    //当前位置为0,或(x,y)已如果队
    if(matrix[x][y] == 0 || inq[x][y] == true) return false;
    return true;
}

void BFS(int x, int y){
    queue<node> 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)//循环四次,得到4个相邻的位置
        {
            int newX = top.x + X[i];
            int newY = top.y + Y[i];
            if(judge(newX, newY)){//如果新位置需要访问
                //将Node坐标设置为(newX, newY)
                Node.x = newX;
                Node.y = newY;
                Q.push(Node);//将结点Node入队
                inq[newX][newY] = true;//设置(newX,newY)已入过队
            }
        }
    }
}

int main(int argc, char const *argv[])
{
    scanf("%d%d", &n, &m);
    for (int x = 0; x < n; ++x)
    {
        for (int y = 0; y < m; ++y)
        {
            scanf("%d", &matrix[x][y]);//读入01矩阵
        }
    }

    int ans = 0;//存放块
    for (int x = 0; x < n; ++x)//枚举每个位置
    {
        for (int y = 0; y < m; ++y)
        {
            //如果元素为1,且未入队
            if(matrix[x][y] == 1 && inq[x][y] == false){
                ans++;
                BFS(x,y);//访问整个块,将该块的所有1的inq都标记为1
            }
        }
    }

    printf("%d\n", ans);
    return 0;
}

4 题目

给定一个nm大小的迷宫,其中 * 代表不可通过的墙壁,而"."代表平地,S代表起点,T代表终点。移动过程中,如果当前位置是(x,y)(下标从0开始),且每次只能前往上下左右(x,y+1),(x,y-1),(x-1,y)(x+1,y)四个位置的平地,求从起点S达到终点T的最少步数。

.
..
.
**.
…T.
上面样例中,S的坐标我饿(2,2),T的坐标为(4,3)

Sample Input:
5 5 	//5行5列 
..... 	//迷宫信息
.*.*.
.***.
...T.
2 2 4 3	//起点S的坐标和终点T的坐标
Sample Output:
11

参考代码:

#include <cstdio>
#include <queue>

using std::queue;

const int MAXN = 100;

struct node
{
    int x, y;
    int step;    
}S, Node, T;

int m,n; //m行,n列
char maze[MAXN][MAXN];
bool inq[MAXN][MAXN] = {false};

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

bool Test(int x, int y){//判断坐标(x,y)是否有效
    if(x < 0 || x >= n || y < 0 || y >= m) return false;
    if(inq[x][y] == true || maze[x][y] == '*') return false;//已入过队列,或者墙壁
    return true;
}

int  BFS(){
    queue<node> Q;//1 定义队列
    Q.push(S);//2 将起点入队
    inq[S.x][S.y] = true;
    while(!Q.empty()){
        node top = Q.front();//3 取出队首元素
        Q.pop();            //4 队首元素出队
        if(top.x == T.x && top.y == T.y){
            return top.step;//终点,直接返回最少步数
        }

        for (int i = 0; i < 4; ++i)//将top下一层结点未入队的入队,并标记为已入队
        {
            int new_x = top.x + X[i];
            int new_y = top.y + Y[i];
            if(Test(new_x, new_y) == true){
                Node.x = new_x;
                Node.y = new_y;
                Node.step = top.step + 1;//Node层数为top层数加1
                Q.push(Node);
                inq[new_x][new_y] = true;
            }
        }
    }
    return -1;//无法达到终点
}

int main(int argc, char const *argv[])
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i)
    {
        getchar();//过滤掉每个换行符
        for (int j = 0; j < m; ++j)
        {
            maze[i][j] = getchar();
        }
        maze[i][m + 1] = '\0';
    }

    scanf("%d%d%d%d", &S.x, &S.y, &T.x, &T.y);
    S.step = 0;//初始化层数为0,S到T的最小步数为0
    printf("%d\n", BFS());
    return 0;
}

5 注意点

  • BFS中设置的inq为是否结点已经入队,而不是结点是否已被访问,
  • 如果设置成是否已被访问,就会造成很多结点重复入队(该结点正在队中,但还没有被访问,其他结点也可以到达该结点,使得该结点再次入队)
  • 使用STL中的queue队列,把元素入队,只是把该元素的一个副本入队,入队后
    • 队列中元素副本的修改,不会改变原元素
    • 对原元素的修改,也不会该拜年队列中元素的副本
    • 这样可能会引入bug(一般由结构体造成)
#include <cstdio>
#include <queue>

using std::queue;

struct node
{
   int data;
}a[10];

int main(int argc, char const *argv[])
{
   queue<node> q;
   for (int i = 1; i != 4; ++i)
   {
       a[i].data = i;
       q.push(a[i]);
   }

   q.front().data = 100;
   printf("%d %d %d\n", a[1].data, a[2].data, a[3].data);

   a[1].data = 200;
   printf("%d\n", q.front().data);
   return 0;
}

运行结果:
在这里插入图片描述

  • 改进办法,当队列中的元素需要修改时,队列中存的元素最好不要时它本身,而是它们的编号(如数组的话,存放其下标)
    可以把上面的程序改为:
#include <cstdio>
#include <queue>

using std::queue;

struct node
{
    int data;
}a[10];

int main(int argc, char const *argv[])
{
    queue<int> q;
    for (int i = 1; i != 4; ++i)
    {
        a[i].data = i;
        q.push(i);
    }

    a[q.front()].data = 100;

    printf("%d\n", a[1].data);
    return 0;
}
发布了321 篇原创文章 · 获赞 51 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_33375598/article/details/104046582
BFS