第三十一篇 玩转数据结构——并查集(Union Find)
Posted xuezou
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第三十一篇 玩转数据结构——并查集(Union Find)相关的知识,希望对你有一定的参考价值。
1.. 并查集的应用场景
-
查看"网络"中节点的连接状态,这里的网络是广义上的网络
-
数学中的集合类的实现
2.. 并查集所支持的操作
-
对于一组数据,并查集主要支持两种操作:合并两个数据、判断两个数据是否属于同一集合(两个数据是否连接)
3.. 定义并查集的接口
- 并查集的接口业务逻辑如下:
-
public interface UF { int getSize(); boolean isConnected(int p, int q); void unionElements(int p, int q); }
4.. 实现并查集
-
第一版并查集Quick Find,业务逻辑如下:
-
public class UnionFind1 implements UF { private int[] id; public UnionFind1(int size) { id = new int[size]; for (int i = 0; i < id.length; i++) { id[i] = i; } } // 实现getSize方法 @Override public int getSize() { return id.length; } private int find(int p) { if (p < 0 || p >= id.length) { throw new IllegalArgumentException("p is out of bound."); } return id[p]; } // 实现isConnected方法,查看元素p与元素q是否所属同一个集合 @Override public boolean isConnected(int p, int q) { return id[p] == id[q]; } // 实现unionElements方法,合并元素p和元素q所属集合 @Override 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; } } } }
- Quick FInd的时间复杂度分析
-
- 第二版并查集Quick Union,业务逻辑如下:
-
public class UnionFind2 implements UF { private int[] parent; public UnionFind2(int size) { parent = new int[size]; for (int i = 0; i < size; i++) { parent[i] = i; } } @Override public int getSize() { return parent.length; } private int find(int p) { if (p < 0 || p >= parent.length) { throw new IllegalArgumentException("p is out of bound."); } while (p != parent[p]) { p = parent[p]; } return p; } // 实现isConnected方法,判断元素p与元素q是否同属一个集合 @Override public boolean isConnected(int p, int q) { return parent[p] == parent[q]; } // 实现unionElements方法,合并元素p和元素q所在集合 @Override public void unionElements(int p, int q) { int pRoot = find(p); int qRoot = find(q); if (pRoot == qRoot) { return; } parent[pRoot] = qRoot; } }
- Quick Union的时间复杂度分析
-
isConnected O(h) 其中,h为树的高度 unionElements O(h)
- 测试Quick Find和Quick Union的性能
- 测试的业务逻辑如下:
-
import java.util.Random; public class Main { private static double testUF(UF uf, int m) { int size = uf.getSize(); Random random = new Random(); long startTime = System.nanoTime(); for (int i = 0; i < m; i++) { int a = random.nextInt(size); int b = random.nextInt(size); uf.unionElements(a, b); } for (int i = 0; i < m; i++) { int a = random.nextInt(size); int b = random.nextInt(size); uf.isConnected(a, b); } long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0; } public static void main(String[] args) { int size = 100000; int m = 10000; UnionFind1 uf1 = new UnionFind1(size); double time1 = testUF(uf1, m); System.out.println("Quick Find, time: " + time1 + " s"); UnionFind2 uf2 = new UnionFind2(size); double time2 = testUF(uf2, m); System.out.println("Quick Union, time: " + time2 + " s"); } }
- 输出结果:
-
Quick Find, time: 0.272248873 s Quick Union, time: 0.001273318 s
5.. Quick Union的优化
- 对unionElements方法进行优化,使元素少的节点指向元素多的节点
- 优化后的业务逻辑如下:
-
public class UnionFind3 implements UF { private int[] parent; private int[] sz; public UnionFind3(int size) { parent = new int[size]; sz = new int[size]; for (int i = 0; i < size; i++) { parent[i] = i; sz[i] = 1; } } @Override public int getSize() { return parent.length; } private int find(int p) { if (p < 0 || p >= parent.length) { throw new IllegalArgumentException("p is out of bound."); } while (p != parent[p]) { p = parent[p]; } return p; } // 实现isConnected方法,判断元素p与元素q是否同属一个集合 @Override public boolean isConnected(int p, int q) { return parent[p] == parent[q]; } // 实现unionElements方法,合并元素p和元素q所在集合 @Override 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]; } } }
- 对unionElements方法进行优化,使深度浅节点指向深度更深的节点
- 优化后的业务逻辑如下:
-
public class UnionFind4 implements UF { private int[] parent; private int[] rank; public UnionFind4(int size) { parent = new int[size]; rank = new int[size]; for (int i = 0; i < size; i++) { parent[i] = i; rank[i] = 1; } } @Override public int getSize() { return parent.length; } private int find(int p) { if (p < 0 || p >= parent.length) { throw new IllegalArgumentException("p is out of bound."); } while (p != parent[p]) { p = parent[p]; } return p; } // 实现isConnected方法,判断元素p与元素q是否同属一个集合 @Override public boolean isConnected(int p, int q) { return parent[p] == parent[q]; } // 实现unionElements方法,合并元素p和元素q所在集合 @Override 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 { parent[pRoot] = qRoot; rank[qRoot] += 1; } } }
- 对find方法进行优化,实现简单路径压缩(非递归实现)
- 优化后业务逻辑如下
-
public class UnionFind5 implements UF { private int[] parent; private int[] rank; public UnionFind5(int size) { parent = new int[size]; rank = new int[size]; for (int i = 0; i < size; i++) { parent[i] = i; rank[i] = 1; } } @Override public int getSize() { return parent.length; } 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; } // 实现isConnected方法,判断元素p与元素q是否同属一个集合 @Override public boolean isConnected(int p, int q) { return parent[p] == parent[q]; } // 实现unionElements方法,合并元素p和元素q所在集合 @Override 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 { parent[pRoot] = qRoot; rank[qRoot] += 1; } } }
- 再次优化find方法,实现终极路径压缩(递归实现)
-
public class UnionFind6 implements UF { private int[] parent; private int[] rank; public UnionFind6(int size) { parent = new int[size]; rank = new int[size]; for (int i = 0; i < size; i++) { parent[i] = i; rank[i] = 1; } } @Override public int getSize() { return parent.length; } private int find(int p) { if (p < 0 || p >= parent.length) { throw new IllegalArgumentException("p is out of bound."); } if (p != parent[p]) { parent[p] = find(parent[p]); // 优化了这里 } return parent[p]; } // 实现isConnected方法,判断元素p与元素q是否同属一个集合 @Override public boolean isConnected(int p, int q) { return parent[p] == parent[q]; } // 实现unionElements方法,合并元素p和元素q所在集合 @Override 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 { parent[pRoot] = qRoot; rank[qRoot] += 1; } } }
以上是关于第三十一篇 玩转数据结构——并查集(Union Find)的主要内容,如果未能解决你的问题,请参考以下文章