并查集(Union-Find)理论基础
1. 基本概念
并查集是一种用于处理集合合并与查询的数据结构,主要支持两种操作:
- Find(查找):判断某个元素属于哪个集合(找到根节点)
- Union(合并):将两个集合合并成一个集合
常用于解决:
连通性问题(是否在同一集合)
2. 核心思想
用一个数组 parent 表示每个节点的"父节点",形成一棵树结构:
- 每个集合有一个根节点(代表元)
- 判断两个元素是否属于同一集合:
👉 看它们的根节点是否相同
3. 基本操作
(1)初始化
for(int i = 0; i < n; i++){
parent[i] = i; // 每个节点的父节点是自己
}
(2)查找(Find)
int find(int x){
if(parent[x] != x){
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
作用:
- 找到集合的根节点
- 顺便进行路径压缩(优化)
(3)合并(Union)
void unite(int x, int y){
int rootX = find(x);
int rootY = find(y);
if(rootX != rootY){
parent[rootY] = rootX; // 合并
}
}
4. 优化技巧
(1)路径压缩
在 find 过程中,把所有节点直接挂到根节点上:
parent[x] = find(parent[x]);
作用:
- 降低树高度
- 加快后续查询
(2)按秩合并(或按大小合并)
if(rank[rootX] < rank[rootY]){
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
}
作用:
- 保持树尽量平衡
- 避免退化成链表
5. 时间复杂度
在路径压缩 + 按秩合并优化下:
- 单次操作复杂度:接近 O(1)
- 精确为:O(α(n))(反阿克曼函数,极慢增长)
可以认为是常数时间
6. 适用场景
并查集常用于:
- 连通性判断(两个点是否连通)
- 图的连通分量统计
- 最小生成树(Kruskal)
- 岛屿问题(动态合并)
- 网络连接问题
107. 寻找存在的路线
题目描述
给定一个包含 n 个节点的无向图中,节点编号从 1 到 n (含 1 和 n )。
你的任务是判断是否有一条从节点 source 出发到节点 destination 的路径存在。
输入描述
第一行包含两个正整数 N 和 M,N 代表节点的个数,M 代表边的个数。
后续 M 行,每行两个正整数 s 和 t,代表从节点 s 与节点 t 之间有一条边。
最后一行包含两个正整数,代表起始节点 source 和目标节点 destination。
输出描述
输出一个整数,代表是否存在从节点 source 到节点 destination 的路径。如果存在,输出 1;否则,输出 0。
输入示例
5 4 1 2 1 3 2 4 3 4 1 4输出示例
1
cpp
#include <iostream>
#include <vector>
using namespace std;
int n, m;
// father[i] 表示节点 i 的父节点
vector<int> father(101, -1);
// 初始化:每个节点的父节点都是自己
void init() {
for (int i = 1; i <= n; i++) {
father[i] = i;
}
}
// 查找根节点(带路径压缩)
int find(int u) {
// 如果不是根节点,就递归查找,并压缩路径
return u == father[u] ? u : father[u] = find(father[u]);
}
// 合并两个集合
void join(int u, int v) {
u = find(u);
v = find(v);
// 如果已经在同一集合,不需要合并
if (u == v) return;
// 将 v 的根挂到 u 上
father[v] = u;
}
// 判断两个节点是否属于同一集合
bool issame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
int main() {
cin >> n >> m;
// 初始化并查集
init();
// 读入边并进行合并
for (int i = 0; i < m; i++) {
int s, t;
cin >> s >> t;
join(s, t);
}
int start, end;
cin >> start >> end;
// 判断 start 和 end 是否连通
cout << issame(start, end) << endl;
return 0;
}
总结
1. 核心思路
用并查集维护连通关系:
join(s, t):表示 s 和 t 连通- 最后判断
start和end是否在同一集合
本质是:
判断两个节点是否连通
2. 关键操作
find(u):查找根节点(带路径压缩)join(u, v):合并两个集合issame(u, v):判断是否在同一集合
3. 优化点
路径压缩:
father[u] = find(father[u]);
作用:
- 降低树高度
- 提高查询效率
4. 复杂度
- 单次操作:接近 O(1)
- 总体复杂度:O(n + m)