并查集的实现

题目描述

给定一个没有重复值的整形数组arr,初始时认为arr中每一个数各自都是一个单独的集合。请设计一种叫UnionFind的结构,并提供以下两个操作。

  1. boolean isSameSet(int a, int b): 查询a和b这两个数是否属于一个集合
  2. void union(int a, int b): 把a所在的集合与b所在的集合合并在一起,原本两个集合各自的元素以后都算作同一个集合

[要求]

如果调用isSameSet和union的总次数逼近或超过O(N),请做到单次调用isSameSet或union方法的平均时间复杂度为O(1)

初始化

按照树形结构给每个数设置自己的父亲 可以用map或者数组来表示

比如{1, 2}, {3}, {4}, {5}, {6} => [1, 1, 3, 4, 5, 6],数组的下标表示数 值表示对应的父节点

如果一个数没有父节点,特别地设置该数的父节点为自身

数组用parent[]来表示

查找

要查找一个数所属的集合(通过祖先来表示) 可以递归向上找到祖先结点(父节点等于自身的)

public int find(int x) {
    if (parent[x] != x) {
        return find(parent[x]);
    }
    return x;
}

路径压缩

上面这种方法就是单纯的树结构,但是这种方法在查找的时候需要逐层向上查询,但是实际上想知道数x的祖先是多少并不需要知道x的父亲是多少,类似给叶子节点加上一个指向根节点的指针的思想,直接将x的父亲设置为x的祖先

public int find(x) {
    if (parent[x] != x) {
        // 构建的同时做了压缩
        parent[x] = find(parent[x]);
    }
    return parent[x];
}

合并

合并两个数所在的集合不需要考虑合并的方法,只要合并后两个数能够找到同一个祖先即可,再根据路径压缩的思想 直接把其中一个的祖先设置为另一个祖先的儿子

public void union(int x, int y) {
    int f1 = find(x);
    int f2 = find(y);
    // 在同一个集合内 不需要合并
    if (f1 == f2) {
        return;
    }
    parent[f1] = f2;
}

启发式合并

上面的合并是两者间随意合并,现有树t1和t2有以下两种情况,合并后的树为t3

  1. t1合并到t2
  2. t2合并到t1

假设t1的高度比t2的高度高

那么情况1相比于情况2合并后的树t3的高度会增加,也就会增大下次查找的路径长度

因此可以根据树的高度 选择较小的合并到较大的树上

因此在初始化的时候引入新的数组rank[]用来表示每棵树的高度,这种方法也称按秩合并

public void union(int x, int y) {
    int f1 = find(x);
    int f2 = find(y);
    if (f1 == f2) {
        return;
    }
    int r1 = rank[f1];
    int r2 = rank[f2];
    if (r1 > r2) {
        parent[f2] = f1;
    } else if (r1 == r2) {
       parent[f2] = f1;
       rank[f1]++; 
    } else {
        parent[f1] = f2;
    }
}

也可以换一个标准,按照树的结点数作为评估标准,此时rank[]表示每棵树的结点个数

public void union(int x, int y) {
    int f1 = find(x);
    int f2 = find(y);
    if (f1 == f2) {
        return;
    }
    int r1 = rank[f1];
    int r2 = rank[f2];
    if (r1 > r2) {
        parent[r2] = r1;
        rank[f1] += r2;
    } else {
        parent[r1] = r2;
        rank[f2] += r1;
    }
}

时间复杂度

使用路径压缩和启发式合并之后,并查集的每个操作平均时间为 O ( α ( n ) ) O(\alpha(n)) O(α(n)),只说下结论可以看成常数时间

空间复杂度

两个数组,复杂度 O ( n ) O(n) O(n)

参考资料

并查集 - OI Wiki

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值