DFS、BFS、Union-Find:找出图中省份数量的最佳方法

题目理解

问题描述:

  • n 个城市,其中一些城市之间直接相连,另一些则不相连。
  • 如果城市 a 和城市 b 直接相连,且城市 b 和城市 c 直接相连,那么城市 a 和城市 c 间接相连。
  • 省份被定义为一组直接或间接相连的城市,组内不包含与之不相连的其他城市。
  • 给定一个 n x n 的矩阵 isConnected,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,isConnected[i][j] = 0 表示不直接相连。
  • 需要返回矩阵中省份的数量。

示例解释:

  • 示例 1:

    输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
    输出:2
    

    解释:城市1和城市2直接相连,城市3独自一省。

  • 示例 2:

    输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
    输出:3
    

    解释:每个城市都独自一省。

547. 省份数量 - 力扣(LeetCode)

解决思路

这个问题实际上是在求图中的连通分量数量。每个省份对应于图中的一个连通分量。我们可以将城市看作图中的节点,直接相连表示节点之间有边。

有几种常见的方法可以求解连通分量:

  1. 深度优先搜索(DFS)
  2. 广度优先搜索(BFS)
  3. 并查集(Union-Find)

我们将详细介绍这三种方法,并探讨它们的优缺点和实际应用场景。

方法一:深度优先搜索(DFS)

思路:

  • 遍历每个城市,如果该城市未被访问过,则开始一次DFS遍历,标记所有与之连通的城市为已访问。
  • 每进行一次DFS遍历,省份数量加一。

步骤:

  1. 初始化一个访问数组 visited,大小为 n,全部设为 false
  2. 初始化省份计数器 count0
  3. 遍历每个城市 i
    • 如果 visited[i]false,则:
      • 进行一次DFS,从城市 i 开始,标记所有连通的城市为已访问。
      • 省份计数器 count 增加 1
  4. 返回 count 作为省份的数量。

实现代码:

#include <vector>using namespace std;class Solution {
public:void dfs(int i, vector<vector<int>>& isConnected, vector<bool>& visited) {visited[i] = true;for(int j = 0; j < isConnected.size(); j++) {if(isConnected[i][j] == 1 && !visited[j]) {dfs(j, isConnected, visited);}}}int findCircleNum(vector<vector<int>>& isConnected) {int n = isConnected.size();vector<bool> visited(n, false);int count = 0;for(int i = 0; i < n; i++) {if(!visited[i]) {dfs(i, isConnected, visited);count++;}}return count;}
};

代码解释:

  • dfs 函数用于深度优先搜索,标记所有与当前城市 i 直接或间接相连的城市。
  • findCircleNum 函数中,遍历每个城市,如果未被访问,则调用 dfs 并增加省份计数。

优点:

  • 易于理解和实现:DFS方法非常直观,尤其是对那些习惯递归思维的程序员来说。通过简单的递归调用就可以完成连通分量的遍历和计数。
  • 适合稀疏图:对于大部分节点之间没有直接连接的图,DFS的性能表现良好。

缺点:

  • 递归深度限制:在处理特别大的图时,递归深度可能会导致栈溢出,进而程序崩溃。在这种情况下,需要转换为迭代实现,或者增加栈的大小。

实际应用场景:

  • 图的连通分量:DFS可以广泛应用于需要识别图中连通分量的问题中,比如社交网络中的群体识别、地图中区域的划分等。

示例讲解:

以示例1为例:

isConnected = [[1,1,0],[1,1,0],[0,0,1]]
  • 初始化 visited = [false, false, false]count = 0
  • 遍历城市0:
    • visited[0]false,调用 dfs(0)
      • 标记 visited[0] = true
      • 检查城市0的连接:
        • 城市0与城市1相连,且 visited[1] = false,调用 dfs(1)
          • 标记 visited[1] = true
          • 检查城市1的连接:
            • 城市1与城市0相连,但 visited[0] = true
            • 城市1与城市1相连,但自身已访问。
        • 城市0与城市2不相连。
      • dfs(0) 完成,count = 1
  • 遍历城市1:
    • visited[1] = true,跳过。
  • 遍历城市2:
    • visited[2] = false,调用 dfs(2)
      • 标记 visited[2] = true
      • 检查城市2的连接:
        • 城市2与城市2相连,但自身已访问。
      • dfs(2) 完成,count = 2
  • 最终返回 2

方法二:广度优先搜索(BFS)

思路:

与DFS类似,BFS也是用于遍历图中所有连通的节点。不同之处在于,DFS使用栈(递归实现),而BFS使用队列。这使得BFS能够层层推进,逐步扩展搜索范围,从起点节点开始,首先访问其所有邻接节点,然后再访问这些邻接节点的邻接节点,依此类推。

