Quick-Find
0,2,4,6,8的标签为0。
1,3,5,7,9的标签为1。
标签为0和1表示两个不同的集合。
查找元素p所对应的集合编号,O(1)复杂度
private int find(int p) {
if(p < 0 || p >= id.length)
throw new IllegalArgumentException("p is out of bound.");
return id[p];
}
查看元素p和元素q是否所属一个集合,O(1)复杂度
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
合并元素p和元素q所属的集合,O(n) 复杂度
public void unionElements(int p, int q) {
int pID = find(p);
int qID = find(q);
if (pID == qID)
return;
// 合并过程需要遍历一遍所有元素, 将两个元素的所属集合编号合并
for (int i = 0; i < id.length; i++)
if (id[i] == pID)
id[i] = qID;
}
总结:find时间复杂度O(1),union时间复杂度O(n)。
Quick-Union
初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
for( int i = 0 ; i < size ; i ++ )
parent[i] = i;
查找过程, 查找元素p所对应的集合编号,O(h)复杂度, h为树的高度
private int find(int p){
if(p < 0 || p >= parent.length)
throw new IllegalArgumentException("p is out of bound.");
// 不断去查询自己的父亲节点, 直到到达根节点
// 根节点的特点: parent[p] == p
while(p != parent[p])
p = parent[p];
return p;
}
查看元素p和元素q是否所属一个集合,O(h)复杂度, h为树的高度
public boolean isConnected( int p , int q ){
return find(p) == find(q);
}
合并元素p和元素q所属的集合,O(h)复杂度, h为树的高度
public void unionElements(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if( pRoot == qRoot )
return;
parent[pRoot] = qRoot;
}
总结:find时间复杂度O(h),union时间复杂度O(h)。
缺点:整个树可能退化成数组链表。
基于size的优化
初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合,sz[i]表示以i为根的集合中元素个数。
for(int i = 0 ; i < size ; i ++){
parent[i] = i;
sz[i] = 1;
}
O(h)复杂度, h为树的高度
public void unionElements(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if(pRoot == qRoot)
return;
// 根据两个元素所在树的元素个数不同判断合并方向
// 将元素个数少的集合合并到元素个数多的集合上
if(sz[pRoot] < sz[qRoot]){
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}
else{ // sz[qRoot] <= sz[pRoot]
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
总结:find时间复杂度O(h),union时间复杂度O(h)。
缺点:如下图,以8为根的节点有3个,以7为根的节点有6个,合并后如图二所示,树的深度会增加。
基于rank的优化
初始化, rank[i]表示以i为根的集合所表示的树的层数。
for( int i = 0 ; i < size ; i ++ ){
parent[i] = i;
rank[i] = 1;
}
合并元素p和元素q所属的集合,O(h)复杂度,h为树的高度。
public void unionElements(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if( pRoot == qRoot )
return;
// 根据两个元素所在树的rank不同判断合并方向
// 将rank低的集合合并到rank高的集合上
if(rank[pRoot] < rank[qRoot])
parent[pRoot] = qRoot;
else if(rank[qRoot] < rank[pRoot])
parent[qRoot] = pRoot;
else{ // rank[pRoot] == rank[qRoot]
parent[pRoot] = qRoot;
rank[qRoot] += 1; // 此时, 我维护rank的值
}
}
路径压缩
就是使树的深度变小。使节点指向父节点的父节点。
查找过程中,就进行路径压缩。
private int find(int p){
if(p < 0 || p >= parent.length)
throw new IllegalArgumentException("p is out of bound.");
while( p != parent[p] ){
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
使用递归使所有节点都指向根节点。
private int find(int p){
if(p < 0 || p >= parent.length)
throw new IllegalArgumentException("p is out of bound.");
// path compression 2, 递归算法
if(p != parent[p])
parent[p] = find(parent[p]);
return parent[p];
}