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

哈喽各位,我是前端小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 变量,它还直接告诉了我们当前的"分裂程度"。

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

下期见!

相关推荐
88号技师40 分钟前
2025年9月一区SCI-孤行尺蠖觅食优化算法Solitary Inchworm Foraging-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法
前端小L1 小时前
图论专题(二十五):最小生成树(MST)——用最少的钱,连通整个世界「连接所有点的最小费用」
算法·矩阵·深度优先·图论·宽度优先
前端小L1 小时前
图论专题(二十三):并查集的“数据清洗”——解决复杂的「账户合并」
数据结构·算法·安全·深度优先·图论
CoovallyAIHub1 小时前
破局红外小目标检测:异常感知Anomaly-Aware YOLO以“俭”驭“繁”
深度学习·算法·计算机视觉
点云SLAM2 小时前
图论中邻接矩阵和邻接表详解
算法·图论·slam·邻接表·邻接矩阵·最大团·稠密图
啊董dong2 小时前
课后作业-2025年11月23号作业
数据结构·c++·算法·深度优先·noi
星释2 小时前
Rust 练习册 80:Grains与位运算
大数据·算法·rust
dlz08362 小时前
从架构到数据结构,到同步逻辑,到 show run 流程优化
数据结构
jllws12 小时前
数据结构_字符和汉字的编码与查找
数据结构