算法学习 《算法》1.5 并查集算法(Union-Find)

并查集算法是简单却又非常实用的一种算法。书上的例子可以非常清楚到位的概括这个算法使用的场景,那就是平面上有n个点,给定一条边既可以将两个点联通起来,现在有大量的这样的边,问最后有多少个连通子图?

以下是并查集算法的功能;

  • 从一堆连接任意两个顶点的边信息中创建并查集
  • 可以查询到任意两个顶点是否连通
  • 对于指定边能够将该边的两个顶点连通起来让他们属于同一个子图
  • 能够找到某一个顶点所在子图的根在哪儿
  • 计算一个图中的连通分量有多少个

    算法的api

public class UF
    UF(int n); // 创建包含n个顶点的并查集
    void union(int p, int q); // 连接顶点p和q
    int find(int p); // 查找p所在分量的标识符
    boolean connect(int p, int q); // 判别两个顶点是否连通
    int count(); //返回图中总共的连通分量数目

书中有三种实现方式,Quick-Find,Quick-Union,Union-Find

简单介绍Quick-Find和Quick-Union,重点为Union-Find

Quick-Find:

所有的节点都赋予一个 ID,如果两个节点相连,则将这两个节点的 ID 设成一样的,这样,这两个节点便属于同一个组了。网络中每个组都有了一个唯一的 ID。只要节点 p 和 q 的 ID 相同,则认为节点 p 和 q 相连。我们用数组来放置节点 ID,find()方法可以快速返回 ID,所以我们的第一个算法就叫做 QuickFind。

Quick-Find 算法分析:

QuickFind 算法在最坏情况下,几乎需要遍历整个数组,如果数组很大 、需要连接的节点对很多的时候,QuickFind算法的复杂度就相当大了。此时时间复杂度为O(N^2)

Quick-Union:

使用树结构来避免遍历整个数组的操作。树的所有节点都有一个共同的根节点,每个树只有一个根节点,那每个树就可以代表一个组。find()方法返回的是根节点,connect()方法判断是否联通时,只需判断两个数的根节点是否相同。union(p,q)的时候,只要把p所在的树附加到q所在的树的根节点,这样,p和q就在同一树中了。

Quick-Union算法分析:

Quick-Union方法比起Quick-Find,其不用遍历整个数组,效率提高很多。但是在最坏的情况下(最后变为一整列的形况,即一只有一棵,没有分支),此时复杂度变成2(1+...+N)=(N+1)N,接近N的平方了。


Union-Find:

Union-Find算法在Quick-Union的基础上算上了树大小的权重,在union(p,q)的方法中加入了对两个树大小的判断,让小的树的根链接到大的树的根,来减少合并后树的深度(相比Quick-Union)。又称为优化路径压缩。

代码实现:

public class WeightedQuickUnionUF {
    private int[] id;//父链接数组
    private int[] sz;//各个根节点对应的分量的大小
    private int count;//连通分量的数量

    public WeightedQuickUnionUF(int N) {
        count = N;
        id = new int[N];
        sz = new int[N];
        for (int i = 0; i < N; i++) {
            id[i] = i;//触点的索引和值相等
            sz[i] = 1;//每个分量都为1,合并后加到大的树上
        }
    }

    public int count() {
        return count;
    }

    public boolean connected(int p, int q) {
        return find(q) == find(p);
    }

    public int find(int p) {
        //跟随链接找到根节点
        while (p != id[p]) p = id[p];
        return p;
    }

    public void union(int p, int q) {
        int i = find(p);
        int j = find(q);
        if (i == j) return;
        //将小树的节点链接到大叔的根节点
        if (sz[i] < sz[i]) {
            id[i]=j;
            sz[j]+=sz[i];
        }else {
            id[j]=i;
            sz[i]+=sz[j];
        }
        count--;
    }

    public static void main(String[] args) {
        WeightedQuickUnionUF test = new WeightedQuickUnionUF(100);//大小为n的父链接数组

        int[] testnumber={1,2,3,3,5,6,56,32,23,56};

       for (int i=0;i<testnumber.length;i=i+2){
           System.out.println("this time is"+testnumber[i]+" "+testnumber[i+1]);
           if (test.connected(testnumber[i],testnumber[i+1])){
               System.out.println("他们已经链接了");
               continue;
           }
           test.union(testnumber[i],testnumber[i+1]);
           System.out.println("新生成链接");
       }
        System.out.println("分量有" + test.count + "个");
    }

}

最后输出

原来有100个分量,组成了4个链接,所以最后份量为96,显示正确

参考链接:

https://blog.csdn.net/rebornyp/article/details/79053804

https://www.cnblogs.com/learnbydoing/p/6896472.html?utm_source=itdadao&utm_medium=referral

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值