【数据结构】 并查集 + 路径压缩与按秩合并 python

目录


前言

并查集的基本实现通常使用森林来表示不同的集合,每个集合用一棵树表示,树的每个节点有一个指向其父节点的指针。

如果一个节点是它自己的父节点,那么它就是该集合的代表(称为根节点)。


模板

P3367 【模板】并查集 https://www.luogu.com.cn/problem/P3367

题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式

第一行包含两个整数 N , M N,M N,M ,表示共有 N N N 个元素和 M M M 个操作。

接下来 M M M 行,每行包含三个整数 Z i , X i , Y i Z_i,X_i,Y_i Zi,Xi,Yi 。

当 Z i = 1 Z_i=1 Zi=1 时,将 X i X_i Xi 与 Y i Y_i Yi 所在的集合合并。

当 Z i = 2 Z_i=2 Zi=2 时,输出 X i X_i Xi 与 Y i Y_i Yi 是否在同一集合内,是的输出
Y ;否则输出 N

输出格式

对于每一个 Z i = 2 Z_i=2 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

**样例输入 **

复制代码
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

样例输出

复制代码
N
Y
N
Y

提示

对于 15 % 15\% 15% 的数据, N ≤ 10 N \le 10 N≤10, M ≤ 20 M \le 20 M≤20。

对于 35 % 35\% 35% 的数据, N ≤ 100 N \le 100 N≤100, M ≤ 1 0 3 M \le 10^3 M≤103。

对于 50 % 50\% 50% 的数据, 1 ≤ N ≤ 1 0 4 1\le N \le 10^4 1≤N≤104, 1 ≤ M ≤ 2 × 1 0 5 1\le M \le 2\times 10^5 1≤M≤2×105。

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1\le N\le 2\times 10^5 1≤N≤2×105, 1 ≤ M ≤ 1 0 6 1\le M\le 10^6 1≤M≤106, 1 ≤ X i , Y i ≤ N 1 \le X_i, Y_i \le N 1≤Xi,Yi≤N, Z i ∈ { 1 , 2 } Z_i \in \{ 1, 2 \} Zi∈{1,2}。


朴素实现

code:

python 复制代码
# 在集合中查找元素的根节点
def find(x):
    if x != pre[x]:
        return find(pre[x])
    return x


# 将两个集合合并为一个集合
def union(x, y, pre):
    x_root = find(x)
    y_root = find(y)
    pre[x_root] = y_root


n, m = map(int, input().split())
pre = [0] * (n + 1)
for i in range(n):
    pre[i] = i  # 初始化
for _ in range(m):
    op, x, y = map(int, input().split())
    if op == 1:
        union(x, y, pre)
    else:
        if find(x) == find(y):
            print('Y')
        else:
            print('N')

事实证明:我们需要进行时间上的优化


路径压缩

由于在查询过程中只关心根结点是什么,所以我们可以在在集合在查找元素的同时,把集合中所有的元素都直接指向根节点,减少查找的时间

示例code

python 复制代码
def find(x):
    if pre[x] != x:
        pre[x] = find(pre[x])  # 在回溯时进行路径压缩
    return pre[x]

tips:可能会破坏原本的结构


按秩合并

之前我们在合并时,是随机合并两个集合

虽然都能得到正确的结果,但存在时间复杂度的差异

怎样降低时间复杂度呢?

通过按秩合并(启发式合并)

"秩"可以理解为树的高度或树的节点数 这两种方式
在合并两棵树时,总是把较矮的树挂到较高的树上,节点较小的树挂在节点较多的树上
这种策略有助于保持树的平衡,从而降低查找操作的时间复杂度。

怎么实现?用一个数组记录每个集合的高度或节点数


按树高为秩

示例:

python 复制代码
# 将两个集合合并为一个集合
def union(x, y):
    x_root = find(x)
    y_root = find(y)
    if x_root != y_root:
        # 谁高,谁就作为根节点
        if rank[x_root] > rank[y_root]:
            pre[y_root] = x_root
        elif rank[x_root] < rank[y_root]:
            pre[x_root] = y_root
        else:
            pre[x_root] = y_root
            rank[y_root] += 1
