并查集可确定两个结点是否连接,合并两个结点,不考虑元素的内容用数组来保存。
接口实现:
public interface UF {
/*
* 并查集的接口
* getsize 元素的个数
* isConnected是否连接
* union合并
* */
/*获得元素个数*/
int getSize();
/*p q 表示元素在数组中的索引
* 判断p q 是否连接
* */
boolean isConnected(int p, int q);
/*将p,q连接起来*/
void union(int p, int q);
}
方法一:
public class UnionFindFirst implements UF{
public int[] id;
public int size;
/*初始化
* 为每一个元素(数组索引)创建一个集合(数组对应索引的值)
* 属于集合表示
* 1 2 3 4 5 6 7 8
* 0 0 0 0 1 1 1 1
* */
public UnionFindFirst(int size) {
this.size = size;
id=new int[size];
for (int i = 0; i < size; i++) {
id[i]=i;
}
}
public int find(int index){
return id[index];
}
@Override
public int getSize() {
return this.size;
}
@Override
public boolean isConnected(int p, int q) {
return find(p)==find(q);
}
@Override
public void union(int p,int q) {
int pNum=find(p);
int qNum=find(q);
if(pNum==qNum){
return;
}
/*
* 遍历;把qNum集合的值,全部变为pNum集合的值
* 即把两个集合,融合为两个
* */
for (int i = 0; i < getSize(); i++) {
if(id[i]==qNum){
id[i]=pNum;
}
}
}
}
方法二;
public class UnionFindSecond implements UF{
/*
* 将每个元素看做结点,节点之间相连接,形成树的结构
*eg:将结点1和结点3合并,将3和1的根节点相连接
* 1-->2 2-->2 3-->2
* */
private int size;
private int[] parents;
public UnionFindSecond(int size) {
this.size = size;
parents=new int[size];
for (int i = 0; i < size; i++) {
parents[i]=i;
}
}
//查找索引为index的结点所在的根节点
public int find(int index){
while(index!=parents[index]){
index=parents[index];
}
return index;
}
@Override
public int getSize() {
return this.size;
}
@Override
public boolean isConnected(int p, int q) {
return find(p)==find(q);
}
/*将p所在的根节点指向q所在的根节点*/
@Override
public void union(int p, int q) {
int pRoot=find(p);
int qRoot=find(q);
if(pRoot==qRoot){
return;
}
parents[pRoot]=qRoot;
}
}
方法三:
public class UnionFindThird implements UF {
/*基于size进行优化,将结点少的挂在结点多的上边;
*只修改union方法
*/
int size;
int[] parents;
int[] nums;
public UnionFindThird(int size) {
this.size = size;
parents = new int[size];
nums = new int[size];
for (int i = 0; i < size; i++) {
parents[i] = i;
nums[i] = 1;
}
}
//查找索引为index的结点所在的根节点
public int find(int index) {
while (index != parents[index]) {
index = parents[index];
}
return index;
}
@Override
public int getSize() {
return this.size;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
/*将p所在的根节点指向q所在的根节点*/
@Override
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
/*判断节点的个数*/
if (pRoot != qRoot) {
if (nums[pRoot] > nums[qRoot]) {
parents[qRoot] = pRoot;
nums[pRoot] += nums[qRoot];
} else {
parents[pRoot] = qRoot;
nums[qRoot] += nums[pRoot];
}
}
}
}
方法四:
public class UnionFindFourth implements UF {
/*基于heigth进行优化,将高度低的树合并在高度高的树上;
*只修改union方法
*/
int size;
int[] parents;
int[] height;
public UnionFindFourth(int size) {
this.size = size;
parents = new int[size];
height = new int[size];
for (int i = 0; i < size; i++) {
parents[i] = i;
height[i] = 1;
}
}
//查找索引为index的结点所在的根节点
public int find(int index) {
while (index != parents[index]) {
index = parents[index];
}
return index;
}
@Override
public int getSize() {
return this.size;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
/*将p所在的根节点指向q所在的根节点*/
@Override
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
/*判断节点的个数*/
if (pRoot != qRoot) {
if (height[pRoot] > height[qRoot]) {
parents[qRoot] = pRoot;
} else if(height[pRoot]<height[qRoot]){
parents[pRoot] = qRoot;
}else{
parents[pRoot]=qRoot;
height[qRoot]+=1;
}
}
}
}
方法五:
public class UnionFindFifth implements UF {
/*
* 路径压缩
* 方法:
* parents[p]=parents[parents[p]];
* 只需要改变find方法
*
* */
int size;
int[] parents;
public UnionFindFifth(int size) {
this.size = size;
parents = new int[size];
for (int i = 0; i < size; i++) {
parents[i] = i;
}
}
//查找索引为index的结点所在的根节点
public int find(int index) {
while (index != parents[index]) {
parents[index]=parents[parents[index]];
index = parents[index];
}
return index;
}
@Override
public int getSize() {
return this.size;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
/*将p所在的根节点指向q所在的根节点*/
@Override
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
/*判断节点的个数*/
if (pRoot != qRoot) {
parents[qRoot] = pRoot;
}
}
}
测试:
import java.util.Random;
public class UFTest {
/**
* @param uf 测试并查集
* @param count 测试合并和是否连接的次数
*/
public static void test(UF uf, int count) {
long startTime = System.nanoTime();
Random random = new Random();
// 合并操作
for (int i = 0; i < count; i++) {
uf.union(random.nextInt(uf.getSize()), random.nextInt(uf.getSize()));
}
// 判断是否连接
for (int i = 0; i < count; i++) {
uf.isConnected(random.nextInt(uf.getSize()), random.nextInt(uf.getSize()));
}
long endTime = System.nanoTime();
System.out.println("共用了" + (endTime - startTime) / 1000000000.0 + "s");
}
public static void main(String[] args) {
int size = 10000;
int count = 100000;
UF uf1 = new UnionFindFirst(size);
System.out.println("方法一:");
test(uf1, count);
UF uf2 = new UnionFindSecond(size);
System.out.println("方法二:");
test(uf2, count);
UF uf3 = new UnionFindThird(size);
System.out.println("方法二基于size的优化:");
test(uf3, count);
UF uf4 = new UnionFindFourth(size);
System.out.println("方法二基于height的优化:");
test(uf4, count);
UF uf5 = new UnionFindFifth(size);
System.out.println("方法二基于压缩路径的优化:");
test(uf5, count);
}
}
测试结果:
方法一:
共用了0.0900325s
方法二:
共用了1.0276614s
方法三基于size的优化:
共用了0.0335062s
方法四基于height的优化:
共用了0.0159129s
方法五基于压缩路径的优化:
共用了0.0136944s