union-find下(加权quick-union)算法

声明:
主要代码和部分算法说明参考自算法(第四版),这里将代码列出,是想和大家交流一些学习心得。

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"); //打印连通分量的数量
    }

}

猜你喜欢

转载自blog.csdn.net/qq_32293345/article/details/78849564