并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解

0. 前言

在前面我们系统掌握了线性结构、树形结构、哈希结构等主流数据结构,能够解决遍历、查找、排序、去重、映射等绝大多数基础问题。但在算法刷题、图论工程场景中,还有一类高频且专属的经典问题:动态连通性问题。

例如:判断两点是否连通、合并两个集合、统计连通块数量、判断是否成环、最小生成树构建等。这类问题如果用DFS、BFS暴力遍历,不仅代码繁琐、逻辑复杂,且时间复杂度极高,大数据场景极易超时。

为此,数据结构专门设计了一种极简、高效、专门处理集合合并与查询 的结构------并查集(DSU,Disjoint Set Union)

并查集是一种树型集合管理结构,核心只有两个操作:合并、查询。配合路径压缩、按秩合并两大优化,时间复杂度无限接近O(1),是图论基础、LeetCode刷题、面试手撕、最小生成树算法的核心前置知识。

很多学习者只会写朴素版并查集,不懂优化原理、分不清秩与高度、不会统计连通块、无法解决判环问题、不理解Kruskal算法底层依托。

今天我们从零吃透并查集全套体系,从朴素原理、两大核心优化、万能代码模板、经典刷题模型、工程图论应用到面试满分问答,一次性彻底掌握并查集所有考点。

1. 并查集核心本质

1.1 核心作用

并查集专门用于管理不相交的集合,高效解决两大核心问题:

  1. 查询:判断两个元素是否属于同一个集合(是否连通);

  2. 合并:将两个独立的集合合并为一个集合。

全程无需遍历、无需递归,通过数组映射树形关系,实现极致高效的集合管理。

1.2 底层结构

并查集底层依托数组 实现,不依赖复杂容器。定义 parent\[\] 数组:parentx 代表元素 x 的父节点

核心规则:

  1. 若 parentx == x,说明 x 是当前集合的根节点

  2. 所有元素最终都会指向唯一根节点,同根即为同集合、连通;

  3. 不同根节点代表属于不同独立集合。

2. 朴素版并查集(无优化)

朴素版并查集包含初始化、查找、合并三个基础函数,逻辑简单,但存在树形退化问题,大数据场景效率低下。

2.1 初始化

每个元素初始独立成集合,自己是自己的父节点。

cpp 复制代码
const int N = 100010;
int parent[N];

// 初始化
void Init(int n)
{
    for(int i = 1; i <= n; i++)
    {
        parent[i] = i;
    }
}

2.2 查找函数(递归找根)

层层向上递归,直到找到根节点(parentx == x)。

cpp 复制代码
int Find(int x)
{
    if(parent[x] == x) 
        return x;
    // 递归向上找根
    return Find(parent[x]);
}

2.3 合并函数

分别找到两个元素的根节点,将其中一个根的父节点指向另一个根,完成集合合并。

cpp 复制代码
void Union(int x, int y)
{
    int fx = Find(x);
    int fy = Find(y);
    if(fx != fy)
    {
        parent[fx] = fy;
    }
}

2.4 朴素版致命缺陷

无优化情况下,合并操作可能导致树形结构退化为链表,查找时需要层层递归遍历,时间复杂度劣化为 O(n),无法适配大数据量刷题场景。

3. 两大核心优化(进阶必懂)

并查集能够成为算法神器,核心依靠两大优化:路径压缩按秩合并。优化后时间复杂度趋近于常数 O(1)(阿克曼函数反函数,几乎恒定)。

3.1 路径压缩(最核心、必加优化)

核心思想 :在查找根节点的过程中,将路径上所有结点的父节点直接指向根节点,彻底拉直树形结构,后续查询一步直达。

路径压缩是收益最高、零成本、必须默认开启的优化,单次操作即可永久优化后续所有查询。

cpp 复制代码
// 路径压缩优化查找
int Find(int x)
{
    if(parent[x] != x)
    {
        parent[x] = Find(parent[x]); // 递归回溯,路径拉直
    }
    return parent[x];
}

3.2 按秩合并(平衡树高)

路径压缩会打乱树的高度,单纯路径压缩在极端场景仍存在轻微效率损耗。按秩合并用于控制树的高度,避免单侧树过长。

秩(rank)可以理解为树的高度或集合节点数量,核心规则:小树合并到大树下,矮树合并到高树下,保证树高始终最小。

cpp 复制代码
int rank_[N]; // 秩数组,记录树的高度

// 初始化增加秩初始化
void Init(int n)
{
    for(int i = 1; i <= n; i++)
    {
        parent[i] = i;
        rank_[i] = 1; // 初始高度为1
    }
}

// 按秩合并
void Union(int x, int y)
{
    int fx = Find(x);
    int fy = Find(y);
    if(fx == fy) return;

    // 矮树合并到高树下面
    if(rank_[fx] < rank_[fy])
        parent[fx] = fy;
    else
    {
        parent[fy] = fx;
        // 等高合并,树高+1
        if(rank_[fx] == rank_[fy])
            rank_[fx]++;
    }
}

4. 并查集万能最终模板(刷题直接拷贝)

