作者 | P.yh

来源 | 五分钟学算法

初识广度优先搜索

在讲解广度优先搜索之前,我们来看看几个常见的数据结构,链表、树、图

先来看看其中比较简单的数据结构 - 链表,它和数组类似,也是一个线性的结构,简单来说就是一条路径,你从头开始遍历,最终会将链表上面的节点都访问到,到达终点。

相比数组来说,链表在内存中的存储可以不是一段连续的区域

链表节点中会有一个变量用来指明其下一个节点,将链表的表示用代码写出来,就会是下面这样:

class ListNode {int val;ListNode next;
}

其中 val 表示的是这个节点上面值,next 表示的是这个节点的下一个节点。

讲完链表,我们来看看另外一个数据结构 - 二叉树,它其实是链表的一个延伸,这里,一个节点的下一个节点不再只有一个节点了,可能会有两个节点,如果把树的结构用代码表示出来,就会是:

class TreeNode {int val;TreeNode left, right;
}

你可能会问,多叉树如何表示呢?这个也简单,一个树节点会有多个子节点:

class TreeNode {int val;List<TreeNode> children;
}

不管是两个节点还是多个节点,树当中是有层级关系的,就是 parent 和 children 的关系。

对于一般的树结构来说,一个节点内只会保存其 children 的信息,不会保存其 parent 的信息,给你一个树节点,你只会往其 children 的方向走,也就是说,树的遍历其实是有方向性的

最后我们来看看图,图的话分为有向图和无向图,树其实是算有向图当中的一种,有向无向,主要是看边,如果两个节点连在一起,它们之间是互通的话就是无向图,如果只能从一个节点到另一个节点,反之可能不行,那就是有向图,不管是有向图还是无向图,在代码中,我们都可以表示下面这样:

class GraphNode {int val;List<Integer> neighbors;
}

这不就是前面多叉树的表示方法吗?没错,但是图中的关系不再是 parent 和 children 的关系了,而是邻居的关系,这里也没有层级结构了,每个节点都是平等的

讲完了这几个数据结构,我们再回过头来看看广度优先搜索这个算法,这个算法经常被用在树上和图上,我们来思考一下这个问题,如果给你一个连通图上的一个节点,如何才能得到图上所有的节点呢?

这个思路其实很简单。

首先我们知道,我们可以把给定节点和其邻居加入到答案中,但是邻居还有邻居,因此我们还是得继续这个过程,直到把所有的点都找到,这之中我们可能会遇到一种情况就是,我们访问到了之前找到过的点,因此,这里我们还需要一个判重的机制。

这里有一点是,每个点只可能找到其邻居,也就是说只会往其周围的点找,一次只向外扩散一格,解决广度优先搜索问题,我们会使用队列这么一个 FIFO 的数据结构,这不难理解,先找到的点我们先考虑其邻居。

问题分类

上面我们简单介绍了一下广度优先搜索这个算法,那么它可以用来解决什么样的问题呢?

  • 层级遍历

  • 由点到面遍历图

  • 拓扑排序

  • 求最短路径

我们一个一个来讲解,首先是层级遍历,前面讲过,每个节点只会找到其周围的节点,你可以想象成当前层的节点只可能找到下一层的节点(前一层遍历过不考虑),因此我们可以把一层找到的东西放在一起,这也就是用层这个概念对找到的所有节点进行归类。

第二点是遍历图,其实就是上面中的例子“给定连通图上面的一个节点,需要找到这个图中的所有节点”,你可能会问,遍历整个图有什么用呢?如果我们知道来所有节点的数量,其实通过遍历整个图我们还可以判断一个图的连通性,如果从一个点出发找不到某些点,那么说明其实这不是一个连通的图,有些节点不在图上,被分开了。

第三点是拓扑排序,这里可以参考我之前写的一篇文章

第四点,也是比较常用的就是找出图上两点的最短路径,当然这里是有条件的,就是这个图必须是简单的连通图,什么是简单图,就是边没有权重,或者说权重都为固定的值。从一个点出发,找到下一层的所有点,从下一层的点出发,找到下下层的所有点,每到一层就算走一步,当我们找到我们要找的点,此时的步数就是最后的答案。

对于广度优先搜索的时间和空间复杂度的分析也是比较简单,一般问题都需要遍历整个图,因此时间复杂度是 O(N + M),空间复杂度是 O(N),这里的 N 表示的是节点的总数量,M 表示的是边的数量,有些图中,比如说全连通图(M = N^2),我们遍历的时候,会尝试去走所有的边,对于空间来说的话,一般只会记录访问过的节点和当前层的节点,不会去考虑边的情况,因此时间复杂度和空间复杂度在这里还是不太一样的。

LeetCode 常见题目

二叉树的锯齿形层次遍历

