并查集(union-find set )
适用组别:提高组
难度系数:6
OJ:P1846/P1850
一、集合(set):
集合是由一个或多个确定的元素所构成的整体。集合中的元素有如下三个特征:
1、确定性:一个元素要么属于集合,要么不属于集合。
2、互异性:集合中的元素互不相同。
3、无序性:集合中的元素没有先后顺序。
二、并查集(union-find set):
并查集是一个可以维护集合的数据结构,它能高效支持集合的基本操作。
1、合并两个集合
2、查询两个指定元素是否属于同一个集合。
需要注意,由于计算机存储结构的限制,并查集维护的集合是离散意义下的集合,而不是广义的集合。集合中的元素是有限的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /**************************************************************** * C++ 并查集 * date: 2023-6-3 * author: Alex Li * version: 1.2 * ****************************************************************/ #include <iostream> using namespace std; int father[200]; int x,y; //找x的祖先,通过递归,当到达祖先位置时,返回祖先 int find(int x){ //不断向上查找祖先 if(father[x]!=x) return find(father[x]); //路径压缩,所有结点指向一个祖先 //if(father[x]!=x) father[x]=find(father[x]); return father[x]; } //合并祖先 void unionn(int r1,int r2){ father[r2]=r1; } bool judge(int x,int y){ x=find(x); y=find(y); if(x==y)return true; else return false; } int main(){ int n,m; cin>>n>>m; //初始化,每个结点的父结点都试成自己 for (int i = 1; i <= n; i++)father[i]=i; for (int i = 1; i <= m; i++){ cin>>x>>y; int r1=find(x); int r2=find(y); if(r1!=r2)unionn(r1,r2); } cin>>x>>y; if(judge(x,y))cout<<"YES"; else cout<<"NO"; } |
求连通分量数和最大连通分量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | /**************************************************************** * 代码作者: Alex Li * 创建时间: 2026-05-16 13:50 * 最后修改: 2026-05-16 13:50 * 文件描述: 并查集算法示例,动态维护无向图的连通分量个数与最大连通块大小,时间复杂度接近 O(m alpha(n)) * 核心算法: 使用路径压缩和按大小合并维护集合,边加入时合并端点所在集合并更新答案 ****************************************************************/ #include <bits/stdc++.h> using namespace std; const int MAXN = 100000; int parent[MAXN], sz[MAXN]; int n, m, components; // 初始化并查集:开始时每个点单独成一个集合。 void initialize() { for (int i = 0; i < n; i++) { parent[i] = i; sz[i] = 1; } } // 查找 a 所在集合的根,并进行路径压缩。 int findRoot(int a) { if (parent[a] == a) { return a; } return parent[a] = findRoot(parent[a]); } // 合并 a 和 b 所在的集合。 // 如果原本属于不同集合,连通分量个数减 1。 void mergeSet(int a, int b) { a = findRoot(a); b = findRoot(b); if (a == b) { return; } --components; // 按大小合并:把小集合挂到大集合下面,避免树退化过高。 if (sz[a] > sz[b]) { swap(a, b); } parent[a] = b; sz[b] += sz[a]; } // 返回 a 所在连通块的大小。 int findSize(int a) { return sz[findRoot(a)]; } int main() { cin >> n >> m; initialize(); components = n; int maxComponentSize = (n > 0 ? 1 : 0); // 输入格式: // n m // 接下来 m 行,每行一条无向边 a b,默认结点编号为 0 ~ n-1。 for (int i = 0; i < m; i++) { int a, b; cin >> a >> b; mergeSet(a, b); maxComponentSize = max(maxComponentSize, findSize(a)); // 输出当前连通分量个数,以及当前最大连通块大小。 cout << components << " " << maxComponentSize << "\n"; } return 0; } |
练习题:见二分图P1525
