声明:
主要代码和部分算法说明参考自算法(第四版),这里将代码列出,是想和大家交流一些学习心得。
1.前言
在上一篇文章中,提到了quick-union算法的一个需要改进的地方,就是在union方法中,树的归并是随机的。如果我们规定节点数小的树总是归并到节点数大的树中,这样就会很大程度上减小树的深度,从而提高查找效率。
2.思路
创建一个数组,该数组保存的是各个触点的树的节点数,数组以触点为索引。
3.代码
以下代码在quick-union的算法的基础上进行添加
3.1
在类中新增如下成员变量:
private int[] sz; //存储每个树(连通分量)的节点数,以触点为索引
3.2
在构造方法中添加如下代码:
sz = new int[N];
for(int i=0;i<N;i++) sz[i] = 1; //初始时每个分量树(只含一个触点)的节点数为1
3.3
修改union方法中的代码,修改后变为:
//将p和q的根节点统一
public void union(int p,int q){
int i = find(p); //找到p触点的根
int j = find(q); //找到q触点的根
if(i == j) return; //两根相同结束函数
//树i的节点数若小于j,则并入树j中,树j节点数加i。反之,将j并入i中。
if(sz[i] < sz[j]) { id[i] = j; sz[j] += sz[i]; }
else { id[j] = i; sz[i] += sz[j]; }
count--; //连通分量的数量减1
}
4.测试
原始触点图:
原始sz[]:1 1 1 1 1 1 1 1 1 1
p:4 q:3
步骤:
sz[4]=1 >= sz[3]=1
sz[4]=sz[4]+sz[3]=1+1=2
触点图:
sz[]:1 1 1 1 2 1 1 1 1 1
p:3 q:8
触点图:
sz[]:1 1 1 1 3 1 1 1 1 1
p:6 q:5
触点图:
sz[]:1 1 1 1 3 1 2 1 1 1
p:9 q:4
触点图:
sz[]:1 1 1 1 4 1 2 1 1 1
p:2 q:1
触点图:
sz[]:1 1 2 1 4 1 2 1 1 1
p:8 q:9
触点图:
无变化
sz[]:无变化
p:5 q:0
触点图:
sz[]:1 1 2 1 4 1 3 1 1 1
p:7 q:2
触点图:
sz[]:1 1 3 1 4 1 3 1 1 1
p:6 q:1
触点图:
sz[]:1 1 3 1 4 1 6 1 1 1
p:1 q:0
触点图:
无变化
sz[]:无变化
p:6 q:7
触点图:
无变化
sz[]:无变化
5.完整代码
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
//p145 union-find(加权quick-union)算法
public class WeightedQuickUnionUF{
private int[] id; //存储分量的数组,以触点作为索引
private int[] sz; //存储每个树(连通分量)的节点数,以触点为索引
private int count; //分量数量
//构造方法
public WeightedQuickUnionUF(int N){
count = N; //开始时,有N个分量,每个触点都构成了只含它自己的分量
//初始化分量数组
id = new int[N];
for(int i=0;i<N;i++) id[i] = i; //以自己为父节点
sz = new int[N];
for(int i=0;i<N;i++) sz[i] = 1; //初始时每个分量树(只含一个触点)的节点数为1
}
//将p和q的根节点统一
public void union(int p,int q){
int i = find(p); //找到p触点的根
int j = find(q); //找到q触点的根
if(i == j) return; //两根相同结束函数
//树i的节点数若小于j,则并入树j中,树j节点数加i。反之,将j并入i中。
if(sz[i] < sz[j]) { id[i] = j; sz[j] += sz[i]; }
else { id[j] = i; sz[i] += sz[j]; }
count--; //连通分量的数量减1
}
//找出分量名称(即找到该触点的根)
public int find(int p){
while(p != id[p]) p = id[p];
return p;
}
//判断p和q是否已连接
public boolean connected(int p,int q){
return find(p) == find(q); //依据:判断二者标识量是否相等
}
//连通分量的数量
public int count(){
return count;
}
//测试
public static void main(String[] args) {
int N = StdIn.readInt(); //读取触点数
WeightedQuickUnionUF wqu = new WeightedQuickUnionUF(N);
while(!StdIn.isEmpty()){
int p = StdIn.readInt(); //读取前一个数
int q = StdIn.readInt(); //读取后一个数
if(wqu.connected(p, q)) continue; //如果已经连接,则继续向下读取
wqu.union(p, q); //连接p、q
StdOut.println(p + " " + q); //打印刚刚建立的连接
}
StdOut.println(wqu.count + " components"); //打印连通分量的数量
}
}