热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

广度优先遍历(BFS)算法的概述、代码实现和应用

本文介绍了广度优先遍历(BFS)算法的概述、邻接矩阵和邻接表的代码实现,并讨论了BFS在求解最短路径或最短步数问题上的应用。以LeetCode中的934.最短的桥为例,详细阐述了BFS的具体思路和代码实现。最后,推荐了一些相关的BFS算法题目供大家练习。


目录


  • 1.概述
  • 2.代码实现
  • 3.应用



1.概述

(1)广度优先遍历 (Breadth First Search),又称宽度优先遍历,是最简便的图的搜索算法之一。

(2)已知图 G = (V, E) 和一个源顶点 start,宽度优先搜索以一种系统的方式探寻 G 的边,从而“发现” start 所能到达的所有顶点,并计算 start 到所有这些顶点的距离(最少边数),该算法同时能生成一棵根为 start 且包括所有可达顶点的广度优先树。对从 start 可达的任意顶点 v,广度优先树中从 start 到 v 的路径对应于图 G 中从 start 到 v 的最短路径,即包含最小边数的路径。该算法对有向图无向图同样适用。

(3)之所以称之为广度优先遍历,是因为算法自始至终一直通过已找到和未找到顶点之间的边界向外扩展,就是说,算法首先搜索和 start 距离为 k 的所有顶点,然后再去搜索和 start 距离为 k + 1 的其他顶点。


2.代码实现

(1)当使用邻接矩阵来表示图时,其代码实现如下:

