6:动态连通性 (并查集)

1: 动态连通性

   可以检测所给的点中 是否有环的:

 

 

概念:

  • 并查集:(union-find sets)

一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。最完美的应用当属:实现Kruskar算法求最小生成树。

  • 并查集的精髓(即它的三种操作,结合实现代码模板进行理解):

1、Make_Set(x) 把每一个元素初始化为一个集合

初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。

2、Find_Set(x) 查找一个元素所在的集合

查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
合并两个集合,也是使一个集合的祖先成为另一个集合的祖先,具体见示意图

3、Union(x,y) 合并x,y所在的两个集合

合并两个不相交集合操作很简单:
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。如图

 

  • 并查集的优化

1、Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。

2、Union(x,y)时 按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。

 

 

 

 
package com.algorithm.common.tree;

import com.algorithm.lib.StdOut;

/**
 * 动态连通性检查 (并查集算法)
 * @author lijunqing
 */
public class UF {

    /**
     * i节点的父节点(集合)
     */
    private int[] id; // id[i] = parent of i

    /**
     * i集合中子节点数量
     */
    private int[] sz; // sz[i] = number of objects in subtree rooted at i

    /**
     * 集合总数
     */
    private int count; // number of components

    /**
     * 初始化N个集合
     */
    public UF(int N) { // n表示所有数值就是count
        if(N < 0)
            throw new IllegalArgumentException();
        count=N;
        id=new int[N];
        sz=new int[N];
        for(int i=0; i < N; i++) {
            id[i]=i;
            sz[i]=1;
        }
    }

    /**
     * 查找一个元素所在的集合(其精髓是找到这个元素所在集合的祖先) 就是找到改元素所在集合的祖先
     */
    public int find(int p) {
        if(p < 0 || p >= id.length)
            throw new IndexOutOfBoundsException();
        while(p != id[p])  相等表示找到祖先
            p=id[p];
        return p;
    }

    /**
     * 返回集合总数
     */
    public int count() {
        return count;
    }

    /**
     * 判断pq是否在同一集合中
     */
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    /**
     * 合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小
     */
    public void union(int p, int q) {
        int i=find(p);
        int j=find(q);
        if(i == j)
            return;

        // make smaller root point to larger one
        if(sz[i] < sz[j]) { // 元素少的集合合并到元素多的集合中
            id[i]=j; // i的父节点是j
            sz[j]+=sz[i];
        } else {
            id[j]=i; // j的父节点是i
            sz[i]+=sz[j]; // 改变父节点的值(值越大表示其子节点越多)
        }
        count--;
    }

    public static void main(String[] args) {
        int N=10;
        UF uf=new UF(N);
        uf.union(1, 2);
        uf.union(3, 4);
        uf.union(6, 7);
        uf.union(2, 7);

        int a=uf.find(6);
        System.out.println(a);

        StdOut.println(uf.count() + " components");
    }

}
 
其中id保存:
  
 0 1 1 3 3 5 1 6 8 9
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值