图论专题(二十一):并查集的“工程应用”——拔线重连,修复「连通网络」

哈喽各位,我是前端小L。

欢迎来到我们的图论专题第二十一篇!在现实世界的网络工程中,资源往往是有限的。如果我们发现公司里的电脑网络分成了好几个互不相通的局域网,而手头的网线又不够用,该怎么办?

我们必须通过"拆东墙补西墙"的策略:找到那些"多余"的网线(在一个连通分量内部形成环的线),把它们拔下来,用来连接那些断开的局域网。

这道题的核心在于:如何判断网线"够不够"?以及如果够,我们需要动多少次手?

力扣 1319. 连通网络的操作次数

https://leetcode.cn/problems/number-of-operations-to-make-network-connected/

题目分析:

  • 输入n 台电脑(节点),connections 网线列表(边)。

  • 操作:拔掉一根已有的网线,把它连到另外两台电脑之间。

  • 目标:让所有电脑都连通。求最少操作次数。

  • 特殊情况 :如果线缆总数太少,无论怎么移都连不通,返回 -1

核心洞察:"硬性门槛"与"连通数学"

第一道门槛:资源够不够? 图论告诉我们:要连通 n 个节点,至少 需要 n - 1 条边(这就构成了一棵生成树)。

  • 如果 connections.size() < n - 1:线缆总数根本不够,神仙难救,直接返回 -1

  • 如果 connections.size() >= n - 1:线缆总数足够!既然总数够,我们一定能通过"拆除冗余、填补空缺"的方式,把图变成连通的。

第二道门槛:需要动几次手? 假设我们用并查集处理完所有的现有连接后,发现网络中还剩下 k互不相通 的连通分量(孤岛)。 要把这 k 个孤岛连成一个整体,我们需要搭几座桥? 很简单,把 k 个点串起来,需要 k - 1 条线。

结论: 只要总线缆数达标,我们只需要计算出当前的连通分量个数 count ,答案就是 count - 1。我们不需要关心具体拔哪根线,因为题目只问"次数"。

算法流程:并查集的标准作业

  1. 硬性检查 :如果 connections.size() < n - 1,返回 -1

  2. 初始化并查集

    • parent 数组,大小为 n

    • 关键 :初始化连通分量计数器 componentCount = n(一开始大家都是孤岛)。

  3. 遍历连接

    • 对于每一条边 [u, v],调用 union(u, v)

    • union 函数内部 :如果 uv 原本不属于同一个集合(rootU != rootV),合并它们,并将 componentCount 减 1

  4. 计算结果

    • 遍历结束后,componentCount 就是剩下的连通分量个数。

    • 需要的操作次数 = componentCount - 1

代码实现 (并查集模板应用)

C++

复制代码
#include <vector>
#include <numeric>

using namespace std;

class Solution {
private:
    // --- 并查集内部类 ---
    class UnionFind {
    private:
        vector<int> parent;
        int count; // 连通分量个数

    public:
        UnionFind(int n) {
            count = n;
            parent.resize(n);
            iota(parent.begin(), parent.end(), 0); // 初始化 0, 1, 2...
        }

        int find(int x) {
            if (parent[x] != x) {
                parent[x] = find(parent[x]); // 路径压缩
            }
            return parent[x];
        }

        void unite(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX != rootY) {
                parent[rootX] = rootY;
                count--; // 每次成功合并,孤岛少一个
            }
        }

        int getCount() const {
            return count;
        }
    };

public:
    int makeConnected(int n, vector<vector<int>>& connections) {
        // 1. 硬性门槛检查:线缆总数是否足够
        if (connections.size() < n - 1) {
            return -1;
        }

        // 2. 初始化并查集
        UnionFind uf(n);

        // 3. 建立现有的连接
        for (const auto& conn : connections) {
            uf.unite(conn[0], conn[1]);
        }

        // 4. 计算需要的操作数
        // 剩下的连通分量个数 - 1,就是连接它们所需的边数
        return uf.getCount() - 1;
    }
};

深度复杂度分析

  • V (Vertices) :电脑数 n

  • E (Edges) :线缆数 connections.size()

  • 时间复杂度 O(n + E * α(n))

    • 初始化并查集需要 O(n)。

    • 遍历 E 条边进行合并,每次操作近似 O(1)(阿克曼反函数)。

    • 总时间近似线性。

  • 空间复杂度 O(n)

    • 并查集的 parent 数组。

总结:冗余与连通的辩证关系

今天这道题,让我们看到了图论中**"数量关系"**的美妙。

  • 冗余边:在一个已经连通的分量内部再加边,就是冗余(这就形成了环)。

  • 连通代价 :要把 k 个分量连通,必须且只需 k-1 条边。

只要我们手中的总边数 (资源)大于等于总节点数-1 (需求),我们就拥有了足够的"冗余边"去填补所有的"连通代价"。并查集在这里,不仅帮我们维护了连通性,通过维护 count 变量,它还直接告诉了我们当前的"分裂程度"。

下一篇,我们将利用并查集处理一种更有趣的逻辑关系------"等式方程的可满足性" 。当 ==!= 混杂在一起时,我们该如何判断逻辑是否冲突?

下期见!

相关推荐
Xy-unu44 分钟前
[LLM]AIM: Adaptive Inference of Multi-Modal LLMs via Token Merging and Pruning
论文阅读·人工智能·算法·机器学习·transformer·论文笔记·剪枝
Hcoco_me1 小时前
算法选型 + 调参避坑指南
算法
Jul1en_1 小时前
【算法】分治-归并类题目
java·算法·leetcode·排序算法
kangk121 小时前
统计学基础之概率(生物信息方向)
人工智能·算法·机器学习
再__努力1点1 小时前
【77】积分图像:快速计算矩形区域和核心逻辑
开发语言·图像处理·人工智能·python·算法·计算机视觉
唯唯qwe-1 小时前
Day22: 贪心算法 | 区间问题,左/右端点排序
算法·贪心算法
Hcoco_me1 小时前
LLM(Large Language Model)系统学习路线清单
人工智能·算法·自然语言处理·数据挖掘·聚类
java修仙传1 小时前
力扣hot100:寻找旋转排序数组中的最小值
算法·leetcode·职场和发展
胖咕噜的稞达鸭1 小时前
算法日记专题:位运算II( 只出现一次的数字I II III 面试题:消失的两个数字 比特位计数)
c++·算法·动态规划
txzrxz2 小时前
图的存储
算法·深度优先·图论