BFS算法题

目录

1.BFS

 2.树里的宽搜 

题目一——429. N 叉树的层序遍历 - 力扣(LeetCode)

 题目二——103. 二叉树的锯齿形层序遍历 - 力扣(LeetCode)

题目三——662. 二叉树最大宽度 - 力扣(LeetCode) 

题目四——515. 在每个树行中找最大值 - 力扣(LeetCode)

3.BFS解决最短路径问题 

为什么BFS可以解决最短路径问题

题目一——1926. 迷宫中离入口最近的出口 - 力扣(LeetCode)

题目二——433. 最小基因变化 - 力扣(LeetCode) 

 题目三——127. 单词接龙 - 力扣(LeetCode)

题目四——675. 为高尔夫比赛砍树 - 力扣(LeetCode) 

4.多源BFS

题目一——542. 01 矩阵 - 力扣(LeetCode)

题目二——1020. 飞地的数量 - 力扣(LeetCode)

题目三—— 1765. 地图中的最高点 - 力扣(LeetCode)

题目四—— 1162. 地图分析 - 力扣(LeetCode)


1.BFS

BFS 全称是 Breadth First Search,中文名是宽度优先搜索,也叫广度优先搜索。

是图上最基础、最重要的搜索算法之一。

所谓宽度优先。就是每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。

BFS广度优先搜索,在处理问题时,优先考虑更多的机会,而不是像DFS那样优先走一条路,再回溯

BFS基于队列实现,目的是把可能的解放在同一层处理,即BFS队列中至多只有两层的解

考虑完前一层可能的解后,再考虑下一层的解。把当前解的后续解再放到队列尾部。

BFS是基于队列实现的 

如上图中,BCDE处在同一层考虑,那么

  1. 考虑到 B 的后续解FGH时,先把B弹出队列,再把FGH 放在 CDE 后面,即CDEFGH 。
  2. 考虑到 C 的后续解IJ时,先把C弹出队列,再把 IJ 放在 DEFGH 后面,即 DEFGHIJ 。
  3. 考虑到 D 的后续解K时,先把D弹出队列,再把 K 放在 EFGHIJ 后面,即 EFGHIJK 。
  4. 考虑到 E 的后续解(这里没有)时,先把E弹出队列,再把这里就不需要放在 FGHIJK 后面了,即 FGHIJK 。

 现在队列里面只剩下第三层的了!!!这样子我们可以一直按照上面这个逻辑执行下去,直到队列为空


下面结合一个图 (graph) 的实例,说明 BFS 的工作过程和原理:

(1)将起始节点1放入队列中,标记为已遍历: 

(2)从queue中取出队列头的节点1,找出与节点1邻接的节点2,3,标记为已遍历,然后放入queue中。

 (3)从queue中取出队列头的节点2,找出与节点2邻接的节点1,4,5,由于节点1已遍历,排除;标记4,5为已遍历,然后放入queue中。

(4)从queue中取出队列头的节点3,找出与节点3邻接的节点1,6,7,由于节点1已遍历,排除;标记6,7为已遍历,然后放入queue中。

(5)从queue中取出队列头的节点4,找出与节点4邻接的节点2,8,2属于已遍历点,排除;因此标记节点8为已遍历,然后放入queue中。

(6)从queue中取出队列头的节点5,找出与节点5邻接的节点2,8,2,8均属于已遍历点,不作下一步操作。

(7)从queue中取出队列头的节点6,找出与节点6邻接的节点3,8,9,3,8属于已遍历点,排除;因此标记节点9为已遍历,然后放入queue中。

(8)从queue中取出队列头的节点7,找出与节点7邻接的节点3, 9,3,9属于已遍历点,不作下一步操作。

(9)从queue中取出队列头的节点8,找出与节点8邻接的节点4,5,6,4,5,6属于已遍历点,不作下一步操作。

(10)从queue中取出队列头的节点9,找出与节点9邻接的节点6,7,6,7属于已遍历点,不作下一步操作。

(11)queue 为空,则遍历结束


 2.树里的宽搜 

我们看一下怎么使用BFS遍历树?

如下动图所示:

下面解释一下整个 BFS 的过程,我们整个遍历的过程是这样的:

1、将节点 A 加入到队列 q 中。此时队列只有一个结点 A。

2、队列 q 不为空,我们弹出队列的首节点,也就是 A,找到 A 的所有邻接节点。从上图可以看出,也就是 B、C、D,我们将 B、C、D 加入到队列中。这样队列内的元素就是 B,C,D。

3、队列 q 不为空,我们弹出队列的首节点,也就是 B,找到 B 的所有邻接节点。从上图可以看出,也就是 E、F,我们将 E、F  加入到队列中。这样队列内的元素就是 C,D,E,F。

4、队列 q 不为空,我们弹出队列的首节点,也就是 C,找到 C 的所有邻接节点。从上图可以看出,C 没有邻接节点。这样队列内的元素就是 D,E,F。

