图论 Union-Find 并查集算法

news2024/11/19 5:51:50

union-find API:

class UF {
public:
    /* 将 p 和 q 连接 */
    void union(int p, int q);
    /* 判断 p 和 q 是否连通 */
    bool connected(int p, int q);
    /* 返回图中有多少个连通分量 */
    int count();
};

连通性概念

    触点:每个单独的不与任何点相连的点叫做触点

    连通分量:简称分量,不存在连接关系的触点和连通在一起的触点的集合都是连通分量

    等价关系:处于同一连通分量的触点为等价关系,即他们是连通的

0~9 任意两个不同的点都不连通,调用 connected 都会返回 false,连通分量为 10 个。

如果现在调用 union(0, 1),那么 0 和 1 被连通,连通分量降为 9 个。

再调用 union(1, 2),这时 0,1,2 都被连通,调用 connected(0, 2) 也会返回 true,连通分量变为 8 个

好啦现在大概知道什么是连通性和连通分量了吧

如果某两个节点被连通,则让其中的(任意)一个节点的根节点接到另一个节点的根节点上

这里connect的实现思路就是把连通的父类之间相连接

class UF {
private:
    int count = 0; // 记录连通分量
    int* parent; // 节点x的父节点是parent[x]

public:
    UF(int n) {
        this->count = n;
        parent = new int[n];
        for(int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    void Union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if(rootP == rootQ) return;
        parent[rootP] = rootQ;
        count--;
    }

    bool connected(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        return rootP == rootQ;
    }

    int find(int x) {
        // 根节点的parent[x] == x,层层向上找就好啦
        while(parent[x] != x) {
            x = parent[x];
        }
        return x;
    }

(只是一种connect方式,充分不必要,不要老想着问为什么不直接点对点接,有些东西就是因为直接做做不了,才退而求其次间接去做)

大体思路就是这样,但是这么做太直接,无法避免不平衡树(退化成链表)的出现

下面就是优化算法的时间复杂度 => 就是在想法子来降低树的高度

1. 平衡性优化

主要就是把小一些的树接到大一些的树下面,引入“重量”来实现这一目的

通过比较树的重量,就可以保证树的生长相对平衡,树的高度大致在 logN 这个数量级

class UF {
private:
    int count = 0; // 记录连通分量
    int* parent; // 节点x的父节点是parent[x]
    int* size;

public:
    UF(int n) {
        this->count = n;
        parent = new int[n];
        size = new int [n];
        for(int i = 0; i < n; i++) {
            parent[i] = i;
            size[i] = 1;
        }
    }

    void Union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if(rootP == rootQ) return;
//        小树接到大树下面
        if(size[rootP] > size[rootQ]) {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
        }else {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP];
        }
        count--;
    }

    bool connected(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        return rootP == rootQ;
    }

    int find(int x) {
        // 根节点的parent[x] == x,层层向上找就好啦
        while(parent[x] != x) {
            x = parent[x];
        }
        return x;
    }

    int getCount() { return count; }

};

2. 路径压缩

我们关注的其实就只有根节点 => 树压的越低越好

int find(int x) {
        // 根节点的parent[x] == x,层层向上找就好啦
        if(parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }

 其实如果使用路径压缩技巧,那么 size 数组的平衡优化就不是特别必要了

完整代码如下:

class UF {
private:
    int count = 0; // 记录连通分量
    int* parent; // 节点x的父节点是parent[x]
    int* size;

public:
    UF(int n) {
        this->count = n;
        parent = new int[n];
        size = new int [n];
        for(int i = 0; i < n; i++) {
            parent[i] = i;
            size[i] = 1;
        }
    }

    void Union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if(rootP == rootQ) return;
//        小树接到大树下面
        if(size[rootP] > size[rootQ]) {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
        }else {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP];
        }
        count--;
    }

    bool connected(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        return rootP == rootQ;
    }

    int find(int x) {
        // 根节点的parent[x] == x,层层向上找就好啦
        if(parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }

    int getCount() { return count; }

};

1、用 parent 数组记录每个节点的父节点,相当于指向父节点的指针,所以 parent 数组内实际存储着一个森林(若干棵多叉树)。

2、用 size 数组记录着每棵树的重量,目的是让 union 后树依然拥有平衡性,保证各个 API 时间复杂度为 O(logN),而不会退化成链表影响操作效率。

