前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【数据结构与算法】并查集的实现与应用

【数据结构与算法】并查集的实现与应用

作者头像
利刃大大
发布于 2025-04-10 00:43:00
发布于 2025-04-10 00:43:00
13300
代码可运行
举报
文章被收录于专栏:csdn文章搬运csdn文章搬运
运行总次数:0
代码可运行

Ⅰ. 并查集原理

​ 在一些应用问题中,需要 n 个不同的元素划分成一些不相交的集合开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为 并查集 (union-findset)

​ 比如:某公司今年校招全国总共招生10人,西安招4人,成都招3人,武汉招3人,10个人来自不同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号:{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个数。(负号下文解释)

​ 毕业后,学生们要去公司上班,每个地方的学生自发组织成小分队一起上路,于是:

​ 西安学生小分队 s1={0,6,7,8},成都学生小分队 s2={1,4,9},武汉学生小分队 s3={2,3,5} 就相互认识了,10 个人形成了三个小团体。假设右三个群主 0,1,2 担任队长,负责大家的出行。

​ 一趟火车之旅后,每个小分队成员就互相熟悉,称为了一个朋友圈。

​ 从上图可以看出:编号 6,7,8 同学属于 0 号小分队,该小分队中有 4 人(包含队长0);编号为 49 的同学属于 1 号小分队,该小分队有 3 人(包含队长1),编号为 35 的同学属于 2 号小分队,该小分队有 3 个人(包含队长1)。

仔细观察数组中内融化,可以得出以下结论:

  1. 数组的下标对应集合中元素的编号
  2. 数组中如果为负数,负号代表根,数字代表该集合中元素个数
  3. 数组中如果为非负数,代表该元素双亲在数组中的下标

​ 在公司工作一段时间后,西安小分队中 8 号同学与成都小分队 1 号同学奇迹般的走到了一起,两个小圈子的学生相互介绍,最后成为了一个小圈子:

现在 0 集合有 7 个人,2 集合有 3 个人,总共两个朋友圈。

通过以上例子可知,并查集一般可以解决一下问题:

  1. 查找元素属于哪个集合:沿着数组表示树形关系以上一直找到根
  2. 查看两个元素是否属于同一个集合:沿着数组表示的树形关系往上一直找到树的根,如果根相同表明在同一个集合,否则不在
  3. 将两个集合归并成一个集合
    • 将两个集合中的元素合并
    • 将一个集合名称改成另一个集合的名称
  4. 集合的个数:遍历数组,数组中元素为负数的个数即为集合的个数。

Ⅱ. 并查集的实现

其中在合并的时候做了一些小优化

  • 让小集合合并到大集合中去,这样子的话合并后层数不会偏差太多
  • 进行 路径压缩,减少层数(使用迭代,用递归容易溢出)
    • 其实原理就是在 FindRoot 的过程中,我们直接 将路径上的节点的双亲变成根节点 即可~
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma once
#include <vector>
#include <iostream>
using namespace std;

class UnionFindSet
{
private:
	vector<int> _ufs; // 存放节点双亲下标(负数代表根节点)
public:
	UnionFindSet(size_t n)
		:_ufs(n, -1)
	{}

	// 将两个集合并起来
	void Union(int x1, int x2)
	{
		// 先找到两个集合的根
		int root1 = FindRoot(x1);
		int root2 = FindRoot(x2);

		// 如果本身就是一个集合,那就不用合并
		if (root1 == root2)
			return;

		// 做一下优化,让小的往大的集合合并
		if (abs(_ufs[root1]) < abs(_ufs[root2]))
			swap(root1, root2);
		
		// 将新的根的值也就是这个集合总个数更新
		_ufs[root1] += _ufs[root2];

		// 将他们链接起来,这里统一把第二个合并到第一个
		_ufs[root2] = root1;
	}

	// 找一个节点的根的值也就是
	int FindRoot(int x)
	{
		int root = x;
		while (_ufs[root] >= 0)
		{
			root = _ufs[root];
		}

		// 进行路径压缩优化
		while (_ufs[x] >= 0)
		{
			// 注意要先用tmp将x的双亲保存下来,再让x的双亲变成根节点
			int tmp = _ufs[x];
			_ufs[x] = root;

			// x不断更新为路径上的双亲,直到遇到根节点
			x = tmp;
		}

		return root;
	}

	// 检测是否在同一个集合
	bool IsInSameSet(int x1, int x2)
	{
		return FindRoot(x1) == FindRoot(x2);
	}

	// 求一共有几个集合
	size_t SetSize(int x)
	{
		size_t n = 0;
		for (size_t i = 0; i < _ufs.size(); ++i)
		{
			if (_ufs[i] < 0)
				n++;
		}
		return n;
	}
};

Ⅲ. 并查集的应用

剑指 Offer II 116. 省份数量

思路:

这道题其实如果我们把上面的并查集实现直接贴上去的话,解决起来就非常方便,因为这道题要求省份的数量,其实就是求并查集的个数嘛,所以直接有并查集的实现的话,直接遍历这个矩阵将值为1的下标加入同一个集合,其他的话就单独为一个集合,这样子就能求出一共有多少省份了~ 但是一般我们要去实现并查集的话,稍微会浪费点时间,其实有了并查集的思想,我们可以直接使用这个思想来完成而没必要去搭建一个完整的并查集!

​ 还是一样,我们用 vector 来存放我们的下标(负数的话代表是根,绝对值代表集合的个数),然后我们这里需要一个 findroot 函数,用来找根的下标,这里利用 lambda 表达式,比较方便一点。

​ 接着就是合并的过程,遍历这个矩阵 isConnected ,然后遇到 isConnected[i][j]1 的说明是相通的,则将其放到我们的 vector 中去,注意还要判断一下这个小集合已经存在大集合了,是的话就没必要重新加入 vector 中了。

​ 最后求出 vector 中集合的总个数然后返回即可~

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        vector<int> ufs(isConnected.size(), -1);
         
        // 寻找x的根
        auto findroot = [&ufs](int x)
        {
            while(ufs[x] >= 0)
                x = ufs[x];
            return x;
        };

        // 联合起来
        for(size_t i = 0; i < isConnected.size(); ++i)
        {
            for(size_t j = 0; j < isConnected[i].size(); ++j)
            {
                // 若为1则当前的i和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 n = 0;
        for(size_t i = 0; i < ufs.size(); ++i)
        {
            if(ufs[i] < 0)
                n++;
        }
        return n;
    }
};

990. 等式方程的可满足性

思路:

​ 思路都是大同小异的,我们无需去实现并查集,只要有并查集的思想即可~

​ 思路就是先遍历第一遍 equations ,将 equations 中的满足等式的字母在 vector 中合并为一个集合;接着遍历第二遍 equations ,将其中不满足 equations 中的等式的字母,判断一下其是否在 vector 中为一个集合,是的话就冲突了,直接返回 false。当所有遍历完毕后没错的话,则返回 true~

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
        };

        // 遍历第一遍将equations满足的放到vector中
        for(size_t i = 0; i < equations.size(); ++i)
        {
            if(equations[i][1] == '=')
            {
                int root1 = findroot(equations[i][0] - 'a');
                int root2 = findroot(equations[i][3] - 'a');

                // 合并
                if(root1 != root2)
                {
                    ufs[root1] += ufs[root2];
                    ufs[root2] = root1;
                }
            }
        }

        // 遍历第二遍的时候判断equations不满足的时候在集合中,在的话就错了
        for(size_t i = 0; i < equations.size(); ++i)
        {
            if(equations[i][1] == '!')
            {
                int root1 = findroot(equations[i][0] - 'a');
                int root2 = findroot(equations[i][3] - 'a');

                // 根相同说明在同一个集合,说明错误了
                if(root1 == root2)
                    return false;
            }
        }

        return true;
    }
};
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【数据结构与算法】并查集
并查集需要建立映射关系,那么下面的代码是建立映射关系的一种方法(并查集的实现不采用这种方法)。
平凡的人1
2023/10/15
1740
【数据结构与算法】并查集
并查集详解(原理+代码实现+应用+优化)
我们可以用vector存名字数组里面的数据,那下标就可以做它们的编号,那这样用编号找名字是很方便的,编号是几,就找下标为几的元素就行了。 但是名字找编号就有点麻烦,所以我们可以借助map给名字和编号建立一个映射关系。
YIN_尹
2024/01/23
4.3K1
并查集详解(原理+代码实现+应用+优化)
并查集的原理及实现
在一些应用问题中,需要将 n 个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集 (union-findset)。
利刃大大
2023/04/12
4890
并查集的原理及实现
【高阶数据结构】秘法(一)——并查集:探索如何高效地管理集合
回家一段时间后,西安小分队中8号游客与成都小分队1号游客奇迹般的走到了一起,两个小圈子的游客相互介绍,最后成为了一个小圈子:
GG Bond1
2024/08/29
1140
【高阶数据结构】秘法(一)——并查集:探索如何高效地管理集合
【数据结构】并查集
在一些应用问题中,需要将 n 个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-find set)。
YoungMLet
2024/03/09
1960
【数据结构】并查集
DS进阶:并查集
在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-find set)。
小陈在拼命
2024/05/03
1010
DS进阶:并查集
【高效管理集合】并查集的实现与应用
并查集,也称为不相交集,是一种树形的数据结构,用于处理一些不相交集合的合并及查询问题。简单来说,它主要用于处理元素分组的问题。
用户11305458
2024/10/09
2160
【高效管理集合】并查集的实现与应用
【c++高阶DS】图
比如:某公司今年校招全国总共招生10人,西安招4人,成都招3人,武汉招3人,10个人来自不同的学校,起先互不相识,每个学生都是一个独立的小团体,现给这些学生进行编号:{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 给以下数组用来存储该小集体,数组中的数字代表:该小集体中具有成员的个数
用户11029103
2024/12/25
1070
【c++高阶DS】图
并查集的原理及实现
在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-findset)。 并查集一般可以解决一下问题:
海盗船长
2020/08/28
9590
如何使用并查集解决朋友圈问题?
今天分享到的是一种相对冷门的数据结构 —— 并查集。虽然冷门,但是它背后体现的算法思想却非常精妙,在处理特定问题上能做到出奇制胜。那么,并查集是用来解决什么问题的呢?
用户9995743
2022/12/22
1.6K0
如何使用并查集解决朋友圈问题?
【数据结构】? 并查集优化全解:从链式退化到近O(1)的性能飞跃 | 路径压缩与合并策略深度实战
在上一篇内容中我们正确认识了并查集,并通过数据元素与其双亲指针的映射关系实现了并查集的查找与合并的。
蒙奇D索隆
2025/03/30
1560
【数据结构】? 并查集优化全解:从链式退化到近O(1)的性能飞跃 | 路径压缩与合并策略深度实战
并查集
本篇博客参照了如下博客内容: http://www.cnblogs.com/horizonice/p/3658176.html
AI那点小事
2020/04/20
4280
并查集
【数据结构】C语言实现并查集:双亲指针映射与动态连通性实现详解
在上一篇内容中我们从数据结构的三要素初步认识了并查集这种数据结构,但是上一篇对并查集的介绍并不准确。
蒙奇D索隆
2025/03/29
900
【数据结构】C语言实现并查集:双亲指针映射与动态连通性实现详解
客户端用不着的数据结构之并查集
并查集可以看作是一个数据结构,如果你根本没有听说过这个数据结构,那么你第一眼看到 “并查集” 这三个字的时候,脑海里会浮现一个什么样的数据结构呢?
五分钟学算法
2019/10/09
6450
客户端用不着的数据结构之并查集
最小生成树算法(下)——Kruskal(克鲁斯卡尔)算法
在我的上一篇文章最小生成树算法(上)——Prim(普里姆)算法 主要讲解对于稠密图较为合适的Prim算法。那么在接下里这片文章中我主要讲解对于稀疏图较为合适的Kruskal算法。
AI那点小事
2020/04/20
1.3K0
最小生成树算法(下)——Kruskal(克鲁斯卡尔)算法
【数据结构】数据结构高手进阶:并查集VS森林,谁才是集合操作的真神?
集合这种逻辑结构是指在集合中的数据元素之间除了同一个集合外,没有其它的关系,如下图所示:
蒙奇D索隆
2025/03/21
710
【数据结构】数据结构高手进阶:并查集VS森林,谁才是集合操作的真神?
《并查集的黑科技:路径压缩×按秩合并×带权扩展|算法核心原理与工程级实践指南》
🔥 在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型成为并查集(union-findset)
IsLand1314
2025/04/05
1470
《并查集的黑科技:路径压缩×按秩合并×带权扩展|算法核心原理与工程级实践指南》
数据结构高频面试题-图
图的基础概念图的基础算法1. 图的遍历深度优先搜索遍历(DFS)广度优先搜索遍历(BFS)2. 单源最短路径问题(Dijkstra算法)3. 拓扑排序4. 最小生成树Kruskal算法(加边法)Prim算法(加点法)经典面试题1.克隆图2.课程表II3.网络延迟问题4.除法求值5.最小高度树6.重新安排行程7. 冗余连接
小萌哥
2020/07/20
2.4K0
并查集(Union-Find Set)课程笔记
猫咪-9527
2025/03/28
1000
数据结构之并查集
并查集(Union Find),从字面意思不太好理解这东西是个啥,但从名字大概可以得知与查询和集合有关,而实际也确实如此。并查集实际上是一种很不一样的树形结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
端碗吹水
2021/02/02
1.1K0
推荐阅读
相关推荐
【数据结构与算法】并查集
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验