数据结构编程实践20讲(Python版)—20并查集

本文目录

    • 20 并查集(Union-Find Set)
      • S1 说明
        • 并查集的定义
        • 并查集基本操作
        • 并查集优化
        • 并查集特点
        • 应用领域
      • S2 示例
      • S3 问题1:朋友圈问题
      • S4 问题2:网络连接恢复问题
      • S5 问题3:随机生成迷宫

往期链接

01 数组02 链表03 栈04 队列05 二叉树06 二叉搜索树07 AVL树08 红黑树09 B树10 B+树
11 线段树12 树状数组13 图形数据结构14 邻接矩阵15 完全图16 有向图17 散列18 哈希表19 字典树

20 并查集(Union-Find Set)

S1 说明

并查集的定义

并查集,又称为不相交集合数据结构,是一种用于处理 合并和查询 操作的数据结构。

并查集基本操作
  • 查找Find:查找元素 x 所在集合的代表元(根节点)。通过代表元,可以确定两个元素是否属于同一个集合。
  • 合并Union:将两个元素所在的集合合并。通常将一个集合的根节点连接到另一个集合的根节点上。
并查集优化
  • 路径压缩优化(Path Compression):在执行 Find 操作时,将查找路径上的所有节点直接连接到根节点。这样可以有效地降低树的高度,加快后续操作。
  • 按秩合并优化(Union by Rank):在 Union 操作时,将树高度(秩)较小的根节点连接到树高度较大的根节点上,避免树高度增加。

同时使用路径压缩和按秩合并,可以使得并查集的操作时间复杂度近似为 O ( α ( n ) ) O(\alpha(n)) O(α(n)),其中 α ( n ) \alpha(n) α(n) 为阿克曼函数的逆,增长极其缓慢,近似为常数时间。

并查集特点
  • 高效性:在经过路径压缩和按秩合并的优化后,并查集的 Find 和 Union 操作的平均时间复杂度近似为常数。
  • 适用性:适用于需要动态维护元素分组的信息,特别是判断两个元素是否在同一集合的场景。
  • 易于实现:并查集的基本实现相对简单,可以根据需要进行优化。
应用领域
  • 动态连通性问题

在一个网络中,动态地添加连接,判断两个节点是否连通。

  • 最小生成树算法

在 Kruskal 算法中,使用并查集判断添加的边是否会形成环,以决定是否将边加入生成树。

  • 等价类划分

将一些具有特定关系的元素分组,例如在语言处理中的同义词分组。

  • 网络社交圈检测

在社交网络中,判断用户之间是否在同一个社交圈中。

  • 图的连通分量

在无向图中,使用并查集可以快速找到连通分量的个数。

S2 示例

class UnionFind:
    def __init__(self, elements):
        """初始化并查集。

        参数:
        elements -- 一个包含所有元素的可迭代对象
        """
        self.parent = {}  # 记录每个节点的父节点
        self.rank = {}    # 记录每个节点的秩(近似树的高度)
        for element in elements:
            self.parent[element] = element
            self.rank[element] = 0

    def find(self, x):
        """查找元素 x 所在集合的根节点,并进行路径压缩。"""
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # 递归查找,并路径压缩
        return self.parent[x]

    def union(self, x, y):
        """合并元素 x 和 y 所在的集合,按秩合并。"""
        xroot = self.find(x)
        yroot = self.find(y)
        if xroot == yroot:
            return  # 已经在同一集合中,无需合并

        # 按秩合并
        if self.rank[xroot] < self.rank[yroot]:
            self.parent[xroot] = yroot
        elif self.rank[xroot] > self.rank[yroot]:
            self.parent[yroot] = xroot
        else:
            self.parent[yroot] = xroot
            self.rank[xroot] += 1

if __name__ == "__main__":
    # 初始化并查集
    elements = [1, 2, 3, 4, 5]
    uf = UnionFind(elements)

    # 合并操作
    uf.union(1, 2)
    print(f"1 和 2 是否连通: {uf.find(1) == uf.find(2)}")  # 输出 True

    uf.union(2, 3)
    print(f"1 和 3 是否连通: {uf.find(1) == uf.find(3)}")  # 输出 True

    uf.union(4, 5)
    print(f"3 和 5 是否连通: {uf.find(3) == uf.find(5)}")  # 输出 False

    uf.union(3, 5)
    print(f"1 和 5 是否连通: {uf.find(1) == uf.find(5)}")  # 输出 True

结果

12 是否连通: True
13 是否连通: True
35 是否连通: False
15 是否连通: True

S3 问题1:朋友圈问题

