/**
* 查找v所属的集合(根节点)
* @param v
* @return
*/publicabstractintfind(int v);
/**
* 合并v1、v2所在的集合
*/publicabstractvoidunion(int v1, int v2);
/**
* 检查v1、v2是否属于同一个集合
*/publicbooleanisSame(int v1, int v2);
初始化
protectedint[] parents;
publicUnionFind(int capacity){
if (capacity < 0) {
thrownew IllegalArgumentException("capacity must be >= 1");
}
parents = newint[capacity];
for (int i = 0; i < capacity; i++) {
parents[i] = i;
}
}
Quick Find
Union
Quick Find 的 union(V1,V2): 让V1所在集合的所有元素都指向V2的根节点
publicvoidunion(int v1, int v2){
int p1 = parents[v1];
int p2 = parents[v2];
if (p1 == p2) return;
for (int i = 0; i < parents.length; i++) {
if (parents[i] == p1) {
parents[i] = p2;
}
}
}
/**
* 将v1的根节点嫁接到v2的根节点上
*/@Overridepublicvoidunion(int v1, int v2){
int p1 = find(v1); // 查找根节点int p2 = find(v2);
if (p1 == p2) return;
parents[p1] = p2;
}
时间复杂度:O(logn)
Find
可以看出,Quick Union下的Union之后,数据呈现出树状排布,因此查找操作是顺着树状查找
find(0) == 2
find(1) == 2
find(3) == 2
find(2) == 2
时间复杂度:O(logn)
/**
* 通过parent链条不断地向上找,直到找到根节点
*/
@Override
public int find(int v) {
rangeCheck(v);
while (v != parents[v]) {
v = parents[v];
}
return v;
}
Quick Union - 优化
在Union的过程中,可能会出现树不平衡的情况,甚至退化成链表
有2种常见的优化方案
基于size的优化:元素少的树 嫁接到 元素多的树
基于rank的优化:矮的树 嫁接到 高的树
Quick Union - 基于Size的优化
元素少的树 嫁接到 元素多的树
// 创建一个数组,用来统计树的节点数量privateint[] sizes;
publicUnionFind_QU_S(int capacity){
super(capacity);
// TODO Auto-generated constructor stub
sizes = newint[capacity];
for (int i = 0; i < parents.length; i++) {
sizes[i] = 1;
}
}
基于Size的优化,不需要改变find的逻辑,只需要改变Union的逻辑
/**
* 将v1的根节点嫁接到v2的根节点上
*/@Overridepublicvoidunion(int v1, int v2){
int p1 = find(v1);
int p2 = find(v2);
if (p1 == p2) return;
if (sizes[p1] < sizes[p2]) {
parents[p1] = p2;
sizes[p2] += sizes[p1];
} else {
parents[p2] = p1;
sizes[p1] += sizes[p2];
}
}
基于size的优化,也可能会存在树不平衡的问题
由此引出基于rank的优化
Quick Union 基于rank的优化
矮的树 嫁接到 高的树
// 创建一个数组,用来统计树的高度privateint[] ranks;
publicUnionFind_QU_R(int capacity){
super(capacity);
// TODO Auto-generated constructor stub
ranks = newint[capacity];
for (int i = 0; i < parents.length; i++) {
ranks[i] = 1;
}
}
基于Rank的优化,不需要改变find的逻辑,只需要改变Union的逻辑
/**
* 将v1的根节点嫁接到v2的根节点上
*/
@Override
public void union(int v1, int v2) {
int p1 = find(v1);
int p2 = find(v2);
if (p1 == p2) return;
if (ranks[p1] < ranks[p2]) {
parents[p1] = p2;
} elseif (ranks[p1] > ranks[p2]) {
parents[p2] = p1;
} else {
parents[p1] = p2;
ranks[p2]++; // 只有树的高度相同时,才需要更新树的高度
}
}
路径压缩 (Path Compression) - 基于rank优化
@Override
public int find(int v) {
rangeCheck(v);
if (parents[v] != v) {
parents[v] = find(parents[v]);
}
return parents[v];
}
路径压缩使路径上的所有节点都指向根节点,所以实现成本稍高。
还有2种更优的做法,不但能降低树高,实现成本也比路径压缩低
路径分裂(Path Spliting)
路径减半(Path Halving)
路径分裂(Path Spliting) - 基于rank优化
使路径上的每个节点都指向其祖父节点(parent的parent)
publicintfind(int v){
// TODO Auto-generated method stub
rangeCheck(v);
while (v != parents[v]) {
int p = parents[v];
parents[v] = parents[parents[v]];
v = p;
}
return v;
}
路径减半(Path Halving) - 基于rank优化
使路径上每隔一个节点就指向其祖父节点(parent的parent)
@Overridepublicintfind(int v){
// TODO Auto-generated method stub
rangeCheck(v);
while (v != parents[v]) {
parents[v] = parents[parents[v]];
v = parents[v];
}
return v;
}