当前位置: 首页 > article >正文

【数据结构】 并查集 + 路径压缩与按秩合并 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 N10 M ≤ 20 M \le 20 M20

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

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

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


朴素实现


code:

# 在集合中查找元素的根节点
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

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

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



按秩合并


之前我们在合并时,是随机合并两个集合
虽然都能得到正确的结果,但存在时间复杂度的差异
怎样降低时间复杂度呢?
通过按秩合并(启发式合并)

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

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



按树高为秩

示例:

# 将两个集合合并为一个集合
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
# 合并是把小的树直接接到根节点上,所以只有两颗树的高度相等的时候合并后高度才会增加


按节点数为秩

示例:

# 将两个集合合并为一个集合
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(路径压缩+按节点数为秩合并):

# 在集合中查找元素的根节点
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(按树高为秩合并):

# 在集合中查找元素的根节点
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)是一种数据结构,主要用于处理一些不相交集合的合并及查询问题。


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


http://www.kler.cn/a/522107.html

相关文章:

  • 1.27补题 回训练营
  • mamba论文学习
  • 认识小程序的基本组成结构
  • 【Linux笔记】Day4
  • 判断子序列
  • 《STL基础之vector、list、deque》
  • vue事件总线(原理、优缺点)
  • Kafka 深入服务端 — 时间轮
  • 利用JSON数据类型优化关系型数据库设计
  • C语言字符串详解
  • 外部网关协议BGP考点
  • Vue.js组件开发-实现HTML内容打印
  • 【Elasticsearch】_reindex api请求
  • 鸿蒙仓颉环境配置(仓颉SDK下载,仓颉VsCode开发环境配置,仓颉DevEco开发环境配置)
  • 蓝桥杯真题 - 景区导游 - 题解
  • 新中地GIS开发特训营:引领地理空间技术革命
  • golang通过AutoMigrate方法自动创建table详解
  • 【蓝桥杯】43692.青蛙跳杯子
  • 【深度学习基础】多层感知机 | 实战Kaggle比赛:预测房价
  • 【JavaScript笔记】01- 原型及原型链(面试高频内容)
  • cherry USB 键盘分析
  • 算法题(49):反转链表II
  • Python3 OS模块中的文件/目录方法说明十二
  • DeepSeek R1:中国AI黑马的崛起与挑战
  • AI Agent的安全实践:权限控制与数据保护
  • 1. Java-MarkDown文件创建-工具类