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

news2024/11/29 10:46:35

本文目录

    • 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 可视化迷宫

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

023_Layout_and_Containers_in_Matlab界面布局与容器

容器 基于uifigure进行的图形用户界面设计&#xff0c;可以分为以下几种容器类型&#xff1a; 图窗&#xff1a;uifigure布局&#xff1a;uigridlayout面板&#xff1a;uipanel标签页&#xff1a;uitabgroup、uitab 这几个对象除uifigure外相互可以形成嵌套的关系&#xff0…

【新人系列】Python 入门(二):Python IDE 介绍

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12801353.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Python 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

leetcode.204.计数质数

#中等#枚举 给定整数 n &#xff0c;返回 所有小于非负整数 n 的质数的数量 。 埃氏筛 枚举没有考虑到数与数的关联性&#xff0c;因此难以再继续优化时间复杂度。接下来我们介绍一个常见的算法&#xff0c;该算法由希腊数学家厄拉多塞&#xff08;Eratosthenes&#xff09;提…

Python爬虫:自动化获取商品评论数据

为什么选择Python爬虫API 高效的数据处理&#xff1a;Python的数据处理能力&#xff0c;结合Pandas等库&#xff0c;可以轻松处理和分析大量的评论数据。丰富的库支持&#xff1a;Python拥有丰富的库&#xff0c;如requests用于发送HTTP请求&#xff0c;BeautifulSoup用于解析…

初识MySQL · 数据库

目录 前言&#xff1a; 数据库 简单使用 存储引擎 前言&#xff1a; 本文也是MySQL的第一篇文章了&#xff0c;新的知识点已经出现&#xff0c;怎么能够停止不前&#xff0c;穿越时空……(迪迦奥特曼乱入哈哈哈)。 言归正传&#xff0c;我们在本文的目标有&#xff1a; …

工厂生成中关于WiFi的一些问题

一 背景: 主要做高通和MTK,工厂生成中通过使用adb wifi,因为这样生产效率高并且避免了新机器有划痕,但是也经常碰到adb wifi无法连接的问题,那么是什么原因导致呢? 二 案例 测试步骤: 使用adb wifi连接手机测试工厂case adb usb adb tcpip 5555 adb connect DU…

高效监控系统:Nightingale本地化部署与远程访问指南

文章目录 前言1. Linux 部署Nightingale2. 本地访问测试3. Linux 安装cpolar4. 配置Nightingale公网访问地址5. 公网远程访问Nightingale管理界面6. 固定Nightingale公网地址 前言 本文主要介绍如何在本地Linux系统部署 Nightingale 夜莺监控并结合cpolar内网穿透工具实现远程…

Android组件化、模块化、Catalogs

前言 下载代码-----》码云下载 下载代码-----》github下载 本篇是Android最新的依赖架构设计&#xff0c;gradle版本要8.0以上&#xff0c;代码实现基于8.5.1。好多年开发过程中&#xff0c;我们碰到config.gradle&#xff0c;buildSrc&#xff0c;composing builds等依赖编译…

新款任天堂switch游戏机方案,支持4K60HZ投屏方案,显示器,手柄方案

据传任天堂将推出新的一代的switch掌机&#xff0c;而新款掌机将支持4K60HZ投屏 都2402年了再做1080P确实有点不太象话了 4K60HZ相较于1080P能够提升很多游戏体验&#xff0c;这时不管是HDMI显示器或者是VR眼睛清晰度都会让人舒服很多。 不过新一代的任天堂似乎也在PD协议上…

用作曲的手法写旋律 什么是动机 ​动机扩大 单音重复 移八度

【你怎么还不会写旋律&#xff01;&#xff01;&#xff01;猴子都听的懂的旋律教程来了&#xff01;两分钟让你快速上手&#xff01;】 你怎么还不会写旋律&#xff01;&#xff01;&#xff01;猴子都听的懂的旋律教程来了&#xff01;两分钟让你快速上手&#xff01;_哔哩哔…

同济子豪兄--图的基本表示【斯坦福CS224W图机器学习】

无向图&#xff08;Undirected Graph&#xff09;&#xff1a; 在无向图中&#xff0c;边没有方向&#xff0c;即如果顶点A和顶点B之间有一条边&#xff0c;那么这条边既表示A到B的关系&#xff0c;也表示B到A的关系。换句话说&#xff0c;边是双向的。无向图的边通常用一条线段…

WPF基础权限系统

一.开发环境 VisualStudio 2022NET SDK 8.0Prism 版本 8.1.97Sqlite 二. 功能介绍 WPF 基础权限系统&#xff0c;是一个支持前后端分离设计的 客户端(C/S)项目&#xff0c;该示例项目前端xaml使用UI库 &#xff0c;Material Design Themes UI 来构建用户界面&#xff0c;确保…

Ubuntu如何显示pcl版本

终端输入&#xff1a; apt-cache show libpcl-dev可以看到&#xff0c;Ubuntu20.04&#xff0c;下载的pcl&#xff0c;应该都是1.10版本的

一次性入门三款分布式定时任务调度框架:Quartz、ElasticJob3.0、xxl-job

分布式定时任务调度框架&#xff08;文末有源码&#xff09; 前言1、Quartz1.1 数据库1.2 maven依赖1.3 代码实现1.3.1 创建一个job1.3.1 为job设置trigger 1.4 配置文件1.5 启动、测试1.1 单机1.2 集群 2、ElasticJob2.1 下载zk2.2 新建三个类型的作业2.3 配置文件2.4 启动项目…

Windows下配置Nginx和PHP

之前在Windows开发php项目用的是phpstudy&#xff0c;好用的很。但是phpstudy好久没有更新了&#xff0c;感觉PHP像没有人再用了一样。但是PHP拿来开发小系统&#xff0c;还是很高效的&#xff0c;今天记录如何在Windows环境下配置Nginx和PHP。 1. 配置nginx Nginx软件下载解压…

基于Springboot+Vue的宠物管理系统(含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 这个系…

实现双向链表的增删改查

头文件 #pragma once #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <stdbool.h> typedef int LTDataType; typedef struct ListNode {LTDataType data;struct ListNode* prev;struct ListNode* next; } LTNode; //v…

QGraphics类型学习使用【Qt】【C++】

QGraphics类型学习使用 需求过程全部完整代码 首先已知&#xff0c;QGraphicsView&#xff0c;QGraphicsScene, QGraphicsItem&#xff0c;分别称为&#xff1a;视图&#xff0c;场景&#xff0c;图元&#xff0c;图表就是各种各样的元素&#xff0c;图片元素&#xff0c;线条元…

react18中在列表项中如何使用useRef来获取每项的dom对象

在react中获取dom节点都知道用ref&#xff0c;但是在一个列表循环中&#xff0c;这样做是行不通的&#xff0c;需要做进一步的数据处理。 实现效果 需求&#xff1a;点击每张图片&#xff0c;当前图片出现在可视区域。 代码实现 .box{border: 1px solid #000;list-style: …

ParallelsDesktop20最新版本虚拟机 一键切换系统 游戏娱乐两不误

让工作生活更高效&#xff1a;Parallels Desktop 20最新版本虚拟机的神奇之处 大家好&#xff01;&#x1f44b; 今天我要跟大家安利一款让我工作效率飞升的神器——Parallels Desktop 20最新版本虚拟机。作为一个日常需要在不同操作系统间来回穿梭的人&#xff0c;这款软件简直…