问题描述:在一个班级里,有 n n n个学生,编号为 0 0 0 n − 1 n-1 n1。每个学生可能认识其他一些学生,形成了好友关系。好友关系是互相的,也就是说,如果学生 A A A是学生 B B B的好友,那么学生 B B B也是学生 A A A的好友。

给定一个 n × n n×n n×n的二维矩阵 M M M,其中

  • M [ i ] [ j ] = 1 M[i][j]=1 M[i][j]=1 表示第 i i i个学生和第 j j j个学生是直接的好友关系。
  • M [ i ] [ j ] = 0 M[i][j]=0 M[i][j]=0 表示第 i i i个学生和第 j j j个学生不是直接好友。

朋友圈:一组学生,他们之间是直接或间接的好友关系。也就是说,如果学生 A A A和学生 B B B是直接好友,或者通过其他学生间接相连(比如 A A A C C C的好友, C C C B B B的好友),那么 A A A B B B属于同一个朋友圈。

请计算班级中共有多少个不同的朋友圈(Friend Circles)。

class UnionFind:
    def __init__(self, n):
        """初始化并查集"""
        self.parent = [i for i in range(n)]  # 父节点
        self.rank = [0] * n                  # 秩
        self.count = n                       # 集合数量,可直接作为朋友圈数量

    def find(self, x):
        """查找根节点(带路径压缩)"""
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]  # 路径压缩:父节点指向祖父节点
            x = self.parent[x]
        return x

    def union(self, x, y):
        """合并两个节点所在的集合"""
        xroot = self.find(x)
        yroot = self.find(y)
        if xroot == yroot:
            return
        # 按秩合并
        if self.rank[xroot] < self.rank[yroot]:
            self.parent[xroot] = yroot
        else:
            self.parent[yroot] = xroot
            if self.rank[xroot] == self.rank[yroot]:
                self.rank[xroot] += 1
        self.count -= 1

def findCircleNum(M):
    n = len(M)
    uf = UnionFind(n)
    for i in range(n):
        for j in range(i+1, n):  # 只遍历上三角矩阵,减少重复
            if M[i][j]:
                uf.union(i, j)
    return uf.count

# 测试代码
if __name__ == "__main__":
    M = [
        [1,1,0,0,0,0,0,0,0,0],
        [1,1,1,0,0,0,0,0,0,0],
        [0,1,1,0,0,0,0,1,0,0],
        [0,0,0,1,1,0,0,0,0,0],
        [0,0,0,1,1,0,0,0,0,0],
        [0,0,0,0,0,1,0,0,0,0],
        [0,0,0,0,0,0,1,1,0,0],
        [0,0,1,0,0,0,1,1,0,0],
        [0,0,0,0,0,0,0,0,1,1],
        [0,0,0,0,0,0,0,0,1,1],
    ]
    print("朋友圈数量:", findCircleNum(M))  # 输出:4

例子说明

M = [
    [1,1,0,0,0,0,0,0,0,0],  # 学生0与学生1是好友
    [1,1,1,0,0,0,0,0,0,0],  # 学生1与学生2是好友
    [0,1,1,0,0,0,0,1,0,0],  # 学生2与学生7是好友
    [0,0,0,1,1,0,0,0,0,0],  # 学生3与学生4是好友
    [0,0,0,1,1,0,0,0,0,0],  # 学生4与学生3是好友
    [0,0,0,0,0,1,0,0,0,0],  # 学生5自己一个朋友圈
    [0,0,0,0,0,0,1,1,0,0],  # 学生6与学生7是好友
    [0,0,1,0,0,0,1,1,0,0],  # 学生7与学生2和6是好友
    [0,0,0,0,0,0,0,0,1,1],  # 学生8与学生9是好友
    [0,0,0,0,0,0,0,0,1,1],  # 学生9与学生8是好友
]

朋友圈1:学生01276组成一个朋友圈,因为他们通过直接或间接的好友关系连接在一起。
朋友圈2:学生3和学生4组成一个朋友圈。
朋友圈3:学生5自己一个人,是一个朋友圈。
朋友圈4:学生8和学生9组成一个朋友圈。

S4 问题2:网络连接恢复问题

在一个网络中,有 n n n个节点,这些节点通过若干边相连。然而,由于某些原因,部分连接(边)被破坏,导致网络分裂成多个连通的子网络。为了恢复整个网络的连接性,我们需要添加最少数量的边,使得所有节点重新连通。目标是最小化需要添加的边的数量,使得网络变为连通图。

