基础知识
- 参考该链接: https://zhuanlan.zhihu.com/p/93647900/
- 假设有1~6号节点,各个节点一开始自己都是自己的父节点
- 经过合并最终可得到1为1 2 3的父节点,4为4 5 6的父节点
- 再经过合并,1战胜4,所有子集的父节点都是1,就可以看成一棵树
初始化
初始化时,每个节点的父节点都是自己
int fa[MAXN];
void init(int n)
{
for (int i = 1; i <= n; ++i)
fa[i] = i;
}
查询
递归查询两个子节点是否为同一集合,就看他们父节点是否相同:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需看它们的根节点是否相同。
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);
}
路径压缩
每次合并时,如果按照原始操作,很可能会形成链表,链表结构的查询效率比树的效率要低。
- 路径压缩:合并时,让每个节点的父节点设置为根节点,减少递归次数。初始化过程与原始并查集一样。
查询合并
int find(int x)
{
if(x == fa[x])
return x;
else{
fa[x] = find(fa[x]); //父节点设为根节点
return fa[x]; //返回父节点
}
// 也可以写为
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
按秩合并
- 路径压缩的情况下,虽然降低了查询复杂度,但是节点多的树形结构中,由于路径压缩只在查询时进行,也只压缩一条路径,所以并查集最终的结构仍然可能是比较复杂的,进而采用按秩合并。
- 原理:简单的树往复杂的树上合并,到根节点距离变长的节点个数比较少,尽可能不增树深度。
具体流程
- 数组rank[]记录每个根节点对应的树的深度(如果不是根节点,其rank相当于以它作为根节点的子树的深度)。
一开始,把所有元素的rank(秩)设为1。
void init(int n)
{
for (int i = 1; i <= n; ++i)
{
fa[i] = i;
rank[i] = 1;
}
}
查询过程与路径压缩的查询合并一致
- 合并时比较两个根节点,把rank较小者往较大者上合并,如果深度相同且根节点不同,则新的根节点的深度+1,下次再调用时就会把两个节点集合合并在一起
void merge(int i, int j)
{
int x = find(i), y = find(j); //先找到两个根节点
if (rank[x] <= rank[y])
fa[x] = y;
else
fa[y] = x;
if (rank[x] == rank[y] && x != y)
rank[y]++; //如果深度相同且根节点不同,则新的根节点的深度+1
}