并查集

1|0并查集

1|1概述

  • 并查集是一种树形数据结构,经常用于处理一些集合之间的操作,例如元素查找,集合合并。
  • 不同集合在并查集中以不同的树表示,一般每棵树的根节点会作为当前集合的代表元
  • 想要查询两个元素是不是在同一集合中,只需要比较两个元素所在集合的代表元是否相同即可。

1|2实现

  • 初始化

  • const int N = 100010; int fa[N];//记录每个元素由谁代表 int sz[N];//记录每个集合元素个数 int dep[N + 1];//记录每个集合树的深度
  • 假设有 n 个元素,这些元素初始都是独立的。显然它们构成了 n 个集合,每个集合的代表元就是元素自己。

  • void initalize(int n) { for (int i = 1; i <= n; i++) { fa[i] = i; sz[i] = dep[i] = 1; } }
  • 将两个元素 xy 所在的集合合并

    • 先找到 xy 对应的代表元。(即 fa 等于自己的元素)

    • 将其中一个代表元的 fa 指向另外一个,那么在这个代表元下的所有元素都会指向另外一个。

    • int findSet(int x) { if (fa[x] == x) { return x; } return findSet(fa[x]); } void Union(int x, int y) { int fx = findSet(x), fy = findSet(y); if (fx == fy) { return ; } fa[fx] = fy;//把fx的代表元改为fy }
  • 路径压缩

    • 如果并查集在合并中形成一条长链,每次查找代表元都可能要花费大量时间。

    • 我们可以缩短并查集中的路径,具体做法就是在查询的过程中,把沿途的每个节点的 fa 都设为集合代表元。

    • 当有 n 个元素和 m 次查询时,时间复杂度 O(mlogn)

      • 1 1 | / | \ 2 2 3 4 | 3 | 4
    • int findSet(int x) { if (x == fa[x]) { return x; } fa[x] = findSet(fa[x]);//路径压缩 return fa[x]; }
    • //简写 int findSet(int x) { return x == fa[x] ? x : (fa[x] = findSet(fa[i])); }
  • 启发式合并

    • 在合并集合的时候,我们尽量选择包含元素个数少的集合把它并入另一个集合中,使需要改变代表元的元素的数量尽可能少。

    • 当有 n 个元素和 m 次查询时,时间复杂度 O(mlogn)(跟路径压缩没差,其实可以用路径压缩)

    • void Union(int x, int y) { int fx = findSet(x), fy = findSet(y); if (fx == fy) { return ; } if (sz[fx] > sz[fy]) { swap(fx, fy); } fa[fx] = fy; sz[fy] += sz[fx]; }
  • 按深度合并

    • 在合并集合的时候,我们尽量选择深度较小的集合把它并入另一个集合中。

    • 在路径压缩的时候,有可能会破坏维护的深度值,但算法总体复杂度不会变差

    • void Union(int x, int y) { int fx = findSet(x), fy = findSet(y); if (fx == fy) return; if (dep[fx] > dep[fy]) swap(fx, fy); fa[fx] = fy; if (dep[fx] == dep[fy]) dep[fy]++; }

1|3例题

  • P1551 亲戚

    • 模板题

    • #include<iostream> #include<algorithm> #include<cstdio> const int N = 5e3 + 10; int n, m, p; int fa[N]; int findSet(int x); void Union(int x, int y) { int fx = findSet(x), fy = findSet(y); if (fx == fy) return; fa[fx] = fy; } int findSet(int x) { if (x == fa[x]) { return x; } fa[x] = findSet(fa[x]); return fa[x]; } void initalize(int n) { for (int i = 1; i <= n; i++) { fa[i] = i; } } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::cin >> n >> m >> p; initalize(n); for (int i = 1; i <= m; i++) { int x, y; std::cin >> x >> y; Union(x, y); } for (int i = 1; i <= p; i++) { int x, y; std::cin >> x >> y; if (findSet(x) == findSet(y)) { std::cout << "Yes\n"; } else { std::cout << "No\n"; } } return 0; }
  • 统计连通块

    • 这类题统计连通块个数就是统计 fa 数组等于其本身的个数。

    • 即集合数量。

    • P8654 蓝桥杯 2017 国 C 合根植物

    • #include<iostream> #include<algorithm> #include<cstdio> int fa[1000010]; int m, n; int vis[1000010]; int ans; int findSet(int x) { if (x == fa[x]) { return x; } fa[x] = findSet(fa[x]); return fa[x]; } void Union(int x, int y) { int fx = findSet(x), fy = findSet(y); if (fx == fy) { return ; } fa[fx] = fy; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::cin >> m >> n; int k; std::cin >> k; for (int i = 1; i <= n * m; i++) { fa[i] = i; } for (int i = 1; i <= k; i++) { int a, b; std::cin >> a >> b; Union(a, b); } for (int i = 1; i <= n * m; i++) { if (fa[i] == i) ans++; } std::cout << ans; return 0; }
    • P1536 村村通

    • #include<iostream> #include<algorithm> #include<cstdio> int n, m; int fa[1010]; void initalize(int n) { for (int i = 1; i <= n; i++) { fa[i] = i; } } int findSet(int x) { if (fa[x] == x) { return x; } fa[x] = findSet(fa[x]); return fa[x]; } void merge(int x, int y) { int fx = findSet(x), fy = findSet(y); if (fx == fy) return; fa[fx] = fy; return ; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); while(std::cin >> n >> m) { if (!n) break; initalize(n); while(m--) { int x, y; std::cin >> x >> y; merge(x, y); } int cnt = 0; for (int i = 1; i <= n; i++) { if (fa[i] == i) { cnt++; } } std::cout << cnt - 1 << std::endl; } return 0; }
  • 并查集的扩展域(敌人的敌人是朋友)

    • P1892 BOI2003 团伙

      #include<bits/stdc++.h> using namespace std; int n,m,x,y,f[100010],b[1010][1010],ans=0; char c; int find(int x) { if(f[x]!=x) f[x]=find(f[x]); return f[x]; } void merge(int fa,int fb) { fa=find(fa); fb=find(fb); f[fb]=fa; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) f[i]=i; for(int i=1;i<=m;i++) { cin>>c>>x>>y; if(c=='F') merge(x,y); if(c=='E') { b[x][y]=b[y][x]=1; for(int j=1;j<=n;j++) { if(b[x][j]==1) merge(y,j); if(b[y][j]==1) merge(x,j); } } } for(int i=1;i<=n;i++) if(f[i]==i) ans++; printf("%d",ans); return 0; }
    • P1525 NOIP2010 提高组 关押罪犯

      #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> int n, m; const int N = 100000 + 10; struct Node { int x, y, z; bool operator < (const Node &rhs) const { return z > rhs.z; } void read() { std::cin >> x >> y >> z; } } a[N]; int fa[N]; int enemy[N]; void initalize(int n) { for (int i = 1; i <= n; i++) { fa[i] = i; } } int findSet(int x) { return x == fa[x] ? x : fa[x] = findSet(fa[x]); } void merge(int x, int y) { int fx = findSet(x), fy = findSet(y); if (fx == fy) return; fa[fx] = fy; } bool check(int x, int y) { return findSet(x) == findSet(y); } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::cin >> n >> m; initalize(n); for (int i = 1; i <= m; i++) { a[i].read(); } std::sort(a + 1, a + m + 1); for (int i = 1; i <= m + 1; i++) { if (check(a[i].x, a[i].y)) { std::cout << a[i].z; return 0; } if (!enemy[a[i].x]) enemy[a[i].x] = a[i].y; else merge(enemy[a[i].x], a[i].y);//合并敌人的敌人与自己 if (!enemy[a[i].y]) enemy[a[i].y] = a[i].x; else merge(enemy[a[i].y], a[i].x);//合并敌人的敌人与自己 } return 0; }
  • 并查集结合筛法

    • P1621 集合

    • 筛的过程中合并,刚好就是质因数有 p 的数。

      #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> const int N = 1e5 + 10; int a, b, p; int fa[N]; bool no_prime[N]; int findSet(int x) { return x == fa[x] ? x : fa[x] = findSet(fa[x]); } void merge(int x, int y) { int fx = findSet(x), fy = findSet(y); if (fx == fy) return; fa[fx] = fy; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::cin >> a >> b >> p; for (int i = a; i <= b; i++) fa[i] = i; for (int i = 2; i <= b; i++) { if (!no_prime[i]) { for (int j = i * 2; j <= b; j += i) { if (i >= p && j - i >= a) { merge(j, j - i); } no_prime[j] = true; } } } int cnt = 0; for (int i = a; i <= b; i++) if (fa[i] == i) cnt++; std::cout << cnt; return 0; }
  • 奇怪的 fa 数组并查集

    • P2814 家谱

      • fa 数组是 map<string, string>

      • #include<iostream> #include<algorithm> #include<cstdio> #include<map> const int N = 5e4 + 10; int cnt; std::string name, f; std::map<std::string, std::string> fa; std::string findSet(std::string x) { return x == fa[x] ? x : fa[x] = findSet(fa[x]); } void merge(std::string x, std::string y) { std::string fx = findSet(x), fy = findSet(y); if (fx == fy) return ; fa[fx] = fy; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); char opt; while(std::cin >> opt && opt != '$') { std::cin >> name; if (opt == '#') { f = name; if (fa.find(name) == fa.end()) fa[name] = name; } else if (opt == '+') { if (fa.find(name) == fa.end()) fa[name] = name; merge(name, f); } else if (opt == '?') { std::cout << name << " " << findSet(name) << std::endl; } } return 0; }

__EOF__

本文作者Kdlyh
本文链接https://www.cnblogs.com/kdlyh/p/17858783.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   加固文明幻景  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示