class UnionFind:
    def __init__(self, n):
        self.parent = [i for i in range(n + 1)]  # 节点编号从 1 到 n
        self.rank = [0] * (n + 1)

    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]  # 路径压缩
            x = self.parent[x]
        return x

    def union(self, x, y):
        xroot = self.find(x)
        yroot = self.find(y)
        if xroot == yroot:
            return False  # 已在同一集合
        if self.rank[xroot] < self.rank[yroot]:
            self.parent[xroot] = yroot
        else:
            self.parent[yroot] = xroot
            if self.rank[xroot] == self.rank[yroot]:
                self.rank[xroot] += 1
        return True


def get_additional_edges(n, edges):
    uf = UnionFind(n)
    for u, v in edges:
        uf.union(u, v)
    # 收集连通分量的根节点
    connected_components = set()
    for i in range(1, n + 1):
        parent = uf.find(i)
        connected_components.add(parent)
    components = list(connected_components)
    # 构造需要添加的边的列表
    additional_edges = []
    for i in range(len(components) - 1):
        u = components[0]
        v = components[i + 1]
        additional_edges.append([u, v])
    return additional_edges


# 测试代码
if __name__ == "__main__":
    n = 8
    edges = [[1, 2], [2, 3], [4, 5], [2, 7]]
    additional_edges = get_additional_edges(n, edges)
    print("需要添加的边:", additional_edges)  
需要添加的边: [[8, 1], [8, 4], [8, 6]]

S5 问题3:随机生成迷宫

import random
import matplotlib.pyplot as plt

class UnionFind:
    def __init__(self, size):
        self.parent = [i for i in range(size)]  # 父节点数组
        self.rank = [0] * size  # 秩数组,用于按秩合并

    def find(self, x):
        # 路径压缩查找
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # 递归压缩路径
        return self.parent[x]

    def union(self, x, y):
        # 按秩合并
        xroot = self.find(x)
        yroot = self.find(y)
        if xroot == yroot:
            return False  # 已在同一集合,不能合并
        if self.rank[xroot] < self.rank[yroot]:
            self.parent[xroot] = yroot
        else:
            self.parent[yroot] = xroot
            if self.rank[xroot] == self.rank[yroot]:
                self.rank[xroot] += 1
        return True  # 合并成功

class Maze:
    def __init__(self, rows, cols):
        self.rows = rows
        self.cols = cols
        # 每个格子存储其墙壁状态,N、S、E、W 分别表示北、南、东、西
        self.maze = [[{'walls': {'N': True, 'S': True, 'E': True, 'W': True}} for c in range(cols)] for r in range(rows)]
        self.walls = []

    def init_walls(self):
        # 初始化所有的墙壁
        for r in range(self.rows):
            for c in range(self.cols):
                cell_index = r * self.cols + c
                # 东边墙壁
                if c < self.cols - 1:
                    self.walls.append((cell_index, cell_index + 1, 'E'))
                # 南边墙壁
                if r < self.rows - 1:
                    self.walls.append((cell_index, cell_index + self.cols, 'S'))

    def remove_wall(self, cell1, cell2, direction):
        r1, c1 = divmod(cell1, self.cols)
        r2, c2 = divmod(cell2, self.cols)
        # 移除墙壁
        if direction == 'E':
            self.maze[r1][c1]['walls']['E'] = False
            self.maze[r2][c2]['walls']['W'] = False
        elif direction == 'S':
            self.maze[r1][c1]['walls']['S'] = False
            self.maze[r2][c2]['walls']['N'] = False

def generate_maze(rows, cols):
    maze = Maze(rows, cols)
    maze.init_walls()

    uf = UnionFind(rows * cols)
    # 随机排列墙壁列表
    random.shuffle(maze.walls)

    for wall in maze.walls:
        cell1, cell2, direction = wall
        # 判断两个格子是否属于不同的集合
        if uf.union(cell1, cell2):
            # 如果合并成功,移除墙壁
            maze.remove_wall(cell1, cell2, direction)
        # 如果合并失败,说明已连通,不能移除墙壁,以避免形成环

    return maze

def add_entry_exit(maze):
    # 移除左上角的西边墙壁作为入口
    maze.maze[0][0]['walls']['W'] = False
    # 移除右下角的东边墙壁作为出口
    maze.maze[maze.rows - 1][maze.cols - 1]['walls']['E'] = False

