求一个图的连通分量的个数。
并查集用来查询一个元素是否在一个集合中 + 合并两个不相交的集合。
他就这么两个元操作;基于这两个操作的衍生操作有
比较两个元素是否在一个并查集中——比较两个并查集中的代表元素是否相同往一个并查集中添加元素——将该元素单独作为一个并查集,然后和目标集合做并集操作。
我们在学习BST的时候知道,一棵二叉树查找的时间复杂度和它的高度相关。如果一棵二叉树呈线性,那么搜索元素的时间复杂度就是O(n)。在并查集中也是如此,我们得想方设法地降低树的高度。
有一点要注意的是其实并查集对应的无向图其实是一棵多叉树,这一点反而有利于我们的编程。
路径优化算法就是用来降低我们树的高度的,它主要从我们并查集的两个操作出发:
我们在查询元素的时候顺便重构了一下查询的分支,把该路径上所有的节点都直接与根节点相连。见下图所示:
我们不能像TBT一样把树挂载到根节点的左子树中最右的节点下(其实这就是完全相反的做法)
我们应该利用其多叉树的性质把一个并查集直接挂载到另一个并查集的根节点上。
我们最好把高度小的树挂载到高度大的树上,这样我们的高度就不会有变化。
当然如果两棵树的高度相等,那就要使高度+1了。
(见此图的下面两张图)
其实这个模板写的不太好(指变量命名方面
但是我觉得只要写过几遍,看了模板以后能够明白其中的思想,那就够了。
// 像union set这种数据结构真的不适合使用成员方法来实现 // 还是像链表一样老老实实地用非成员方法来做好了 // 发现还是使用数组比较方便 constexpr int kNum = 100000; int parent_[kNum]; // 记录每一个节点的父亲节点编号 int rank_[kNum]; // 用bits/stdc++.h还不能使用rank,不然会重名发生ambigous错误 void Initialize() { // 初始化 for (int i = 0; i < kNum; ++i) { parent_[i] = i; rank_[i] = i; } } int Find(int x) { // 找到相应并查集的根节点 // 根节点的特征 if (x == parent_[x]) { return x; } return parent_[x] = Find(parent_[x]); // 继续查找+路径压缩 } void Union(int x, int y) { int parent_x = Find(x); int parent_y = Find(y); // 如果在同一个并查集内 if (parent_x == parent_y) { return; } // 如果不在一个并查集内, 根据rank来合并并查集 // 注意对rank的更新 if (rank_[parent_x] > rank_[parent_y]) { parent_[parent_y] = parent_x; // y挂到x上 } else { if (rank_[parent_x] == rank_[parent_y]) { ++rank_[parent_y]; // 由于接下来要挂到y上 } parent_[parent_x] = parent_y; // x挂到y上 }
