union-find并查集算法,是高效的动态连通算法,在判断无向图中的连通分量效率很高;
文章中的算法和图片均来自《算法4》
结合之前《无向图-连通分量》算法,暂目前无向图连通分量的2种算法:
算法1:通过深度优先遍历算法从未标记的顶点开始遍历,遍历完无向图中的所有顶点,得到连通分量;
算法2:使用union-find并查集算法,动态连通算法,得到连通分量;
区别:
算法1需要初始化逐个遍历整个图,效率低,但可以利用图的数据结构;
算法2可以动态得到连通分量,效率高;在只关心连通分量的情况下,union-find并查集算法是非常高效的连通分量算法;
定义union-find并查集抽象类 UnionFind.java
/**
* @Author: ltx
* @Description: union-find并查集 抽象类
*/
public abstract class UnionFind {
public int[] id; //分量id
public int count; //连通分量数量
public UnionFind(int n) {
//初始化连通分量数
count = n;
//初始化分量id
id = new int[n];
for (int i = 0; i < n; i++) {
id[i] = i;
}
}
/**
* 是否连通
*
* @param p 顶点p
* @param q 顶点q
* @return
*/
public boolean connected(int p, int q) {
return find(p) == find(q);
}
/**
* union-find并查集算法-find方法
*
* @param p 顶点p
* @return
*/
public abstract int find(int p);
/**
* union-find并查集算法-union方法
* 将顶点v和w放一个连通分量里面
*
* @param p 顶点v
* @param q 顶点w
*/
public abstract void union(int p, int q);
}
quick-find算法类 QuickFind.java
/**
* @Author: ltx
* @Description: quick-find 算法
*/
public class QuickFind extends UnionFind {
public QuickFind(int n) {
super(n);
}
/**
* 是否连通
*
* @param p 顶点p
* @param q 顶点q
* @return
*/
@Override
public boolean connected(int p, int q) {
return find(p) == find(q);
}
/**
* union-find并查集算法-find方法
*
* @param p 顶点p
* @return
*/
@Override
public int find(int p) {
return id[p];
}
/**
* union-find并查集算法-union方法
* 将顶点v和w放一个连通分量里面
*
* @param p 顶点p
* @param q 顶点q
*/
@Override
public void union(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 (pID == id[i]) {
id[i] = qID;
}
}
count--;
}
}
quick-union算法类 QuickUnion.java
/**
* @Author: ltx
* @Description: quick-union 算法
*/
public class QuickUnion extends UnionFind {
public QuickUnion(int n) {
super(n);
}
/**
* union-find并查集算法-find方法
*
* @param p 顶点p
* @return
*/
@Override
public int find(int p) {
//跟随链接找到根节点
while (p != id[p]) {
p = id[p];
}
return p;
}
/**
* union-find并查集算法-union方法
* 将顶点v和w放一个连通分量里面
*
* @param p 顶点p
* @param q 顶点q
*/
@Override
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
id[pRoot] = qRoot;
count--;
}
}
加权quick-union算法类 WeightedQuickUnion.java
/**
* @Author: ltx
* @Description: 加权 quick-union 算法
*/
public class WeightedQuickUnion extends UnionFind {
public int[] sz; //根节点对应的分量大小
public WeightedQuickUnion(int n) {
super(n);
//根节点对应的分量大小
sz = new int[n];
for (int i = 0; i < n; i++) {
sz[i] = 1;
}
}
/**
* union-find并查集算法-find方法
*
* @param p 顶点p
* @return
*/
@Override
public int find(int p) {
//跟随链接找到根节点
while (p != id[p]) {
//路径压缩(沿途就修改id分量)
// id[p] = id[id[p]];
p = id[p];
}
return p;
}
/**
* union-find并查集算法-union方法
* 将顶点v和w放一个连通分量里面
*
* @param p 顶点p
* @param q 顶点q
*/
@Override
public void union(int p, int q) {
int i = find(p);
int j = find(q);
if (i == j) {
return;
}
//将小树根节点链接到大树根节点
if (sz[i] < sz[j]) {
id[i] = j;
sz[j] += sz[i];
} else {
id[j] = i;
sz[i] += sz[j];
}
count--;
}
}
路径压缩的加权quick-union算法,在加权quick-union算法的find()方法中,在查找root节点过程中沿途设置id分量;
/**
* union-find并查集算法-find方法
*
* @param p 顶点p
* @return
*/
@Override
public int find(int p) {
//跟随链接找到根节点
while (p != id[p]) {
//路径压缩(沿途就修改id分量)
id[p] = id[id[p]];
p = id[p];
}
return p;
}
测试方法:
@Test
void unionFind() {
//quick-find
System.out.println("------quick-find------");
UnionFind un = new QuickFind(10);
// //quick-union
// System.out.println("------quick-union------");
// UnionFind un = new QuickUnion(10);
// //加权 quick-union
// System.out.println("------加权quick-union------");
// UnionFind un = new WeightedQuickUnion(10);
// //压缩路径的加权quick-union
// System.out.println("------压缩路径的加权quick-union------");
// UnionFind un = new WeightedQuickUnion(10);
System.out.println("集合: " + Arrays.toString(un.id));
un.union(4, 3);
un.union(3, 8);
un.union(6, 5);
un.union(9, 4);
un.union(2, 1);
un.union(8, 9);
un.union(5, 0);
un.union(7, 2);
un.union(6, 1);
un.union(1, 0);
un.union(6, 7);
System.out.println("分量: " + Arrays.toString(un.id));
System.out.println("连通分量: " + un.count);
System.out.println("0和5是否连通: " + un.connected(0, 5));
}
测试结果:
------quick-find------
集合: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
分量: [1, 1, 1, 8, 8, 1, 1, 1, 8, 8]
连通分量: 2
0和5是否连通: true
------quick-union------
集合: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
分量: [1, 1, 1, 8, 3, 0, 5, 1, 8, 8]
连通分量: 2
0和5是否连通: true
------加权quick-union------
集合: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
分量: [6, 2, 6, 4, 4, 6, 6, 2, 4, 4]
连通分量: 2
0和5是否连通: true
------压缩路径的加权quick-union------
集合: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
分量: [6, 6, 6, 4, 4, 6, 6, 6, 4, 4]
连通分量: 2
0和5是否连通: true