def draw_maze(maze):
    rows = maze.rows
    cols = maze.cols
    maze_map = maze.maze

    # 设置绘图尺寸
    plt.figure(figsize=(cols / 2, rows / 2))
    ax = plt.gca()
    ax.set_aspect('equal')
    plt.axis('off')  # 关闭坐标轴

    # 绘制迷宫
    for r in range(rows):
        for c in range(cols):
            x = c
            y = rows - r - 1  # Matplotlib 的 y 轴方向与迷宫的行数相反

            walls = maze_map[r][c]['walls']

            # 设置墙壁的线宽
            line_width = 2

            # 如果北边有墙壁,绘制从 (x, y+1) 到 (x+1, y+1) 的线
            if walls['N']:
                plt.plot([x, x + 1], [y + 1, y + 1], color='black', linewidth=line_width)
            # 如果南边有墙壁,绘制从 (x, y) 到 (x+1, y) 的线
            if walls['S']:
                plt.plot([x, x + 1], [y, y], color='black', linewidth=line_width)
            # 如果西边有墙壁,绘制从 (x, y) 到 (x, y+1) 的线
            if walls['W']:
                plt.plot([x, x], [y, y + 1], color='black', linewidth=line_width)
            # 如果东边有墙壁,绘制从 (x+1, y) 到 (x+1, y+1) 的线
            if walls['E']:
                plt.plot([x + 1, x + 1], [y, y + 1], color='black', linewidth=line_width)

    plt.tight_layout()
    plt.show()

if __name__ == "__main__":
    rows = 20  # 迷宫的行数
    cols = 20  # 迷宫的列数
    maze = generate_maze(rows, cols)
    add_entry_exit(maze)  # 添加入口和出口
    draw_maze(maze)  # 使用 Matplotlib 可视化迷宫

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/894333.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【热门】用ChatGPT做智慧农业云平台——农业ERP管控系统

随着科技的进步,原有农业种植方式已经不能满足社会发展的需要,必须对传统的农业进行技术更新和改造。经过多年的实践,人们总结出一种新的种植方法——温室农业,即“用人工设施控制环境因素,使作物获得最适宜的生长条件,从而延长生产季节,获得最佳的产出”。这种农业生产方式…

vue3中监视 Reactive对象中的属性

watch 的第一个参数可以是不同形式的“数据源”&#xff1a;它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组 一、框架&#xff1a; <template><div class"divBox"><h2>姓名&#xff1a;{{ person.…

2024年9月中国电子学会青少年软件编程(Python)等级考试试卷(一级)答案 + 解析

一、单选题 1、下列选项中关于 turtle.color(red) 语句的作用描述正确的是&#xff1f;&#xff08; &#xff09; A. 只设置画笔的颜色为红色 B. 只设置填充的颜色为红色 C. 设置画笔和填充的颜色为红色 D. 设置画笔的颜色为红色&#xff0c;设置画布背景的颜色为红色 正…

告别ELK,APO提供基于ClickHouse开箱即用的高效日志方案——APO 0.6.0发布

ELK一直是日志领域的主流产品&#xff0c;但是ElasticSearch的成本很高&#xff0c;查询效果随着数据量的增加越来越慢。业界已经有很多公司&#xff0c;比如滴滴、B站、Uber、Cloudflare都已经使用ClickHose作为ElasticSearch的替代品&#xff0c;都取得了不错的效果&#xff…

C#教程笔记

C#开发的程序依附.NET平台 编译器->IL中间语言->CLR->机器指令 .NET CORE平台 跨平台 .cs后缀名 快捷键 CtrlKD格式化CtrlL或CtrlX删除一行CtrlY反撤销cwTab快速生成命令行输出Ctrl空格或CtrlJ获取提示///方法注释CtrlMO代码全部折叠CtrlML代码全部展开 上升沿0变1 安…

【AIGC】优化长提示词Prompt:提升ChatGPT输出内容的准确性与实用性

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;长提示词的挑战&#x1f4af;谷歌的优化长提示词技术关键因素分析 &#x1f4af;长提示词的设计原则&#x1f4af;优化长提示词的新框架方法&#x1f4af;实验结果分析不…

Qt第十三天:网络编程:TCP和UDP的使用

我发现了有些人喜欢静静看博客不聊天呐&#xff0c; 但是ta会点赞。 这样的人呢帅气低调有内涵&#xff0c; 美丽大方很优雅。 说的就是你&#xff0c; 不用再怀疑哦 ❤️TCP&#xff1a; 一、创建项目&#xff0c;命名为Server&#xff0c;继承QWidget 二、添加Qt设计师…

【JavaEE初阶】深入透析文件-IO关于文件内容的操作(四种文件流)

前言 &#x1f31f;&#x1f31f;本期讲解关于CAS的补充和JUC中有用的类&#xff0c;这里涉及到高频面试题哦~~~ &#x1f308;上期博客在这里&#xff1a;【JavaEE初阶】文件-IO之实现文件系统的操作如何进行实现-CSDN博客 &#x1f308;感兴趣的小伙伴看一看小编主页&…