常见题目一来源于 LeetCode 上第 103 号问题:二叉树的锯齿形层次遍历。题目难度为 Medium,目前通过率为 43.8% 。

题目描述

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

例如:[3,9,20,null,null,15,7],

    3/ 9  20/  15   7

返回锯齿形层次遍历如下:

[[3],[20,9],[15,7]
]

题目分析

考察上面提到的第一点,层级遍历

这道题其实是树的层级遍历的变形,我们需要记录每一层的信息,但是记录的顺序有区分,第一层从左向右记录,第二层反过来,从右向左记录,第三层从左向右记录,。。。,

你可以看到每一层的记录方向和上下层都不一样,是一种交错的形式,当然我们可以都从左向右记录,然后到特定的层就把记录的结果给反转一下,但是这里有一个小技巧就是,我们都是从左向右记录,但是记录方式不一样,一种记录方式是从列表的尾部加入,另一种是从链表的头部加入

在树上的遍历相对来说比较简单,因为树的遍历是有方向性的,这个方向性确保了我们不会访问到我们之前访问过的节点,因此,这里我们不需要使用 Set 或者 boolean 数组去帮助去重。

参考代码

public List<List<Integer>> zigzagLevelOrder(TreeNode root) {List<List<Integer>> result = new ArrayList<>();if (root == null) {return result;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);boolean isReverse = false;while (!queue.isEmpty()) {int size = queue.size();List<Integer> curLevel = new ArrayList<>();for (int i = 0; i < size; ++i) {TreeNode cur = queue.poll();if (cur.left != null) {queue.offer(cur.left);}if (cur.right != null) {queue.offer(cur.right);}if (isReverse) {curLevel.add(0, cur.val);} else {curLevel.add(cur.val);}}isReverse = !isReverse;result.add(curLevel);}return result;
}

以图判树

常见题目二来源于 LeetCode 上第 261 号问题:以图判树。

题目描述

给定从 0 到 n-1 标号的 n 个结点,和一个无向边列表(每条边以结点对来表示),请编写一个函数用来判断这些边是否能够形成一个合法有效的树结构。

示例 1:

输入: n = 5, 边列表 edges = [[0,1], [0,2], [0,3], [1,4]]
输出: true

示例 2:

输入: n = 5, 边列表 edges = [[0,1], [1,2], [2,3], [1,3], [1,4]]
输出: false

注意:你可以假定边列表 edges 中不会出现重复的边。由于所有的边是无向边,边 [0,1] 和边 [1,0] 是相同的,因此不会同时出现在边列表 edges 中。

题目分析

考察第二点,由点及面遍历图。首先需要知道的问题是,如何判断一个树是不是有效的? 这里有两点,第一就是树上是没有环的,怎样才能确定其无环呢?如果你画出一个树,你就会发现,不管怎么画,其节点数和边的数量都是 n - 1 = edges 的关系,那么有这一点是不是就够了,并不是,我们还需要判断连通性,也就是说从一个节点出发,可以遍历到所有的点,满足这两个条件的图才能算是树。

参考代码

public boolean validTree(int n, int[][] edges) {        if (n - 1 != edges.length) {return false;}Map<Integer, Set<Integer>> graph = new HashMap<>();buildGraph(graph, edges, n);Queue<Integer> queue = new LinkedList<>();Set<Integer> visited = new HashSet<>();queue.offer(0);while (!queue.isEmpty()) {int cur = queue.poll();if (!visited.add(cur)) {return false;}for (int nei : graph.get(cur)) {queue.offer(nei);graph.get(nei).remove(cur);}}return visited.size() == n;
}private void buildGraph(Map<Integer, Set<Integer>> graph, int[][] edges, int n) {for (int i = 0; i < n; ++i) {graph.put(i, new HashSet<Integer>());}for (int[] edge : edges) {graph.get(edge[0]).add(edge[1]);graph.get(edge[1]).add(edge[0]);}
}

总结

广度优先搜索就说到这里,这里我没有列出很多的题目,理解算法本身,及其适合解决的问题是关键,广度优先搜索主要适合解决层级遍历、由点及面遍历图、拓扑排序以及在求在简单图上两点之间的最短距离,理解了这些原理性的东西后,再去刷一些题目去巩固这些知识点,最后才能对这个算法了然于心。