3、在 find 函数中进行路径压缩,保证任意树的高度保持在常数,使得各个 API 时间复杂度为 O(1)。使用了路径压缩之后,可以不使用 size 数组的平衡优化。

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

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

相关文章

绿色智慧档案馆构想之智慧档案馆环境综合管控一体化平台

【智慧档案馆整体效果图】 智慧档案库房一体化平台通过智慧档案管理&#xff0c;实现智慧档案感知协同处置功能&#xff1b;实现对档案实体的智能化识别、定位、跟踪监控&#xff1b;实现对档案至智能密集架、空气恒湿净化一体设备、安防设备&#xff0c;门禁设备等智能化巡检与…

camunda流程引擎receive task节点用途

Camunda的Receive Task用于在流程中等待外部系统或服务发送消息。当接收到消息后&#xff0c;Receive Task将流程继续执行。Receive Task通常用于与Send Task配合使用&#xff0c;以便流程可以在发送和接收消息之间进行交互。 Receive Task可以用于以下场景&#xff1a; 1、等…

DAB-DETR代码学习记录之模型解析

DAB-DETR是吸收了Deformable-DETR&#xff0c;Conditional-DETR&#xff0c;Anchor-DETR等基础上完善而来的。其主要贡献为将query初始化为x,y,w,h思维坐标形式。 这篇博文主要从代码角度来分析DAB-DETR所完成的工作。 DAB-DETR主要是对Decoder模型进行改进。博主也主要是对Dec…

【C++】6. 内联函数

文章目录 前言一、宏函数二、内联函数三、内联函数的易错点 前言 当我们调用函数时&#xff0c;是有很多消耗的。其中最大的销毁就是为函数开辟空间 - 函数栈帧。 如果我们有一个函数&#xff0c;很短&#xff0c;而且要调用很多次&#xff0c;比如Swap()。它所造成消耗就比较…

机器学习笔记Python笔记:HMM(隐马尔科夫模型)

1 引子&#xff1a;猜天气小游戏 一对异地恋的情侣&#xff0c;女朋友想根据男友的心情猜测男友所在城市的天气 1.1 天气和心情一定一一对应 晴天——>高兴雨天——>烦躁 可以根据心情唯一确定天气 1.2 天气和心情没有一一对应 晴天——>80%高兴&#xff0c;20%烦…

有关实现深拷贝的四种方法

深拷贝与浅拷贝: 在开始之前我们需要先了解一下什么是浅拷贝和深拷贝&#xff0c;其实深拷贝和浅拷贝都是针对的引用类型&#xff0c;JS中的变量类型分为值类型&#xff08;基本类型&#xff09;和引用类型&#xff1b;对值类型进行复制操作会对值进行一份拷贝&#xff0c;而对…

Logstash学习

一、Logstash基础 1、什么是Logstash logstash是一个数据抽取工具&#xff0c;将数据从一个地方转移到另一个地方。下载地址&#xff1a;https://www.elastic.co/cn/downloads/logstash logstash之所以功能强大和流行&#xff0c;还与其丰富的过滤器插件是分不开的&#xff…

CDGP认证|ChatGPT的出现,对数据治理行业冲击如何?

ChatGPT的出现对数据治理有很多好处&#xff0c;其中最明显的是提供了更高效、更准确和更自动化的数据处理和分析服务,可以帮助企业和组织更好地管理和利用数据资源&#xff0c;提高数据质量和决策效率。此外&#xff0c;ChatGPT还能够发现隐藏在大量数据中的信息和趋势&#x…

OJ练习第82题——填充书架

填充书架 力扣链接&#xff1a;1105. 填充书架 题目描述 给定一个数组 books &#xff0c;其中 books[i] [thicknessi, heighti] 表示第 i 本书的厚度和高度。你也会得到一个整数 shelfWidth 。 按顺序 将这些书摆放到总宽度为 shelfWidth 的书架上。 先选几本书放在书架…

Nexus 组件发布失败、npm 登录失败 解决过程

目录 参考发布文章进行打包 提示发布成功&#xff0c;但在 Nexus 里没发现组件 测试 yarn 发布 测试 npm 发布&#xff08;解决登录失败&#xff09; Nexus 设置 Sonatype Nexus Repository Manager 相关权限 参考发布文章进行打包 整体发布&#xff1a;根目录运行 yarn r…

