以下是一个并查集离散化的模板(我个人觉得相对于开数组方式来说,当然这种数组转哈希的技巧在线段树离散化里面同样适用,可能是我孤陋寡闻了),以前背的并查集板子是开数组并且使用负数来作为连通分量的秩的,但是遇到一些键不是整型的题目,还需要自己做键类型之间的映射比较麻烦,以及一些取值范围很大的键,数组根本开不到那个范围。其实把数组替换为哈希表就好了,其他的基本没什么改动的地方,麻烦的就是使用一个根之前必须先手动插入,给我的感觉大概是 懒汉式 和 饿汉式 的区别:
- 饿汉式:开数组,数组每个元素代表了一个节点,虽然后续大量的节点可能根本没有参与合并的过程,就是像饿汉式一样不管有没有用到一开始就创建
- 懒汉式:哈希表,存在合并行为的根再执行创建
template <typename Tkey>
class DisjointSet {
private:
unordered_map<Tkey, Tkey> G;
unordered_map<Tkey, int> R;
public:
void insert(Tkey x) {
if (G.count(x)) return;
G[x] = x;
R[x] = 1;
}
Tkey find(Tkey x) {
if (!G.count(x)) return x;
if (R[x] > 0) return x;
return G[x] = find(G[x]);
}
void merge(Tkey x, Tkey y) {
if (!G.count(x) || !G.count(y)) return;
Tkey rx = find(x);
Tkey ry = find(y);
if (rx == ry) return;
if (R[rx] > R[ry]) {
R[rx] += R[ry];
R[ry] = 0; // not a root anymore
G[ry] = rx;
} else {
R[ry] += R[rx];
R[rx] = 0; // not a root anymore
G[rx] = ry;
}
}
int rank(Tkey x) {
// 所在连通分量的顶点数量
if (!G.count(x)) return 1;
return R[find(x)];
}
};
G G G 指的是 graph 图本身, R R R 指的是图中每个顶点所在连通分量的秩,是用于按秩合并的时候使用的,路径压缩总是会被执行,任何的合并都将导致查询先发生,因而一棵连通分量树的高度要么是 1 要么是 2,合并以后树的高度不会超过 3 。
每次合并的时候,被成为子节点的节点将失去根节点标志性的非零秩,我们总是把一棵连通分量树的所含顶点数量(秩)存放在根节点,非根节点的秩总是 0 ,这样便于我们区分哪些节点是根节点,作为路径压缩时候停止递归的条件。
一个使用示例可以看我的另一篇博客 Leetcode 399. 除法求值 【离散化并查集】