【算法】广度优先遍历 (BFS)

news2024/11/18 20:19:52

目录

  • 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 - 1
        int 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 - 1
        int 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 章节。如果大家发现文章中的错误之处,可在评论区中指出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/157388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

让我用Python自制软件,看视频畅通无阻

前言 一个账号只能登录一台设备&#xff1f;涨价就涨价&#xff0c;至少还能借借朋友的&#xff0c;谁还没几个朋友&#xff0c;搞限制登录这一出&#xff0c;瞬间不稀罕了 这个年头谁还不会点技术了&#xff0c;直接拿python自制一个可以看视频的软件… 话不多说&#xff0…

终于弄懂了 非极大抑制 NMS

NMS的作用就是有效地剔除目标检测结果中多余的检测框&#xff0c;保留最合适的检测框。 以YOLOv5为例&#xff0c;yolov5模型的输入三个feature map的集合&#xff0c;加上batch的维度&#xff0c;也就是三维张量&#xff0c;即[batch&#xff0c;(p0∗p0p1∗p1p2∗p2)∗3&…

SWC步骤

纲要&#xff1a; SWC属于AUTOSAR的Component文件夹下&#xff0c;而Composition属于Composition文件夹下。 目录 1. Import "Data Type" and "Interface" information 2. Creat Software Component(SWC) 3. Create "Port" for this SWC 4.…

nexus raw 仓库代理(node-sass离线安装node-sass: Command failed)

问题背景 内网环境中使用 node 构建项目&#xff0c;项目中依赖了 node-sass&#xff0c;环境自动下载 node-saas 失败&#xff08;内网&#xff09;。 下面是构建 node-sass 的错误代码&#xff1a; [5/5] Building fresh packages... error /workspace/node_modules/node-…

nuxt概念

文章目录前言nuxt项目结构介绍网页导航文字显示&#xff08;商标&#xff09;package.jsonnuxt.config.js路由固定路由动态路由总结前言 首先了解下B2C模式&#xff0c;分前后台&#xff0c;后台一般为管理系统&#xff0c;不需要展示给过多的用户&#xff0c;而前台需要展示给…

2023年有哪些具备潜力的加密投资标的?

随着2022年一系列的黑天鹅事件&#xff08;Terra、Luna的暴雷、FTX、Three Arrows Capital等知名加密机构的破产&#xff09;&#xff0c;加密货币总市值已经从最高点的2.9万亿美元&#xff08;2021年的11月&#xff09;&#xff0c;下降到8500亿美元&#xff08;与2021年1月的…

NKOJ P7842 疫情防控

分析 这道题的本质就是找可以使得每座城市有且仅有一条道单行路进入该市的图有什么特点; 首先,我们假设图联通,则由于每个城市只有一条单行道可以进入,即一个城市必须有且仅有一条单行道与之配对,所以这个图至少要有nnn条边,即图中必须要有环才可以满足要求! 那如果图不连通…

Java多线程之读写锁ReentrantReadWriteLock类使用

在JDK中提供了一种读写锁ReentrantReadWriteLock类&#xff0c;相比ReentrantLock类&#xff0c;使用前者可以加快运行效率。ReentrantLock类是具有完全互斥排他的效果&#xff0c;即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务&#xff0c;这样做虽然保证了…

【UE4 第一人称射击游戏】40-改变武器的可见性

上一篇&#xff1a;【UE4 第一人称射击游戏】39-“M4A1”武器设置本篇效果&#xff1a;步骤&#xff1a;打开“Weapon_M4A1”&#xff0c;删除带有“AK47”的那个骨架网格体打开事件图表&#xff0c;将“SkeletalMesh1”拖入打开“ThirdPersonCharacter”&#xff0c;在事件图表…

Docker:独具魅力的开源容器引擎

Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux 或 Windows操作系统的机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口&#…

MATLAB-控制系统模型之间的转换

系统的线性时不变&#xff08;LTI&#xff09;模型有传递函数&#xff08;tf&#xff09;模型、零极点增益&#xff08;zpk)模型和状态空间(ss&#xff09;模型,它们之间可以相互转换。模型之间的转换函数可以分为以下两类。第一类是把其他类型的模型转换为函数表示的模型自身&…

PHP多进程(一)

多进程的作用是一个程序启动多个进程。 一个程序启动起来本应该是一个进程&#xff0c;但它可作为父进程启动多个子进程来一起操作 形成并发操作 pcntl是php官方的多进程扩展,只能在linux环境使用 以下所有操作请在Linux环境下操作: 先认识两个函数,下面是官方文档地址: …

铜缆测试——近端和远端串扰(NEXT和FNEXT)

如果您非常熟悉铜缆测试&#xff0c;那么很可能听说过串扰——一对或一个通道上传输的信号对另外一对或一个通道产生不良影响的现象。(杂讯) 串扰会对具体的一对导线或整根电缆形成干扰&#xff0c;导致误码或数据无法传输。例如&#xff0c;您是否曾经在电话中听到有其他人说话…

马蹄集 数组最大公约数

给定一个由N个正整数组成的数组&#xff0c;求所有数组元素的最大公约数。 格式 输入格式&#xff1a;第一行输入数组长度N,第二行输入数组元素&#xff0c;整型 空格分隔。 输出格式&#xff1a;输出整型 #include <bits/stdc.h> using namespace std;int gcd(int a…

(十八)Java的时间与日期(2)

目录 前言: 一、JDK8新增日期类 二、LocalDate&#xff0c;LocalTime,LocalDateTime 三、Instant时间戳 四、DateTimeFormatter类 五、Duration/Period类 六、ChronoUnit类 前言: JDK 8中增加了一套全新的日期时间API&#xff0c;这套API设计合理&#xff0c;是线程安全的。新的…

ctfshow学习记录-web入门(sql注入191-200)

目录web191web192web193web194web195web196web197web198web199web200九某人来更新啦&#xff1a;2023年第一篇wp新鲜出炉&#xff5e; web191 解答&#xff1a;增加了过滤 过滤了ascii&#xff0c;可以用ord方法代替。&#xff08;这里手册中也有告知~&#xff09; web190的pa…

【数据结构与算法】Trie

&#x1f600;大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#x1f62b;&#xff0c;但是也想日更的人✈。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4…

集合引用类型 下

目录 Map Map.set() Map.get() Map.delete() Map.has() Map.values() Map.entries() Map.clear() 选择Object 还是Map 数据转换 转为数组 转为 JSON 对象转为 Map 数组转为 Map 转为Object WeakMap 基本API 弱键 不可迭代 Set 创建Set实例 Set实例转数组 si…

STM32-启动文件详解

✅作者简介&#xff1a;嵌入式入坑者&#xff0c;与大家一起加油&#xff0c;希望文章能够帮助各位&#xff01;&#xff01;&#xff01;&#xff01; &#x1f4c3;个人主页&#xff1a;rivencode的个人主页 &#x1f525;系列专栏&#xff1a;玩转FreeRTOS &#x1f4ac;推荐…

python基础篇之数字类型(下)

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a;lqj_本人的博客_CSDN博客-微信小程序,前端,vue领域博主lqj_本人擅长微信小程序,前端,vue,等方面的知识https://blog.csdn.net/lbcyllqj?spm1000.2115.3001.5343 哔哩哔哩欢迎关注&…