数据结构:并查集的原理和运用

news2024/12/26 11:18:45

文章目录

  • 什么是并查集?
  • 并查集的模拟实现
  • 并查集的应用
    • 省份数量
    • 等式方程的可满足性

本篇总结的是并查集的使用方法和运用

什么是并查集?

给定这样一个场景,n个不同的元素划分成不同的,不相交的集合,在开始的时候,每个元素都是一个集合,经过一些规则后把这些集合进行适当的划分,此时要查询某个元素是属于哪个集合,这样的抽象数据类型就叫做并查集

来一个具体的例子,假设现在有10个人,现在每个人各自是一个小组,那么现在就有10个小组,用一个数组来表示这10个人,每个人都是一个单独的小格子

在这里插入图片描述

现在由于要减少管理的成本,因此要对这些人进行合并,原本是十个组,现在要变成三个组,那么就要选出来三个人作为组长代领其余的人进行各种工作,那么就可以表示成这样

在这里插入图片描述
那如何用数组进行表示呢?下面给出了其中一种表示的方法

在这里插入图片描述
简单来说,数组的下标对应的是集合中元素的编号,如果数组是负数,那么首先说明这是根,其次数字代表的是集合中元素的个数,如果数组是一个非负数,那么这个数字代表的就是父亲所在的数组的下标

现在又出现了意外情况,三个小组又要进行合并了,那么此时如何进行合并?很简单,只需要让组长变成另外一个组的组员即可,他所代领的组员就会变成另外一个组的组员

在这里插入图片描述
上面的这个过程,就描述了并查集所需要的各种功能和所能完成的各种任务,一般而言,并查集需要具备解决下面这些问题的能力:

  1. 查找元素属于哪个集合
  2. 查看两个元素是不是一个集合
  3. 两个集合合并成一个集合
  4. 集合的个数

那么基于上面的这四个功能,下面来模拟实现一下并查集

并查集的模拟实现

#pragma once

class UnionFindSet
{
public:
	UnionFindSet(int nums)
		:_ufs(nums, -1)
	{}

	// 查找元素属于哪个集合
	int find(int root)
	{
		while (_ufs[root] >= 0)
		{
			int parent = _ufs[root];
			root = parent;
		}
		return root;
	}

	// 查看两个元素是否是一个集合
	bool IsSame(int x1, int x2)
	{
		int root1 = find(x1);
		int root2 = find(x2);
		return root1 == root2;
	}

	// 将两个集合合并成一个集合
	void merge(int x1, int x2)
	{
		int root1 = find(x1);
		int root2 = find(x2);
		// 把小的集合合并到大的集合里面
		if (abs(_ufs[root1]) < abs(_ufs[root2]))
			swap(root1, root2);
		if (root1 != root2)
		{
			_ufs[root1] += _ufs[root2];
			_ufs[root2] = root1;
		}
	}

	// 集合的个数
	size_t GetSize()
	{
		int size = 0;
		for (auto e : _ufs)
			if (e < 0)
				size++;
		return size;
	}

private:
	// 底层就是一个数组
	vector<int> _ufs;
};

并查集的应用

省份数量

在这里插入图片描述

首先一个最简便的方法,就是直接用已经实现的并查集套一下即可

class UnionFindSet
{
public:
	UnionFindSet(int nums)
		:_ufs(nums, -1)
	{}

	// 查找元素属于哪个集合
	int find(int root)
	{
		while (_ufs[root] >= 0)
		{
			int parent = _ufs[root];
			root = parent;
		}
		return root;
	}

	// 查看两个元素是否是一个集合
	bool IsSame(int x1, int x2)
	{
		int root1 = find(x1);
		int root2 = find(x2);
		return root1 == root2;
	}

	// 将两个集合合并成一个集合
	void merge(int x1, int x2)
	{
		int root1 = find(x1);
		int root2 = find(x2);
		// 把小的集合合并到大的集合里面
		if (abs(_ufs[root1]) < abs(_ufs[root2]))
			swap(root1, root2);
		if (root1 != root2)
		{
			_ufs[root1] += _ufs[root2];
			_ufs[root2] = root1;
		}
	}

	// 集合的个数
	size_t GetSize()
	{
		int size = 0;
		for (auto e : _ufs)
			if (e < 0)
				size++;
		return size;
	}

private:
	// 底层就是一个数组
	vector<int> _ufs;
};

class Solution 
{
public:
    int findCircleNum(vector<vector<int>>& isConnected) 
    {
        UnionFindSet ufs(isConnected.size());
        // 利用并查集来解题
        for(int i = 0; i < isConnected.size(); i++)
        {
            for(int j = 0; j < isConnected[i].size(); j++)
            {
                // 表示这这两个之间应建立关系
                if(isConnected[i][j] == 1)
                {
                    ufs.merge(i, j);
                }
            }
        }
        return ufs.GetSize();
    }
};

