数据结构与算法复习(01)-并查集

本文首发于我的Github博客

本文是数据结构预算法复习系列的第一篇博文,会介绍写作该系列博文的原因

本文复习了并查集的概念,基础的API,良好的实现(路径压缩与权重),简单的应用和变式

相关代码

写作本篇博文的原因

  1. 生活所迫
    1. 懂的都懂,IT行业从业者(或者是考研保研),跑不了的
  2. 数据结构与算法是大学生活里花费的时间很多的部分,复习一下,也算是对自己的大学生涯有个交代
  3. 三次元的生活真的无聊,找点代码写,嘿嘿(难道不能写工程项目吗?质问!)

并查集的概念与基础API

并查集的概念

假设拥有N个元素1…N,我们需要对其做两种基础的操作:

  1. 宣告某两个元素a, b处在同一个等价类里
  2. 判断某两个元素a, b是否在一个等价类里

用于处理这种问题,提供这两种操作接口的数据结构,可以被称为并查集

并查集基础的API

interface UnionFind {
    void union(int a, int b);
    boolean isConnected(int a, int b);
}

良好的实现

良好的实现往往将一个等价类以的形式进行组织,使用一个数组int[] parentparent[i]记录了元素i的父亲节点

如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tY25IREj-1592185859107)(union-find-tree.png)]

  • isConnected(a, b)接口即判断ab是否在同一棵树内(是否有同样的根)
  • union(a, b)接口即需要将a所属的树归并到b所属的树下,或者将b所属的树归并到a所属的树下
  • 提高执行速度,就是想办法降低树的高度
路径压缩

路径压缩基本上能够完全解决并查集的接口执行速度问题

其基本思想是:

在沿着某条路径查询到a元素的根之后,将这条路径上所有的元素父亲,都直接设为根节点
这样,在以后查询的时候,查找到这条路径上的元素,只需要一次向上查询,就可以找到根节点了

路径压缩实现
public class PathCompressUnionFindImpl implements UnionFind {
    int[] parent;

    public PathCompressUnionFindImpl(int n) {
        parent = new int[n];
        for (int i = 0; i < parent.length; ++i) {
            parent[i] = i;
        }
    }

    private int find(int a) {
        if (parent[a] == a) {
            return a;
        }
        return parent[a] = find(parent[a]);
    }

    @Override
    public void union(int a, int b) {
        int rootA = find(a), rootB = find(b);
        parent[rootA] = rootB;
    }

    @Override
    public boolean isConnected(int a, int b) {
        return find(a) == find(b);
    }
}
加权并查集

虽然路径压缩基本能够解决并查集的执行速度问题,但是还有一个解决方案值得我们思考

加权并查集,其实是基于这样的观察:

在代表一个等价类的树的高度快速增长时,往往是由于将高的树合并成为了矮的树的子树
如果每次合并,都将矮的树合并成为高的树的子树,那么树的高度增长就会减缓

从数学的角度来看,减缓的原因是

每次当树的高度增加1,就意味着树的节点总数至少翻倍了
那么一共有N个节点,树的高度最多也就是lg N

加权并查集实现
public class WeightedUnionFindImpl implements UnionFind {
    int[] parent, size;

    private int find(int a) {
        while (parent[a] != a) {
            a = parent[a];
        }
        return a;
    }

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

    @Override
    public void union(int a, int b) {
        int rootA = find(a), rootB = find(b);
        if (size[rootA] > size[rootB]) {
            parent[rootB] = rootA;
            size[rootA] += size[rootB];
        } else {
            parent[rootA] = rootB;
            size[rootB] += size[rootA];
        }
    }

    @Override
    public boolean isConnected(int a, int b) {
        return find(a) == find(b);
    }
}

并查集的简单应用

贴两道OJ题目,都是简单练手的

洛谷P3367 模板题
洛谷UVA10583 简单变式

并查集的简单变式

等价类的个数

也就是树的根节点的个数,一个节点a是根节点当且仅当parent[a] == a

int nClass() {
    int nClass = 0;
    for (int i = 0; i < parent.length; ++i) {
        if (parent[i] == i) {
            nClass++;
        }
    }
    return nClass;
}
可以维护的等价类性质

比如:

  • 给定元素a,求a所在等价类的元素个数
  • 给定元素a,求a所在等价类的最大值
  • 给定元素a,求a所在等价类的最小值

这类性质只需要新开一个数组,并在union时进行维护就可以了:

void union(int a, int b) {
    //....
    parent[rootA] = rootB;
    size[rootB] += size[rootA];
    maximum[rootB] = Math.max(maximum[rootA], maximum[rootB]);
    minimum[rootB] = Math.max(minimum[rootA], minimum[rootB]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值