一、引言:为什么一定要会并查集
无论你是冲 蓝桥杯 (第十七届省赛已在 2026 年 4 月落幕、第十八届报名季随之开启),还是在准备 全国青少年信息素养大赛 算法应用赛项(复赛大纲强调字符串、列表、函数与递归等基础能力),又或是正在报名 中国电子学会 2026 年 12 月软件编程考级(C/C++ 一级至十级、Python 一级至六级,报名 9 月 23 日---12 月 2 日,12 月 12 日开考)------「集合合并与连通性判断」都是绕不开的一类题。
这类题在真题里常化身成:
- 班里有几「朋友圈」(连通块计数);
- 两个人是不是同一个社团的(连通性查询);
- 网络里加一条线会不会成环(判环);
- 若干村庄最少修几段路全连通(最小生成树的前置思路)。
它们的底层数据结构,就是 并查集(Disjoint Set Union,DSU,也叫不相交集合)。本文用一个原创题带你吃透它,并给出 C++ 与 Python 双版本,再延伸到网格、判环与种类并查集。
二、题目:校园朋友圈统计
【原创题】校园朋友圈
某班有
n名同学(编号1 ~ n)。已知m对「朋友关系」,朋友关系双向传递:如果 A 和 B 是朋友,B 和 C 是朋友,那么 A、B、C 就属于同一个朋友圈。请完成两件事:
输出全班最终形成的朋友圈数量 ;
支持
q次询问:编号为a和b的两名同学是否在同一朋友圈?
输入示例
6 4
1 2
2 3
4 5
2 5
3
1 4
1 6
5 6
输出示例
朋友圈数量 = 2
同朋友圈
不同朋友圈
不同朋友圈
样例解释 :1-2-3 通过 2-5 与 4-5 连通,于是 {1,2,3,4,5} 是一个朋友圈;6 单独一个,共 2 个朋友圈。1 和 4 同圈,1 和 6 不同圈,5 和 6 也不同圈。
三、核心考点拆解
| 考点 | 说明 |
|---|---|
| 初始化 | 每个人最开始是自成一个集合,parent[i] = i,朋友圈数 = n |
| find 查找根 | 顺着 parent 往上找,直到 parent[x] == x;用路径压缩把沿途节点直接挂在根上,加速后续查询 |
| unite 合并 | 找到两点的根,若不同根则把一棵树挂到另一棵下面,并让朋友圈数减一 |
| same 连通判断 | 两人同圈 ⇔ 它们的根相同(find(a) == find(b)) |
| 按大小合并 | 把「小树」挂到「大树」下,保证树高不超过 O(log n),避免退化成链 |
关键直觉 :每成功合并一次,集合数就减一;一开始有 n 个孤立点,最后剩下的集合数就是朋友圈数量。
四、解法与代码实现
思路
- 初始化
n个集合,comps = n。 - 对每条朋友关系
(u, v):若u、v当前不在同一集合,就合并,并把comps减一。 - 所有关系处理完后,
comps即朋友圈数量。 - 询问直接比较两人根是否相同。
C++ 版本
cpp
#include <iostream>
#include <vector>
using namespace std;
struct DSU {
vector<int> parent, sz;
int comps;
DSU(int n) : parent(n + 1), sz(n + 1, 1), comps(n) {
for (int i = 1; i <= n; i++) parent[i] = i;
}
// 查找根 + 路径压缩(迭代写法,避免递归爆栈)
int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]]; // 路径减半压缩
x = parent[x];
}
return x;
}
// 合并两集合,返回是否真正发生了合并
bool unite(int a, int b) {
a = find(a); b = find(b);
if (a == b) return false; // 已在同一集合
if (sz[a] < sz[b]) swap(a, b); // 按大小合并
parent[b] = a;
sz[a] += sz[b];
comps--;
return true;
}
bool same(int a, int b) { return find(a) == find(b); }
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
DSU dsu(n);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
dsu.unite(u, v);
}
cout << "朋友圈数量 = " << dsu.comps << "\n";
int q; cin >> q;
while (q--) {
int a, b; cin >> a >> b;
cout << (dsu.same(a, b) ? "同朋友圈" : "不同朋友圈") << "\n";
}
return 0;
}
Python 版本
python
class DSU:
def __init__(self, n):
self.parent = list(range(n + 1))
self.sz = [1] * (n + 1)
self.comps = n
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]]
x = self.parent[x]
return x
def unite(self, a, b):
a, b = self.find(a), self.find(b)
if a == b:
return False
if self.sz[a] < self.sz[b]:
a, b = b, a
self.parent[b] = a
self.sz[a] += self.sz[b]
self.comps -= 1
return True
def same(self, a, b):
return self.find(a) == self.find(b)
def solve():
n, m = map(int, input().split())
dsu = DSU(n)
for _ in range(m):
u, v = map(int, input().split())
dsu.unite(u, v)
print("朋友圈数量 =", dsu.comps)
q = int(input())
for _ in range(q):
a, b = map(int, input().split())
print("同朋友圈" if dsu.same(a, b) else "不同朋友圈")
if __name__ == "__main__":
solve()
五、时间与空间复杂度
- 时间复杂度 :每次
find经过路径压缩 + 按大小合并后,单次操作近似O(α(n)),α是反阿克曼函数(增长极慢,n=10^9时也基本不超过 4)。整体约O((n + m) · α(n)) ≈ O(n + m)。 - 空间复杂度 :两个数组
parent、sz,O(n)。
对比朴素做法(每次合并暴力改标记 O(n)),并查集把「集合合并」从 O(n²) 压到接近线性,是蓝桥杯大赛里处理「动态连通」的标配。
六、进阶拓展
进阶 1:网格岛屿数量(二维并查集)
把 r 行 c 列的格子 (r, c) 映射成一维编号 r * cols + c,其余逻辑与一维完全相同:
cpp
int id(int r, int c, int cols) { return r * cols + c; }
// 对每个陆地格子,与上下左右四个邻居(若都是陆地)unite 即可
这就是蓝桥杯里常见的「岛屿数量 / 省份数量」题的并查集解法(也可用 DFS/BFS Flood Fill,思路相通)。
进阶 2:并查集判环
在无向图中加边 (u, v) 前先判断:if (dsu.same(u, v)) 说明两点已连通,再加这条边必成环;否则 unite。常用于「最少删几条边不成环」「K 度约束生成树」等。
进阶 3:按秩(rank)合并
本文用「按大小」合并;也可以记录树高 rank,把矮树挂到高树下,效果等价,都能保证树高 O(log n)。
进阶 4:与种类 / 带权并查集对照
当关系不止「同/不同」,而是「A 吃 B、B 吃 C」这类带相对关系 时,就要在 find 时同步维护偏移量、在 unite 时列方程求新根权值------这就是「种类并查集 / 带权并查集」(如经典的食物链、阵营冲突模型)。它是在本篇「基础连通」之上加了一层关系编码,建议先吃透本文再上难度。
进阶 5:最小生成树(Kruskal)
Kruskal 算法每选一条最小权边,都要用并查集判断「两端是否已连通」来避免成环------所以并查集也是最小生成树的前置积木。
七、易错点清单
- 忘记初始化 :
parent[i]必须初始为i,否则所有查找都会错。 - 路径压缩写错 :应写成
parent[x] = find(parent[x])(或迭代版parent[x] = parent[parent[x]]),返回的是根,而不是find(x)后再赋值给无关变量。 - 合并前没先
find:直接拿原始编号去挂,会把树接错;一定要先取各自的根。 comps乱减 :只有「真正发生合并」时才comps--,两人本就同圈时不能减。- 多组测试未重置 :有多组数据时要重新
new一个并查集,别复用旧数组。 - 下标从 0 还是 1 起 :题目编号若从 1 开始,数组开
n+1更省心;若是 0 起,注意映射不要越界。
八、小结与互动
并查集三句话记牢:初始化各自为营 → 查根用路径压缩 → 合并按大小(或秩)挂、合并成功才计数。它能把「连通块数量、是否连通、是否成环、最小生成树」一串题一次性串起来,是蓝桥杯、信息素养大赛算法应用、电子学会 C/C++ 与 Python 考级的高级必备结构。
今日小互动:如果把题目改成「每次询问两人是否同圈」改成「动态加朋友关系、随时问两人是否同圈」,你的并查集需要改哪一部分?欢迎在评论区写下你的思路,或贴出你用其他语言(如 Java)的实现版本,一起交流~
如果觉得有帮助,点赞 + 收藏 + 关注,后续还会更新「差分」「前缀和」「LCA」等蓝桥杯高频考点专题。
📚 免费少儿编程资料(夸克网盘领取)
以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:
- 全国青少年信息素养大赛复赛集训题目Python&C++.docx
https://pan.quark.cn/s/93995d3cb150 - 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
https://pan.quark.cn/s/da97b5dbf75d - Python背记手册.pdf
https://pan.quark.cn/s/7568ae9ca92b - Python课程
https://pan.quark.cn/s/a94bf02d00c6 - 2024信息素养大赛图形化复赛集训题答案3-9
https://pan.quark.cn/s/6ccab7ec3cbc - 2025年03月份电子学会考级真题
https://pan.quark.cn/s/4403c4228912 - 2025全国青少年信息素养大赛赛项说明
https://pan.quark.cn/s/d9d0df4a9f29 - 青少儿信息素养大赛编程资料
https://pan.quark.cn/s/4ab6bd83be8a
资料持续更新,关注获取最新分享。