步骤:

  1. 初始化一个访问数组 visited,大小为 n,全部设为 false
  2. 初始化省份计数器 count0
  3. 遍历每个城市 i
    • 如果 visited[i]false,则:
      • 进行一次BFS,从城市 i 开始,标记所有连通的城市为已访问。
      • 省份计数器 count 增加 1
  4. 返回 count 作为省份的数量。

实现代码:

#include <vector>
#include <queue>using namespace std;class Solution {
public:int findCircleNum(vector<vector<int>>& isConnected) {int n = isConnected.size();vector<bool> visited(n, false);int count = 0;queue<int> q;for(int i = 0; i < n; i++) {if(!visited[i]) {q.push(i);while(!q.empty()) {int current = q.front();q.pop();if(!visited[current]) {visited[current] = true;for(int j = 0; j < n; j++) {if(isConnected[current][j] == 1 && !visited[j]) {q.push(j);}}}}count++;}}return count;}
};

代码解释:

  • 使用队列 q 来实现BFS。
  • 对于每个未访问的城市,加入队列,并依次访问其所有直接相连的城市,标记为已访问。

优点:

  • 避免栈溢出:与DFS相比,BFS的迭代实现避免了深度递归可能带来的栈溢出问题,因此在处理大型图时更加稳定。
  • 按层次遍历:BFS按层次遍历所有节点,能够保证先访问的节点离起点最近,这在某些特定问题中非常有用,比如求最短路径等。

缺点:

  • 空间复杂度较高:BFS需要维护一个队列,因此在空间上比

DFS略显不足,特别是在图的节点较多且连通性较高时,队列的最大长度会增大,导致内存占用增加。

实际应用场景:

  • 图的广度优先遍历:BFS广泛应用于各种图遍历任务中,尤其是那些要求按距离优先访问节点的任务,如最短路径问题、层次遍历等。

示例讲解:

以示例1为例:

isConnected = [[1,1,0],[1,1,0],[0,0,1]]
  • 初始化 visited = [false, false, false]count = 0
  • 遍历城市0:
    • visited[0]false,将城市0加入队列 q
    • BFS开始:
      • q 中有城市0,弹出 current = 0
      • 标记 visited[0] = true
      • 检查城市0的连接:
        • 城市0与城市1相连,将城市1加入队列 q
      • q 中有城市1,弹出 current = 1
      • 标记 visited[1] = true
      • 检查城市1的连接:
        • 城市1与城市0相连,但 visited[0] = true
      • BFS结束,count = 1
  • 遍历城市1:
    • visited[1] = true,跳过。
  • 遍历城市2:
    • visited[2] = false,将城市2加入队列 q
    • BFS开始:
      • q 中有城市2,弹出 current = 2
      • 标记 visited[2] = true
      • 检查城市2的连接:
        • 城市2与城市2相连,但自身已访问。
      • BFS结束,count = 2
  • 最终返回 2

方法三:并查集(Union-Find)

思路:

并查集是一种数据结构,常用于处理不相交集合的合并和查询问题。对于图中的连通分量问题,使用并查集可以高效地合并连通的节点,并在最后通过集合的数量来得出连通分量的数量。

步骤:

  1. 初始化一个并查集 parent 数组,每个城市的父节点指向自己。
  2. 遍历 isConnected 矩阵,对于每个直接相连的城市对 (i, j)
    • 如果 isConnected[i][j] == 1,则合并城市 i 和城市 j
  3. 遍历所有城市,统计根节点的数量,即为省份的数量。

实现代码:

