并查集算法
->解决动态连通性问题
供自己使用的学习笔记,原文链接:https://cloud.tencent.com/developer/article/1880833
主要API:
class UF {
/* 将 p 和 q 连接 */
public void union(int p, int q);
/* 判断 p 和 q 是否连通 */
public boolean connected(int p, int q);
/* 返回图中有多少个连通分量 */
public int count();
}
我们使用森林(若干棵树)来表示图的动态连通性,用数组来具体实现这个森林。
具体实现:
public void union(int p, int q)
{
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ)
return;
// 将两棵树合并为一棵
parent[rootP] = rootQ; /*一个树的树根接到另一个树上*/
// parent[rootQ] = rootP 也一样
count--; // 两个分量合二为一
}
/* 返回某个节点 x 的根节点 */
private int find(int x)
{
// 根节点的 parent[x] == x
while (parent[x] != x) /*不断寻找父节点直到根父节点*/
x = parent[x];
return x;
}
/* 返回当前的连通分量个数 */
public int count()
{
return count;
}
/*判断是否连通,即判断祖先是否相同即可*/
public boolean connected(int p, int q)
{
int rootP = find(p);
int rootQ = find(q);
return rootP == rootQ;
}
时间复杂度如何呢?
我们发现union函数和connected函数的时间复杂度取决于find函数,而find的时间复杂度取决于树的高度,平均情况即O(logN),最坏情况变为单支树,变成了O(N).
->那么如何避免树的不平衡呢?我们引出下面的平衡性优化。
我们要知道哪种情况下可能出现不平衡现象,关键在于union
过程:
我们一开始就是简单粗暴的把p
所在的树接到q
所在的树的根节点下面,那么这里就可能出现「头重脚轻」的不平衡状况,比如下面这种局面:
长此以往,树可能生长得很不平衡。我们其实是希望,小一些的树接到大一些的树下面,这样就能避免头重脚轻,更平衡一些。解决方法是额外使用一个size
数组,记录每棵树包含的节点数,我们不妨称为「重量」:
class UF {
private int count;
private int[] parent;
// 新增一个数组记录树的“重量”
private int[] size;
public UF(int n) {
this.count = n;
parent = new int[n];
// 最初每棵树只有一个节点
// 重量应该初始化 1
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
/* 其他函数 */
}
这样,通过比较树的重量,就可以保证树的生长相对平衡,树的高度大致在logN
这个数量级,极大提升执行效率。
此时,find
,union
,connected
的时间复杂度都下降为 O(logN),即便数据规模上亿,所需时间也非常少。
再优化->路径压缩,使树高保持为常数:
要做到这一点,非常简单,只需要在find
中加一行代码:
private int find(int x) {
while (parent[x] != x) {
// 进行路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
调用find
函数每次向树根遍历的同时,顺手将树高缩短了,最终所有树高都不会超过 3(union
的时候树高可能达到 3)。
最后总结:完整代码
class UF {
// 连通分量个数
private int count;
// 存储一棵树
private int[] parent;
// 记录树的“重量”
private int[] size;
public UF(int n) {
this.count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ)
return;
// 小树接到大树下面,较平衡
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
count--;
}
public boolean connected(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
return rootP == rootQ;
}
private int find(int x) {
while (parent[x] != x) {
// 进行路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
}
{
while (parent[x] != x) {
// 进行路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
}