用数组表示,
0和0的相联,1和1的相联
package bobo.algo;
// 我们的第一版Union-Find
public class UnionFind1 {
private int[] id; // 我们的第一版Union-Find本质就是一个数组
private int count; // 数据个数
public UnionFind1(int n) {
count = n;
id =new int[n];
// 初始化, 每一个id[i]指向自己, 没有合并的元素
for (int i =0; i < n; i++)
id[i] = i;
}
// 查找过程, 查找元素p所对应的集合编号
// O(1)复杂度
private int find(int p) {
assert p >=0 && p < count;
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 < count; i++)
if (id[i] == pID)
id[i] = qID;
}
}
Quick union
另一种解法,union将元素的父指针指向要连接的元素的根节点,根节点的父亲指针指向自己
可以用数组表示,其值是父亲节点的索引,数组初始值是自己
联合算法:
找到p的根
找到q的根
判断是否相等
不等则将p的根指向q的根就好了
缺点有可能多的节点指向短的节点的根,线长,效率也差,应该让短的指向长的根节点,形成层数较低的树。
package bobo.algo;
// 我们的第二版Union-Find
public class UnionFind2 {
// 我们的第二版Union-Find, 使用一个数组构建一棵指向父节点的树
// parent[i]表示第一个元素所指向的父节点
private int[] parent;
private int count; // 数据个数
// 构造函数
public UnionFind2(int count){
parent =new int[count];
this.count = count;
// 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
for(int i =0 ; i < count; i ++ )
parent[i] = i;
}
// 查找过程, 查找元素p所对应的集合编号
// O(h)复杂度, h为树的高度
private int find(int p){
assert( p >=0 && p < count );
// 不断去查询自己的父亲节点, 直到到达根节点
// 根节点的特点: 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;
}
}
更好的优化,层数低的树连上层数高的树,这样更好优化
package bobo.algo;
// 我们的第三版Union-Find
public class UnionFind3 {
private int[] parent; // parent[i]表示第一个元素所指向的父节点
private int[] sz; // sz[i]表示以i为根的集合中元素个数
private int count; // 数据个数
// 构造函数
public UnionFind3(int count){
parent =new int[count];
sz =new int[count];
this.count = count;
// 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
for(int i =0 ; i < count; i ++ ){
parent[i] = i;
sz[i] =1;
}
}
// 查找过程, 查找元素p所对应的集合编号
// O(h)复杂度, h为树的高度
private int find(int p){
assert( p >=0 && p < count );
// 不断去查询自己的父亲节点, 直到到达根节点
// 根节点的特点: 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;
// 根据两个元素所在树的元素个数不同判断合并方向
// 将元素个数少的集合合并到元素个数多的集合上
if( sz[pRoot] < sz[qRoot] ){
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}
else{
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
}
package bobo.algo;
路径压缩,将子节点连接到其父亲的父亲的节点, 从而降低路径的层数。
// 我们的第四版Union-Find
public class UnionFind4 {
private int[] rank; // rank[i]表示以i为根的集合所表示的树的层数
private int[] parent; // parent[i]表示第i个元素所指向的父节点
private int count; // 数据个数
// 构造函数
public UnionFind4(int count){
rank =new int[count];
parent =new int[count];
this.count = count;
// 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
for(int i =0 ; i < count; i ++ ){
parent[i] = i;
rank[i] =1;
}
}
// 查找过程, 查找元素p所对应的集合编号
// O(h)复杂度, h为树的高度
private int find(int p){
assert( p >=0 && p < count );
// 不断去查询自己的父亲节点, 直到到达根节点
// 根节点的特点: 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;
// 根据两个元素所在树的元素个数不同判断合并方向
// 将元素个数少的集合合并到元素个数多的集合上
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的值
}
}
}
public int find(int p){
assert( p >=0 && p < count );
// path compression 1
while( p != parent[p] ){
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
最优的路径压缩是压缩成2层
package bobo.algo;
// 我们的第六版Union-Find, 路径压缩使用递归实现
public class UnionFind6implements UF {
// rank[i]表示以i为根的集合所表示的树的层数
// 在后续的代码中, 我们并不会维护rank的语意, 也就是rank的值在路径压缩的过程中, 有可能不在是树的层数值
// 这也是我们的rank不叫height或者depth的原因, 他只是作为比较的一个标准
// 关于这个问题,可以参考问答区:http://coding.imooc.com/learn/questiondetail/7287.html
private int[] rank;
public int[] parent; // parent[i]表示第i个元素所指向的父节点
// 后续, 我们要在外部操控并查集的数据, 在这里使用public
private int count; // 数据个数
// 构造函数
public UnionFind6(int count){
rank =new int[count];
parent =new int[count];
this.count = count;
// 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
for(int i =0 ; i < count; i ++ ){
parent[i] = i;
rank[i] =1;
}
}
// 查找过程, 查找元素p所对应的集合编号
// O(h)复杂度, h为树的高度
public int find(int p){
assert( p >=0 && p < count );
// path compression 2, 递归算法
if( p != parent[p] )
parent[p] = find( parent[p] );
return parent[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;
// 根据两个元素所在树的元素个数不同判断合并方向
// 将元素个数少的集合合并到元素个数多的集合上
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的值
}
}
// 打印输出并查集中的parent数据
public void show(){
for(int i =0 ; i < count; i ++ )
System.out.print( parent[i] +" ");
System.out.println();
}
}