并查集UnionFind

并查集是一个支持集合快速合并的结构

对外提供两个方法:

(1)合并操作  将两个结点所在的集合合并   void union(a,b)

(2)查询两个结点是否属于同一个集合 bool find(a,b),或者说查询某个结点是否属于某个集合

如果一个结点a连接到了结点b,我们就说a的父结点就是b

我们用一个数组记录每个结点的父节点

findunion操作的时间复杂度为O(logn),n为节点总数。

public class UnionFind
{
    private int count; // 连通分量的个数
    private int[] parent;  // 记录每个节点的父节点,父节点为自身的是根节点
    private int[] size; // 记录每个连通分量的大小

    public UnionFind(int n) 
   {
        this.count = n;
        this.parent = new int[n];
        this.size = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            size[i] = 1;
        }
    }

    public int count() {
        return this.count;
    }

    // 查询所属连通分量
    public int find(int p) {
        int root = p;
        while (root != parent[root]) {
            root = parent[root];
        }
        // 路径压缩
        while (p != root) {
            int next = parent[p];
            parent[p] = root;
            p = next;
        }
        return root;
    }

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

    // 合并所属的两个连通分量
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) return;
        // 总是将较小的连通分量连接到较大的连通分量上
        if (size[rootP] < size[rootQ]) {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP];
        } else {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
        }
        count--; // 合并两个连通分量后,连通分量数量减1
    }
}

1.将两个集合合并

2.询问两个元素是否在一个集合里面

基本原理:每个集合用一棵树来表示,树根的编号就是整个集合的编号,p[x]表示x的父节点

树根的话p[x]=x

如何求x的集合编号::只要x不是树根,就一直找父节点,直到找到根节点

while(p[x]!=x)
{
  x=p[x];
}

如何合并两个集合:

将一个集合的根节点成为另一个集合根节点的孩子节点   

例如;p[x]是x的集合编号,p[y]是y的集合,p[x]=y,也就是说x的父节点变成y

                

一共有n个数,编号分别是1~n

最开始每个数各自在一个集合中,

 并查集代码最核心的操作就是find函数

public class Main6
{
    public static int find(int x)//返回x所在集合的编号,或者说返回x的祖宗结点
    {
        if(p[x]!=x)
        {
            p[x]=find(p[x]);
        }
        return p[x];
    }



    public static void main(String[] args)
    {
        int[] p = new int[100010];
        int n = 100;


        //p[i]=i,表示这个结点自己本身就是根节点
        for (int i = 1; i <= n; i++) {
            p[i] = i;
        }


        int a = 5, b = 8;


        //将a,b两个集合所在的集合合并,如果本身这两个元素就在一个集合,就不用合并
        p[find(a)] = find(b);
        //find函数是找出一个节点的根节点,这句代码的含义就是让a的根节点成为b根节点的孩子节点

        //查询两个数字是不是在同一个集合
        if(find(a)==find(b)) System.out.println("YES");

    }


}

 也就是说亲戚关系具有传递性

 

  

 最后你要查询两个人是否有亲戚关系,只需查询最后两人的集合号是否相同即可

典型并查集题目:

Leetcode 547  省份数量

 

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
示例1:
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
示例2
输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

将同一省份的城市都加入到同一个集合,最终有多少个省份就会有多少个不相交集合

 这个例子中答案是2,三个元素会形成两个不相交的集合

class Solution
{
    public int findCircleNum(int[][] isConnected)
    {
        int n = isConnected.length;
        UnionFind uf = new UnionFind(n);
        for (int i = 0; i < n; i++) 
        {
            for (int j = i + 1; j < n; j++)
            {
                if (isConnected[i][j] == 1)
                {
                    uf.union(i, j);
                }
            }
        }
        return uf.count();
    }
}

class UnionFind {
    private int count; // 连通分量的个数
    private int[] parent;  // 记录每个节点的父节点,父节点为自身的是根节点
    private int[] size; // 记录每个连通分量的大小

    public UnionFind(int n) {
        this.count = n;
        this.parent = new int[n];
        this.size = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            size[i] = 1;
        }
    }

    public int count() {
        return this.count;
    }

    // 查询所属连通分量
    public int find(int p) {
        int root = p;
        while (root != parent[root]) {
            root = parent[root];
        }
        // 路径压缩
        while (p != root) {
            int next = parent[p];
            parent[p] = root;
            p = next;
        }
        return root;
    }

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

    // 合并所属的两个连通分量
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) return;
        // 总是将较小的连通分量连接到较大的连通分量上
        if (size[rootP] < size[rootQ]) {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP];
        } else {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
        }
        count--;
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值