算法:并查集

概念:并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题(百度百科)

博主理解:
1.并查集可以用来快速查询某一元素在哪个集合中
2.并查集可以快速将两个不在同一个集合的元素,将它们两个所在的集合合并为一个集合

基本介绍:
一般并查集使用数组进行实现
ids = [0, 1, 2, 3, 4, 5, 6, 7]
ids 为一维数组,这个一维数组和其他的数组不一样,
数组下标:用来表示节点编号
数组元素:用来表示父节点编号

起初所有的节点都是一个个独立的集合,每个节点都是集合的根节点
集合的根节点:数组下标=数组元素(节点编号=父节点编号)

1. 查询:查询某一项元素在哪个集合中(实际是找到元素对应节点所在哪个集合的根节点)

    ids  = [0, 1, 4, 6, 4, 5, 2, 7]
下标:= [0, 1, 2, 3, 4, 5, 6, 7]
上图表示其中某一个集合,集合的根为 4 号节点,
现在想要查询 3 号节点在哪个集合中,需要找到不断地向上查找,找到最后的根 4 号节点

如何完成向上查找,找到根节点的操作呢?
1. ids[3] = 6
2. ids[6] = 2
3. ids[2] = 4
4. ids[4] = 4(找到根节点)

路径压缩:

    ids  = [0, 1, 4, 6, 4, 5, 2, 7]  变为  ids   = [0, 1, 4, 2, 4, 5, 2, 7]
下标:= [0, 1, 2, 3, 4, 5, 6, 7]                   = [0, 1, 2, 3, 4, 5, 6, 7]
在 3 号节点查找根节点的时候,对路径进行压缩,压缩为上图的样子

那么是如何进行路径压缩的呢?
1. ids[3] = 6   ids[3] = ids[ids[3]] = 2
2. ids[2] = 4   ids[2] = ids[ids[2]] = 4
4. ids[4] = 4(找到根节点)

2.合并:并查集可以快速将两个不相交集合合并为一个集合(实际上是将找到两个集合的根节点,然后两个根节点合并为一个根节点)

把 2 号节点所在集合与 1 号节点所在集合进行合并
步骤:
1.查询 2 号节点的根节点,得到 4 号节点
2.查询 1 号节点的根节点,得到 5 号节点
3.然后根据两颗子树的大小,将小的子树的根节点作为大的子树根节点下的子节点,如果一样大小就随便了(完成合并)

3.代码实现:博主使用的是 Java 语言
 

public class DisjointSet {
    /**
     * count 表示集合总个数
     */
    int count;
    /**
     * int[] size 表示根节点代表集合内部有多少个节点
     */
    int[] size;
    int[] ids;

    /**
     * 初始化并查集,传入节点个数
     *
     * @param nodeCount 节点个数
     */
    public DisjointSet(int nodeCount) {
        this.ids = new int[nodeCount];
        this.size = new int[nodeCount];
        // 初始时,集合总个数等于节点总个数
        this.count = nodeCount;
        // 每个节点都是各自集合的根节点,所以每个集合内部节点个数为 1
        for (int i = 0; i < nodeCount; i++) {
            this.ids[i] = i;
            this.size[i] = 1;
        }
    }

    /**
     * 根据节点编号查找所在集合的根节点编号
     *
     * @param node 节点编号
     * @return 所在集合的根节点编号
     */
    int find(int node) {
        while (node != ids[node]) {
            // 路径压缩操作
            ids[node] = ids[ids[node]];
            node = ids[node];
        }
        return node;
    }

    /**
     * 将节点编号 node1 所在的集合与节点编号 node2 所在的集合进行合并
     *
     * @param node1 节点编号 1
     * @param node2 节点编号 2
     */
    void union(int node1, int node2) {
        int root1 = find(node1);
        int root2 = find(node2);
        // 如果两个节点在同一集合,则无需合并
        if (root1 == root2) {
            return;
        }
        // 两个集合根节点合并,小的集合根节点作为大的集合根节点的子节点
        if (size[root1] < size[root2]) {
            ids[root1] = ids[root2];
            size[root2] += size[root1];
        } else {
            ids[root2] = ids[root1];
            size[root1] += size[root2];
        }
        // 每合并一个集合,总集合个数减一
        count--;
    }
}

4.代码测试:

 public static void main(String[] args) {
        DisjointSet disjointSet = new DisjointSet(8);
        disjointSet.union(0, 1);
        disjointSet.union(2, 3);
        disjointSet.union(4, 5);
        disjointSet.union(1, 4);
        System.out.println("集合个数:" + disjointSet.count);
        System.out.println("0 号节点和 4 号节点是否在同一集合:" + (disjointSet.find(0) == disjointSet.find(4)));
        System.out.println("1 号节点和 2 号节点是否在同一集合:" + (disjointSet.find(1) == disjointSet.find(2)));
    }

5.实战演练

拿上述的知识点去解决一个实际问题,例如:LeetCode 547 题,朋友圈问题
问题描述:

 

6.总结:

算法这个东西太玄妙,得好好学习,我也是刚入门, 希望可以跟大家共同进步!
如果本篇博客有什么不对的地方,请在下方评论区留言,我看到后会更正,
如果有什么不懂的地方,也可以在下方留言,我看到后会一一进行回复。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值