Server-Sent Event(SSE) GPT场景实现

关于SSE的基本概念可以看一下阮一峰老师的这篇文章&#xff1a;Server-Sent Events教程。 现在比较常见的场景是gpt回答的时候类似下图这种打字机的情况&#xff0c;因为AI一般响应时间会比较长&#xff0c;使用这种方式能让人别等那么久&#xff0c;是一个相对比较良好的用户…

JVM篇(学习预热 - JVM正式展开 - (实战课程学习总结))(持续更新迭代)

目录 除了了解JVM的一些基本常识&#xff0c;我们并没有提到JVM的架构&#xff0c;就像我们做项目之前的预热&#xff0c;还是有必要先了解好它的架构&#xff0c;让我们开始吧&#xff01; 一、JVM程序执行流程 1. 执行流程图 2. 热点代码 3. 热点检测方式 方法一&#x…

离散数学实验二c语言(输出关系矩阵,输出矩阵性质,输出自反闭包,对称闭包,传递闭包,判断矩阵是否为等价关系,相容关系,偏序关系)

离散数学实验二 一、算法描述&#xff0c;算法思想 &#xff08;一&#xff09;相关数据结构 typedef struct Set *S; //存放集合 struct Set {int size; //集合的元素个数char *A; //存放该集合的元素 }; Set存放有限集合A&#xff0c;该集合的元素个数为size&#xff0…

数据分析方法(回归分析,决策树与神经网络,提升树,时间序列分析,假设检验,用户画像,竞品分析)等

1.回归分析 回归分析是一种统计方法&#xff0c;用于探索自变量&#xff08;预测变量&#xff09;和因变量&#xff08;目标变量&#xff09;之间的关系。它可以帮助预测变量的变化对目标变量的影响大小。例如&#xff0c;简单线性回归用于分析两个变量之间的线性关系&#xf…

能源领域下暖通行业现状-研究

基于AI大语言模型的暖通行业能源管理系统构建研究 一、能源管理中的突出问题 1. **能源消耗监测不准确** 现有的监测系统在获取设备实时能耗数据方面存在精度不足的问题&#xff0c;难以准确反映能源的实际使用情况。这使得节能决策缺乏可靠的数据支持&#xff0c;无法精准定位…

react18中的计算属性及useMemo的性能优化技巧

react18里面的计算属性和使用useMemo来提升组件性能的方法 计算属性 实现效果 代码实现 函数式组件极简洁的实现&#xff0c;就这样 import { useState } from "react"; function FullName() {const [firstName, setFirstName] useState("");const [la…

『 Linux 』HTTP(三)

文章目录 HTTP的请求方法HTTP的状态码模拟404状态重定向状态码状态码与浏览器的联系 TCP的短连接与长连接Connection 头部Content-Type 头部Set-Cookie 头部Session ID 本文代码参照前一篇博客 HTTP的请求方法 HTTP协议存在多种请求方法,但是较为常用的请求方法基本为GET方法与…

开源 AI 智能名片 2+1 链动模式 S2B2C 商城小程序:企业产供销全流程的创新驱动

摘要&#xff1a;本文探讨了开源 AI 智能名片、链动 21 模式以及 S2B2C 商城小程序源码在企业产供销过程中的作用。通过分析社交电商与企业产供销的关联、数据运营体系的支撑作用以及小程序功能在企业产供销中的应用等方面&#xff0c;阐述了其在产品研发、传播、营销和公关方面…

2013 lost connection to MySQL server during query

1.问题 使用navicat连接doris&#xff0c;会有这个错误。 2.解决 换低版本的navicat比如navicat11。

Leetcode—192. 统计词频【中等】(Shell)

2024每日刷题&#xff08;188&#xff09; Leetcode—192. 统计词频 实现代码 # Read from the file words.txt and output the word frequency list to stdout. cat words.txt | tr -s \n | sort | uniq -c | sort -nr | awk {print $2, $1}运行结果 之后我会持续更新&…

spring 启动失败 active: @env@

参考&#xff1a;SpringBoot启动失败报错&#xff0c;spring.profiles.active:env中环境变量无法识别报错_active: env_profileactive启动报错 ine 3, column 13:-CSDN博客

开源vGPU方案 HAMi实现细粒度GPU切分——筑梦之路

前言 为什么需要 GPU 共享、切分等方案&#xff1f; 在使用GPU的过程中我们会发现&#xff0c;直接在裸机环境使用&#xff0c;都可以多个进程共享 GPU&#xff0c;怎么到 k8s 环境就不行了&#xff1f; 1. 资源感知 在 k8s 中资源是和节点绑定的&#xff0c;对于 GPU 资源…