直播软件app开发:如何保证音视频质量?

随着社交媒体的发展&#xff0c;视频直播已成为越来越流行的社交方式。直播软件app开发也因此成为了一个热门话题。在开发直播软件app时&#xff0c;保证音视频质量是至关重要的。本文将介绍如何确保你的直播软件app在音视频质量方面表现出色。 确定音视频质量标准 首先&…

第七章 建造者模式

文章目录 前言一、传统方式解决盖房子需求完整代码抽象房子类 AbstractHouse实现子类 普通房子实现子类 高楼大厦客户端盖房子 二、引入建造者模式建造者模式的四个角色&#xff1a; 产品、抽象建造者、具体建造者、指挥者完整代码House类 (产品角色)抽象父类&#xff08;抽象建…

CV 领域的 ChatGPT?MetaAI 推出“最强”大视觉模型 SAM

出品人&#xff1a;Towhee 技术团队 随着 ChatGPT 引起一波又一波的“GPT热潮”&#xff0c;自然语言领域又一次成为了人工智能的讨论焦点。大家不由得思考&#xff0c;计算机视觉领域里是否会出现这样一个堪称划时代的模型&#xff1f;在这种万众瞩目的时候&#xff0c;一直处…

Python3《机器学习实战》学习笔记(七):支持向量机原理篇之手撕线性SVM

文章目录 一、SVM介绍二、线性SVM2.1 数学建模2.1.1决策面方程2.1.2"分类间隔"方程2.1.3约束条件2.1.4线性SVM优化问题基本描述2.1.5求解准备(讲讲凸函数)2.1.6拉格朗日函数2.1.7KKT条件2.1.8对偶问题求解2.1.9最后求解 2.2 SMO算法 三、代码实战3.1准备数据 一、SVM…

M_Map工具箱简介及地理图形绘制

M_Map工具箱简介及地理图形绘制 1 M_Map简介1.1 具体代码说明 2 地理图形绘制案例2.1 M_Map给定案例2.1.1 M_Map Logo2.1.2 Lambert Conformal Conic projection of North American Topography2.1.3 Stereographic projection of North Polar regions2.1.4 Colourmaps 2.2 案例…

vue封装公共组件库并发布到npm库详细教程

vue组件封装的原理&#xff1a;利用vue框架提供的api: Vue.use( plugin )&#xff0c;我们需要把封装好组件的项目打包成vue库&#xff0c;并提供install方法&#xff0c;然后发布到npm中。Vue.use( plugin )的时候会自动执行插件中的install方法。 一、组件库代码目录 目录…

常见的四种排名函数的用法(sql)

四个排名函数&#xff1a; 1.row_number 2.rank 3.dense_rank 4.ntile 1. ROW_NUMBER&#xff08;排名场景推荐&#xff09; 1.1 介绍 在 SQL 中&#xff0c;ROW_NUMBER() 是一个窗口函数&#xff0c;它为结果集中的每一行分配一个唯一的序号。该函数的语法如下&#xff1a; …

内网渗透之横向移动PTHPTTPTK

0x00前言 pass the hash&#xff08;哈希传递攻击&#xff0c;简称pth&#xff09; pass the ticket&#xff08;票据传递攻击&#xff0c;简称ptt&#xff09; pass the key&#xff08;密钥传递攻击&#xff0c;简称ptk&#xff09; PTH(pass the hash) #利用的lm或ntlm的值…

【社区图书馆】 Go佬—Go程序开发实战宝典书评

文章目录 前言内容介绍文章大致划分总结 前言 《Go 程序开发实战宝典》是一本非常实用的 Go 语言开发工具书&#xff0c;本书由深入浅出的案例讲解、详细的技术实现、贴近实际的应用开发等组成&#xff0c;非常适合 Go 语言开发爱好者、从事相关行业的工程师、技术负责人以及深…

ThinkPHP视图

ThinkPHP视图 前言视图一、运算符二、模版函数三、循环标签四,volist 循环标签五&#xff0c;if 判断标签六&#xff0c;switch 判断标签七、包含文件八、其他标签1. 条件标签2. 比较标签3. 循环标签4. 杂项标签 总结 前言 ThinkPHP视图基本语法和PHP语法非常的像&#xff0c;所…