【数据结构】 并查集 + 路径压缩与按秩合并 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)是一种数据结构,主要用于处理一些不相交集合的合并及查询问题。


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

相关推荐
Future_yzx20 分钟前
算法基础学习——快排与归并(附带java模版)
学习·算法·排序算法
eybk2 小时前
Qpython+Flask监控添加发送语音中文信息功能
后端·python·flask
思逻辑维2 小时前
强大到工业层面的软件
数据结构·sql·sqlite·json
weixin_307779133 小时前
Spark Streaming的背压机制的原理与实现代码及分析
大数据·python·spark
所以遗憾是什么呢?3 小时前
【题解】Codeforces Round 996 C.The Trail D.Scarecrow
数据结构·算法·贪心算法
deephub3 小时前
十大主流联邦学习框架:技术特性、架构分析与对比研究
人工智能·python·深度学习·机器学习·联邦学习
qystca3 小时前
【16届蓝桥杯寒假刷题营】第2期DAY4
数据结构·c++·算法·蓝桥杯·哈希
JNU freshman3 小时前
线段树 算法
算法·蓝桥杯
英国翰思教育4 小时前
留学毕业论文如何利用不同问题设计问卷
人工智能·深度学习·学习·算法·学习方法·论文笔记
西猫雷婶4 小时前
python学opencv|读取图像(四十七)使用cv2.bitwise_not()函数实现图像按位取反运算
开发语言·python·opencv