《Algorithms》(4th)读书笔记 —— 并查集

算法概述

《Algorithms》(4th)在第一章第五节介绍了并查集算法(使用路径压缩的加权 quick - union算法)。该算法主要用于高效求解动态连通性相关问题:

  • 判断指定两点是否连通。
  • 计算连通分量个数。

适用条件

  • 点集固定。
  • 只允许动态添加连通关系,不可动态删除连通关系。

API

public class UF 功能
UF(int N) 初始化 N 个点,序号为 0 ~ N - 1
void union(int p, int q) 在点 p 与点 q 之间添加连通关系
int find(int p) 查询点 p 所在连通分量的标识符
boolean connected(int p, int q) 判断点 p 与点 q 是否连通
int count() 查询连通分量的数量

实现代码(Java)

public class UF {
    private int[] id;
    private int[] sz;
    private int count;

    public UF(int N) {
        count = N;
        id = new int[N];
        for (int i = 0; i < N; ++i)
            id[i] = i;
        sz = new int[N];
        for (int i = 0; i < N; ++i)
            sz[i] = 1;
    }

    public void union(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot)
            return;
        if (sz[pRoot] < sz[qRoot]) {
            id[pRoot] = qRoot;
            sz[qRoot] = sz[pRoot] + sz[qRoot];
        } else {
            id[qRoot] = pRoot;
            sz[pRoot] = sz[pRoot] + sz[qRoot];
        }
        count = count - 1;
    }

    public int find(int p) {
        int oldP = p, tmp;
        while (p != id[p])
            p = id[p];
        while (oldP != id[oldP]) {
            tmp = id[oldP];
            id[oldP] = p;
            oldP = tmp;
        }
        return p;
    }

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

    public int count() {
        return count;
    }
}

算法详解

算法使用树(有根树)表达连通分量,使用森林表示整个集合。

《Algorithms》(4th)读书笔记 —— 并查集_第1张图片
森林结构

每个点都有对应的 idsz 值。id 为该点在树中的父亲的序号,树根的 id 指向自身。只有树根的 sz 值有意义,代表树的大小,即连通分量中点的数目。count 变量用于记录森林中树的个数。

int count() 函数直接返回 count 变量,即连通分量的个数。

boolean connected(int p, int p) 函数中,先寻找两个点所在树的树根。

  • 如果树根序号一致,表示两者在一棵树(连通分量)中,返回 true
  • 如果树根序号不同,返回 false

void union(int p, int q) 函数中,首先获取两个点对应的树根。

  • 如果两点树根相同,则已在同一连通分量中,为了保证树结构的性质,直接退出,不再次连接两点。
  • 如果不在同一分量中,需要将一棵树设置为另一棵树的子树,合并两个连通分量。为了保证树的平衡性,需要将规模较小的树并入规模较大的树中,更新小树树根的 id,更新合并之后的树根的 sz,更新 count 连通分量个数。
《Algorithms》(4th)读书笔记 —— 并查集_第2张图片
union 操作

int find(int p) 函数中首先备份查询点的序号,然后查询至根节点。接下来将路径上所有的节点的 id 都设置为根节点,压缩树的高度。

性能分析

该算法的所有操作的均摊时间复杂度在反 Ackermann 函数(α(x))范围之内。对于任何实际的有意义的数字,α(x) 小于 5。

你可能感兴趣的:(《Algorithms》(4th)读书笔记 —— 并查集)