class Solution {
/*
adjMatrix 为邻接矩阵,adjMatrix[i][j] = 0 表示节点 i 和 j 之间没有边直接相连
start 为遍历的起点
*/

public static void bfs(int[][] adjMatrix, int start) {
// n 表示图中的节点数量,节点编号为 0 ~ n - 1
int n = adjMatrix.length;
//定义 visited 数组,防止对节点进行重复遍历
boolean[] visited = new boolean[n];
Queue<Integer> queue &#61; new LinkedList<>();
if (start < 0 || start > n - 1) {
System.out.println("起点编号应为 [0, " &#43; (n - 1) &#43; "] 之间的整数&#xff01;");
return;
}
//起点入队
queue.offer(start);
//标记起点
visited[start] &#61; true;
System.out.print(start &#43; " ");
while (!queue.isEmpty()) {
int node &#61; queue.poll();
//将与节点 node 相连的节点加入到 queue 中
for (int i &#61; 0; i < n; i&#43;&#43;) {
if (adjMatrix[node][i] !&#61; 0 && !visited[i]) {
System.out.print(i &#43; " ");
visited[i] &#61; true;
queue.offer(i);
}
}
}
}
}

&#xff08;2&#xff09;当使用邻接表来表示图时&#xff0c;其代码实现如下&#xff1a;

class Solution {
/*
adjList 为邻接表&#xff0c;adjList[i] 中存储与节点 i 相邻的节点
start 为遍历的起点
*/

public static void bfs(List<Integer>[] adjList, int start) {
// n 表示图中的节点数量&#xff0c;节点编号为 0 ~ n - 1
int n &#61; adjList.length;
//定义 visited 数组&#xff0c;防止对节点进行重复遍历
boolean[] visited &#61; new boolean[n];
Queue<Integer> queue &#61; new LinkedList<>();
if (start < 0 || start > n - 1) {
System.out.println("起点编号应为 [0, " &#43; (n - 1) &#43; "] 之间的整数&#xff01;");
return;
}
//起点入队
queue.offer(start);
//标记起点
visited[start] &#61; true;
System.out.print(start &#43; " ");
while (!queue.isEmpty()) {
int node &#61; queue.poll();
//将与节点 node 相连的节点加入到 queue 中
for (int nextNode : adjList[node]) {
while (!visited[nextNode]) {
System.out.print(nextNode &#43; " ");
visited[nextNode] &#61; true;
queue.offer(nextNode);
}
}
}
}
}

&#xff08;3&#xff09;下面以图 G 为例来说明&#xff1a;

在这里插入图片描述

① 构造邻接矩阵&#xff1a;

int[][] adjMatrix &#61; {
{0, 1, 1, 0, 1},
{1, 0, 0, 1, 1},
{1, 0, 0, 0, 1},
{0, 1, 0, 0, 1},
{1, 1, 1, 1, 0}
};

② 构造邻接表&#xff1a;

int n &#61; 5;
List<Integer>[] adjList &#61; new ArrayList[n];
for (int i &#61; 0; i < n; i&#43;&#43;) {
adjList[i] &#61; new ArrayList<>();
}
adjList[0].add(1);
adjList[0].add(2);
adjList[0].add(4);
adjList[1].add(0);
adjList[1].add(3);
adjList[1].add(4);
adjList[2].add(0);
adjList[2].add(4);
adjList[3].add(1);
adjList[3].add(4);
adjList[4].add(0);
adjList[4].add(1);

如果 start &#61; 2&#xff0c;那么遍历的节点依次为&#xff1a;

2 0 4 1 3

遍历过程如下图所示&#xff1a;
在这里插入图片描述

&#xff08;4&#xff09;无论是邻接表还是邻接矩阵的存储方式&#xff0c;BFS 算法都需要借助一个辅助队列 queue&#xff0c;n 个顶点均需入队一次&#xff0c;在最坏的情况下&#xff0c;空间复杂度为 O(|V|)&#xff0c;而时间复杂度与图的存储方式有关&#xff1a;


  • 采用邻接矩阵存储方式时&#xff0c;查找每个顶点的邻接点所需的时间为O(|V|)&#xff0c;故算法总的时间复杂度为 O(|V|2)&#xff1b;
  • 采用邻接表存储方式时&#xff0c;每个顶点均需搜索一次(或入队一次)&#xff0c;故时间复杂度为 O(|V|)&#xff0c;在搜索任一顶点的邻接点时&#xff0c;每条边至少访问一次&#xff0c;故时间复杂度为 O(|E|)&#xff0c;算法总的时间复杂度为 O(|V| &#43; |E|)&#xff1b;

3.应用

&#xff08;1&#xff09;除了对图进行遍历以外&#xff0c;BFS 在求解最短路径或者最短步数上有很多的应用。

&#xff08;2&#xff09;LeetCode 中的934.最短的桥这题便是对 BFS 的应用&#xff1a;

在这里插入图片描述

思路如下&#xff1a;
① 通过遍历找到数组 grid 中的 1 后进行广度优先搜索&#xff0c;此时可以得到第一座岛的位置集合&#xff0c;记为 island&#xff0c;并将其位置全部标记为 −1。
② 从 island 中的所有位置开始进行 BFS&#xff0c;当它们到达了任意的 1 时&#xff0c;即表示搜索到了第二个岛&#xff0c;搜索的层数就是答案。

代码实现如下&#xff1a;

class Solution {
public int shortestBridge(int[][] grid) {
int n &#61; grid.length;
// dirs 记录遍历的四个方向
int[][] dirs &#61; {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
List<int[]> island &#61; new ArrayList<>();
Queue<int[]> queue &#61; new ArrayDeque<>();
for (int i &#61; 0; i < n; i&#43;&#43;) {
for (int j &#61; 0; j < n; j&#43;&#43;) {
if (grid[i][j] &#61;&#61; 1) {
queue.offer(new int[]{i, j});
grid[i][j] &#61; -1;
while (!queue.isEmpty()) {
int[] land &#61; queue.poll();
int x &#61; land[0];
int y &#61; land[1];
island.add(land);
for (int k &#61; 0; k < 4; k&#43;&#43;) {
int nextX &#61; x &#43; dirs[k][0];
int nextY &#61; y &#43; dirs[k][1];
if (nextX >&#61; 0 && nextY >&#61; 0 && nextX < n && nextY < n && grid[nextX][nextY] &#61;&#61; 1) {
queue.offer(new int[]{nextX, nextY});
//标记 (nextX, nextY)&#xff0c;表示已经访问过该点
grid[nextX][nextY] &#61; -1;
}
}
}
/*
(1) 此时已经找到了题目中描述的两座岛中的一座&#xff0c;并且组成岛的每块陆地的坐标位置都保存在 island 中。
(2) 从 island 中的所有坐标位置开始进行 BFS&#xff0c;当它们达到了任意的 1 时&#xff0c;即表示搜索到了第二座岛&#xff0c;
此时搜索的层数即为答案&#xff0c;在下面的代码中使用 step 来记录。
*/

for (int[] land : island) {
queue.offer(land);
}
int step &#61; 0;
while (!queue.isEmpty()) {
int size &#61; queue.size();
for (int k &#61; 0; k < size; k&#43;&#43;) {
int[] land &#61; queue.poll();
int x &#61; land[0];
int y &#61; land[1];
for (int d &#61; 0; d < 4; d&#43;&#43;) {
int nextX &#61; x &#43; dirs[d][0];
int nextY &#61; y &#43; dirs[d][1];
if (nextX >&#61; 0 && nextY >&#61; 0 && nextX < n && nextY < n) {
if (grid[nextX][nextY] &#61;&#61; 0) {
queue.offer(new int[]{nextX, nextY});
//标记 (nextX, nextY)&#xff0c;表示已经访问过该点
grid[nextX][nextY] &#61; -1;
} else if (grid[nextX][nextY] &#61;&#61; 1) {
return step;
}
}
}
}
step&#43;&#43;;
}
}
}
}
return 0;
}
}

&#xff08;3&#xff09;大家可以去 LeetCode 上找相关的 BFS 的题目来练习&#xff0c;或者也可以直接查看LeetCode算法刷题目录&#xff08;Java&#xff09;这篇文章中的 BFS 章节。如果大家发现文章中的错误之处&#xff0c;可在评论区中指出。







推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
author-avatar
荷塘一只嫩藕
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有