当然也可以使用lambda表达式进行一个对象的调用

class Solution 
{
public:
    int findCircleNum(vector<vector<int>>& isConnected) 
    {
        vector<int> ufs(isConnected.size(), -1);

        auto findroot = [&ufs](int x)
        {
            while (ufs[x] >= 0)
			    x = ufs[x];
            return x;
        };

        // 利用并查集来解题
        for(int i = 0; i < isConnected.size(); i++)
        {
            for(int j = 0; j < isConnected[i].size(); j++)
            {
                // 表示这这两个之间应建立关系
                if(isConnected[i][j] == 1)
                {
                    int root1 = findroot(i);
                    int root2 = findroot(j);
                    if (root1 != root2)
                    {
                        ufs[root1] += ufs[root2];
                        ufs[root2] = root1;
                    }
                }
            }
        }

        int size = 0;
		for (auto e : ufs)
			if (e < 0)
				size++;
		return size;
    }
};

等式方程的可满足性

在这里插入图片描述

class Solution 
{
public:
    bool equationsPossible(vector<string>& equations) 
    {
        vector<int> ufs(26, -1);

        auto findroot = [&ufs](int x)
        {
            while(ufs[x] >= 0)
                x = ufs[x];
            return x;
        };

        // 第一遍把相等的值放到一个集合里面
        for(auto& str : equations)
        {
            if(str[1] == '=')
            {
                int root1 = findroot(str[0] - 'a');
                int root2 = findroot(str[3] - 'a');
                if(root1 != root2)
                {
                    ufs[root1] += ufs[root2];
                    ufs[root2] = root1;
                }
            }
        }

        // 第二遍看看有没有相悖的情况
        for(auto& str : equations)
        {
            if(str[1] == '!')
            {
                int root1 = findroot(str[0] - 'a');
                int root2 = findroot(str[3] - 'a');
                if(root1 == root2)
                    return false;
            }
        }
        return true;
    }
};

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

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

相关文章

JavaEE之多线程编程:2.创建线程及Thread类常见方法(超全!!!)

一、创建线程 Java中创建线程的写法有很多种&#xff01;&#xff01;&#xff01;这里介绍其中5种。 方法1&#xff1a;继承Thread类&#xff0c;重写run 创建一个类&#xff0c;让这个类继承自Thread父类&#xff0c;再重写我们的run方法就可以了。 使用Thread类&#xff…

文件批量管理方法:100个文件要怎样快速放在100个指定的文件夹中

处理大量文件时&#xff0c;经常要将多个文件放入相应的文件夹中。如果要处理的文件数量较大&#xff0c;例如100个文件要放入100个指定的文件夹中&#xff0c;那么如何快速有效地完成这个任务呢&#xff1f;下面看下云炫文件管理批量管理文件的方法&#xff0c;快速将100个文件…

数据结构之----二叉树、二叉树遍历、二叉树数组表示、二叉搜索树

数据结构之----二叉树、二叉树遍历、二叉树数组表示、二叉搜索树 什么是二叉树&#xff1f; 二叉树是一种非线性数据结构&#xff0c;代表着祖先与后代之间的派生关系&#xff0c;体现着“一分为二”的分治逻辑。 与链表类似&#xff0c;二叉树的基本单元是节点&#xff0c;每…

jsonpath:使用Python处理JSON数据

使用Python处理JSON数据 25.1 JSON简介 25.1.1 什么是JSON JSON全称为JavaScript Object Notation&#xff0c;一般翻译为JS标记&#xff0c;是一种轻量级的数据交换格式。是基于ECMAScript的一个子集&#xff0c;采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清…

强化学习--背景

背景 强化学习 背景方向马尔可夫决策过程动态规划 方向 从数据中学习&#xff0c;或者从演示中学习包含丰富的门类&#xff0c;例如以模仿学习为代表的来自专家的数据中学习策略、以强化逆学习&#xff0c;代表来自数据中学习奖励函数以及来自人类反馈中学习&#xff0c;为代表…

大数据机器学习与深度学习—— 生成对抗网络(GAN)

GAN概述 在讲GAN之前&#xff0c;先讲一个小趣事&#xff0c;你知道GAN是怎么被发明的吗&#xff1f;据Ian Goodfellow自己说&#xff1a; 之前他一直在研究生成模型&#xff0c;可能是一时兴起&#xff0c;有一天他在酒吧喝酒时&#xff0c;在酒吧里跟朋友讨论起生成模型。然…

使用 Timm 库替换 YOLOv8 主干网络 | 1000+ 主干融合YOLOv8

文章目录 前言版本差异说明替换方法parse_moedl( ) 方法_predict_once( ) 方法修改 yaml ,加载主干论文引用timm 是一个包含最先进计算机视觉模型、层、工具、优化器、调度器、数据加载器、数据增强和训练/评估脚本的库。 该库内置了 700 多个预训练模型,并且设计灵活易用。…