# 合并是把小的树直接接到根节点上,所以只有两颗树的高度相等的时候合并后高度才会增加

按节点数为秩

示例:

python 复制代码
# 将两个集合合并为一个集合
def union(x, y):
    x_root = find(x)
    y_root = find(y)
    if x_root != y_root:
        # 谁的节点数多,谁就作为根节点
        if size[x_root] > size[y_root]:
            pre[y_root] = x_root
            size[x_root] += size[y_root]
        else:
            pre[x_root] = y_root
            size[y_root] += size[x_root]

题解code1(路径压缩+按节点数为秩合并):

python 复制代码
# 在集合中查找元素的根节点
def find(x):
    global pre
    if pre[x] != x:
        pre[x] = find(pre[x])  # 在回溯时进行路径压缩
    return pre[x]


# 将两个集合合并为一个集合
def union(x, y):
    global pre, size
    x_root = find(x)
    y_root = find(y)
    if x_root != y_root:
        # 谁的节点数多,谁就作为根节点
        if size[x_root] > size[y_root]:
            pre[y_root] = x_root
            size[x_root] += size[y_root]
        else:
            pre[x_root] = y_root
            size[y_root] += size[x_root]


n, m = map(int, input().split())
pre = list(range(n + 1))  # 初始化pre数组
size = [1] * (n + 1)  # 初始化size数组
for _ in range(m):
    op, x, y = map(int, input().split())
    if op == 1:
        union(x, y)
    else:
        if find(x) == find(y):
            print('Y')
        else:
            print('N')

路径压缩与按节点大小合并完全兼容


题解code2(按树高为秩合并):

python 复制代码
# 在集合中查找元素的根节点
def find(x):
    global pre
    if pre[x] != x:
        pre[x] = find(pre[x])  # 在回溯时进行路径压缩
    return pre[x]

# 将两个集合合并为一个集合
def union(x, y):
    global pre, rank
    x_root = find(x)
    y_root = find(y)
    if x_root != y_root:
        # 谁高,谁就作为根节点
        if rank[x_root] > rank[y_root]:
            pre[y_root] = x_root
        elif rank[x_root] < rank[y_root]:
            pre[x_root] = y_root
        else:
            pre[x_root] = y_root
            rank[y_root] += 1
# 合并是把小的树直接接到根节点上,所以只有两颗树的高度相等的时候合并后高度才会增加


n, m = map(int, input().split())
pre = list(range(n + 1))  # 初始化pre数组
rank = [1] * (n + 1)  # 初始化rank数组
for _ in range(m):
    op, x, y = map(int, input().split())
    if op == 1:
        union(x, y)
    else:
        if find(x) == find(y):
            print('Y')
        else:
            print('N')

路径压缩不完全与按树高合并兼容,因为路径压缩可以改变树的高度。


总结

并查集(Union-Find 或 Disjoint Set Union, DSU)是一种数据结构,主要用于处理一些不相交集合的合并及查询问题。


如果有更多问题或需要进一步的帮助,可以在评论区留言讨论哦!
如果喜欢的话,请给博主点个关注 谢谢

相关推荐
卷福同学32 分钟前
不用服务器,不用配环境,我10分钟上线了一个AI Agent
人工智能·后端·算法
问君能有几多愁~3 小时前
C++ 数据结构复习笔记
数据结构·c++·笔记
至乐活着3 小时前
深入解析跳表SkipList:原理、实现与性能优化实战
数据结构·算法·跳表·skiplist·java实现
Jerry4 小时前
LeetCode 383. 赎金信
算法
聪明的一休丶4 小时前
VLLM v0.24.0 版本深度解析:新引擎、新架构与大规模服务全家桶升级
python·架构·vllm
ai产品老杨4 小时前
H264 H265视频分析常见问题和排查清单
人工智能·算法·音视频
Jerry4 小时前
LeetCode 454. 四数相加 II
算法
可编程芯片开发5 小时前
基于CPS-SPWM链式STATCOM系统在电压不平衡环境下控制策略的simulink建模与仿真
算法
万亿少女的梦1685 小时前
基于Python的高考志愿填报辅助系统设计与实现
java·spring boot·python·mysql·vue
Jerry5 小时前
LeetCode 202. 快乐数
算法