结合路径压缩 + 按秩合并,适配所有并查集题型,支持连通性查询、集合合并、连通块统计、判环等全套功能。

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 100010;
int parent[N];
int rank_[N];

// 初始化并查集
void DSU_Init(int n)
{
    for(int i = 1; i <= n; ++i)
    {
        parent[i] = i;
        rank_[i] = 1;
    }
}

// 查找 + 路径压缩
int DSU_Find(int x)
{
    if(parent[x] != x)
        parent[x] = DSU_Find(parent[x]);
    return parent[x];
}

// 合并 + 按秩合并
void DSU_Union(int x, int y)
{
    int fx = DSU_Find(x);
    int fy = DSU_Find(y);
    if(fx == fy) return;

    if(rank_[fx] < rank_[fy])
        parent[fx] = fy;
    else
    {
        parent[fy] = fx;
        if(rank_[fx] == rank_[fy])
            rank_[fx]++;
    }
}

// 判断是否连通
bool IsConnect(int x, int y)
{
    return DSU_Find(x) == DSU_Find(y);
}

5. 四大经典刷题模型(全覆盖考点)

5.1 模型一:连通性判定模型

场景:给定多点多边,判断两点是否连通、查询连通状态。

思路:遍历所有边执行合并操作,最终通过根节点比对判定连通。

5.2 模型二:连通块计数模型

场景:统计整张图中有多少个独立不连通的集合。

思路:遍历所有节点,统计「自己是自己父节点」的根节点数量,即为连通块个数。

5.3 模型三:无向图判环模型

场景:判断无向图是否存在环路。

思路 :对每条边的两个节点查询根节点,若未合并前已经同根,说明两点已连通,再次连边必然成环。

5.4 模型四:最小生成树(Kruskal算法)

场景:求解无向带权图的最小生成树总权值。

核心依托并查集 :将所有边按权值升序排序,从小到大依次选边,不连通则合并、连通则跳过,最终选出n-1条边构成最小生成树。并查集是Kruskal算法的唯一核心数据结构。

6. 高频坑点汇总(刷题避坑)

  1. 忘记初始化:多组测试用例必须重置parent、rank数组,否则数据残留导致答案错误;

  2. 查找不写路径压缩:朴素查找大数据量超时,刷题必须默认开启路径压缩;

  3. 混淆秩与节点数:秩代表树高,不是节点数量,不要自定义错误合并逻辑;

  4. 判环逻辑写反:必须先查询、再合并,同根即存在环;

  5. 下标越界:并查集节点多从1开始,遍历范围不要从0开始出错。

7. 面试满分问答(必背)

Q1:并查集的核心原理与作用?

并查集是管理不相交集合的树形数据结构,核心支持集合合并与连通性查询。通过父节点数组维护集合关系,配合路径压缩、按秩合并优化,将时间复杂度压缩至近似O(1),高效解决连通性判定、连通块统计、图判环、最小生成树等问题。

Q2:路径压缩和按秩合并的作用?

路径压缩用于拉直树形路径,让所有节点直接指向根,极致优化查询速度;按秩合并用于控制树的高度,避免单侧树退化。二者结合彻底杜绝链表退化问题,保证并查集常数级效率。

Q3:并查集时间复杂度是多少?

严格来说是阿克曼函数的反函数 α(n),增长极度缓慢,日常所有数据场景下等价于常数 O(1),是复杂度最低的图论基础结构。

Q4:Kruskal算法为什么需要并查集?

Kruskal算法核心是选边避环,需要快速判断两个顶点是否连通、避免成环,并查集可以高效完成连通查询与集合合并,是该算法的唯一最优底层支撑。

Q5:并查集能否处理有向图?

不能。并查集只适用于无向图的等价连通关系,无法处理有向图的单向指向关系、传递关系。

8. 全文总结

今天我们彻底吃透了并查集(DSU)完整知识体系。从朴素版原理、路径压缩与按秩合并两大核心优化、刷题万能模板,到连通性判定、连通块统计、图判环、最小生成树四大经典模型,全覆盖刷题与面试考点,彻底掌握图论最基础、最高效的集合管理结构。

并查集代码极简、效率极高、适用性极强,是算法竞赛、笔试刷题、后端工程图论建模的必备基础,从此所有连通性问题无需DFS/BFS暴力遍历,一行并查集即可高效解决。

相关推荐
小冷爱读书1 小时前
allocator
开发语言·c++
森G1 小时前
71、打包发布---------打包发布
c++·qt
小冷爱读书1 小时前
C++ 单例四种实现完整演进逻辑
开发语言·c++·c++学习
sdm0704272 小时前
多路转接-select
网络·c++·select·多路转接
凌波粒2 小时前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
beethobe2 小时前
PythonQt 学习之旅(一):从零构建 C++ 与 Python 的桥梁
c++·python·学习
鹏易灵2 小时前
C++——2.常量与 const、constexpr 初识详解
java·开发语言·c++
神仙别闹2 小时前
基于C++ 实现 BP 神经网络
开发语言·c++·神经网络
郝学胜-神的一滴3 小时前
CMake 019:程序生成与清理全解析
开发语言·c++·qt·程序人生·软件构建·cmake