Python 从入门到精通 学习笔记 Day04

Python 从入门到精通 第四天 今日目标 数据类型-又见str、数据类型-又见list 列表切片&排序&反转&循环、字典 数据类型 - 又见str 字符串定义 字符串是一个有序的字符的集合&#xff0c;用于在计算机里存储和表示文本信息 创建 a "Hello ,my name is Ha…

鸿蒙开发框架(ArkUI)简单解析

方舟开发框架&#xff08;简称ArkUI&#xff09;为HarmonyOS应用的UI开发提供了完整的基础设施&#xff0c;包括简洁的UI语法、丰富的UI功能&#xff08;组件、布局、动画以及交互事件&#xff09;&#xff0c;以及实时界面预览工具等&#xff0c;可以支持开发者进行可视化界面…

Jetson Xavier NX开发环境配置——编译libusb-1.0.9

背景 新买的Jetson Xavier NX 8G微雪的开发板&#xff0c;刷机后虽然已经带了libusb的库&#xff0c;在命令窗口输入lsusb也能够找到usb设备。但是&#xff0c;光机的usb配置说明中提示最好把老版本的libusb卸载掉&#xff0c;安装libusb-1.0.9版本&#xff0c;因此&#xff0…

【语义分割数据集】——imagenet语义分割

地址&#xff1a;https://github.com/LUSSeg/ImageNet-S 1 例图 2. 类别和数量信息 疑问 根据原文的描述&#xff1a;Based on the ImageNet dataset, we propose the ImageNet-S dataset with 1.2 million training images and 50k high-quality semantic segmentation annot…

【Vue第5章】vuex_Vue2

目录 5.1 理解vuex 5.1.1 vuex是什么 5.1.2 什么时候使用vuex 5.1.3 案例 5.1.4 vuex工作原理图 5.2 vuex核心概念和API 5.2.1 state 5.2.2 actions 5.2.3 mutations 5.2.4 getters 5.2.5 modules 5.3 笔记与代码 5.3.1 笔记 5.3.2 23_src_求和案例_纯vue版 5.3…

2023年12月12日 Go生态洞察:探索不可达函数与`deadcode`工具

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

现代雷达车载应用——第2章 汽车雷达系统原理 2.5节 检测基础

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.5 检测基础 对于要测试目标是否存在的雷达测量&#xff0c;可以假定下列两个假设之一为真&#xff1a; •H0:—测量结果仅为噪声。 •H1:—测量是噪…

波奇学Linux:环境变量,本地变量和内建命令

Windows下的环境变量 echo $PATH 查看指令搜索命令路径 在bash命令行输入的指令&#xff0c;系统根据PATH中的路径查询。 增加PATH指令 $PATH等于上面的路径 :表示不同路径分割符 /home/boki/lesson13代表新的路径 相当于一个赋值语句。 相当于指令&#xff0c;可以直接使用…

一张图片组合一组动作就可以生成毫无违和感的视频!

你敢信&#xff0c;1张人物图片 1张动作动画&#xff0c;就可以生成一段视频。网友直呼&#xff1a;“主播/视频UP主可能快要下岗了&#xff01;” &#xff08;模型视频来源于网络&#xff09; 本周&#xff0c;字节跳动联合新加坡国立大学发布了一款开源项目 MagicAnimate&…

超声波测距HC-SR04模块的简单应用

文章目录 一、HC-SR04HC-SR04是什么&#xff1f;HC-SR04测距的原理 二、使用步骤1.硬件最远探测距离调节硬件连接 2.软件1.初始化配置代码如下&#xff08;示例&#xff09;&#xff1a;引脚初始化定时器初始化 2.引脚输入输出配置代码如下&#xff08;示例&#xff09;&#x…

verilog基础,连续赋值之组合逻辑

连续赋值语句可以完成任意组合逻辑&#xff0c;本节对基本的逻辑电路进行测试分析&#xff0c;主要包含一下内容&#xff1a; 1. 反相器 2. 与门 3.与非门 4.或门 5.或非门 6.异或门 7.同或门 verilog实现逻辑操作的算符如下 // ~ .... Invert a single-bit signal…

【网络通信原理之套接字】

目录 概念 分类 数据报套接字&#xff1a;使用传输层UDP协议 流套接字&#xff1a;使用传输层TCP协议 原始套接字 Socket编程注意事项 前言&#xff1a;本文主要介绍了在什么是套接字及在Java中套接字是什么&#xff0c;和在套接字编程的注意事项。 概念 Socket套接…

Postman接口测试工具使用

一、前言 在前后端分离开发时&#xff0c;后端工作人员完成系统接口开发后&#xff0c;需要与前端人员对接&#xff0c;测试调试接口&#xff0c;验证接口的正确性可用性。而这要求前端开发进度和后端进度保持基本一致&#xff0c;任何一方的进度跟不上&#xff0c;都无法及…