并查集Union Find
一、并查集解决什么问题?
可以非常高效的解决网络中节点的连接问题
二、操作
1>合并 union(p,q)
2>是否连接 isConnected(p,q)
注意:在使用并查集时,我们并不关心元素内容,因此可以使用数组保存元素,p和q分别对
应元素所在数组的索引
接口:
public interface UF {
// 获取元素的个数
int getSize();
// 合并操作
void union(int p, int q);
// 判断是否连接
boolean isConnection(int p, int q); // p和q代表的是元素所在数组的索引
}
三、并查集的基本数据表示
索引为0和3的元素是否连接?因为它们同属于一个集合,所以是连接的
索引为0和5的元素是否连接?因为它们不属于同一个集合,所以是不连接的
四、代码表示
int[] ids; // 元素所属的集合
public UnionFind01(int length) {
ids = new int[length];
for (int i = 0; i < ids.length; i++) {
ids[i] = i;
}
}
其中:
id数组,代表元素所属的集合
默认情况下,每个元素都是独立的,相当于每个元素所属不同的集合,没有元素相连接。
五、合并操作
合并元素索引为1和4的集合 union(1,4)
所以复杂度为O(n)
六、真正的并查集实现
将每个元素看作是一个结点,结点之间相连接,形成树的结构
结点3与结点2相连接
将结点1与结点3相合并,只要将结点1指向结点3所在那棵树的根结点。
public class UnionFind01 implements UF {
int[] ids; // 元素所属的集合
public UnionFind01(int length) {
ids = new int[length];
for (int i = 0; i < ids.length; i++) {
ids[i] = i;
}
}
// 辅助的方法 (根据元素所在数组的索引找所在集合的编号)
int find(int index) {
return ids[index];
}
@Override
public int getSize() {
return ids.length;
}
@Override
public void union(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
if (pBoxNum != qBoxNum) {
for (int i = 0; i < ids.length; i++) {
if (find(i) == qBoxNum) {
ids[i] = pBoxNum;
}
}
}
}
@Override
public boolean isConnection(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
return pBoxNum == qBoxNum;
}
}
七、数据表示
依次进行union(6,5).union(9,4),union(2,1),union(5,0),union(7,2),union(6,2),最终得到
public class UnionFind02 implements UF {
int[] parents;
public UnionFind02(int length){
parents = new int[length];
for (int i = 0; i < parents.length; i++) {
parents[i] = i;
}
}
private int find(int index){
while (index != parents[index]) {
index = parents[index];
}
return index;
}
@Override
public int getSize() {
return parents.length;
}
@Override
public void union(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
if (pBoxNum != qBoxNum) {
parents[pBoxNum] = qBoxNum;
}
}
@Override
public boolean isConnection(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
return pBoxNum == qBoxNum;
}
}
判断索引为6和4的元素是否连接?
索引为6的元素所在树的根节点为1,索引为4的元素所在树的根节点为8,所以没有连接。
八、测试并查集
public class DemoTest {
public static void test(UF uf, int count) {
long startTime = System.nanoTime();
int size = uf.getSize();
Random random = new Random();
for (int i = 0; i < 1000; i++) {
int p = random.nextInt(size);
int q = random.nextInt(size);
uf.union(p, q);
}
for (int i = 0; i < count; i++) {
int p = random.nextInt(size);
int q = random.nextInt(size);
uf.isConnection(p, q);
}
long endTime = System.nanoTime();
System.out.println("Times:" + (endTime - startTime) / 1000000000.0);
}
public static void main(String[] args) {
int size = 10000000;
int count = 10000000;
UF uf = new UnionFind01(size);
test(uf,count);
UF uf2 = new UnionFind02(size);
test(uf2,count);
/* UF uf3 = new UnionFind03(size);
test(uf3,count);
UF uf4 = new UnionFind04(size);
test(uf4,count);
UF uf5 = new UnionFind05(size);
test(uf5,count);
UF uf6 = new UnionFind06(size);
test(uf6,count);*/
}
}
结果会发现:当数据size = 100000,count = 100000时,使用树结构形式存储连接关系的并查集
比使用数组的形式耗时要长。归其原因:不管是查询操作还是合并操作,使用树结构形式的
并查集的复杂度为O(h),在极端的情况下,这棵树的深度会比较大。
九、基于size的优化
/**
* 基于Size优化
*/
public class UnionFind03 implements UF {
int[] parents;
int[] size; // 来保存每棵树结点的个数
public UnionFind03(int length){
parents = new int[length];
size = new int[length];
for (int i = 0; i < parents.length; i++) {
parents[i] = i;
size[i]= 1;
}
}
private int find(int index){
while (index != parents[index]) {
index = parents[index];
}
return index;
}
@Override
public int getSize() {
return parents.length;
}
@Override
public void union(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
if (pBoxNum != qBoxNum) {
if (size[pBoxNum] > size[qBoxNum]) {
parents[qBoxNum] = pBoxNum;
size[pBoxNum] += size[qBoxNum];
} else {
parents[pBoxNum] = qBoxNum;
size[qBoxNum] += size[pBoxNum];
}
}
}
@Override
public boolean isConnection(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
return pBoxNum == qBoxNum;
}
}
解决的思路:将元素个数少的树合并到元素个数多的树上。需要开辟空间,存放每棵树的元
素个数。 【int[] size】
十、基于rank优化
基于size优化的缺点:
/**
* 基于高度进行优化
*/
public class UnionFind04 implements UF {
int[] parents;
int[] rank; // 基于高度进行优化 ---由低到高
public UnionFind04(int length) {
parents = new int[length];
rank = new int[length];
for (int i = 0; i < parents.length; i++) {
parents[i] = i;
rank[i] = 1;
}
}
private int find(int index) {
while (index != parents[index]) {
index = parents[index];
}
return index;
}
@Override
public int getSize() {
return parents.length;
}
@Override
public void union(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
if (pBoxNum != qBoxNum) {
if (rank[pBoxNum] > rank[qBoxNum]) {
parents[qBoxNum] = pBoxNum;
} else if (rank[qBoxNum] > rank[pBoxNum]) {
parents[pBoxNum] = qBoxNum;
} else {
parents[pBoxNum] = qBoxNum;
rank[qBoxNum] += 1;
}
}
}
@Override
public boolean isConnection(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
return pBoxNum == qBoxNum;
}
}
十一:路径压缩
方法:parent[p]= parent[parent[p]] 路径压缩发生的时机:find操作
在查询时执行,执行
/**
* 进行路径压缩
*/
public class UnionFind05 implements UF {
int[] parents;
public UnionFind05(int length) {
parents = new int[length];
for (int i = 0; i < parents.length; i++) {
parents[i] = i;
}
}
private int find(int index) {
while (index != parents[index]) {
// 路径压缩
parents[index] = parents[parents[index]];
index = parents[index];
}
return index;
}
@Override
public int getSize() {
return parents.length;
}
@Override
public void union(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
if (pBoxNum != qBoxNum) {
parents[qBoxNum] = pBoxNum;
}
}
@Override
public boolean isConnection(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
return pBoxNum == qBoxNum;
}
}
/**
* 进行路径压缩
*/
public class UnionFind06 implements UF {
int[] parents;
public UnionFind06(int length) {
parents = new int[length];
for (int i = 0; i < parents.length; i++) {
parents[i] = i;
}
}
private int find(int index) {
if (index != parents[index]) {
parents[index] = find(parents[index]);
}
return parents[index];
}
@Override
public int getSize() {
return parents.length;
}
@Override
public void union(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
if (pBoxNum != qBoxNum) {
parents[qBoxNum] = pBoxNum;
}
}
@Override
public boolean isConnection(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
return pBoxNum == qBoxNum;
}
}
public int getSize() {
return parents.length;
}
@Override
public void union(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
if (pBoxNum != qBoxNum) {
parents[qBoxNum] = pBoxNum;
}
}
@Override
public boolean isConnection(int p, int q) {
int pBoxNum = find(p);
int qBoxNum = find(q);
return pBoxNum == qBoxNum;
}
}