#include <vector>using namespace std;class Solution {
public:int find(int x, vector<int>& parent) {if(parent[x] != x) {parent[x] = find(parent[x], parent); // 路径压缩}return parent[x];}void unionSets(int x, int y, vector<int>& parent) {int rootX = find(x, parent);int rootY = find(y, parent);if(rootX != rootY) {parent[rootX] = rootY; // 合并集合}}int findCircleNum(vector<vector<int>>& isConnected) {int n = isConnected.size();vector<int> parent(n);for(int i = 0; i < n; i++) {parent[i] = i; // 初始化每个城市的父节点为自己}for(int i = 0; i < n; i++) {for(int j = i + 1; j < n; j++) {if(isConnected[i][j] == 1) {unionSets(i, j, parent);}}}int count = 0;for(int i = 0; i < n; i++) {if(parent[i] == i) {count++;}}return count;}
};

代码解释:

  • find 函数用于查找某个城市的根节点,并通过路径压缩优化查找效率。
  • unionSets 函数用于合并两个城市所属的集合。
  • 最后遍历 parent 数组,统计根节点的数量即为省份数量。

优点:

  • 高效处理连通性问题:并查集非常适合处理动态连通性问题,在合并和查找操作上都能保持较高的效率,尤其适合处理大规模图。
  • 路径压缩和按秩合并:通过路径压缩和按秩合并优化,并查集在实际应用中非常高效,几乎达到了常数级别的性能。

缺点:

  • 实现复杂度较高:相较于DFS和BFS,并查集的实现较为复杂,理解并查集的操作对于初学者来说可能有一定的难度。

实际应用场景:

  • 动态连通性:并查集常用于解决动态连通性问题,例如网络中设备的连接性判断、社交网络中的群组划分等。

示例讲解:

以示例1为例:

isConnected = [[1,1,0],[1,1,0],[0,0,1]]
  • 初始化 parent = [0, 1, 2]
  • 遍历 isConnected 矩阵:
    • i = 0, j = 1isConnected[0][1] == 1,合并城市0和城市1:
      • find(0) 返回 0, find(1) 返回 1。
      • 合并, parent[0] = 1parent = [1, 1, 2]
    • i = 0, j = 2isConnected[0][2] == 0,跳过。
    • i = 1, j = 2isConnected[1][2] == 0,跳过。
  • 遍历 parent 数组,统计根节点的数量:
    • parent = [1, 1, 2],根节点为 12,省份数量为 2

各方法的比较与选择

  • DFS:适合稀疏图,代码简单易实现,但递归深度受限。
  • BFS:适合处理递归深度受限的问题,按层次遍历,空间复杂度略高。
  • 并查集:最优解法,适合大规模图,处理动态连通性问题高效,但实现复杂度较高。

在实际应用中,根据问题规模和图的稠密程度选择合适的方法。在省份问题中,如果图的规模较小且递归深度不是问题,DFS和BFS都是不错的选择;如果图的规模较大或需要频繁处理连通性问题,并查集则更为高效。

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

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

相关文章

美团2024秋招编程题:小美的red子序列数量之和

题目为&#xff1a; 小美有一个字符串&#xff0c;小美想知道这个字符串的所有连续子串中&#xff0c;red 子序列的数量之和。 子串是指从原字符串中&#xff0c;连续的选择一段字符组成的新字符串。 定义 red 子序列为从原字符串中从左到右依次取出r、e和d组成的新字符串。 …

1999-2023年上市公司年报文本数据(PDF+TXT)

1999-2023年上市公司年报文本数据&#xff08;PDFTXT&#xff09; 1、时间&#xff1a;1999-2023年 2、来源&#xff1a;上市公司年度报告 3、范围&#xff1a;A股上市公司&#xff0c;5600企业&#xff0c;6.3W份 4、格式&#xff1a;PDFTXT 5、下载链接&#xff1a; 199…

C#实现快速傅里叶变换(FFT)

1、FFT类 using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Threading.Tasks;namespace DFT_FFTApp.Utils {public class FFT{/// <summary>/// FFT/// </summ…

Java12 Excel和Json文件解析

Excel文件解析&#xff1a; Excel文件解析(EasyExcel框架解析) Excel文件解析(Apache POl框架解析) &#xff08;1&#xff09;Excel文件对象创建&#xff1a;POI 《1》创建工作簿对象: XSSFWorkbook workbooknew XSSFWorkbook&#xff08;&#xff09;&#xff1b; 《2》创…

SQL语句复习

一、CTE和WITH CTE是一种命名的临时结果集,CTE是通过WITH子句来定义的. WITH cte_name (column1, column2, ...) AS (-- CTE查询定义SELECT ... ) -- 主查询 SELECT ... FROM cte_name WHERE ... column1, column2, ... 是可选的列名列表&#xff0c;通常用于给CTE的列命名。…

c++11新特性-lambda表达式

1. 概念 lambda表达式实际上是一个匿名类的成员函数&#xff0c;该类由编译器为lambda创建&#xff0c;该函数被隐式地定义为内联。因此&#xff0c;调用lambda表达式相当于直接调用它的operator()函数&#xff0c;这个函数可以被编译器内联优化&#xff08;建议&#xff09;。…

Ubuntu服务器时间和本地时间不一致怎么解决——Linux的Local Time和RTC time

最近一直在搞大模型的相关工作&#xff0c;所以一直在用Linux服务器&#xff0c;前面的文章里也提到了&#xff0c;我用的是一台Dell PowerEdge R730xd。 但在使用中发现&#xff0c;IDRAC中的日志时间和本地时间存在时差&#xff0c;大概相关8小时。 对于技术人员&#xff0c…

数据结构:树形结构(树、堆)详解

数据结构&#xff1a;树形结构&#xff08;树、堆&#xff09;详解 一、树&#xff08;一&#xff09;树的性质&#xff08;二&#xff09;树的种类二叉树多叉树满N叉树完全N叉树 &#xff08;三&#xff09;二叉树的实现1、二叉树结构定义2、二叉树功能实现&#xff08;1&…

windows安全中心永久卸载工具分享

使用方法 博客&#xff1a;h0ck1r丶羽~从零到一 卸载工具下载链接&#xff1a; 夸克网盘分享 一路回车&#xff0c;选项Y即可 耐心等待几秒种&#xff0c;自动重启 此时打开windows安全中心&#xff0c;已经完全不能使用了&#xff0c;响应的杀毒功能也关了 往期推荐 【渗透测…

机器人末端阻抗控制Simulink仿真

机器人末端阻抗控制是一种重要的机器人控制策略&#xff0c;它主要用于调节机器人末端执行器与环境之间的动态关系&#xff0c;以保证机器人在适当的柔顺性下进行轨迹跟踪或与环境交互。在使用Simulink进行机器人末端阻抗控制仿真时&#xff0c;主要步骤可以归纳如下&#xff1…

QT做一个USB HID设备识别软件

1.下载 HidApi库&#xff1a;GitHub - yigityuce/HidApi: Human Interface Device Api (HidApi) with C 2.pro文件添加 DEFINES - UNICODE LIBS -lsetupapi 3.建立三个对象 HidApi hidApi;HidDevice hidDev;//HID设备HidDeviceList devList;//HID设备列表 4.对 HID 设备进…

JavaWeb - Spring Boot

Spring 官网​​​​​Spring | Home Spring Boot Spring Boot是一个由Pivotal团队提供的开源框架&#xff0c;旨在简化Spring应用的初始搭建以及开发过程。在Spring Boot项目中&#xff0c;通常会有Controller、Service、Mapper和Entity等层次结构。下面将详细介绍这些层次的…

用 Higress AI 网关降低 AI 调用成本 - 阿里云天池云原生编程挑战赛参赛攻略

作者介绍&#xff1a;杨贝宁&#xff0c;爱丁堡大学博士在读&#xff0c;研究方向为向量数据库 《Higress AI 网关挑战赛》正在火热进行中&#xff0c;Higress 社区邀请了目前位于排行榜 top5 的选手杨贝宁同学分享他的心得。下面是他整理的参赛攻略&#xff1a; 背景 我们…

Serilog文档翻译系列(三) - 基础配置

Serilog 使用简单的 C# API 来配置日志记录。当需要外部配置时&#xff0c;可以&#xff08;慎用&#xff09;通过使用 Serilog.Settings.AppSettings 包或 Serilog.Settings.Configuration 包进行混合配置。 创建日志记录器 日志记录器是通过 LoggerConfiguration 对象创建的…

基于springboot的汽车租赁管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的汽车租赁管理系统,java…

【RabbitMQ】快速上手

目 录 一. RabbitMQ 安装二. RabbitMQ 核心概念2.1 Producer 和 Consumer2.2 Connection 和 Channel2.3 Virtual host2.4 Queue2.5 Exchange2.6 RabbitMQ 工作流程 三. AMQP四. web界面操作4.1 用户相关操作4.2 虚拟主机相关操作 五. RabbitMQ 快速入门5.1 引入依赖5.2 编写生产…

stm32 8080时序驱动lcd屏幕

PSAM使用的硬件接口 PSAM读时序 PSAM写时序 相关时序 PSAM_RCRx NOR 和PSRAM控制寄存器

28. 双耳配对 - 配置

1. 概述 通过MAC地址的最后一位的奇偶来判断左右耳 2. 验证 右耳:奇数(主耳)-》BT ADDR: 12:42:22:34:34:6d 左耳:偶数(从耳)-》BT ADDR: 12:42:22:34:34:6c

TPH-YOLOv5:基于Transformer预测头的改进YOLOv5,用于无人机捕获场景的目标检测

摘要 提出了TPH-YOLOv5。在YOLOv5的基础上&#xff0c;增加了一个预测头来检测不同尺度的目标。然后用Transformer Prediction Heads&#xff08;TPH&#xff09;代替原有的预测头&#xff0c;探索自注意机制的预测潜力。还集成了卷积块注意力模型&#xff08;CBAM&#xff09;…

前端学习Day36

Day36:P177-P181 学习笔记: 1.面向过程&#xff1a; 就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候再一个一个的依次调用就可以了。 2.面向对象&#xff1a; 是把事务分解成为一个个对象&#xff0c;然后由对象之间…