5、队列 q 不为空,我们弹出队列的首节点,也就是 D,找到 D 的所有邻接节点。从上图可以看出,也就是 H、I、J,我们将 H、I、J 加入到队列中。这样队列内的元素就是 E,F,H,I,J。

6、队列 q 不为空,我们弹出队列的首节点,也就是 E,找到 E 的所有邻接节点。从上图可以看出,C 没有邻接节点。这样队列内的元素就是 F,H,I,J。

7、队列 q 不为空,我们弹出队列的首节点,也就是 F,找到 F 的所有邻接节点。从上图可以看出,F 没有邻接节点。这样队列内的元素就是 H,I,J。

8、队列 q 不为空,我们弹出队列的首节点,也就是 H,找到 H 的所有邻接节点。从上图可以看出,也就是 K,我们将 K 加入到队列中。这样队列内的元素就是 I,J,K。

9、队列 q 不为空,我们弹出队列的首节点,也就是 I,找到 I 的所有邻接节点。从上图可以看出,也就是 G、L,我们将 G、L 加入到队列中。这样队列内的元素就是 I,J,K,G,L。

10、队列 q 不为空,我们弹出队列的首节点,也就是 I,找到 I 的所有邻接节点。从上图可以看出,I 没有邻接节点。这样队列内的元素就是 J,K,G,L。

11、队列 q 不为空,我们弹出队列的首节点,也就是 J,找到 J 的所有邻接节点。从上图可以看出,J 没有邻接节点。这样队列内的元素就是 K,G,L。

12、队列 q 不为空,我们弹出队列的首节点,也就是 K,找到 K 的所有邻接节点。从上图可以看出,K 没有邻接节点。这样队列内的元素就是 G,L。

13、队列 q 不为空,我们弹出队列的首节点,也就是 G。从上图可以看出,G 没有邻接节点。这样队列内的元素就是 L。

14、队列 q 不为空,我们弹出队列的首节点,也就是 L。从上图可以看出,L 没有邻接节点。这样队列内就没有元素,结束。

题目一——429. N 叉树的层序遍历 - 力扣(LeetCode)

 

 这个题目的意思是相当简单易懂的吧。

有人可能好奇:为什么层序遍历需要借助队列这个数据结构呢?

我们看下面这个图就明白了

额,这个不是和那个队列的先进先出的性质是一模一样的吗?所以我们就借助队列来解决这种问题。

  1. 我们先搞一个队列,如果根节点不为空,我们让根节点入队列
  2. 接下来是一个while循环,当队列不为空的时候,一直执行下面这个
  3. 先把队头元素从队列里删除,把队头元素的孩子入队。
  4. 直到这个队列为空为止,这个层序遍历就算是结束了。

我们现在来考虑一下,我们怎么分层呢?

我们其实可以另外设置一个变量,先统计队列有多少个元素,有多少的元素,我们就先出队多少次。这个时候,队列里就只剩孩子结点了(即下一层的),然后我们重复上面这个步骤就好了。

我们很快就能写出下面这个代码啊! 

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        vector<vector<int>>ret;//记录最终结果
        queue<Node*>q;

        if(root==nullptr)
        {
            return ret;
        }

        q.push(root);//将根结点添加进队列里面
        
        while(q.size()!=0)
        {
            vector<int>tmp;//统计本层的元素
            int s=q.size();//记录当前队列里面的元素个数
            //先把队列里所有结点的孩子结点加入到队列里面
            for(int i=0;i<s;i++)
            {
                Node* t=q.front();//获取队列第一个元素
                q.pop();//删除队列第一个元素
                tmp.push_back(t->val);//将队列第一个元素的值添加进tmp里面
                //将第一个元素的孩子结点添加进队列里面
                //注意孩子结点是以数组的方式访问的
                for(Node* child:t->children)
                {
                    if(child!=nullptr)
                    {
                        q.push(child);
                    }
                }
            }
            ret.push_back(tmp);//将本层的结果集添加进总的结果里面
        }
        return ret;
    }
};

 

 题目二——103. 二叉树的锯齿形层序遍历 - 力扣(LeetCode)

 

 这题和上面那题好像差不多,只不过就是改变了层序遍历的规则而已。

我们还是使用BFS来解决,我们增加一个标记位,这个锯齿形遍历就只需要在偶数行将结果逆序一下就好了。

class Solution 
{
 public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) 
    {
        vector<vector<int>>ret;//总的结果
        queue<TreeNode*> q;//队列

        if(root==nullptr)
        {
            return ret;
        }
        q.push(root);//将根节点加入到队列里面
        int level=1;//每一行的标记位
        while(q.size()!=0)
        {
            vector<int>tmp;//记录这一行的结果
            int sz=q.size();
            for(int i=0;i<sz;i++)
            {
                TreeNode* f=q.front();//获取队列的第一个元素
                q.pop();//删除队列的第一个元素
                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值