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


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

相关推荐
头发还没掉光光1 小时前
C++STL之list
c语言·数据结构·c++·list
Dxy12393102161 小时前
python如何通过链接下载保存视频
python·spring·音视频
Terio_my2 小时前
Java bean 数据校验
java·开发语言·python
无咎.lsy3 小时前
裸K初级篇 - (一)蜡烛突破信号
python
virtual_k1smet4 小时前
#等价于e * d ≡ 1 mod φ(n) #模逆元详解
人工智能·算法·机器学习
可触的未来,发芽的智生5 小时前
新奇特:神经网络的集团作战思维,权重共享层的智慧
人工智能·python·神经网络·算法·架构
_屈臣_5 小时前
卡特兰数【模板】(四个公式模板)
c++·算法
jerryinwuhan5 小时前
Python数据挖掘之基础分类模型_支持向量机(SVM)
python·支持向量机·数据挖掘
StarPrayers.5 小时前
基于PyTorch的CIFAR10加载与TensorBoard可视化实践
人工智能·pytorch·python·深度学习·机器学习
坚持编程的菜鸟5 小时前
LeetCode每日一题——交替合并字符串
c语言·算法·leetcode