【算法】广度优先遍历 (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 void bfs(int[][] adjMatrix, int start) {// n 表示图中的节点数量,节点编号为 0 ~ n - 1int n = adjMatrix.length;//定义 visited 数组,防止对节点进行重复遍历boolean[] visited = new boolean[n];Queue<Integer> queue = new LinkedList<>();if (start < 0 || start > n - 1) {System.out.println("起点编号应为 [0, " + (n - 1) + "] 之间的整数!");return;}//起点入队queue.offer(start);//标记起点visited[start] = true;System.out.print(start + " ");while (!queue.isEmpty()) {int node = queue.poll();//将与节点 node 相连的节点加入到 queue 中for (int i = 0; i < n; i++) {if (adjMatrix[node][i] != 0 && !visited[i]) {System.out.print(i + " ");visited[i] = true;queue.offer(i);}}}}
}
(2)当使用邻接表来表示图时,其代码实现如下:
class Solution {/*adjList 为邻接表,adjList[i] 中存储与节点 i 相邻的节点start 为遍历的起点*/public void bfs(List<Integer>[] adjList, int start) {// n 表示图中的节点数量,节点编号为 0 ~ n - 1int n = adjList.length;//定义 visited 数组,防止对节点进行重复遍历boolean[] visited = new boolean[n];Queue<Integer> queue = new LinkedList<>();if (start < 0 || start > n - 1) {System.out.println("起点编号应为 [0, " + (n - 1) + "] 之间的整数!");return;}//起点入队queue.offer(start);//标记起点visited[start] = true;System.out.print(start + " ");while (!queue.isEmpty()) {int node = queue.poll();//将与节点 node 相连的节点加入到 queue 中for (int nextNode : adjList[node]) {while (!visited[nextNode]) {System.out.print(nextNode + " ");visited[nextNode] = true;queue.offer(nextNode);}}}}
}
(3)下面以图 G 为例来说明:
① 构造邻接矩阵:
int[][] adjMatrix = {{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}};
② 构造邻接表:
int n = 5;
List<Integer>[] adjList = new ArrayList[n];
for (int i = 0; i < n; i++) {adjList[i] = 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 = 2,那么遍历的节点依次为:
2 0 4 1 3
遍历过程如下图所示:
(4)无论是邻接表还是邻接矩阵的存储方式,BFS 算法都需要借助一个辅助队列 queue,n 个顶点均需入队一次,在最坏的情况下,空间复杂度为 O(|V|),而时间复杂度与图的存储方式有关:
- 采用邻接矩阵存储方式时,查找每个顶点的邻接点所需的时间为O(|V|),故算法总的时间复杂度为 O(|V|2);
- 采用邻接表存储方式时,每个顶点均需搜索一次(或入队一次),故时间复杂度为 O(|V|),在搜索任一顶点的邻接点时,每条边至少访问一次,故时间复杂度为 O(|E|),算法总的时间复杂度为 O(|V| + |E|);
3.应用
(1)除了对图进行遍历以外,BFS 在求解最短路径或者最短步数上有很多的应用。
(2)LeetCode 中的934.最短的桥这题便是对 BFS 的应用:
思路如下:
① 通过遍历找到数组 grid 中的 1 后进行广度优先搜索,此时可以得到第一座岛的位置集合,记为 island,并将其位置全部标记为 −1。
② 从 island 中的所有位置开始进行 BFS,当它们到达了任意的 1 时,即表示搜索到了第二个岛,搜索的层数就是答案。
代码实现如下:
class Solution {public int shortestBridge(int[][] grid) {int n = grid.length;// dirs 记录遍历的四个方向int[][] dirs = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};List<int[]> island = new ArrayList<>();Queue<int[]> queue = new ArrayDeque<>();for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {if (grid[i][j] == 1) {queue.offer(new int[]{i, j});grid[i][j] = -1;while (!queue.isEmpty()) {int[] land = queue.poll();int x = land[0];int y = land[1];island.add(land);for (int k = 0; k < 4; k++) {int nextX = x + dirs[k][0];int nextY = y + dirs[k][1];if (nextX >= 0 && nextY >= 0 && nextX < n && nextY < n && grid[nextX][nextY] == 1) {queue.offer(new int[]{nextX, nextY});//标记 (nextX, nextY),表示已经访问过该点grid[nextX][nextY] = -1;}}}/*(1) 此时已经找到了题目中描述的两座岛中的一座,并且组成岛的每块陆地的坐标位置都保存在 island 中。(2) 从 island 中的所有坐标位置开始进行 BFS,当它们达到了任意的 1 时,即表示搜索到了第二座岛,此时搜索的层数即为答案,在下面的代码中使用 step 来记录。*/for (int[] land : island) {queue.offer(land);}int step = 0;while (!queue.isEmpty()) {int size = queue.size();for (int k = 0; k < size; k++) {int[] land = queue.poll();int x = land[0];int y = land[1];for (int d = 0; d < 4; d++) {int nextX = x + dirs[d][0];int nextY = y + dirs[d][1];if (nextX >= 0 && nextY >= 0 && nextX < n && nextY < n) {if (grid[nextX][nextY] == 0) {queue.offer(new int[]{nextX, nextY});//标记 (nextX, nextY),表示已经访问过该点grid[nextX][nextY] = -1;} else if (grid[nextX][nextY] == 1) {return step;}}}}step++;}}}}return 0;}
}
(3)大家可以去 LeetCode 上找相关的 BFS 的题目来练习,或者也可以直接查看LeetCode算法刷题目录(Java)这篇文章中的 BFS 章节。如果大家发现文章中的错误之处,可在评论区中指出。
【算法】广度优先遍历 (BFS)相关推荐
- 算法笔记 揭开广度优先遍历BFS的神秘面纱 HERODING的算法之路
揭开广度优先遍历BFS的神秘面纱 前言 1. 审题 1.1 树的BFS 1.2 图的BFS 2. 解法 2.1 树的BFS 2.2 图的BFS 3. 总结 前言 最近到了面试的高峰时期,前段时间也刷了 ...
- 图论算法(5):图的广度优先遍历 BFS
本章节内容使用 java 实现,Github 代码仓:https://github.com/ZhekaiLi/Code/tree/main/Graph/src 查看文章内的图片可能需要科学上网! 因为 ...
- 数据结构与算法(7-2)图的遍历(深度优先遍历DFS、广度优先遍历BFS)(分别用邻接矩阵和邻接表实现)
目录 深度优先遍历(DFS)和广度优先遍历(BFS)原理 1.自己的原理图 2.官方原理图 一.邻接矩阵的深度优先遍历(DFS) 1.原理图 2. 过程: 3.总代码 二.邻接表的深度优先遍历(DFS ...
- Java数据结构之图的基本概念和算法,深度优先遍历DFS,广度优先遍历BFS(图解)
文章目录 前言 一.图的基本概念 1.图的定义 2.基本术语 二.图的基本算法 1.初始化图 2.插入顶点和边 3.矩阵打印 4.返回第一个邻接结点的下标 5.返回第一个邻接结点的下一个结点的下标 三 ...
- 图的遍历(深度优先遍历DFS,广度优先遍历BFS)以及C语言的实现
遍历的定义: 从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算. 一:深度优先遍历(DFS) 1,在访问图中某一起始顶点V后,由V ...
- 广度优先搜索_计算机入门必备算法——广度优先遍历搜索
1. 序言 又很久没有学习了,上次学到哈希表又称散列表的相关知识,这次我们学习一种新的数据结构来建立网络模型.这种数据结构被称作图.首先,我们先应该先了解一下什么是图,其次学习第一种图的算法,这种图 ...
- 数据结构-图的深度优先遍历(DFS)和广度优先遍历(BFS)算法分析
https://www.cnblogs.com/qzhc/p/10291430.html 最后一个广度优先有错误,H不指向E,只有G指向E,所以顺序应该是ABCFDHGE
- 题目1457:非常可乐(广度优先遍历BFS)
题目链接:http://ac.jobdu.com/problem.php?pid=1457 详解链接:https://github.com/zpfbuaa/JobduInCPlusPlus 参考代码: ...
- 图 深度优先遍历 广度优先遍历 非递归遍历 图解算法过程
图的邻接矩阵表示 通常图的表示有两种方法:邻接矩阵,邻接表. 本文用邻接矩阵实现,一是代码量更少,二是代码风格也更贴近C语言.但不论是图的哪种实现方式,其基本的实现思想是不变的. 1:节点的信息,我们 ...
最新文章
- mysql修复坏表的方法
- Android动态加载技术
- No module named Crypto--转
- oracle cpu 利用率过高 kswapd0_服务器带宽监测与利用率过高的解决办法
- 【数据结构与算法】之深入解析“路径交叉”的求解思路与算法示例
- 【无码专区13】最小公倍数(线段树)
- 50种Java编程技巧,越早知道越好!(建议收藏)
- 常见数据结构List之LinkedList
- hdu 3093 动态规划
- 助力双十一,促销海报设计模板收好!
- VS2017+Opencv3.3+Opencv_contribute编译
- 很好的开源UI框架Chico UI
- 台式计算机怎么关闭无线网络,台式机无线网卡被禁用了如何解决
- 【Pix4d精品教程】Pix4d修编正射影像DOM的两种方法案例详解
- Vue中如何解决跨域问题
- python库文件简介整理
- 一、安装 1、kafka 需要java环境;(百度网盘地址:https://pan.baidu.com/s/1i3YXtiBH9YYvZn9vQy1g8w 提取码:dot8)
- Java 埃拉托色尼筛选法
- C编译报错: implicit declaration of function xxx is invalid in C99 [-Wimplicit-function-declaration]
- 将系统必备组件打包进安装文件(以vs15打包.net formwork 4.5为例)有点鸡肋
热门文章
- labview教程:十六进制和ASCII之间的相互转换转
- Windows Server 2012 R2更新(KB2919355)
- 南京计算机科学与技术专业就业怎样,南京北大青鸟:计算机科学与技术就业方向与前景...
- python progressbar2_python progressbar2 使用
- android progressbar换背景,Android自定义ProgressBar背景
- 编译链接实战(8)认识elf文件格式
- 企业NAS中的AD域控制器
- 苏州企业须知科技成果转化活动如何衡量及转化途径有哪些
- 蚂蚁全媒体中心刘鑫炜:有什么好的网站可以为自己树立品牌形象?
- python三门问题_如何用Python解决蒙特霍尔三门问题