C++怎么实现并查集_C++集合合并查询【数据】

2次阅读

unionfind 类应使用 vector parent 和 size,初始化 parent[i]=i;find 必须路径压缩,union 前先 find 根并按 size 合并,避免树高退化。

C++怎么实现并查集_C++集合合并查询【数据】

怎么写一个轻量可靠的 UnionFind

直接用数组 + 路径压缩 + 按秩合并,别手写链表或哈希映射——除非你真要处理字符串 ID。整型下标最稳,vector<int></int> 存父节点,初始化时 parent[i] = i

常见错误是忘记在 find 里做路径压缩,导致后续查询退化成 O(n);或者 union 时只改了一个方向的父节点,没比较秩(rank)或大小(size),合并后树高失控。

  • find 必须递归或迭代地把沿途所有节点指向根,不能只返回根而不动路径
  • union 前先 find 两个节点的根,相同就直接返回,避免自环
  • 按秩合并用 vector<int> rank</int>,只在两棵树秩相等时才让一方当父,且该方秩 +1;按大小合并更直观,适合判断连通分量大小
class UnionFind {     vector<int> parent, size; public:     UnionFind(int n) : parent(n), size(n, 1) {         for (int i = 0; i < n; ++i) parent[i] = i;     }     int find(int x) {         if (parent[x] != x) parent[x] = find(parent[x]); // 路径压缩         return parent[x];     }     void unite(int x, int y) {         x = find(x), y = find(y);         if (x == y) return;         if (size[x] < size[y]) swap(x, y);         parent[y] = x;         size[x] += size[y];     } };

为什么 find 用递归比循环更容易出错

不是不能用循环,而是新手常漏掉“更新父指针”这一步:循环找到根后,只返回根值,没把路径上每个节点的 parent 改成根。结果下次查还走老路,压缩失效。

递归写法天然带回溯赋值机会,只要写对 parent[x] = find(parent[x]) 这一行,压缩就自动完成。但要注意深度——10⁵ 节点+链式结构可能爆栈,这时得切迭代。

立即学习C++免费学习笔记(深入)”;

  • 递归版简洁安全,适用于 n ≤ 10⁴ 的常规场景
  • 迭代版需额外存路径节点(如 vector<int></int>),再统一设父,代码略长但可控
  • 别用 while(true) + break 模拟递归逻辑,容易漏更新中间节点

合并字符串 ID 时怎么避免 map 查找开销

如果输入是 "nodeA""nodeB" 这类字符串,硬套 map<string int></string> 映射会拖慢每次 find/unite,尤其高频调用时。

真正高效的做法是预处理:一次性读完所有 ID,用 unordered_map 映射到连续整数 0..n−1,之后全程用整数下标操作 UnionFind。别在 unite("a", "b") 里实时查 map。

  • 预处理阶段允许 O(n log n),运行期必须保持接近 O(α(n))
  • 别用 map(红黑树),用 unordered_map 防止插入变慢
  • 如果 ID 是带前缀的数字(如 "user_123"),可考虑提取数字部分直接转 int,跳过 map

UnionFind 在离线查询中怎么配合排序使用

比如「按边权从小到大加边,问第 k 次合并后有多少连通分量」——这种题不能边加边输出,得先把操作序列排好序,再逐个 unite,同时维护当前连通块数量或最大分量大小。

关键点在于:并查集本身不记录历史,所有依赖顺序的逻辑必须由外部控制。容易踩的坑是把查询混进合并循环里,结果查的是中间态而非题目要求的某个快照时刻。

  • 把所有操作(合并/查询)按时间戳或条件排序,用索引驱动流程
  • 查询类操作(如「此时连通块数」)只需统计 find(i) == i 的个数,O(n) 一次就够了,别每步都扫
  • 需要支持撤销?标准 UnionFind 不行,得换可回滚版本(带栈的 union-by-size + 时间戳),那是另一回事了

路径压缩和按秩合并不是锦上添花,是保性能的底线。漏掉任何一个,面对 10⁵ 数据就可能从秒级变成秒超时。实际写的时候,先写对 find 的压缩,再补 unite 的秩/大小判断,比反过来调试成本低得多。

text=ZqhQzanResources