BFS:广度优先搜索 | 从入门到熟练,到精通?

前言

最近刷题,凑巧刷到一些关于BFS的题,所以顺便总结一下BFS的解题方法。该博客面向新手小白,从入门到熟练BFS。如有不当的地方,也请真正精通BFS的大佬指正!

具体的内容会分为三个板块,以二叉树的层序遍历为引子,介绍BFS的原理,最后是多源BFS问题。具体的算法题会先描述解题思路,然后提供我的参考代码。

二叉树的层序遍历

二叉树的层序遍历与常规三件套,前、中、后序遍历有所不同,个人认为层序遍历理解起来更直观。其核心思想为“老带新”,使用一个队列(先进先出)达成此目的。

在这里插入图片描述

  1. 如动图所示,我们从根节点开始遍历,首先初始化队列,将节点“1”入队列。

  2. 当节点“1”出队列的时候,对节点进行操作,同时让节点“1”的左右孩子,节点“2”,“3”入队列。【老节点将新节点带入队列

  3. 反复循环入队列,出队列操作。如果某个节点的左节点(右节点)为空,就不进行入队列操作。

  4. 直到队列为空的时候,我们就对一棵二叉树完成了遍历。

    【多叉树的层序遍历原理相同】

void levelorder(TreeNode* root){
    if(root == nullptr)
            return;
    
    queue<TreeNode> level;
    level.push(root);
    while(!level.empty()){
        TreeNode* temp = level.front();
        level.pop();
        
        //对节点temp进行操作
        cout << temp->val << ' ';
        
        //对孩子进行入队列操作
        if(temp->left){
			level.push(root->left);
        }
        
        if(temp->right){
            level.push(root->right);
        }
    }
}

如果在遍历的时候需要考虑数据来自于哪一层,上述代码就失效了,因为队列中可能同时存在来自两层的节点,这时可以加入一个cot用以计数,就能解决问题。

102. 二叉树的层序遍历 - 力扣(LeetCode)

在这里插入图片描述

题目要求很简单,层序遍历二叉树,最终以二维数组的形式进行输出。思路便是上文提到的,在开始出队列操作前,使用一个cot记录队列中的节点数量(即属于同样深度的节点的数量),while(cot--)保证一个数组内的数都来自同一层。

vector<vector<int>> levelOrder(TreeNode* root) {
        if(root==nullptr)
            return {};
    
        queue<TreeNode*> bfs;
        bfs.push(root);
        int cot=-1;
        vector<vector<int>> ans;

        while(!bfs.empty()){
            cot=bfs.size();	//记录一层的节点数量
            ans.resize(ans.size()+1,vector<int>(cot,0));
            int i=0;	//用以控制插入数组的列的位置
            
            while(cot--){
                TreeNode* temp=bfs.front();
                bfs.pop();

                if(temp->left){
                    bfs.push(temp->left);
                }
                if(temp->right){
                    bfs.push(temp->right);
                }
                ans[ans.size()-1][i++]=temp->val;
            }
        }
        return ans;
    }

当然,你也可以尝试再使用一个队列,在节点入队列的时候记录它来自哪一层,也可以达到相同目的。如果感兴趣,可以自己尝试实现以下。不过这样做的话,空间复杂度就是O(N)。

BFS

真正的BFS算法更多是运用于图的一种搜索策略,但是其实际思想仍然是上文介绍的“老带新”,只不过因为图本身的结构特性,我们在还需要一个布尔数组记录某个节点的状态(是否被搜素过),以避免重复地搜索。做两道题,你就能明白是怎么回事了:)

1306. 跳跃游戏 III - 力扣(LeetCode)

在这里插入图片描述
该题是对BFS算法的基础运用,分析题目后你会发现,它实际上和二叉树的结构很相似:要么向左跳跃(左孩子),要么向右跳跃(右孩子),或者无法跳跃(孩子为空,nullptr)。所以直接采用层序遍历的策略进行遍历,但是可能会出现跳到已经跳过的格子上,所以加一个与原数组相同大小的布尔数组以记录格子状态。

我的参考代码如下:

bool bfs(vector<int> arr, int loc){
        vector<bool> status(arr.size(),false);	//用以记录状态,被搜索过状态变为true
        queue<int> temp;
        temp.push(loc);
        status[loc]=true;

        while(!temp.empty()){
            int size=temp.size();//记录一层的节点数量
            while(size--){
                int cur=temp.front();
                temp.pop();

                if(arr[cur]==0)
                    return true;	//满足题意则返回true
        

                //跳跃到左边的节点入队列
                if(cur-arr[cur]>=0 && status[cur-arr[cur]] == false){
                    status[cur-arr[cur]]=true;
                    temp.push(cur-arr[cur]);
                }

                //跳跃到右边的节点入队列
                if(cur+arr[cur]<arr.size() && status[cur+arr[cur]] == false){
                    status[cur+arr[cur]]=true;
                    temp.push(cur+arr[cur]);
                }
            }
        }
        return false;
    }
    
    bool canReach(vector<int>& arr, int start) {
        
        return bfs(arr,start);
    }

总结一下,BFS的基本套路:

  1. 初始化:创建一个队列来存储待访问的节点,并将起始节点加入队列。
  2. 记录状态:创建一个相同大小的布尔数组记录节点的状态;一个cot计数器,记录同层的节点数量。
  3. 访问节点:出队列操作,对节点进行操作。
  4. 入队列:判断节点邻居的状态,将它们入队列,并更改状态。
  5. 重复上述操作,直到所有节点已经被遍历。

多源BFS

多源BFS本质上并没有比BFS难,只是要同时处理多个格子的状态,处理好不同源之间的邻居关系,不能重复入队列或漏入队列即可。

1162. 地图分析 - 力扣(LeetCode)

在这里插入图片描述

我们在BFS中遍历的层数就是该题所求的曼哈顿距离,稍微改变BFS的一些细节就能解决这道题。

如图解所示,初始化队列的时候,将四角的陆地都入队列;第一轮遍历,遍历四个浅色格子;第二轮遍历,遍历到中间的深蓝色格子。总共遍历了两层格子,所以最终的答案就是2。
在这里插入图片描述
我的参考代码如下:

int maxDistance(vector<vector<int>>& grid) {
        struct loc {
            int x;
            int y;
        };

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

        const int N = grid.size();
        vector<vector<bool>> statu(N, vector<bool>(N, false));
        queue<loc> land;
        // 遍历grid,将初始的陆地单元格进入队列
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (grid[i][j] == 1) {
                    land.push(loc(i, j));
                    statu[i][j] = true; // 入队列后就更新该格子的状态
                }
            }
        }
        // 判断是否只有陆地和海洋
        if (land.size() == N * N || land.size() == 0)
            return -1;

        int cot = 0;
        while (!land.empty()) {
            cot++;
            int temp_size = land.size();
            // 让一层的陆地都出队列
            while (temp_size--) {
                loc node(land.front());
                land.pop();

                // 二维矩阵(图)的邻居是上下左右的四个格子
                for (int i = 0; i < 4; i++) {
                    int x1 = node.x + dx[i];
                    int y1 = node.y + dy[i];
                    //判断节点状态,以及坐标的合法性
                    if (x1 >= 0 && x1 < N && y1 >= 0 && y1 < N &&
                        statu[x1][y1] == false) {
                        land.push(loc(x1, y1));
                        statu[x1][y1] = true;
                    }
                }
            }
        }
        return --cot;

你可以用下面这道题检验一下自己,看有没有真的学会BFS!

994. 腐烂的橘子 - 力扣(LeetCode)

感谢你能看到这里,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值