并查集小结
并查集
并查集可用来解决一些元素分组的问题,管理一系列不相交的集合
- 合并:把不相交的集合合并到一个集合
- 查询:查询两个元素是否在同一集合
原理 起初有1~n
个元素,它们分别指向自己,父节点即为自己
当两个元素要合并时,用其中一个元素指向另外一个fa[i] = j
,
代表 i 的父元素是 j 。
- 查询
// 查询根节点 int find(int x){ if(fa[x] == x) return x; else return find(fa[x]); }
- 合并
void merge(int i,int j) { fa[find[i]] = find(j); // 将i和j的父节点相连 }
- 路径压缩
// 一句话 int find(int x) { return x == fa[x] ? x :(fa[x] == find(fa[x])); } // 拆分 int find(int x) { if(x == fa[x]) return x; else{ fa[x] = find(fa[x]); //父节点设为根节点 return fa[x]; //返回父节点 } }
ACwing836合并集合
一共有 n个数,编号是 1∼n,最开始每个数各自在一个集合中。
现在要进行 m个操作,操作共有两种:
M a b
,将编号为 a和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;Q a b
,询问编号为 a和 b 的两个数是否在同一个集合中;
模板
#include <iostream> using namespace std; const int N = 100010; int p[N]; int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } int main() { int n, m; scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) p[i] = i; while (m -- ) { char op[2]; int a, b; scanf("%s%d%d", op, &a, &b); if (*op == 'M') p[find(a)] = find(b); // 合并,将a 和 b的父节点相连 else { if (find(a) == find(b)) puts("Yes"); else puts("No"); } } return 0; }
自己写的
#include<iostream> using namespace std; const int N = 100010, M = 100010; int fa[N]; // 查找父节点,顺便将路径上的点都指向集合中的根节点 int find(int x) { if(x == fa[x]) return x; else fa[x] = find(fa[x]); return fa[x]; } // 合并 void merge(int i, int j) { fa[find(i)] = fa[find(j)]; // 将i和j的父节点链接 } // 查询是否在同一集合 bool query(int i, int j) { if(find(i) == find(j)) return true; return false; } int main() { int n, m; cin >> n >> m; for(int i = 1;i <= n;i++) fa[i] = i; // 初始化指向自己 char op; while(m--) { int x, y; cin >> op; if(op == 'M') { cin >> x >> y; merge(x,y); }else if(op == 'Q') { cin >> x >> y; if(query(x, y)) cout << "Yes" << endl; else cout << "No" << endl; } } return 0; }
连通块中点的数量
给定一个包含 n个点(编号为1∼n)的无向图,初始时图中没有边。
现在要进行 m个操作,操作共有三种:
C a b
,在点 a 和点 b 之间连一条边,a 和 b 可能相等;Q1 a b
,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;Q2 a
,询问点 aa 所在连通块中点的数量;
本题主要是要记录连通块中点的数量,初始化一个size
数组,当合并的时候更新根节点的size
数组就可以了。
自己写的
#include <iostream> #include <string> using namespace std; const int N = 100010; int p[N], Size[N]; int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } // 合并时同一个集合不一定所有节点指向根节点,但查询时,find函数路径压缩会让一个集合中元素都指向根节点。 void merge(int i, int j) { int x = find(i); int y = find(j); p[x] = y; // y是根节点了 Size[y] += Size[x]; } int main() { int n, m; scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) { p[i] = i; Size[i] = 1; } while (m -- ) { string op; int a,b; cin >> op; if(op == "C") { scanf("%d%d", &a, &b); if(find(a) != find(b)) { merge(a,b); // 不是一个连通块才相连 } } else if(op == "Q1") { scanf("%d%d", &a, &b); if(find(a) == find(b)) puts("Yes"); else puts("No"); }else { scanf("%d", &a); int parent = find(a); // 找到根节点 cout << Size[parent] << endl; } } return 0; }
模板
#include <iostream> using namespace std; const int N = 100010; int n, m; int p[N], cnt[N]; int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; } int main() { cin >> n >> m; for (int i = 1; i <= n; i ++ ) { p[i] = i; cnt[i] = 1; } while (m -- ) { string op; int a, b; cin >> op; if (op == "C") { cin >> a >> b; a = find(a), b = find(b); if (a != b) // 不是同一个连通块的才相连 { p[a] = b; cnt[b] += cnt[a]; } } else if (op == "Q1") { cin >> a >> b; if (find(a) == find(b)) puts("Yes"); else puts("No"); } else { cin >> a; cout << cnt[find(a)] << endl; } } return 0; }
带权并查集
普通的并查集仅仅记录的是集合的关系,这个关系只是同一个集合或不在同一集合。而带权并查集,不仅记录集合的关系,还记录着集合内元素的关系(或者说元素连接线的权值)。
解决这类问题主要是要明确距离在题目中指的是什么,
- 距离是各个集合中每个元素到根节点之间的距离,当合并两个集合时,根节点也发生变化,所以距离也要更新。
- 通过画图、数学求出不同元素到根节点之间的联系。假设X、Y是同一集合中的元素,通过
X和根节点的关系
与Y和根节点的关系
推出X和Y之间的关系
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人