初识广度优先搜索与解题套路相关推荐

  1. 小白的算法初识课堂(part6)--广度优先搜索

    学习笔记 学习书目:<算法图解>- Aditya Bhargava 文章目录 图简介 图是啥 广度优先搜索 寻找最短路径 队列 实现图 实现算法 运行时间 图简介 今天是五一,假如我要从家 ...

  2. 步步为营(十六)搜索(二)BFS 广度优先搜索

    上一篇讲了DFS,那么与之相应的就是BFS.也就是 宽度优先遍历,又称广度优先搜索算法. 首先,让我们回顾一下什么是"深度": 更学术点的说法,能够看做"单位距离下,离起 ...

  3. 深度优先搜索和广度优先搜索的比较与分析

    一)深度优先搜索的特点是: (1)无论问题的内容和性质以及求解要求如何不同,它们的程序结构都是相同的,即都是深度优先算法(一)和深度优先算法(二)中描述的算法结构,不相同的仅仅是存储结点数据结构和产生 ...

  4. 深度优先遍历和广度优先遍历_利用广度优先搜索解LeetCode第515题:在每个树行中找最大值...

    题目描述(难度中等) 您需要在二叉树的每一行中找到最大的值. 示例: 输入: 1/ 3 2/ 5 3 9 输出: [1, 3, 9] 解题思路 利用广度优先搜索找到每一层的所有数字,将其中最大的数字存 ...

  5. LeetCode 1306. 跳跃游戏 III(广度优先搜索BFS)

    1. 题目 这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处. 当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]. 请你判断自己是否能够 ...

  6. 广度优先搜索——岛屿数量(Leetcode 200)

    题目选自Leetcode 200. 岛屿数量 经典的搜索题,求岛屿数量 这里我用的是广度优先搜索BFS 最朴素的方法, 虽然效率不高,但是简单易懂 主要的问题在于:如何确定有多少个岛屿? 每次对一个& ...

  7. 广度优先搜索——奇怪的电梯(洛谷 P1135)

    广度优先搜索普及/提高篇,今天讲述的是洛谷里的一道题 奇怪的电梯(洛谷 P1135) 说一下我解题时候的思路吧. 首先读清楚题目,题目要求输出从 a楼 到 b楼的最少次数,楼层必须在[1,n]之间升降 ...

  8. 广度优先搜索(BFS)——抓住那头牛(POJ 4001)

    本文将以(POJ 4001)抓住那头牛 为例,讲解经典算法广度优先搜索(BFS)的STL写法 在实际写算法中,怎么能不使用更快.更方便.更准确.更高效的C++ STL模板呢 相信很多人都了解过广度优先 ...

  9. 广度优先搜索c语言矩阵,算法7-6:图的遍历——广度优先搜索 (C++代码)

    解题思路: 首先要开一个二维数组储存邻接矩阵,一般的方法是开一个足够大的数组,例如这道题是n不大于50,不过这样做会造成空间不必要的浪费.因此手动分配空间会更为合理.一种方法是用malloc,对应销毁 ...

最新文章

  1. Github 3.4k星,200余行代码,让你实时从视频中隐身
  2. JS-WEB-API(存储)
  3. 上传问题总结(文件大小检测,大文件上传)
  4. php.ini-dist和php.ini区别,php.ini-dist 和 php.ini-recommended 的区别介绍(方便开发与安全的朋友)...
  5. Python库安装相关问题
  6. c语言中x的n次方怎么表示_线性代数的本质及其在人工智能中的应用
  7. 分离整数的各个数(信息学奥赛一本通-T1088)
  8. 锤子t1重置后怎么显示无服务器,解决锤子手机smartisanT1关机后无法正常开机(附带刷机教程图文)...
  9. 移动开发—媒体查询(Media Query)
  10. envi反演水质参数_Landsat8单窗算法地表温度反演
  11. 419.甲板上的战舰
  12. 振型叠加法 matlab,Ansys模态叠加法谐响应分析
  13. android 高通替换开机logo,高通平台 开机logo 替换
  14. iphone 扩容测试软件,拯救iPhone 12 64G!闪迪打造的扩容神器上手:轻松省钱
  15. android 读取excel表格数据,并存入数据库
  16. 索引的作用?和它的优点缺点是什么?
  17. 如何避免软件行业的薪资天花板?
  18. Spring Boot 自定义注解支持EL表达式(基于 MethodBasedEvaluationContext 实现)
  19. 1.Linux中超频及cpufreq相关汇总
  20. Github搭建个人博客(2019最新版,亲测)

热门文章

  1. mysql实训报告_mysql数据库技术》实验报告.doc
  2. Python 快速生成 web 动态展示机器学习项目!
  3. NumPy学的还不错?来试试这20题
  4. 特斯拉AI团队招兵买马:“英雄不问出处”
  5. 通向人工智能产业落地化的道路在哪?
  6. Google是如何做Code Review的?| CSDN原力计划
  7. AI+DevOps正当时
  8. 腾讯拥抱开源:首次公布开源路线图,技术研发向共享、复用和开源迈进
  9. Java 24岁!Google加持的Kotlin真能取代它?
  10. 吴恩达最新斯坦福课程《深度学习》全部视频已送达,请签收!